Tag Archives: Advance

Objective-C Programming Chapter 18 (Part1)

Network

 

การเขียนโปรแกรมที่เกี่ยวข้องกับระบบเครือข่ายด้วย Objective-C นั้น มีไลบรารีและเฟรมเวิร์คให้ใช้งานแบ่งออกเป็นหลายระดับด้วยกัน โดยเริ่มตั้งแต่ระดับล่างสุดที่ต้องจัดการ network packet เอง ไปจนถึงระดับสูง ในบทนี้จะได้เรียนรู้เกี่ยวกับคลาสและเฟรมเวิร์คต่างๆที่เกี่ยวระบบเครือข่าย รวมไปถึงสถาปัตยกรรมของคลาสที่ใช้งาน เนื้อหาในบทนี้จะครอบคลุมพื้นฐานการใช้งานทั่วไป เช่นการ download , upload ส่วนการใช้งานระดับสูงเช่นการ authentication , cache , cookie และการใช้ API ระดับล่างๆเช่น CFNetwork ไม่ได้คลอบคลุมในบทนี้ ถึงแม้ว่าเนื้อหาในบทนี้ผู้ที่ไม่มีความรู้ทาง network หรือไม่เคยเขียนโปรแกรมที่เกี่ยวกับเครือข่ายมาก่อนสามารถอ่านทำความเข้าใจได้ไม่ยาก แต่ถ้าหากผู้อ่านควรมีความรู้เบื้องต้นเกี่ยวกับโพรโทคอล TCP/IP จะช่วยให้เข้าใจปัญหาและสามารถแก้ไขสิ่งต่างๆที่เกิดขึ้นเมื่อมีข้อผิดพลาดในการเขียนโปรแกรม เพราะเป็นโพรโทคอลหลักที่ใช้ใน cocoa network framework

Foundation Class Method

หากย้อนกลับไปบทก่อนๆ จะพบว่าโปรแกรมที่เราได้เขียนไปนั้นมีการใช้งาน network มาบ้างแล้ว นั่นก็คือการใช้เมธอด ของ Foundation Class ที่มีชื่อ initWithContentsOfURL:  หรือมีชื่อลักษณะเดียวกัน ดังเช่นตัวอย่างต่อไปนี้

การใช้เมธอดที่มากับ Foundation Class มีข้อดีคือ ง่าย ไม่ซับซ้อน และสะดวกมาก แต่เมธอดเหล่านี้ไม่ได้ออกแบบมาเพื่อใช้กับข้อมูลขนาดใหญ่ เช่น ถ้าหากต้องการจะโหลดไฟล์เอกสารขนาด 2 mb ไว้ใน NSString ด้วยการเรียกเมธอด initWithContentsOfURL:encoding:error: คงไม่ใช่เรื่องที่ดีแน่นอน เพราะเมธอดเหล่านี้มีการทำงานแบบ synchronous หรือต้องรอให้เมธอดเหล่านี้ทำงานเสร็จสิ้นเสียก่อนจึงจะสามารถทำอย่างอื่นได้ และในทางตรงกันข้าม ลักษณะการทำงานแบบไม่ต้องรอให้เสร็จจะเรียกว่า asynchronous ถ้าหากเขียนโปรแกรมแบบ console ดังที่เราเขียนๆมาตั้งแต่เริ่มต้นของหนังสือ การรอให้เมธอดที่เกี่ยวกับระบบเครือข่ายทำงานเสร็จสิ้นเสียก่อนคงไม่ใช่ปัญหาใหญ่ แต่เมื่อเขียนโปรแกรมที่มี User Interface เช่น iOS และ Mac OSX จะกลายเป็นเรื่องใหญ่ทันที เพราะ UI ต่างๆจะไม่ตอบสนอง เกิดการหยุดชะงักหรือ freezing เนื่องจากต้องรอให้เมธอดเหล่านี้ทำงานเสร็จเสียก่อนนั่นเอง

NSURLSession

ตั้งแต่แอปเปิ้ลได้ปล่อย Mac OSX 10.9 Maverick และ iOS 7 ก็ได้เพิ่มคลาส NSURLSession เข้ามาเป็นส่วนหนึ่งของ Network Framework ซึ่งเป้าหมายของคลาสนี้คือมาทำหน้าที่แทนคลาส NSURLConnection ที่เคยใช้กันมานาน ดังนั้นเราจึงควรใช้คลาสใหม่นี้แทน NSURLConnection และคลาส NSURLSession นี้มีก็ความสามารถที่เหนือกว่า NSURLConnection หลายๆอย่าง เช่นการทำงานในโหมด background หรือความสามารถในการหยุดทำงานชั่วคราว และสั่งให้เริ่มต้นใหม่ หรือทำงานต่อจากสิ่งที่ค้างไว้ได้ เป็นต้น และนอกจากคลาส NSURLSession แล้วยังมีคลาสที่เกี่ยวข้องกันอีกหลายคลาส ดังนั้นก่อนที่จะเริ่มลงมือเขียนโค้ดเราควรเข้าใจโครงสร้างรวมไปถึงส่วนประกอบอื่นๆที่เกี่ยวข้องกันเสียก่อน

NSURLSession

จากรูปการทำงานของ NSURLSession API นั้นจะประกอบไปด้วยเซสชั่น (session) และในโปรแกรมก็มีได้หลายๆเซสชั่น ซึ่งแต่ละ session จะประกอบไปด้วยกลุ่มของการทำงานย่อย (task) เช่นการดาวน์โหลด หรือการส่งข้อมูล นอกจาก task แล้วเซสชั่นจะทำงานร่วมกับ delegate และคลาสสำหรับกำหนดค่าต่างๆของเซสขั่น ถ้าจะเปรียบการทำงานของเซสชั่นให้ง่ายขึ้น ให้นึกถึงการทำงานของเว็บบราวเซอร์ที่มีหลายหน้าต่าง โดยหน้าต่างแต่ละอันนั้นก็เปรียบเสมือน session ที่ได้กำหนด URL ที่จะใช้งาน และในแต่ละหน้าต่างก็มีการทำงานย่อยหลายๆอย่าง เช่น โหลดรูปภาพ โหลดสคริป เป็นต้น และเมื่อทำลายเซสขั่นลง การทำงานย่อยๆในเซสชั่นนั้นก็จะปิดตัวลง เช่นกัน

NSURLSessionConfiguration

คลาสนี้อาจจะเป็นคลาสแรกที่จะต้องประกาศเมื่อเริ่มใช้งาน NSURLSession API หน้าที่หลักของคลาสนี้คือช่วยให้ผู้ใช้ได้ปรับค่าใช้งานต่างๆก่อนเริ่มเซสชั่นเช่น cache , http header , cookie , security และอื่นๆ  คลาสนี้จะถูกเรียกใช้เพียงครั้งเดียวตอนเริ่มต้น และหลักจากที่เซสชั่นได้ทำงานไปแล้ว การปรับเปลี่ยนค่า NSURLSessionConfiguration จะไม่มีผลกระทบอะไรต่อเซสชั่นนั้น การประกาศ NSURLSessionConfiguration จะใช้คลาสเมธอดในการประกาศ ซึ่งสามารถทำได้ 3 เมธอดด้วยกันคือ

+ defaultSessionConfiguration เป็นค่าเริ่มต้นของเซสชั่น ซึ่งจะมีการเก็บ cookie , cache ไว้ในฮาร์ดดิส การทำงานโดยทั่วไปจะใช้เมธอดนี้
+ ephemeralSessionConfiguration จะไม่มีการเก็บข้อมูลไว้ฮาร์ดดิส แต่จะเก็บไว้ที่หน่วยความจำแทน ดังนั้นหากโปรแกรมยกเลิกการใช้เซสชั่น ข้อมูลต่างๆก็จะหายไปด้วย ซึ่งเหมาะกับการทำงานลักษณะ private mode ที่ไม่ต้องการเก็บข้อมูลไว้หลังจากการใช้งาน
+ backgroundSessionConfiguration ถ้าต้องการให้ network ทำงานเมื่อ application หยุดการทำงานชั่วขณะ (suspend) เช่นในกรณีที่ของการทำงานแบบ background mode ของ iOS Application ก็จะใช้การประกาศแบบนี้ อย่างไรก็ตามการทำงานในลักษณะ background นี้มีข้อจำกัดหลายอย่าง เช่น ต้องเป็นโพรโทคอล http หรือ https เท่านั้น , การทำงานต้องเป็น download , upload เท่านั้นไม่สามารถใช้ data task ได้ (จะอธิบาย task แบบ data ภายหลัง) และจะต้องมี delegate เสมอ

Task

อย่างที่ได้กล่าวไปก่อนหน้านี้ว่า session ประกอบได้ด้วยการทำงานย่อยหรือ task และ NSURLSession ได้แบ่ง task ออกเป็นทั้งหมด 3 แบบด้วยกันคือ

NSURLSessionDownloadTask ใช้สำหรับการ download และจัดเก็บข้อมูลลงไฟล์ ทำงานแบบ background ได้
NSURLSessionUploadDataTask ใช้สำหรับการ upload ไฟล์ ทำงานแบบ background ได้เช่นเดียวกัน
NSURLSessionDataTask จะใช้ส่งและรับข้อมูลด้วย NSData การใช้งานโดยทั่วไปจะประกาศใช้ task แบบนี้ แต่เนื่องจากคลาสนี้มีการรับและส่งข้อมูลด้วย NSData ซึ่งข้อมูลจะเก็บไว้ยังหน่วยความจำ ดังนั้นจึงไม่สามารถใช้ task นี้กับการทำงานแบบ background ได้ ดังนั้นในกรณีที่ต้องการจะให้ระบบ network ยังทำงานในโหมด background ต้องใช้คลาส NSURLSessionDownloadTask หรือ NSURLSessionUploadDataTask

NSURLSession with Delegate

วัฎจักรการทำงานของ NSURLSession จะมีด้วยกันสองแบบ โดยแบบแรกจะใช้ delegate ส่วนแบบที่สองคือไม่ได้ใช้ delegate และโปรแกรมที่จะได้เขียนต่อไปนี้จะเป็นการใช้งานแบบกำหนด delegate ให้กับ session ถ้ายังจำกันได้ในบทที่ 10 เราได้เขียนโปรแกรมจำลองการใช้งาน network โดยมี delegate กันไปแล้ว โดยกระบวนในการทำงาน ก็ได้แสดงดังที่เห็นในรูป

network_session

 

การทำงานของโปรแกรมที่จะได้เขียน ก็มีลักษณะการทำงานเช่นเดียวกัน เพียงแค่เปลี่ยนจาก NetworkController ไปใช้คลาส NSURLSession แทน จากรูปจะเห็นว่าคลาส NetworkController เป็นส่วนที่ใช้ในการติดต่อเครือข่าย ซึ่งจะทำหน้าที่รับหรือส่งข้อมูลอย่างเดียวเท่านั้น แต่ในการจัดการข้อมูลจะมีคลาส MyDelegate มาทำหน้าที่แทน เราจะเขียนคลาส MyDelegate ขึ้นมาใหม่ เพื่อให้รองรับ NSURLSessionDataDelegate โดยมีโค้ดดังนี้

Program 18.1

MyDelegate.h

 

MyDelegate.m

คลาส MyDelegate ที่ได้เขียนไปมี delegate method ที่ต้องเขียนด้วยกันทั้งหมด 2 เมธอดด้วยกัน โดยเมธอดแรกนั้นจะถูกเรียกเมื่อได้รับข้อมูล และจากโค้ดก็ได้เก็บข้อมูลที่ได้มาไว้ใน netData ส่วนเมธอดต่อมาจะถูกเรียกหลังจาก task ได้ทำงานเสร็จสิ้นลง

main.m

 

มาถึงในส่วนของโค้ดของโปรแกรมหลัก ได้เริ่มต้นด้วยการประกาศ defaultConfiguration ที่เป็นอ็อบเจ็กของคลาส NSURLSessionConfiguration จากนั้นก็ประกาศ NSURLSession โดยกำหนดค่าเป็น defaultConfiguration พร้อมกับกำหนด delegateเมื่อเสร็จจากขึ้นตอนนี้ก็เหลือแค่กำหนด task ที่ต้องการใช้งาน จากโค้ดของโปรแกรมในบรรทัดที่ 20 ก็ได้ประกาศให้ dataTask ให้เป็น task ที่จะใช้งาน และไม่ว่าจะประกาศ task แบบใดก็ตาม หลังจากประกาศเสร็จแล้ว task จะมีสถานะเป็น suspend ซึ่งหมายถึงว่ายังไม่เริ่มทำงาน  ดังนั้นจึงต้องสั่งให้เริ่มทำงานด้วยการเรียกเมธอด resume ในส่วนของ while loop ทำหน้าที่แค่รอ dataTask ทำงานจนเสร็จ และถ้าหากโปรแกรมรอการรับจากเซิฟเวอร์นานเกินไปก็ให้ยกเลิกการทำงาน เมื่อให้โปรแกรมทำงานจนเสร็จสิ้นก็จะได้ผลลัพธ์ดังนี้

Program 18.1 Output

{
    “firstName”: “John”,
    “lastName”: “Smith”,
    “age”: 25
}

Download

โปรแกรมที่ได้เขียนไปเป็นการใช้ task แบบ data เราจะเขียนโปรแกรมอีกสักโปรแกรมเพื่อดาวน์โหลดไฟล์มาเก็บไว้ที่เครื่อง สิ่งที่ต้องเขียนเพิ่มเติมเมธอดของ NSURLSessionDownloadDelegate ให้กับคลาส MyDelegate นั่นเอง

Program 18.2

MyDelegate.m

โค้ดที่ได้เพิ่มเข้าไปมีด้วยกัน 3 เมธอด โดยเมธอดแรกนั้น จะถูกเรียกเมื่อการทำงานของ download task ได้เสร็จสิ้น ในเมธอดนี้ได้เขียนโค้ดเพื่อคัดลอกไฟล์มาไว้ยัง Desktop ของผู้ใช้งาน เนื่องจากเมื่อ download task ได้เริ่มต้นทำงานก็จะสร้าง temp file เพื่อใช้สำหรับเขียนข้อมูล ดังนั้นจึงเขียนโค้ดให้คัดลอกข้อมูลจาก temp file มาเก็บไว้นั่นเอง ส่วนอีกเมธอดเป็นเมธอดที่บอกจำนวนของข้อมูลที่ได้เขียนเสร็จ และเมธอดที่อยู่ล่างสุดเป็นส่วนที่เกี่ยวกับการจัดการ task ในกรณีที่ต้องทำงานต่อจากงานที่ค้างไว้ (resume) เมื่อเพิ่มโค้ดของ download delegate เสร็จเรียบร้อยแล้ว ในส่วนของโปรแกรมหลักให้เปลี่ยน NSURLSessionDataTask เป็น NSURLSesssionDownloadTask ดังนี้

main.m

เมื่อโปรแกรมทำงานก็จะดาวน์โหล smith.json ไฟล์มาไว้ที่ desktop พร้อมกับแสดงผลที่ console ดังนี้

Program 18.2 Output

Download done

 

Upload

สองโปรแกรมที่ผ่านมาเป็นโปรแกรมที่รับข้อมูลจากเซิฟเวอร์ แต่โปรแกรมที่จะได้เขียนต่อไปนี้ เป็นโปรแกรมเพื่อใช้สำหรับส่งรูปภาพไปยังเซิฟเวอร์ เนื่องจากต้องมีเซิฟเวอร์สำหรับการ upload ดังนั้นก่อนที่จะเริ่มลงมือเขียนโค้ด เราจะใช้เครื่อง Mac ที่มีอยู่แล้วทำหน้าที่เป็น server สำหรับโค้ดของโปรแกรมเซิฟเวอร์ที่จะใช้ในโปรแกรมนี้ สามารถโหลดได้จาก https://developer.apple.com/library/ios/samplecode/SimpleURLConnections/SimpleURLConnections.zip หลังจากโหลดไฟล์เสร็จก็ unzip ให้เรียบร้อย จากนั้นจะพบกับไฟล์ ImageReceiveServer.py ซึ่งเป็นโปรแกรมเซิฟเวอร์อย่างง่ายที่เขียนด้วยภาษา pythone จากนั้นให้เปิดโปรแกรม terminal และสั่งให้เซิฟเวอร์ทำงานได้ด้วยคำสั่ง python ImageReceiveServer.py ดังที่แสดงดังรูป (กด ctr+c เพื่อสั่งให้เซิฟเวอร์หยุดทำงาน)

start_simple

 

เมื่อสั่งให้เซิฟเวอร์ทำงานได้แล้ว ในลำดับต่อไปก็จะเขียนโปรแกรมสำหรับการ upload file ซึ่งมีโค้ดโปรแกรมดังนี้

Program 18.3

MyDelegate.m

ในส่วนของ delegate นี้มีเมธอดที่ต้องเขียนเพียงหนึ่งเมธอดเท่านั้น ซึ่งเป็นเมธอดที่บอกจำนวนข้อมูลที่ได้ส่งไปยังเซิฟเวอร์ในแต่ละครั้ง  และโค้ดในส่วนของโปรแกรมหลักมีดังนี้

main.m

เริ่มต้นด้วยการประกาศ url เซิฟเวอร์ที่จะส่งข้อมูลไป ในส่วนของ session configuration ได้กำหนดค่า http header ซึ่งเป็นข้อมูลที่จะถูกส่งไปยังเซิฟเวอร์ ได้ด้วยการกำหนดพร๊อพเพอร์ตี้ HTTPAdditionalHeader นอกจากการกำหนดค่า http header ใน session configuration ยังสามารถกำหนดใน NSURLRequest ได้อีกทาง แต่การกำหนดด้วย session configuration จะเป็นการกำหนดค่าเริ่มต้น ให้กับทุก request ที่ทำงานใน session นี้ ส่วนในกรณีที่กำหนดให้กับ NSURLRequest จะเป็นการกำหนดเฉพาะเจาะจง request ที่ต้องการ

โค้ดในลำดับต่อมาเริ่มด้วยการเตรียมข้อมูลที่จะส่งไปยังเซิฟเวอร์ ซึ่งประกอบไปด้วยข้อมูลของโพรโทคอล http/1.1 เช่น content-type รวมไปถึงข้อมูลรูปภาพที่จะส่งไป  จากนั้นก็เริ่มเขียนโค้ดที่ใช้สำหรับส่งข้อมูล

เมื่อดูจากโค้ดตั้งแต่บรรทัดที่ 47 จะเห็นว่ามีรูปแบบเหมือนกับการเขียน download  แต่สิ่งที่เพิ่มเข้ามาคือ NSURLRequest ที่ระบุว่าใช้ POST สำหรับส่งข้อมูล ส่วนการสร้าง task ที่จะทำงานใช้เมธอด uploadTaskWithRequest: fromeData พร้อมกับส่งค่าพารามิเตอร์ด้วยข้อมูลที่ได้เตรียมไว้ ก็เป็นอันเสร็จสิ้น เมื่อให้โปรแกรมทำงาน โปรแกรมทางฝั่งเซิฟเวอร์ก็จะแสดงข้อมูลดังรูป

simpleServer

 

และข้อมูลรูปภาพจะถูกเก็บไว้ยังโฟลเดอร์ images ซึ่งเป็น sub folder ภายในโฟลเดอร์ที่เซิฟเวอร์ได้ทำงาน

Program 18.3 Output

send: 24148 , total send: 24148 , total byte: 24148

 

 

Cocoa Programming II : NSTableView Advance

หลังจากได้ลองเขียน NSTableView ดูกันแล้ว วันนี้ก็ยังคงต่อด้วยเรื่องของ NSTableView เหมือนเดิมแต่ว่าเป็นขั้น Advance มากกว่าเดิม

More about datasource

จากโปรแกรมแสดงตารางรายชื่อครั้งก่อน จะเห็นว่าเราได้ set datasource มาที่ AppController และเราก็เขียน implement delegate ของ datasource ที่ AppController จะเห็นว่าวิธีแบบนี้ไม่ค่อยจะยืดหยุ่นมากนัก เพราะเนื่องจากว่าเราให้ AppController เป็นทั้งตัวควบคุม interface และยังให้เป็น datasource

สมมติว่าเราต้องการมี table มากกว่า 1 table และแต่ละ table มี datasource ที่มีข้อมูลต่างกัน เราก็คงต้องเพิ่ม datasource ให้เท่ากับจำนวนของ table ซึ่งนั่นก็แปลว่าเราก็ต้องสร้าง AppController instance มาหลายๆอัน มันก็คงจะไม่ใช่วิธีที่ดีนัก และที่สำคัญตัวโปรแกรมจะมองว่า AppController instance ที่เราสร้างขึ้นมาใหม่ มันเป็นตัวเดียวกัน ดังนั้นข้อมูลใน table A และ B ก็จะเหมือนกัน ( ดูรูปประกอบ ) ฉนั้นแล้วการที่เราให้ AppController เป็น datasource โดยตรงก็คงไม่ดีแน่

งั้นแทนที่เราจะให้ Class AppController เป็น datasource โดยตรง เราก็เปลี่ยนให้ AppController มี member เป็น datasource ก็น่าจะดีกว่า

จากรูปข้างบน จะเห็นว่า เราก็สามารถมีหลายๆ datasource ที่ต่างกันได้ แล้วเราจะเขียน class ออกมาได้ยังไง ? งั้นมาดูตัวอย่างจริงกันเลยดีกว่า Continue reading Cocoa Programming II : NSTableView Advance

Ojective-C Programming – Copying Object

นี่ก็น่าจะเป็นเรื่อง objective-c แบบเพียวๆเกือบๆจะสุดท้ายละ ก็เหลืออีกไม่กี่เรื่องที่ผมคิดว่าน่าจะเพียงพอสำหรับการเริ่มต้นกับ cocoa เอาละเข้าเรื่องเลยดีกว่า

โดยปกติเราเวลาที่เราประกาศตัวแปรขึ้นมาสักตัว ถ้าไม่ใช่ class ก็จะเป็น primitive type เป็นต้นว่า int , char , double การที่เราจะ copy ข้อมูลจาก ตัวแปรหนึ่งไปยังอีกตัวแปร หนึ่งนั้นทำได้ง่ายมาก เพียงแค่ใช้เครื่องหมาย = แค่นี้เองเช่นเป็นต้นว่า

จาก code เบื้องต้นจะเห็นว่า

  • บรรทัดที่ 3 เราได้ให้ b มีค่าเท่ากับ a
  • บรรทัดที่ 4 เพิ่มค่า a ด้วย 15
  • บรรทัดที่ 5 เพิ่มค่า b ด้วย 10
  • แน่นอนว่า จาก code ข้างบนนี้ ค่า a กับ b มีค่าต่างกันคือ 20 กับ 25
  • สรุปง่ายๆว่า เราได้ให้ค่า b เท่ากับ ค่า a ในบรรทัดที่ 3 แต่เมื่อเราเปลี่ยนค่า a ในบรรทัดที่ 4 ก็ไม่ได้มีผลกระทบอะไรกับค่า b
  • และในทำนองเดียวกัน ถ้าเราทำการแก้ไขค่าของ a ก็ไม่ได้มีผลอะไรกับ b

แต่การประกาศ class instance นั้นถ้าเราเขียนโปรแกรมในลักษณะเหมือนๆกับ ข้างบน ดังเช่นตัวอย่างข้างล่าง

จาก code ข้างบน

  • เราได้ประกาศ instance ของ NSMutableString มา 2 ตัวคือ hello กับ temp
  • ในบรรทัดที่ 8 และเราก็ได้ให้ ค่า temp มีค่าเท่ากับ hello นั่นก็แปลว่า temp ควรจะมีค่า เป็น Hello
  • จากนั้นในบรรทัดที่ 9 เราก็ได้เปลี่ยนแปลงค่า ของ temp โดยการต่อท้ายด้วย World
  • และบรรทัดที่ 10 เราก็ให้พิมพ์ค่าตัวแปร hello ออกมา
  • แต่ผลปรากฎว่า มันกลับพิมพ์คำว่า Hello World ออกมา ทั้งๆที่เราก็ไม่ได้ไปทำการแก้ไขค่าใน ตัวแปร hello เลยสักนิด

ทำไมถึงเป็นแบบนี้ ?

เพราะว่า การประกาศ class instance นั้นเป็นการประกาศ pointer ( คือตัวแปรที่เก็บค่าของตำแหน่ง memory ) ของ class นั้นๆ ฉนั้นการที่เขียน code ว่า

temp = helloString

ก็เป็นการบอกว่า ให้ temp นั้นชี้ไปยังตำแหน่ง memory เดียวกันกับ hello ฉนั้นแล้วเมื่อมันเป็น ตำแหน่ง memory เดียวกัน ถ้าหากเราทำการแก้ไข temp จึงทำให้ hello ต้องเปลี่ยนไปด้วย

เหมือนในภาษา c/c++ถ้าดู code ต่อไปนี้

การที่เราเขียน code แบบนี้เป็นการให้ temp ชี้ไปที่ตำแหน่ง memory ของ hello ไม่ได้เป็นการ copy ค่า ( เราต้องการให้ temp มีค่าเป็นคำว่า Hello World!! ) ฉนั้นแล้วเมื่อเราสั่ง printf ตัวแปร temp ออกมาก็จะไม่ได้คำว่า Hello World!! เพราะเป็นการชี้ตำแหน่ง memory ไปยังตำแหน่งใหม่ ถ้าหากเราจะ ให้ค่า temp เป็น Hello World เหมือนกับ hello เราต้องใช้ ฟังชั่นที่ชื่อว่า strcpy ทำการ copy string ถ้าเป็นภาษา c/c++ อาจจะเขียนได้แบบนี้

ปล. ใครที่ยังไม่เข้าใจเรื่อง pointer ให้ไปอ่านเพิ่มเติมน่ะครับ เดี๋ยวจะงง
http://www.vcharkarn.com/vlesson/showlesson.php?lessonid=1&pageid=7

แล้วเราจะ copy มันได้ยังไง ?

ใน Foundation class นั้นจะมี method ที่ชื่อว่า copy และ mutableCopy เพื่อให้เราสามารถ clone ค่าจาก instance หนึ่งไปยังอีก instance หนึ่งได้ และสอง method นี้ต่างกันกันตรงที่ว่า ถ้าเป็นหากเราต้องการจะคัดลอกไปแล้วให้มันแก้ไขได้ ก็ต้องใช้ mutableCopy ( สำหรับ mutable object นั้นก็ควรจะใช้ mutableCopy )

มาดูกันว่าจากข้างบนเราจะเปลี่ยนให้ temp มันเท่ากับ helloString ได้ยังไง

ถ้าเรา compile และ run ดูจะเห็นว่าผลลัพธ์ที่ได้จะออกมาเป็น

Hello
HelloWorld

ซึ่งจะต่างจากโปรแกรมแรกที่เราเขียน เพราะว่า เราทำการ copy ค่าจาก helloSting มาให้ยัง temp ( การ copy ในที่นี้หมายถึงการคัดลอกข้อมูลในตำแหน่ง memory ที่ต้องการไปยังอีกที่) นั่นก็แปลว่าค่าของตัวแปรทั้งสองอันนี้แยกจากกัน ฉนั้นถ้าเราแก้ไขตัวแปร temp ก็ไม่ได้เกี่ยวอะไรกับ helloString

Shallow – Deep

อย่างที่บอกไปแล้วว่า instance นั้นคือ pointer ดีๆนี่เอง ลองพิจารณา code ข้างล่างนี้

จากโปรแกรมข้างบน

    เราเริ่มด้วยการประกาศตัวแปรมาทั้ง 3 ตัวคือ arrayOne arrayTwo และ helloString

  • หลังจากนั้น เราก็ได้เอาตัวแปร helloString ไปเก็บไว้ที่ arrayOne
  • จากนั้นก็ arrayTwo ก็ได้ mutableCopy จาก arrayOne
  • สรุปว่า ตอนนี้ arrayOne มี member 1 ตัวคือ helloString ส่วน arrayTwo ก็มี member 1 ตัวเหมือนกันเพราะว่าไปก๊อปจาก arrayOne มา
  • หลังจากนั้นเราได้ทำการแก้ไข ค่าของ member ใน arrayTwo โดยเพิ่มคำว่า World เข้าไป
  • สุดท้ายเราพิมพ์ค่าของ arrayOne ออกมาร แต่ผลที่ได้คือ มันพิมพ์คำว่า Hello World !!!!
  • อ้าวทำไมมันเป็นแบบนี้ละ ทั้งๆที่เราแก้ไขตัวแปร ใน arrayTwo แล้วมันเกี่ยวอะไรกับ arrayOne ?

    จริงอยู่ครับว่า arrayTwo นั้นก๊อปปี้ arrayOne มา นั่นก็แปรได้ว่ามันใช้ memory คนละส่วนกันแล้วดังนั้นมันก็ควรจะไม่เกี่ยวกัน ก็ถูกต้องแล้วละครับ ว่ามันไม่ได้เกียวกันการแก้ไข member ใน arrayOne ก็ไม่ได้เกียวกับ arrayTwo

    แต่เผอิญว่า ตัวแปร ที่ arrayOne นั้นเก็บไว้มันเป็น pointer ที่ชี้ไปยัง helloString นั่นก็แปลได้ง่ายๆว่า member ใน array นั้นไม่ได้ถูก clone มาด้วย เป็นแค่การ copy reference มาแค่นั้น ฉนั้นการแก้ไข ตัวแปรใน arrayOne ย่อมมีผลกระทบต่อ arrayTwo สิ่งที่มันเป็นลักษณะ นี้เรียกว่า shallow copy

    แล้วจะทำยังไงให้มันแยกขาดจากกันเลย ?

    คำตอบคือ เราต้อง ทำเองครับ เพราะว่ามันไม่ได้ทำให้เรา แต่จะทำยังไงละ ?

    Copying Class Instance

    จริงอยู่ว่าเราสามารถใช้ copy , mutableCopy ได้แต่ถ้าหากเป็น class ที่เราเขียนขึ้นมาเองนั้นไม่สามารถทำได้ เราต้องเขียนเองเหมือนกัน

    สรุปว่าตอนนี้ก็มีตัวอย่างที่เราต้องทำเอง ถึง 2 เคสด้วยกัน วิธีการก็ไม่ได้ยากเย็นอะไรนัก เราเพียงแค่ implement interface <NSCopy> หรือ <NSMutableCopy> เท่านั้นเอง มาดูกันเลยดีกว่า ว่ามันหน้าตาเป็นยังไง

    ก็ดูจาก code ข้างบนจะเห็นว่า ตอนที่ประกาศ class จะมีส่วนเพิ่มเติมเข้ามาคือ <NSCopying> เมื่อเรามี interface NSCopying แล้วสิ่งที่ต้องทำต่อมาก็คือ implement ส่วนของ method ที่มีชื่อว่า -(id) copyWithZone: (NSZone*) zone แล้วภายใน method ก็ประกาศตัวแปร และก็ copy ค่าต่างๆมาให้ยัง object ใหม่ที่ทำการประกาศ เพียงเท่านี้ก็เสร็จ แล้วครับ

    ลอง compile และ run ดูจะเห็นว่า myclass_B นั้นมีค่าเหมือนกับ myclass_A ทุกอย่าง

    งงตรงไหน ติชม คอมเม้นกันมาได้นะครับ

Ojective-C Programming – Thread II

หลังจากเขียน Thread พอเป็นแล้วก็ยังมีเรื่องเกี่ยวกับ Thread ที่ต้องรู้อีก อยู่อย่างหนึ่งก็คือ การ Synchronizes ระหว่าง Thread

อย่างที่บอกไปแล้ว ว่าการเขียนโปรแกรมโดยการใช้ Thread นั้นมีข้อดีมากมาย แต่ก็มีข้อเสียเหมือนกัน และหนึ่งในข้อเสียนั้นก็คือการเขียนโปรแกรมแบบที่มีหลายๆ thread นั้นทำให้โปรแกรมของเราซับซ้อนมากขึ้น และลำบากต่อการจัดการข้อมูลที่มีการใช้งานร่วมกันระหว่าง thread เช่นเป็นต้นว่า

ถ้าโปรแกรมที่เราเขียนขึ้นมีหลายๆ Thread โดยแต่ละ Thread นั้นมีการแชร์ข้อมูลร่วมกัน ถ้าหากเราไม่ได้เขียนโปรแกรมให้มันมีการควบคุมการทำงานของแต่ละ Thread หรือเขียนได้ไม่ดี อาจจะเกิดเหตการณ์ในลักษณะแบบนี้

สมมติว่า โปรแกรม ของเรามี 2 thread ที่มีการใช้ตัวแปรร่วมกันคือ Money และแต่ละ thread นั้นก็จะทำการอ่านค่า Money และเขียนผลลัพธ์ที่ได้จากการคำนวนใหม่ลงไป

  1. Thread A : อ่านตัวแปร Money ได้มีค่าเป็น 10$ และระหว่างการคำนวนนี้ ใช้เวลาประมาณ 10 นาโนวินาที
  2. และระหว่างที่ A กำลังคำนวนอยู่นั้น Thread B ก็ได้อ่านค่า Money เหมือนกัน ได้ค่า 10$ เหมือนกัน แต่ใช้เวลาในการคำนวนเพียงแค่ 5 นาโนวินาที
  3. เนื่องจาก ว่า B นั้นทำงานเสร็จก่อน A ถึง 5 นาโนวินาที. Thread B จึงทำการ เขียนค่า Money เข้าไปใหม่เป็น 30$
  4. เมื่อ A ทำงานเสร็จ A ก็เลยเขียนค่า Money ที่ได้จากการคำนวนใหม่เป็น 15$

**** จะเห็นว่าค่าผลลัพธ์ที่ได้นั้น มันเกิดการ เขียนทับกันขึ้นมา ลองจินตนาการว่า สมมติว่า มี คนสองคนทำการฝากเงิน ในเวลาพร้อมๆกัน เข้าบัญชีเดียวกัน ขณะที่นาย A กำลังฝากเงินต้องใช้เวลาในการประมวลเสร็จใช้เวลา 10 วินาทีและในระหว่างนี้ นาย B ก็ฝากเงินมาเหมือนกัน แต่ใช้เวลา 5 วินาที ถ้าหากเราไม่มีการจัดการตรงนี้ นั่นก็แปรว่า เมื่อข้อมูลในบัญชี update จากการฝากเงินของ B ก็จะถูกเขียนทับด้วย ข้อมูลใหม่ที่นาย A ได้ฝากเข้ามา นันก็แปลว่า ข้อมูลเงินฝากนั้นผิดพลาด สิ่งที่ควรจะเป็นก็คือ ต้องรอให้ A ทำงาน เสร็จก่อน แล้ว B จึงอ่านข้อมูล ****

ลองมาเขียนโปรแกรมสักโปรแกรม โดยโปรแกรมนี้มี 2 Thread โดยที่
thread เป็นการเพ่ิมจำนวน member ใน array, ส่วนอีก thread นั้นทำหน้าที่ดึงข้อมูลจาก array แล้วทำการเปลี่ยนแปลงค่า

ลองดูโปรแกรม ข้างล่างนี้

และจากการ Compile & Run โปรแกรม ผลลัพธ์ที่ได้จะมีเป็นประมาณนี้

0 Hello World!!
1 hello World!!
.
.
8 Hello World!!
9 Hello World!!
10 Hello World!! World!!
11 Hello World!! World!!
12 Hello World!! World!!
13 Hello World!! World!!
14 Hello World!! World!!
15 Hello World!!
16 Hello World!!
17 Hello World!!
18 Hello
.
.
98 Hello
99 Hello

จากผลลัพธ์จะเห็นว่า เนื่องจาก Thread ทั้งสองคือ thread ที่เรียกใช้งาน AddMember ที่ทำหน้าที่เพิ่มจำนวน member ใน m_array  และ อีก thread ทำหน้าที่ AddMoreText ทำหน้าที่เรียกใช้ ตัวแปร m_array และแก้ไขค่า ในแต่ละ member ของ m_array ซึ่งสอง thread ทำงานแยกจากกันและเริ่มทำงานพร้อมกัน

ดังนั้น ในขณะที่ Thread ทั้งสองทำงานและมีการใช้งานข้อมูลร่วมกัน โดยไม่มีการ synchronize ข้อมูลระหว่างกันว่า m_array ว่าตอนนี้ข้อมูลเป็นอย่างไร จึงเป็นเหตุทำให้โปรแกรมทำงานผิดพลาดได้ ( มันควรจะเป็น Hello World!! ทั้งหมด ไม่ใช่ Hello World!! World!! หรือ Hello อย่างเดียว )

วิธีการแก้ปัญหา หรือป้องกัน สิ่งที่เกิดขึ้น มักนิยมใช้ คือ การ Lock ตัวแปร หรือ code ให้ทำงานได้ทีละ Thread เพื่อกันไม่ให้ Thread อื่นๆเข้าใช้จนกว่าจะใช้งานเสร็จ

NSLock

เราจะใช้ class ที่ชื่อ NSLock เพื่อจะเข้ามาช่วยในการจัดการ การทำงานของแต่ละ thread โดยมันจะทำหน้าที่ lock code ในส่วนที่ต้องการให้สามารถเข้าทำงานได้ทีละ Thread ไว้ ส่วน thread อื่นๆ ก็ต้องทำการรอ จนกว่า จะมีการ unlock เกิดขึ้น

การใช้งาน NSLock นั้น ก็เพียงแค่ประกาศ NSLock ขึ้นมา และเรียกใช้ lock หรือ unlock เท่านั้นเอง

จากตัวอย่าง code ข้างบน ก็แก้ไข ส่วนของ interface โดยการเพิ่มตัวแปร NSLock และในส่วนของ method ก็ทำการ เรียกใช้ lock และ unlock

ส่วน Method ก็ทำการแก้ไขได้เป็น

เพียงเท่านี้ก็แก้ปัญหาได้แล้ว สำหรับผลลัพธ์ ที่่ออกมาก็คือ

0 Hello World!!
1 Hello World!!
.
.
98 Hello World!!
99 Hello World!!

จะเห็นว่า จะไม่เกิดการซ้อนทับ เหมือนกับ ตัวอย่างแรก

@synchronized

ยังมีอีกวิธี ที่สามารถทำการ lock ในส่วนที่ต้องการได้ ถ้าไม่ต้องการใช้ NSLock เราเพียงแค่เขียน code ที่ต้องการจะ lock ไว้ภายในส่วนของ @synchronized { } ก็ทำได้เหมือนกัน

เป็นต้นว่า

ก็ติดปัญหาอะไร ถามได้น่ะครับ

Ojective-C Programming – Thread

ก็ยังคงต่อเนื่องด้วย Objective-C เหมือนเดิม ว่าจะขึ้น cocoa มา 3 รอบแล้วก็ยังไม่ได้ขึ้นสักที บอกตั้งแต่ iPhone SDK เพิ่งจะออกจนตอนนี้มันเป็น Beta 6 ไปละ หวังไม่โกรธกันนะครับ เอาละวันนี้เข้าเรื่องเลยดีกว่า

Thread

คืออะไร ?  ก็อธิบายได้ง่ายๆว่า มันคือส่วนย่อยๆของ Process หรือชุดคำสั่งนั่นเหละ โดยทำงานแยกจากกัน โดยปกติแล้วโปรแกรมที่เขียนขึ้นมาง่ายๆมักจะเป็นลักษณะ 1 thread หรืออาจจะเรียกได้ว่า “Single Thread” ถ้ามีหลายๆ thread ก็เรียกว่า “Multithread” สำหรับโปรแกรมใหญ่ๆแล้วจะมีการใช้ thread มากกว่า 1

การใช้งาน thread มีข้อดีหลายอย่างตั้งแต่ การใช้ทรัพยาการร่วมกัน การสนับสนุนการทำงานของ multiprocessor และอื่นๆอีกมากมาย สำหรับใครที่ไม่เคยรู้เรื่อง thread นั้นแนะนำให้ไปอ่านเพิ่มเติม

http://www.thaiall.com/os/os04.html

http://en.wikipedia.org/wiki/Thread_(computer_science)

เมื่อพอเข้าใจเบื้องต้นแล้ว เข้าสู้เนื้อหาของเราเลยดีกว่า ว่าเราจะสร้าง Thread และใช้งานมันได้อย่างไร

NSThread

ใน objective-c นั้นสามารถทำได้ตั้งแต่การเรียกใช้ thread ของ ภาษา c เช่น pthread แต่ไหนๆเราจะเขียน objective-c กันแล้ว จะไปใช้ pthread กันทำไม เพราะใน objective-c เองนั้นก็มี class ที่อำนวยความสะดวกในการสร้าง thread อยู่แล้วนั่นคือ NSThread

การสร้าง thread นั้นทำได้ อยู่ 2 วิธีคือ

  • เรียกใช้ class method ที่มีชื่อว่า detachNewThreadSelector:toTarget:withObject:

การสร้าง thread ด้วย detachNewThreadSelector แบบนี้เราไม่จำเป็นต้องประกาศ ตัวแปรอะไรเลย จะมีก็แต่ parameter ที่ต้องใส่เข้าไป มาดูการประกาศ thread อีกแบบกันเลย

  • สร้าง NSThread ขึ้นมาแล้วเรียก start

ส่วนแบบวิธีนี้ต่างจากอันแรกคือ เราสร้างตัวแปร NSThread ขึ้นมาแล้วก็ใส่ค่า parameter ให้กับมัน หลังจากนั้นก็เรียก start สำหรับการสร้างแบบนี้มีข้อดี ต่างจากอย่างแรกก็คือ เราสร้างไว้ก่อนแล้วค่อยเรียกให้ thread start ทีหลังได้

ก็หลังจากสร้างเป็นแล้ว มาดูการใช้งาน กันเลยดีกว่า

สมมติว่า เราจะเขียนโปรแกรม ที่เอาไว้เขียน console แบบง่ายๆกัน แต่เป็นแบบ หลายๆ thread

จากโปรแกรม ข้างบน อธิบายคร่าวๆนะครับว่า เราประกาศ  class ชื่อ Console ขึ้นมาโดยที่มี method  เพียงแค่ PrintToConsole โดยการทำงานของ method นี้ก็คือให้พิมพ์ข้อความทั้งหมด 5 ครั้ง

หลังจากนั้นก็ทำการประกาศ ตัวแปร Console ขึ้นมาอีก 3 โดยแต่ละ object นั้นก็ทำการพิมพ์ข้อความออกมาไม่เหมือนกันนั่นก็คือ Hello , Sample , Ok

ลำดับต่อไปก็ทำการ สร้าง Thread ขึ้นมา โดยจากตัวอย่าง ผมได้ทำการใช้วิธีการประกาศทั้ง 2 แบบคือทั้งแบบ ประกาศ ตัวแปร NSThread ขึ้นมาก่อน แล้วค่อยเรียก Start และอีกแบบก็คือไม่ต้องประกาศ ตัวแปร แต่เรียกใช้ class method ขึ้นมาเลย

ยังมีอีก method ที่น่าสนใจก็คือ sleepForTimeInterval โดยทำหน้าที่คือ หยุดการทำงานตามเวลาที่กำหนด สาเหตุก็เพราะว่า เนื่องจากว่า Thread นั้นทำงานแยกกัน อาจจะมีบาง Thread ที่ทำงานเสร็จก่อน หรือเสร็จหลัง Main Program ก็ได้ ฉนั้นจึงทำการรอให้ แต่ละ Thread ทำงานเสร็จก่อน แล้วค่อยจบโปรแกรม เพื่อที่ว่าป้องกันปัญหา Memory Leak หรือโปรแกรมทำงานผิดพลาด

และหลังจาก compile และ run แล้วผลลัพธ์ที่ได้ จะได้ประมาณแบบนี้

จะเห็นว่า ลำดับ แต่อันจะไม่เรียงกัน อาจจะสลับกันไปมา ก็เนื่องจากว่า Thread แต่ละอันนั้นทำงานแยกจากกัน

*** ข้อระวังสำหรับการใช้ Thread ใน Objective-C ***
ใน Thread ถ้ามีการประกาศตัวแปร หรือว่าใช้งานในลักษณะที่ต้องมีการจอง memory ต้องมี NSAutoreleasePool ด้วยเสมอ

เช่น

– (void) PrintToConsole : NSString* text  ถ้าเราจะเปลี่ยน code การทำงานให้มันพิมพ์ออกที่หน้า console เหมือนกันแต่เราไม่อยากใช้ NSLog ก็อาจจะเขียนใหม่ได้ว่า

การเขียนแบบนี้เวลา run จะเกิด error น่ะครับ เพราะว่า [text UTF8String] มันจะไปจอง memory ใหม่แล้วส่งค่า char* กลับมา ฉนั้น ถ้าไม่มี NSAutoreleasePool มันจะ error

การเขียนที่ถูกต้องควรจะเป็น

สำหรับ tutorial นี้จริงๆผมกะว่าจะเป็น guide ง่ายๆสำหรับคนที่รู้เรื่อง thread อยู่แล้ว ก็ถ้าใครยังไม่เข้าใจ ต้องการรู้เรื่องเกี่ยวกับ thread เพิ่มมากขึ้นก็แนะนำให้ไปอ่าน link ที่ข้างบน แล้วก็ค้นหาจาก google ก็ได้ครับ

ครั้งหน้า ก็ยังอยู่กับ objective-c น่ะครับ ส่วน source สำหรับ วันนี้ก็โหลดได้ที่นี่เลย