Objective-C Programming Chapter 10 (Part2)

Protocols

ในบางครั้งการกำหนดข้อตกลงร่วมกันระหว่างสิ่งต่างๆเป็นเรื่องจำเป็น ยกตัวอย่างเช่นอุปกรณ์ที่เชื่อมต่อกับคอมพิวเตอร์ได้ต้องเชื่อมต่อผ่าน usb port เป็นต้น ข้อตกลงระหว่างคลาสต่างๆก็เป็นสิ่งจำเป็น เช่นสมมติว่าเราเขียนคลาสเพื่อแสดงข้อมูลแบบกราฟ แต่ต้องการให้คลาสอื่นเป็นผู้ให้ข้อมูลกับกราฟนี้ได้ ดังนั้นเราจึงต้องตั้งข้อตกลงว่าผู้ให้ข้อมูล จะต้องมีเมธอดมาตรฐานอะไรบ้าง เพื่อให้กราฟสามารถติดต่อขอข้อมูลได้ เช่น จำนวนของแท่งกราฟทั้งหมด , สีของกราฟ , ความสูงของกราฟแต่ละแท่ง เป็นต้น
โปรโตคอลในภาษา Objective-C ก็คือการกำหนดเมธอดที่ใช้ติดต่อร่วมกันระหว่างคลาส การประกาศ protocol ทำได้ง่ายมากเพียงแค่ใช้ @protocol และตามด้วยชื่อของ protocol หลังจากนั้นก็ประกาศเมธอดเหมือนที่เคยทำใน interface และปิดท้ายด้วย @end เช่นกันเดียว ดังตัวอย่าง

เมื่อประกาศ protocol แล้ว คลาสที่ต้องการจะร่วมใช้งาน protocol ต้องประกาศว่าคลาสนั้นเข้ากันได้กับโปรโตคอล ( conform protocol ) โดยการเพิ่มด้วยเครื่องหมาย < ตามด้วยชื่อโปรโตคอล และปิดท้ายด้วย >

ในกรณีที่ต้องการให้คลาสรองรับหลายโปรโตคอลสามารถใช้เครื่องหมาย , คั่นระหว่างชื่อ ได้ดังเช่นตัวอย่าง

หลังจากประกาศว่าคลาสเข้ากันกับโปรโตคอลแล้วก็ต้องเขียน implementation เมธอดของโปรโตคอลนั้นด้วย

เราจะพบเห็นการประกาศโปรโตคอลใน Foundation Framework หลายๆคลาส โดยเฉพาะเมื่อต้องเขียนโปรแกรมด้วย iOS ยกตัวอย่างโปรโตคอลที่ใช้บ่อยๆก็เช่น NSCoding ซึ่งเป็นคลาสที่ใช้ในการแปลงออบเจ็กต์ให้เป็นข้อมูลรูปแบบอื่น (เราจะได้ใช้ NSCoding อย่างละเอียดในบทที่เกี่ยวกับ File และ Archiving) โปรโตคอล NSCoding ได้ประกาศไว้ใน NSObject.h ถ้าหากเปิดดูจะเห็นการประกาศโปรโตคอล NSCoding ดังนี้

สิ่งที่โปรโตคอลได้กำหนดคือเมธอด initWithCoder: และ encodeWithCoder: หมายความว่าหากเราจะประกาศคลาสที่เข้ากันได้หรือรองรับโปรโตคอลนี้ คลาสที่เราประกาศนั้นจำเป็นต้องเขียน implementation ของทั้งสองเมธอด (หากไม่เขียนก็สามารถคอมไพล์ผ่านได้ แต่ XCode จะแจ้ง warning เตือน)

Optional method

การเขียนโปรโตคอลอนุญาติให้มีเมธอดที่ไม่จำเป็นต้องเขียน implementation และเรียกเมธอดนั้นว่าเป็น optional method การจะประกาศเมธอดนั้นว่าเป็น optional ทำได้อย่างง่ายเพียงแค่ประกาศ @optional และตามด้วยเมธอดที่ต้องการดังเช่นตัวอย่าง

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

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

หลังจากสร้างโปรเจคใหม่แล้วให้ สร้างไฟล์ใหม่โดยเลือกให้เป็น Objective-C Protocol ดังรูป พร้อมกับตั้งชื่อโปรโตคอลว่า BarGraphDataSource

ch10_pro

เสร็จแล้วจะได้ไฟล์ BarGraphDataSource.h เพื่อใช้สำหรับการประกาศโปรโตคอล จากนั้นก็ให้เขียนโค้ดส่วนของ protocol โดยเพิ่มเมธอดดังนี้

Program 10.3

BarGraphDataSource.h

จากนั้นให้สร้างคลาสใหม่โดยตั้งชื่อ BarGraph และเขียนประกาศสมาชิกและเมธอดของคลาส ดังนี้

BarGraph.h

สิ่งที่ไม่คุ้นตาสำหรับการประกาศสมาชิกของคลาส BarGraph ก็คือ

บรรทัดนี้หมายความว่าเราประกาศตัวแปรชื่อ _dataSource ให้เป็นแบบ id ที่ใช้แทนออบเจ็กต์ใดๆก็ได้แต่เราได้กำหนดคุณสมบัติของออบเจ็กต์นี้เพิ่มว่าต้องสามารถรับรอง BarGraphDataSource ได้ หรือพูดอีกอย่างได้ว่า _dataSource ต้องมีเมธอดที่โปรโตคอล BarGraphDataSource กำหนดไว้นั่นเอง

BarGraph.m

เมธอด drawGrap มีจุดประสงค์เพื่อใช้สำหรับการวาดกราฟแท่งแนวนอนอย่างง่าย  เมธอด conformsToProtocol: ในบรรทัดที่ 8 ใช้ตรวจสอบว่า _dataSource นั้นรองรับโปรโตคอลที่กำหนดไว้หรือไม่หลังจากตรวจสอบเสร็จเรียบร้อย ต่อไปก็เป็นโค้ด loop สำหรับของการแสดงผล จำนวนของกราฟแท่งจะเป็นไปตามค่าที่ได้จากการจากการส่งแมสเซจ numberOfBar ไปยัง _dataSource และส่ง valueForBar เพื่อขอขนาดความยาวของแต่ละกราฟ ต่อไปเราจะเขียนคลาสเพื่อใช้เป็น data source โดยให้ชื่อคลาสว่า FileData

FileData.h

เพื่อประกาศว่าคลาสนี้รองรับโปรโตคอล BarGraphDataSource ก็ต้องเขียน <BarGraphDataSource> ต่อท้าย เมื่อประกาศว่ารองรับแล้วสิ่งที่ต้องทำต่อไปก็คือเขียน implementation เมธอดที่โปรโตคอลกำหนด รวมถึงเมธอดของตัวคลาสเอง

FileData.m

เราได้เขียนเมธอดของคลาส และเขียนเมธอดที่โปรโตคอลกำหนดนั่นคือ numberOfBar และ ValueForBar: การเขียนเมธอดทั้งสองนี้โปรโตคอลไม่ได้กำหนดว่าจะทำงานอย่างไร เพียงแต่กำหนดพารามิเตอร์ และค่าที่ต้องส่งกลับมาเท่านั้น ดังนั้นแล้วเราจึงมีอิสระในการเขียนตามใจชอบ ในโค้ดตัวอย่าง คลาส FileData ได้เก็บข้อมูลของกราฟไว้ในอาเรย์ เราจึงส่งจำนวนกราฟตามจำนวนสมาชิกของอาเรย์ และขนาดของความยาวของกราฟตามค่าที่ถูกเก็บไว้ในอาเรย์ เมื่อเขียนคลาสนี้เสร็จแล้วเราจะประกาศคลาสเพื่อใช้เป็น data source เพิ่มอีกหนึ่งคลาสนั่นคือ NetData

NetData.h

NetData.m

คลาส NetData ก็ต้องเขียนเมธอดที่โปรโตคอลกำหนดเช่นเดียวกันกับคลาส FileData การทำงานต่างๆของคลาสนี้ก็คลายกันยกเว้นส่วนที่ใช้กำหนดค่ากราฟในอาเรย์ โดยจะทำผ่านเมธอด loadInternetData: ที่สมมติว่าโหลดข้อมูลจากอินเทอเน็ต ลำดับต่อไปเราจะเขียนโปรแกรมขึ้นมาทดสอบ

main.m

โปรแกรมเริ่มด้วยการประกาศออบเจ็กต์ของคลาสที่ได้เขียนไปทั้งสามคลาส ต่อมาก็เพิ่มข้อมูลให้กับอ๊อบเจ็ก netDataSource โดยการเรียก loadInternetData: เมื่อมีข้อมูลพร้อมแล้วต่อไปต่อไปคือกำหนด data source ให้กับกราฟ

โค้ดในบรรทัด 21 ได้กำหนดให้ fileDataSource เป็น data source ของ barGraph และเรียกเมธอด drawGraph เพื่อให้วาดกราฟ เมื่อวาดเสร็จเราก็ได้เปลี่ยน data source ใหม่ให้เป็น netDataSource และสั่งให้วาดกราฟเช่นเดิม

ผลลัพธ์ของโปรแกรมก็จะได้ดังนี้

Program 10.3 Output
—– File data —–
0 |XXXXXX
1 |XXXX
2 |XXXXXXXX
3 |XXXXXXX
— Internet data —
0 |XX

1 |XXXXX
2 |XXXXXXXXXX

จากโปรแกรมที่ได้เขียนไปจะเห็นว่าคลาส Bargraph มีความยืดหยุ่นสูงมาก เพราะสามารถเปลี่ยนใช้ data source ที่เป็นคลาสใดๆก็ได้ เพียงแค่คลาสนั้นต้องรองรับโปรโตคอลที่กำหนด ถ้าศึกษาเพิ่มเติมเกี่ยวกับ iOS และ Cocoa จะพบเห็นรูปแบบการใช้ data source นี้อยู่บ่อยๆในคลาสที่ใช้ในการแสดงผล (GUI) เช่น การแสดงผลด้วยตารางเป็นต้น ถ้าพิจารณากันดีๆ จะพบว่าการใช้ protocol ช่วยให้การออกแบบคลาสมีความยืดหยุ่นและเพิ่มประสิทธิภาพการนำโค้ดกลับมาใช้ใหม่ได้อย่างดี เช่น หากจะเพิ่มคลาสแสดงกราฟแบบสี ถ้าออกแบบให้กราฟใหม่มี data source ที่ใช้ได้กับ BargrapDataSource เราก็จะใช้คลาสที่เป็น data source เดิมได้ทันที

Delegation

การประยุกต์ใช้โปรโตคอลที่พบได้บ่อยมากๆอีกอย่างคือ delegate pattern รูปแบบของ pattern นี้จะพบได้บ่อยมากใน iOS และ Cocoa เช่นเดียวกันกับ data source เพราะแนวความคิดและหลักการทำงานของ delegate ก็เหมือนกับ data source ที่เราได้เขียนกันไปแล้ว แต่สิ่งที่ต่างกันคือ data source จะเน้นไปยังการเป็นผู้ให้ข้อมูล ส่วน delegate จะเน้นไปยังการควบคุม เช่นการควบคุม interface เพื่อให้เข้าใจภาพชัดเจนขึ้น ดูภาพประกอบch10_dele

คลาส TableView ที่ใช้แสดงตารางได้ส่งเมสเซจ cellForRow: ไปยัง delegate เพื่อถามว่าตารางแถวที่ 4 นี้ cell ที่ใช้ในการแสดงผลมีหน้าเป็นอย่างไร จากนั้น delegate ก็ส่ง cell ที่ต้องใช้ในการแสดงผลสำหรับแถวนั้นกลับมาตามรูป หรือ เมื่อผู้ใช้สัมผัสแถวที่ 2 ของตาราง TableView ก็จะส่งเมสเซจถาม delegate อีกว่าต้องการให้ทำหรือไม่ เมื่อ delegate ได้รับเมสเซจก็ตอบ presentEQ กลับมาเพื่อบอกให้ TableView แสดงหน้าปรับ EQ นั่นเอง เห็นได้ว่าคลาส TableView นั้นยืดหยุ่นมากๆ เพราะการแสดงผลแต่ละแถวก็ขึ้นอยู่กับ delegate คลาส TableView มีหน้าที่แค่เอา cell นั้นมาจัดวางในแถว ถ้าต้องการเปลี่ยนการแสดงผลก็เปลี่ยนแค่ delegate และเมื่อมีเหตุการณ์ต่างๆเกิดขึ้นคลาส TableView ก็จะส่งต่อให้ delegate เป็นคนจัดการ หากจะยกตัวอย่างในชีวิตประจำวันให้เห็นภาพ ก็เป็นต้นว่า สมมติผู้รับเหมาจ้างช่างมา 3 คน โดยคนแรกเป็นคนก่อกำแพง ส่วนอีกสองคน คือช่างทาสีและช่างวอลเปเปอร์ จะเห็นว่าช่างปูนทำหน้าที่ก่อกำแพงอย่างเดียวโดยไม่ต้องสนใจว่ากำแพงออกมาหน้าตาอย่างไร เมื่อกำแพงเสร็จหากอยากได้สีฟ้า ก็ให้ช่างทาสีเป็นคนจัดการต่อ แต่ถ้าเกิดเปลี่ยนใจอยากติดวอลเปเปอร์ก็ให้ช่างอีกคนทำให้ ช่างทาสีและติดวอลเปเปอร์ก็เหมือนผู้ช่วยหรือ delegate นั่นเอง

เราจะสร้างโปรเจคใหม่ขึ้นมาเพื่อเขียน delegate ง่ายๆกันสักโปรแกรม โดยโปรแกรมที่จะเขียนต่อไปนี้จะเป็นการจำลองการเชื่อมต่อ Internet เพื่อโหลดข้อมูล โดยโปรแกรมจะประกอบไปด้วยคลาส NetworkContoller เพื่อใช้เป็นตัวจำลองการติดต่อ internet และเราจะเขียนคลาสที่เป็น delegate เพื่อใช้จัดการกับข้อมูลที่คลาส NetworkController ได้โหลดมา โดยมีแผนภาพคร่าวๆดังนี้

ch10_net

Program 10.4

NetworkDelegate.h

ในส่วนของโปรโตคอล NetworkDelegate นั้นประกาศเมธอดด้วยกัน 4 เมธอด โดยเมธอดแรก connectSuccess: ใช้สำหรับบอกสถานะการเชื่อมต่อว่าเชื่อมต่อสำเร็จ และมีเมธอด connectFail: สำหรับกรณีเชื่อมต่อผิดพลาด ส่วน receiveData: จะใช้สำหรับจัดการข้อมูลที่ได้จากอินเทอร์เน็ต และสุดท้ายคือ authentication เป็น optional method

NetworkController.h

คลาส NetworkController ประกาศเมธอด startConect เพื่อจำลองเหตุการณ์เชื่อมต่อกับอินเทอร์เน็ต และมี class variable เป็นแบบ id โดยระบุว่าต้องเข้ากันได้กับโปรโตคอล NetworkDelegate

NetworkController.m

เพื่อความสมจริงในการจำลอง โค้ดส่วน startConnect ใช้ฟังก์ชั่น arc4random_uniform เพื่อสุ่มค่าตั้งแต่ 0 – 100 หากค่าที่ได้เกิน 50 ก็จะถือว่าเชื่อมต่อสำเร็จ เมื่อเชื่อมต่อสำเร็จแล้วในโค้ดบรรทัดที่ 12 จะส่งเมสเซจถาม delgate ว่าได้เขียนเมธอด authentication รองรับไว้หรือไม่ ถ้าหากมีก็จะเรียกเมธอดใหทำงาน แต่ในตัวอย่างโปรแกรมของเราไม่ได้เขียนเมธอดนี้ไว้

เมื่อตรวจสอบเรียบร้อยแล้ว ก็จะส่งสถานะการเชื่อมต่อด้วยเมธอด connectSuccess: พร้อมกับรหัสการเชื่อมต่อ 200 ลำดับต่อมาในบรรทัดที่ 18 เป็นส่วนการทำงานสำคัญ โค้ดส่วน for loop จะหยุดทำงานทุก 1 วินาทีเพื่อจำลองเหตุการณ์ว่ากำลังโหลดข้อมูลจาก internet เมื่อได้ข้อมูลเรียบร้อยจะส่งเมสเซจ receiveData: ให้กับ delegate พร้อมกับข้อมูลที่ได้มา ส่วนสุดท้ายก็คือส่วนที่ใช้จัดการในกรณีที่เชื่อต่อผิดพลาดโดยจะส่งรหัส 404 กลับไป

ต่อไปเราจะเขียนโค้ด MyDelegate ที่เป็นตัวประมวลผลข้อมูล

 MyDelegate.h

MyDelegate.m

โค้ดในการประมวลข้อมูลของคลาส MyDelegate แค่แสดงผลทาง console ด้วยคำสั่ง NSLog  และในขั้นตอนสุดท้ายคือเขียนโค้ดของโปรแกรม

เริ่มโปรแกรม เราได้กำหนดให้ myDelegate เป็น delegate ของ netController จากนั้น netController ก็จะจำลองการเชื่อมต่อกับอินเทอร์เน็ตด้วยคำสั่ง startCoonect ออบเจ็กต์ netController ไม่ได้เป็นคนจัดการกับข้อมูลโดยตรงแต่ ผลของการเชื่อมต่อและข้อมูลต่างๆจะถูกส่งไปยัง myDelgate ให้จัดการแทน  เมื่อโปรแกรมทำงานก็จะได้ผลดังนี้

Program 10.4 Output ( > 50 )
Connect success with code 200
Received 0
Received 1
Received 2
Received 3
Received 4
Received 5

Program 10.4 Output ( <= 50 )
Connect fail with code 404

โปรแกรมที่ได้เขียนไปเป็นเพียงจำลองการเชื่อมต่ออินเทอร์เน็ท ไม่ได้เชื่อมต่อจริงๆแต่ประการใด อย่างไรก็ตามเป้าหมายที่แท้จริงของโปรแกรมคือให้เราได้ศึกษาและทดลองเขียน delegate pattern เพราะเป็นคอนเซ็ปสำคัญมาก ยิ่งถ้าต้องเขียน iOS หรือ Mac Application ด้วยแล้ว data source และ delegate เป็นสิ่งต้องเจออย่างแน่นอน

Objective-C delegate , C# delegate , Java Listener

สำหรับผู้ที่เคยเขียนโปรแกรมด้วย C# อาจจะเกิดความสับสนระหว่าง delegate ในภาษา C# กับ delegate ของภาษา Objective-C ในภาษา C# การทำงานของ delegate จะเหมือนกับ function pointer ในภาษา C
แต่สำหรับภาษา Objective-C จะต่างออกไป เพราะ delegate นั้นเป็นออบเจ็กต์ที่ทำงานให้กับออบเจ็กต์อื่นๆ เช่นโปรแกรมที่เพิ่งได้เขียนไป คลาส MyDelegate จะเป็นตัวที่ทำหน้าที่ประมวลผลข้อมูลแทนคลาส NetworkController
และสำหรับผู้เคยเขียน Java มาก่อนอาจจะมองว่า delegate ทำหน้าที่คล้ายกับ java listener แต่ทั้งสองแตกต่างกันเพราะ delegate เป็นเพียงออบเจ็กต์ผู้ช่วยเท่านั้นไม่ได้เป็น sub class เหมือนกับ java listener / observer
นอกจากนี้ java listener ยังไม่มีการทำงานแบบ @optional


หากไม่กำหนดภาษา Objective-C จะกำหนดให้เป็น @required โดยอัตโนมัต

โหลด PDF ไปอ่านได้ครับ ส่วน source code ก็ที่ github

One thought on “Objective-C Programming Chapter 10 (Part2)”

Leave a Reply