Objective-C Programming Chapter 19 (Part2)

NSOperation

NSOperation นี้เปรียบเสมือนหนึ่งหน่วยของการทำงาน แต่เนื่องจากคลาส NSOperation เป็น abstract class ดังนั้นจึงไม่สามารถใช้คลาสนี้ได้โดยตรง ต้องประกาศซับคลาสขึ้นมาใช้งาน หรืออาจจะใช้ซับคลาสของระบบ เช่น NSBlockOperation หรือ NSInvocationOperation ได้เช่นเดียวกัน จุดประสงค์หลักของคลาสนี้คือเตรียมฟังชั่นการทำงานบางอย่างที่จำเป็นของ task ไว้ให้ เช่น กำหนดลำดับความสำคัญ และบอกสถานะการทำงาน ซึ่งมีด้วยกันทั้งหมด 4 สถานะคือ ready, executing, finish และ cancel  อ็อบเจ็กของคลาสนี้มีลักษณะเป็นแบบ single-shot นั่นคือ ทำงานได้ครั้งเดียว เมื่อให้ทำงานไปแล้วโอเปอร์เรชันจะไม่สามารถนำกลับมาใช้ได้อีก

NSInvocationOperation

NSInvocationOperation เป็นซับคลาสของ NSOperation มีจุดประสงค์เพื่อใช้กับเมธอดของอ็อบเจ็ก ดังนั้นการประกาศอ็อบเจ็กของคลาสนี้ต้องกำหนด selector พร้อมกับอ็อบเจ็กที่ต้องการให้ทำงาน เช่นตัวอย่าง โปรแกรมต่อไปนี้

Program 19.5

NetworkController.h

NetworkController.m

จากโค้ดตัวอย่าง เมธอด downloadWithURL: นั้นทำหน้าที่เพียงแค่สร้าง operation object และได้กำหนด selector เป็น performDownload: ซึ่งเป็นเมธอดที่ใช้สำหรับดาวน์โหลดข้อมูล เมื่อโอเปอร์เรชันทำงานก็จะเรียกใช้เมธอดนี้นั่นเอง

main.m

โค้ดของโปรแกรมหลักได้ประกาศโอเปอร์เรชัน taskA , taskB ด้วยการเรียกเมธอด downloadWithURL โอเปอร์เรชันอ็อบเจ็กที่ได้จะอยู่ในสถานะพร้อมใช้ทำงาน จากนั้นเรียกเมธอด start เพื่อให้โอเปอร์เรชันทำงาน ซึ่งจะได้ผลลัพธ์ดังนี้

Program 19.5 Output

Download http://www.server.com/image_1.jpg done
Download http://www.server.com/image_2.jpg done

NSBlockOperation

คลาส NSBlockOperation จะใช้ block เพื่อกำหนดสิ่งที่ต้องการทำงาน โดยทั่วไปแล้วเมื่อสร้าง block operation จะกำหนด block ที่ต้องการตั้งแต่เริ่มแรก และยังสามารถเพิ่มเติมบล็อคได้อีกภายหลัง ในกรณีที่โอเปอร์เรชันมีหลายบล็อค สถานะการทำงานของโอเปอร์เรชันจะถูกเปลี่ยนเป็น finish ก็ต่อเมื่อบล็อคทั้งหมดได้ทำงานเสร็จแล้ว

Program 19.6

main.m

โปรแกรมประกาศโอเปอร์เรชัน blockA และ blockB ในส่วนของ blockA นั้นได้เรียกใช้ NSLog ในขณะที่ blockB เริ่มด้วยบล็อคของการดาวน์โหลดข้อมูล จากนั้นก็เพิ่มบล็อคที่ให้แสดงข้อความ Extra task done เมื่อให้โปรแกรมทำงานก็จะได้ผลลัพธ์ดังต่อไปนี้

Program 19.6 Output

Block A done
Block B download done
Block B extra task done

ถ้าหากให้โปรแกรมทำงานหลายๆครั้งจะเห็นว่า ่ลำดับการแสดงผลลัพธ์ของ blockB อาจจะแตกต่างออกไปแล้วแต่ว่าบล็อคใดจะทำงานเสร็จก่อน
เราจะหยุด NSOperation ไว้ตรงนี้ก่อน แล้วไปทำความเข้าใจคลาส NSOperationQueue กันสักนิด เนื่องจากเป็นคลาสที่เกี่ยวข้องกันกับ NSOperation โดยตรง

NSOperationQueue

จากชื่อของคลาสก็พอจะเดาได้ว่าคลาส NSOperationQueue นี้ก็คือคิวที่ใช้สำหรับควบคุมการทำงานอ็อบเจ็กของคลาส NSOperation นั่นเอง โดยลำดับการทำงานภายในคิวจะถูกจัดเรียงตามความสำคัญของโอเปอร์เรชันซึ่งจะได้เห็นในตัวอย่างภายหลัง เมื่อเพิ่ม operation เข้ามาในคิวเรียบร้อยแล้ว จะไม่สามารถที่จะลบออกจากคิวได้ อย่างไรก็ตามยังสามารถที่จะยกเลิกโอเปอร์ชันในคิวได้ แต่การยกเลิกโอเปอร์ชันนี้จะไม่สามารถยกเลิกทีละโอเปอร์เรชันได้ ต้องยกเลิก operation ทั้งหมดที่อยู่ในคิว เราจะเขียนโปรแกรมง่ายๆสักโปรแกรมเพื่อทดลองการใช้งาน NSOperationQueue

Program 19.7
main.m

โปรแกรมได้ประกาศโอเปอร์เรชันคิว aQueue หลังจากนั้นก็ได้เพิ่ม taskA ซึ่งเป็น NSInvocationOperation เข้าไปในคิวเป็นโอเปอร์เรชันแรก เมื่อเพิ่ม operation เข้ามาในคิว หากคิวว่างก็จะให้โอเปอร์เรชั่นเริ่มทำงานทันที และไม่ต้องเรียกเมธอด start แต่อย่างใด ในลำดับต่อมาก็เพิ่ม taskB ซึ่งเป็นบล็อคโอเปอร์เรชันเข้ามาในคิว หรืออาจจะใช้เมธอด addOperationWithBlock เพื่อเพิ่มโอเปอร์เรชันได้เช่นเดียวกัน และในช่วงท้ายของโปรแกรม ได้เรียกเมธอด wainUntilAllOperationAreFinished เพื่อให้คิวรอให้โอเปอร์เรชันทำงานจนครบทุกโอเปอร์เรชัน เพราะเนื่องจากโปรแกรมเป็นแบบ console ถ้าไม่รอให้โอเปอร์ชันทำงานเสร็จโปรแกรมก็จะปิดตัวลง เช่นเดียวกันกับโปรแกรมที่ผ่านมาที่ต้องมี while loop และในกรณีที่เขียนโปรแกรม iOS , Mac OSX การเรียกใช้เมธอดนี้ขึ้นอยู่กับความต้องการของโปรแกรมเมอร์ เมื่อโปรแกรมทำงานก็จะได้แสดงผลดังนี้

Program 19.7 Output

Download http://www.server.com/image_1.jpg done
Task C
Task B
All tasks are finished

Serial Queue & Main Queue

คลาส NSOperationQueue มีการทำงานแบบ concurrent จำนวนโอเปอร์เรชันที่ทำงานได้พร้อมกันนั้นจะขึ้นอยู่กับทรัพยากรของระบบ ในกรณีที่อยากให้คิวทำงานแบบ serial เช่นเดียวกับ dispatch queue สามารถทำได้ด้วยใช้เมธอด setMaxCurrentOperationCount  เพื่อจำกัดจำนวนของ operation ที่ทำงานพร้อมกันให้เป็น 1 ดังเช่นโค้ดต่อไปนี้

NSOperationQueue จะให้โอเปอร์ชันทำงานในเทรดของตัวเองที่ได้สร้างขึ้นมา หากต้องการให้โอเปอร์ชันทำงานที่ main thread ต้องใช้ Operation Queue แบบพิเศษซึ่งจะทำงานที่ main thread โดยสามารถเรียกใช้คิวได้ด้วยคลาสเมธอด mainQueue ดังตัวอย่างต่อไปนี้

Operation Dependency

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

Program 19.8
main.m

จากโปรแกรมจะเห็นว่าโอเปอร์เรชัน resize ได้กำหนด dependency เป็น download นั่นหมายถึงว่าโอเปอร์เรชัน resize จะทำงานก็ต่อเมื่อ download ทำงานเสร็จสิ้นก่อน ส่วน upload นั้นเป็นโอเปอร์ชันที่ทำงานแยกจากสอง operation นี้ ไม่ได้มีความเกี่ยวข้องกัน  เมื่อให้โปแกรมทำงานก็ได้ผลดังนี้

Program 19.8 Output

2014-02-03 02:02:56.239 Program 19.7[52011:1403] Upload
2014-02-03 02:02:56.239 Program 19.7[52011:1a03] Download
2014-02-03 02:02:56.241 Program 19.7[52011:1a03] Resize

จากผลลัพธ์จะเห็นว่า Resize นั้นจะแสดงผลตามหลัง Download ในกรณีที่ยกเลิกโอเปอร์เรชัน Download โอเปอร์เรชัน Resize ก็จะไม่ได้ทำงานไปด้วย

Operation Priority

ลำดับความสำคัญของโอเปอร์เรชันมีด้วยกันทั้งหมด 5 ลำดับ หากไม่กำหนดจะมีค่าเริ่มต้นเป็น normal ซึ่งเป็นค่าตรงกลางในห้าลำดับนี้ การกำหนด priority นี้ สามารถทำได้โดยใช้เมธอด setQueuePriority ดังเช่นตัวอย่างโค้ดต่อไปนี้

Program 19.9
main.m

Program 19.9 Output

1
4
2
3

ความสำคัญหรือ priority ของโอเปอร์เรชันมีผลต่อลำดับการทำงานในคิว หากคิวมีโอเปอร์เรชันที่อยู่ในสถานะพร้อมทำงานหลายโอเปอร์เรชัน คิวจะเลือกโอเปอร์เรชันที่มี priority สูงกว่าให้ทำงานก่อน อย่างไรก็ตามถ้าโอเปอร์ชัน priority ต่ำสถานะพร้อมทำงาน และอยู่ในคิวก่อนหน้าโอเปอร์เรชั่นที่สูงกว่า คิวก็จะเลือกเอาโอเปอร์ชันต่ำกว่าให้ทำงานก่อน ดังนั้นแล้วจะเห็นว่าแม้ว่า one จะมี priority ต่ำกว่า four แต่ก็ได้ทำงานก่อน เพราะ one เป็นโอเปอร์ชันแรกของคิว ซึ่งไม่มีโอเปอร์ชันอื่นอยู่ก่อนหน้านั้น ดังนั้นคิวจึงให้ one ทำงานได้เลย ส่วน two และ three ใส่เข้ามาทีหลัง คิวจึงจัดลำดับตามลำดับของ priority ที่ได้กำหนดไว้ ดังนั้นผลลัพธ์จึงแสดงดังที่เห็น

Summary

การเปลี่ยนมาใช้ dispatch queues และ NSOperationQueue แทนการใช้ threads แบบเดิมนั้นมีข้อดีหลายอย่าง อย่างแรกก็คือการใช้ dispatch queues นั้นง่ายกว่า เนื่องจาก dispatch queues ลดภาระในการเขียนโค้ดเทรด เช่นโค้ดที่ในการสร้างและบริหารการทำงานของเทรด อย่างที่สองคือการขยายระบบทำได้ง่ายกว่ามาก เพราะ GCD นั้นช่วยการจัดการบริหาร core ของ CPU รวมไปถึงทรัพยากรของระบบ อย่างที่สามก็คือการใช้ dispatch queues นั้นคาดเดาได้ ในกรณีที่เขียนเทรดด้วยตัวเอง หากมีการใช้ทรัยากรร่วมกันระหว่างเทรด เราไม่อาจจะคาดเดาการทำงานของเทรดได้เลยว่า เทรดใดจะเข้าใช้ทรัพยากรก่อนหรือหลัง หรืออาจจะเกิดกรณีทำงานพร้อมกัน  ดังนั้นเมื่อใช้เทรดสิ่งที่โปรแกรมเมอร์ต้องทำคือใช้ lock ป้องกันไม่ให้เทรดเข้าใช้ทรัพยาในเวลาเดียวกัน ไม่เช่นนั้นก็อาจจะเกิดปัญหา dead lock , race condition เป็นต้น ในขณะที่การใช้ dispatch queues นั้นง่ายกว่ามาก เพราะสามารถที่จะใช้ serial queues เพื่อกำหนดให้ทำงานทีละอย่างได้
ยังมีอีกหลายเรื่องเกี่ยวกับ GCD ที่หนังสือเล่มนี้ไม่ได้อธิบายการใช้งาน เช่น barrier จึงแนะนำให้ผู้อ่านควรศึกษาเกี่ยวกับ GDC เพิ่มเติมจากเอกสารของ Apple โดยตรง

 

โหลด PDF ไปอ่านกันได้ครับ

ส่วน Source code ตัวอย่างก็โหลดได้ที่ Github

Leave a Reply