chapter 10 · chapter 10 category ,protocol , delegate!...

17
Chapter 10 Category ,Protocol , Delegate ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโคด จากนั้นก็เพิ่มเติมเมธอดใหกับคลาส ขยาย ความสามารถออกไปเรื่อยๆ ดังเชนคลาส Product ในบทที่ผานมา ในชวงแรกอาจจะมีแคเมธอด setter/getter หลังจากนั้นเรา ก็ขยายความสามารถดวยการเพิ่มเมธอดตางๆเขาไป เชน เมธอดแสดงราคาที่รวมภาษีมูลคาเพิ่มเขาไปแลว เปนตน ในกรณี แบบนีการแกไขโคดเพิ่มเติมไมใชปญหาใหญสำหรับเรา เพราะวาเราเปนคนเขียนคลาส Product ขึ้นมาเอง สมมติวาเราอยากจะเขียนโคดเพิ่มเติมใหกับคลาส NSMutableString เชนมีเมธอดในการสลับตัวอักษรแบบกลับดาน จะเห็น วากรณีแบบนี้เกิดปญหาขึ้นมาทันที เนื่องจากเราเขาไปแกไขโคดของ NSMutableString ไมได เพราะวาสิ่งทีFramework ให เรามามีแค interface (header) และ source code ที่ผานการคอมไพลกลายเปนไฟลไบนารี่แลวเทานั้น ดังนั้นหากจะแกไข คลาส NSMutableString เราก็เพียงแคขอ source code จาก Apple เทาน้ันเอง แตวิธีนี้คงเปนไปไมไดแนนอน วิธีการแก ปญหาที่พอจะเปนไปไดก็คือสรางคลาสใหมโดยการ subclass จาก NSMutableString แลวเขียนเมธอดที่ตองการเขาไปใหมก็ แกปญหาไดเรียบรอยแลว แตวิธีการ subclass อาจจะไมใชคำตอบที่ดีที่สุด เพราะสิ่งที่เกิดขึ้นตามมาก็คือคลาสมีขนาดใหญ ขึ้นเรื่อยๆ เพื่อใหเห็นภาพชัดเจนขอยกตัวอยางงายๆเชนเปนตนวาคลาส Product ที่เราเคยไดเขียนไป เราสรางโปรเจคที่สอง ขึ้นมาใหมและเขียนเมธอดเกี่ยวกับการจัดการราคาสินคาเพิ่มเขาไปทั้งสิ้น 20 เมธอด หลังจากนั้นคลาสนี้ถูกใชตอในโปรเจคที3,4,5 แตทั้งสามโปรเจคนี้ไมไดใชเมธอดที่เพิ่มเติมเขามาเลย แตกลับตองมีโคดทั้ง 20 เมธอดเพิ่มเขา เพียงเพราะโปรเจคที่สอง จำเปนตองใช ไมเพียงแคโปรแกรมมีขนาดใหญขึ้น แตคลาส Product เองก็มีความซับซอนเพิ่มมากขึ้น Category นับวาเปนขอไดเปรียบของภาษา Objective-C ที่มีวิธีการเพิ่มความสามารถใหกับคลาสโดยไมตอง sub class ดวยวิธีการทีเรียกวาแคทิกอรี(Category) วิธีนี้เปนหนทางที่ชวยใหเราแกไขและเพิ่มเติมเมธอดของคลาสโดยไมตองไปยุงเกี่ยวกับโคด ตนฉบับเลย การประกาศแคทิกอรี่มีรูปแบบการประกาศดังนี@interface class_name ( category_name ) methods @end เราจะสรางโปรเจคขึ้นมาใหมเพื่อทำความเขาใจกับแคทิกอรี่โดยโปรเจคนี้จะยังใชคลาส Product จากโปรแกรม 8.7 เมื่อสราง โปรเจคใหมและเพิ่มคลาส Product เขามาเรียบรอยแลว ตอไปเราจะสราง category โดยการเลือกที่เมนู New > File หรือจะ กดเมาสขวาแลวเลือก New File ก็ไดเชนกัน จากนั้น XCode จะแสงหนาตางใหเลือก template สำหรับการสรางไฟลใหมให เลือก Objective-C category ดังรูป Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Upload: others

Post on 05-Jul-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

Chapter 10Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโคด จากนั้นก็เพ่ิมเติมเมธอดใหกับคลาส ขยาย

ความสามารถออกไปเรื่อยๆ ดังเชนคลาส Product ในบทที่ผานมา ในชวงแรกอาจจะมีแคเมธอด setter/getter หลังจากนั้นเราก็ขยายความสามารถดวยการเพ่ิมเมธอดตางๆเขาไป เชน เมธอดแสดงราคาที่รวมภาษีมูลคาเพ่ิมเขาไปแลว เปนตน ในกรณีแบบนี้ การแกไขโคดเพ่ิมเติมไมใชปญหาใหญสำหรับเรา เพราะวาเราเปนคนเขียนคลาส Product ขึ้นมาเอง สมมติวาเราอยากจะเขียนโคดเพ่ิมเติมใหกับคลาส NSMutableString เชนมีเมธอดในการสลับตัวอักษรแบบกลับดาน จะเห็นวากรณีแบบนี้เกิดปญหาขึ้นมาทันที เนื่องจากเราเขาไปแกไขโคดของ NSMutableString ไมได เพราะวาสิ่งที่ Framework ใหเรามามีแค interface (header) และ source code ที่ผานการคอมไพลกลายเปนไฟลไบนารี่แลวเทานั้น ดังนั้นหากจะแกไขคลาส NSMutableString เราก็เพียงแคขอ source code จาก Apple เทาน้ันเอง แตวิธีนี้คงเปนไปไมไดแนนอน วิธีการแกปญหาที่พอจะเปนไปไดก็คือสรางคลาสใหมโดยการ subclass จาก NSMutableString แลวเขียนเมธอดที่ตองการเขาไปใหมก็แกปญหาไดเรียบรอยแลว แตวิธีการ subclass อาจจะไมใชคำตอบที่ดีที่สุด เพราะสิ่งที่เกิดขึ้นตามมาก็คือคลาสมีขนาดใหญขึ้นเรื่อยๆ เพ่ือใหเห็นภาพชัดเจนขอยกตัวอยางงายๆเชนเปนตนวาคลาส Product ที่เราเคยไดเขียนไป เราสรางโปรเจคที่สองขึ้นมาใหมและเขียนเมธอดเก่ียวกับการจัดการราคาสินคาเพ่ิมเขาไปทั้งสิ้น 20 เมธอด หลังจากนั้นคลาสนี้ถูกใชตอในโปรเจคที่ 3,4,5 แตทั้งสามโปรเจคนี้ไมไดใชเมธอดที่เพ่ิมเติมเขามาเลย แตกลับตองมีโคดทั้ง 20 เมธอดเพ่ิมเขา เพียงเพราะโปรเจคที่สองจำเปนตองใช ไมเพียงแคโปรแกรมมีขนาดใหญขึ้น แตคลาส Product เองก็มีความซับซอนเพ่ิมมากขึ้น

Category

นับวาเปนขอไดเปรียบของภาษา Objective-C ที่มีวิธีการเพ่ิมความสามารถใหกับคลาสโดยไมตอง sub class ดวยวิธีการที่เรียกวาแคทิกอรี่ (Category) วิธีนี้เปนหนทางที่ชวยใหเราแกไขและเพ่ิมเติมเมธอดของคลาสโดยไมตองไปยุงเก่ียวกับโคดตนฉบับเลย การประกาศแคทิกอรี่มีรูปแบบการประกาศดังนี้

@interface class_name ( category_name )methods

@end

เราจะสรางโปรเจคขึ้นมาใหมเพ่ือทำความเขาใจกับแคทิกอรี่โดยโปรเจคนี้จะยังใชคลาส Product จากโปรแกรม 8.7 เมื่อสรางโปรเจคใหมและเพ่ิมคลาส Product เขามาเรียบรอยแลว ตอไปเราจะสราง category โดยการเลือกที่เมนู New > File หรือจะกดเมาสขวาแลวเลือก New File ก็ไดเชนกัน จากนั้น XCode จะแสงหนาตางใหเลือก template สำหรับการสรางไฟลใหมใหเลือก Objective-C category ดังรูป

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 2: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

เมื่อเลือกเสร็จแลวจะเจอหนาตางถามชื่อของ cateory ใหตั้งชื่อตามตองการ (โคดตัวอยางของโปรเจคนี้ตั้งชื่อวา MyExtend) และสวน category on ใหเลือกคลาส Product

เมื่อผานขั้นตอนนี้จะไดไฟลเพ่ิมขึ้นมาอีก 2 ไฟลคือ Product+MyExtend.h และ Product+MyExtend.m ดังรูป

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 3: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

เมื่อเปดดูไฟล Product+MyExtend.h ที่ XCode สรางใหก็จะเจอโคดลักษณะดังนี้

1 #import "Product.h"23 @interface Product (MyExtend)45 @end

โคดมีลักษณะคลายกับการประกาศคลาสทั่วๆไป ส่ิงที่แตกตางก็คือโคดสวนที่บอกวา sub class มาจากคลาสอะไรไดหายไป และมีโคดใหมเขามาแทนคือ (MyExtend) ซึ่งหมายถึงเราไดประกาศ category ใหกับคลาส Product โดยมีชื่อ MyExtend นั่นเอง เราจะเพ่ิมเติมเมธอดใหมเขาไปอีก 3 เมธอดดังนี้

Product+MyExtend.h1 #import "Product.h"23 @interface Product (MyExtend)45 -(float) priceIncludeVat;6 -(float) price;7 -(NSString*) name;89 @end

เมธอดที่เราไดประกาศเพ่ิมเติมคือแสดง ราคาสินคา ชื่อสินคา และ ราคาหลังจากรวมภาษีมูลคาเพ่ิม 7% จากนั้นเราก็จะเขียนโคดในสวนของ implementation ซึ่งมีโคดดังนี้

Product+MyExtend.m1 #import "Product+MyExtend.h"23 @implementation Product (MyExtend)45 -(float) priceIncludeVat6 {7 return _price + (_price * 0.07 );8 }9 -(float) price10 {11 return _price;12 }13 -(NSString*) name14 {15 return [NSString stringWithString:_name];16 }17 @end

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 4: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

โคด implementation ของแคทิกอรี่จะมีสวนที่เพ่ิมเติมเขามาพิเศษกวาการเขียนคลาสปกติก็คือ มี (MyExtend) ตอทายชื่อของคลาส เพ่ือบอกใหรูวาโคดในสวนนี้เปนแคทิกอรี่ของ MyExtend เมื่อทุกอยางครบแลวตอไปก็เขียนโปรแกรมขึ้นมาทดสอบ

Program 10.1

main.m1 #import <Foundation/Foundation.h>2 #import "Product+MyExtend.h"34 int main(int argc, const char * argv[])5 {67 @autoreleasepool {8 9 Product* iMac = [[Product alloc] init];10 11 [iMac setName:@"iMac" andPrice:40000];1213 NSLog(@"%@", [iMac name]);14 NSLog(@"price:%.2f", [iMac price]);15 NSLog(@"price(VAT):%.2f", [iMac priceIncludeVat]);16 17 [iMac release];18 19 }20 return 0;21 }

Program 10.1 Output iMacprice:40000.00price(VAT):42800.00

เมื่อโปรแกรมผานการคอมไพล โคดของ category จะไมไดเพ่ิมใหกับคลาสตอน compile-time แตจะเพ่ิมตอน run-time ดังนั้นเราจึงไมจำเปนตองคอมไพลโคดของคลาสที่เราไดเพ่ิมแคทิกอรี่เขาไป พูดอีกอยางคือเราไมจำเปนตองมี source code ตนฉบับ จากโปรแกรมที่ 10.1 จะเห็นวาเราสามารถเขียนเมธอดเพ่ิมเติมใหกับคลาส Product ไดโดยไมไดแกไขโคดเดิมเลย ขอดีอีกอยางของ category คือเมธอดที่เราเขียนเพ่ิมสามารถใชงานตัวแปรตางๆภายในคลาสได แตไมสามารถเพ่ิมได

Objective-C Category ไมสามารถที่จะเพิ่ม member variable ใหกับคลาสได ถาตองการจะเพิ่มตัวแปรใหใช sub class แทน

การใช category มีขอควรระวังคือ ถาเราประกาศเมธอดซ้ำกับเมธอดเดิมที่คลาสมีอยูแลว จะเปนการเขียนทับเมธอดเดิม เชนสมมติวาเราเพ่ิม setName:andPrice ซึ่งซ้ำกับเมธอดของคลาส

-(void) setName:(NSString*) name andPrice:(float) price;

และโคดสวน category implement ไดแกไขใหราคาเปน 2 เทา

-(void) setName:(NSString*) name andPrice:(float) price{ [_name setString:name]; _price = price * 2;}

เมื่อโปรแกรมทำงานก็จะไดผลลัพธดังนี้

iMacprice:80000.00price(VAT):85600.00

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 5: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

จากผลลัพธของโปรแกรมไดแสดงใหเห็นวาคลาสไดใชเมธอดที่เราเขียนใหมแทนเมธอดเดิม

การ Overring เมธอดของ sub class สามารถเรียกเมธอดเดิมผานทาง super ได แตการเขียนทับของแคทิกอรี่นั้นจะไมใชเรียกใช

เมธอดเดิมไดเลย นอกจากนี้ซับคลาสทั้งหมดจะไดรับแคทิกกอรี่ที่เพิ่มเขาไปดวย ฉะนั้นแลวหากเขียน category ใหกับคลาสที่เปน

Framework เชน NSObject ตองระวังใหมาก เพราะจะสงผลกระทบตอคลาสอื่นๆ

อยางไรก็ตามการเขียนทับเมธอดเดิมไมใชขอเสียเสมอไป เพราะเราสามารถใชใหเปนประโยชนได เชน กรณีที่เราตองการจะแก bug ของโปรแกรมก็สามารถที่จะใชประโยชนโดยการเขียนทับได ขอควรระวังอีกอยางในการใชเขียนโปรแกรมคือในกรณีที่มี 2 category และใชชื่อเดียวกัน โปรแกรมจะไมสามารถกำหนด category ที่จะใชงานได

เราจะลองเขียนโปรแกรมอีกสักโปรแกรมเพ่ือทำความเขาใจกับ category ใหมากขึ้น โดยครั้งนี้เราจะเพ่ิม category ใหกับ NSString เพ่ือใชหาวาสตริงนั้นเปน palindrome หรือไม ( palindrom คือสตริงอานจากซายหรือขวาก็จะเหมือนกัน เชน ABBA , LEVEL , OHO เปนตน )

Program 10.2

NSString+MyExtend.h1 #import <Foundation/Foundation.h>23 @interface NSString (MyExtend)4 -(BOOL) isPalindrome;5 @end

NSString+MyExtend.m1 @implementation NSString (MyExtend)23 -(BOOL) isPalindrome4 {5 6 for ( int begin = 0; begin < [self length]/2 ; begin++)7 {8 int end = (int)[self length] - 1 - begin;9 if([self characterAtIndex:begin] != [self characterAtIndex:end] )10 return NO;11 }12 return YES;13 }1415 @end

หลักการทำงานของเมธอด isPalindrome คือจะเปรียบเทียบตัวอักษรสองตัว ตัวแรกคือตัวอักษรที่อยูตำแหนงดานหนาของสตริง และตัวที่สองคือตำแหนงที่อยูดานทาย หากมีคาเทากันก็จะขยับตำแหนงทั้งสอง ใหเขาใกลตรงกลางของสตริง ทำเชนนี้ไปเรื่อยๆ จนกวาตำแหนงทั้งเทากับตรงกลางของสตริง

begin end

A A B A A

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 6: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

main.m1 #import <Foundation/Foundation.h>2 #import "NSString+MyExtend.h"34 int main(int argc, const char * argv[])5 {67 @autoreleasepool {8 9 NSString* text1 = @"AABAA";10 NSString* text2 = @"AACBAA";11 12 if( [text1 isPalindrome] == YES)13 NSLog(@"%@ is palindrome",text1);1415 if( [text2 isPalindrome] == YES)16 NSLog(@"%@ is palindrome",text2);17 18 }19 return 0;20 }

Program 10.2 Output AABAA is palindrome

จะเห็นไดวาไมไดมี source code ของ NSString เลย แตเราสามารถเขียนเมธอดเพ่ือใชในตรวจสอบ palindrome เพ่ิมใหกับคลาส NSString ได

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

@protocol BarGraphDataSource-(int) numberOfBar;-(int) valueForBar:(int)bar;@end

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

@interface SimpleData : NSObject <BarGraphDataSource>

@end

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 7: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

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

@interface SimpleData : NSObject <BarGraphDataSource,OtherProtocol>

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

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

@protocol NSCoder- (id)initWithCoder:(NSCoder *)decoder;- (void)encodeWithCoder:(NSCoder *)encoder;@end

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

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

@protocol BarGraphDataSource-(int) numberOfBar;-(int) valueForBar:(int)bar;@optional-(int) colorForBar:(int)bar;-(int) barWidth:(int)bar;@end

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

เกิดขอผิดพลาด เพราะโปรโตคอลไดประกาศชัดเจนไวแลววาจำเปนตองใช

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

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

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

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

Page 8: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

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

Program 10.3

BarGraphDataSource.h1 #import <Foundation/Foundation.h>23 @protocol BarGraphDataSource <NSObject>45 -(int) numberOfBar;6 -(int) valueForBar:(int)bar;78 @end

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

BarGraph.h1 #import <Foundation/Foundation.h>2 #import "BarGraphDataSource.h"34 @interface BarGraph : NSObject5 {6 id <BarGraphDataSource> _dataSource;7 }8 @property (retain) id<BarGraphDataSource> dataSource;9 -(void) drawGraph;1011 @end

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

id <BarGraphDataSource> _dataSource;

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 9: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

บรรทัดนี้หมายความวาเราประกาศตัวแปรชื่อ _dataSource ใหเปนแบบ id ที่ใชแทนออบเจ็กตใดๆก็ไดแตเราไดกำหนดคุณสมบัติของออบเจ็กตนี้เพ่ิมวาตองสามารถรับรอง BarGraphDataSource ได หรือพูดอีกอยางไดวา _dataSource ตองมีเมธอดที่โปรโตคอล BarGraphDataSource กำหนดไวนั่นเอง BarGraph.m1 #import "BarGraph.h"23 @implementation BarGraph4 @synthesize dataSource = _dataSource;56 -(void)drawGraph7 {8 if ([_dataSource conformsToProtocol:@protocol(BarGraphDataSource)])9 {10 for ( int row = 0 ; row < [_dataSource numberOfBar] ; row ++ )11 {12 NSMutableString* bar = [NSMutableString string];13 for ( int v = 0 ; v < [_dataSource valueForBar:row] ; v++ )14 {15 [bar appendString:@"X"];16 }17 NSLog(@"%d |%@",row , bar);18 }19 }20 }

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

FileData.h1 #import <Foundation/Foundation.h>2 #import "BarGraphDataSource.h"34 @interface FileData : NSObject <BarGraphDataSource>5 {6 NSArray* _barList;7 }8 @end

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

FileData.m1 #import "FileData.h"23 @implementation FileData4 -(id) init5 {6 self = [super init];7 if( self != nil)8 {9 _barList = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:6],10 [NSNumber numberWithInt:4],

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 10: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

11 [NSNumber numberWithInt:8],12 [NSNumber numberWithInt:7],nil];13 }14 return self;15 }1617 -(void) dealloc18 {19 [_barList dealloc];20 [super dealloc];21 }2223 -(int) numberOfBar24 {25 return (int)[_barList count];26 }27 -(int) valueForBar:(int)bar28 {29 if( bar >= [_barList count] )30 return 0;31 32 NSNumber* value = [_barList objectAtIndex:bar];33 return [value intValue];34 }35 @end

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

NetData.h1 #import <Foundation/Foundation.h>2 #import "BarGraphDataSource.h"34 @interface NetData : NSObject <BarGraphDataSource>5 {6 NSMutableArray* _dataList;7 }8 -(void) loadInternetData:(int) value;9 @end

NetData.m1 #import "NetData.h"23 @implementation NetData4 -(id) init5 {6 self = [super init];7 if( self != nil)8 {9 _dataList = [[NSMutableArray alloc] init];10 }11 return self;12 }13 -(void) loadInternetData:(int) value14 {15 [_dataList addObject:[NSNumber numberWithInt:value]];16 }1718 -(void) dealloc

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 11: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

19 {20 [_dataList dealloc];21 [super dealloc];22 }2324 -(int) numberOfBar25 {26 return (int)[_dataList count];27 }28 -(int) valueForBar:(int)bar29 {30 if( bar >= [_dataList count] )31 return 0;32 33 NSNumber* value = [_dataList objectAtIndex:bar];34 return [value intValue];35 }36 @end

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

main.m1 #import <Foundation/Foundation.h>2 #import "BarGraph.h"3 #import "FileData.h"4 #import "NetData.h"56 int main(int argc, const char * argv[])7 {89 @autoreleasepool {10 11 BarGraph* barGraph = [[BarGraph alloc] init];12 FileData* fileDataSource = [[FileData alloc] init];13 NetData* netDataSource = [[NetData alloc] init];14 15 [netDataSource loadInternetData:2];16 [netDataSource loadInternetData:5];17 [netDataSource loadInternetData:10];18 19 20 NSLog(@"----- File data -----");21 barGraph.dataSource = fileDataSource;22 [barGraph drawGraph];23 24 NSLog(@"--- Internet data ---");25 barGraph.dataSource = netDataSource;26 [barGraph drawGraph];27 28 [fileDataSource release];29 [netDataSource release];30 [barGraph release];31 32 33 }34 return 0;35 }

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

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 12: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

21 barGraph.dataSource = fileDataSource;22 [barGraph drawGraph];

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

25 barGraph.dataSource = netDataSource;26 [barGraph drawGraph];

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

Program 10.3 Output

----- File data -----0 |XXXXXX1 |XXXX2 |XXXXXXXX3 |XXXXXXX--- Internet data ---0 |XX1 |XXXXX2 |XXXXXXXXXX

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

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 13: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

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

Table Delegate

cellForRow: 4

touchAtRow: 2

presentEQ

TableView

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

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

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 14: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

Program 10.4

NetworkController Delegate

internet

receiveData

download

processData

NetworkDelegate.h1 #import <Foundation/Foundation.h>23 @protocol NetworkDelegate <NSObject>45 -(void) connectSuccess:(int)errorCode;6 -(void) receiveData:(NSString*)data;7 -(void) connectFail:(int)errorCode;89 @optional10 -(void) authentication;1112 @end

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

NetworkController.h1 #import <Foundation/Foundation.h>2 #import "NetworkDelegate.h"3 @interface NetworkController : NSObject4 {5 id <NetworkDelegate> _delegate;6 }7 @property (retain) id <NetworkDelegate> delegate;8 -(void) startConnect;9 @end

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

NetworkController.m1 #import "NetworkController.h"23 @implementation NetworkController4 -(void) startConnect5 {6 if( _delegate == nil )7 return;8 9 int rand = arc4random_uniform(100);10 if( rand > 50 )11 {12 if( [_delegate respondsToSelector:@selector(authentication)])13 [_delegate authentication];

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 15: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

14 15 if( [_delegate respondsToSelector:@selector(connectSuccess:)])16 [_delegate connectSuccess:200];17 18 if( [_delegate respondsToSelector:@selector(receiveData:)])19 {20 for(int i = 0 ; i <= 5 ; i++)21 {22 sleep(1);23 NSString* data = [NSString stringWithFormat:@"%d",i];24 [_delegate receiveData:data];25 }26 }2728 }29 else30 if( [_delegate respondsToSelector:@selector(connectFail:)])31 [_delegate connectFail:404];32 }33 @end

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

12 if( [_delegate respondsToSelector:@selector(authentication)])

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

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

MyDelegate.h1 #import <Foundation/Foundation.h>2 #import "NetworkDelegate.h"34 @interface MyDelegate : NSObject <NetworkDelegate>56 @end

MyDelegate.m1 #import "MyDelegate.h"23 @implementation MyDelegate4 -(void) connectSuccess:(int)errorCode5 {6 NSLog(@"Connect success with code: %d", errorCode);7 }8 -(void) receiveData:(NSString*)data9 {10 NSLog(@"Receive: %@", data);11 }12 -(void) connectFail:(int)errorCode13 {14 NSLog(@"Connect fail with code: %d", errorCode);15 }16 @end

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 16: Chapter 10 · Chapter 10 Category ,Protocol , Delegate! ในการพัฒนาโปรแกรมเราเริ่มจากการออกแบบคลาสและลงมือเขียนโค

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

main.m1 #import <Foundation/Foundation.h>2 #import "NetworkController.h"3 #import "MyDelegate.h"45 int main(int argc, const char * argv[])6 {78 @autoreleasepool9 {10 NetworkController* netController = [[NetworkController alloc] init];11 MyDelegate* myDelegate = [[MyDelegate alloc] init];12 13 netController.delegate = myDelegate;14 [netController startConnect];15 16 [myDelegate release];17 [netController release];18 19 }20 return 0;21 }

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

Program 10.4 Output ( > 50 )

Connect success with code 200Received 0Received 1Received 2Received 3Received 4Received 5

Program 10.4 Output ( <= 50 )

Connect fail with code 404

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

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1

Page 17: Chapter 10 · Chapter 10 Category ,Protocol , 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

Copyright macfeteria.com 2013. All Rights Reserved. -- Chapter 10 Version 1