รับทําเว็บไซต์ รับทําseo
 
รับทําเว็บไซต์ รับทําseo
บทความที่น่าสนใจ

บทความ ที่น่าสนใจ

Objective-C : Category ,Protocol , Delegate (ตอนที่2)

    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

     

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

     

    @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

     

    ch10_pro

     

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

     

    Program 10.3

    BarGraphDataSource.h

    1
    2
    3
    4
    5
    6
    7
    8
    
    #import <Foundation/Foundation.h>
     
    @protocol BarGraphDataSource <NSObject>
     
    -(int) numberOfBar;
    -(int) valueForBar:(int)bar;
     
    @end

     

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

     

    BarGraph.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    #import <Foundation/Foundation.h>
    #import "BarGraphDataSource.h"
     
    @interface BarGraph : NSObject
    {
        id <BarGraphDataSource> _dataSource;
    }
    @property (assign) id<BarGraphDataSource> dataSource;
    -(void) drawGraph;
     
    @end

     

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

     

    id <BarGraphDataSource> _dataSource;

     

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

     

    BarGraph.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    #import "BarGraph.h"
     
    @implementation BarGraph
    @synthesize dataSource = _dataSource;
     
    -(void)drawGraph
    {
        if ([_dataSource conformsToProtocol:@protocol(BarGraphDataSource)])
        {
            for ( int row = 0 ; row < [_dataSource numberOfBar] ; row ++ )
            {
                NSMutableString* bar = [NSMutableString string];
                for ( int v = 0 ; v < [_dataSource valueForBar:row] ; v++ )
                {
                    [bar appendString:@"X"];
                }
                NSLog(@"%d |%@",row , bar);
            }
        }
     
    }
    @end

     

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

     

    FileData.h

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

     

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

     

    FileData.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    #import "FileData.h"
     
    @implementation FileData
    -(id) init
    {
        self = [super init];
        if( self != nil)
        {
            _barList = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:6],
                        [NSNumber numberWithInt:4],
                        [NSNumber numberWithInt:8],
                        [NSNumber numberWithInt:7],nil];
        }
        return self;
    }
     
    -(void) dealloc
    {
        [_barList dealloc];
        [super dealloc];
    }
     
    -(int) numberOfBar
    {
        return (int)[_barList count];
    }
    -(int) valueForBar:(int)bar
    {
        if( bar >= [_barList count] )
            return 0;
     
        NSNumber* value = [_barList objectAtIndex:bar];
        return [value intValue];
    }
    @end

     

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

     

    NetData.h

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

     

    NetData.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    
    #import "NetData.h"
     
    @implementation NetData
    -(id) init
    {
        self = [super init];
        if( self != nil)
        {
            _dataList = [[NSMutableArray alloc] init];
        }
        return self;
    }
    -(void) addNewData:(int) value
    {
        [_dataList addObject:[NSNumber numberWithInt:value]];
    }
     
    -(void) dealloc
    {
        [_dataList dealloc];
        [super dealloc];
    }
     
    -(int) numberOfBar
    {
        return (int)[_dataList count];
    }
    -(int) valueForBar:(int)bar
    {
        if( bar >= [_dataList count] )
            return 0;
     
        NSNumber* value = [_dataList objectAtIndex:bar];
        return [value intValue];
    }
     
    @end

     

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

     

    main.m

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

     

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

     

            barGraph.dataSource = fileDataSource;
            [barGraph drawGraph];

     

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

     

            barGraph.dataSource = netDataSource;
            [barGraph drawGraph];

     

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

    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 เดิมได้ทันที

     

     

บทความที่น่าสนใจ

บทความ ล่าสุด

บทความ ความรู้ด้านไอที, คอมพิวเตอร์ Techonlogy, Gadget, ความรู้เกี่ยวกับคอมพิวเตอร์ กับทาง SoftMelt.com