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

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

Objective-C : More on classes & Property (ตอนที่1)

    More on classes & Property

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

     

    The id type

    แต่ก่อนอื่นจะขอแนะนำตัวแปรแบบใหม่ที่เคยได้เคยเกริ่นไว้ตั้งแต่ช่วงแรก แล้วว่า เป็นตัวแปรแบบชนิดพิเศษ นั่นก็คือตัวแปร id เราเรียกตัวแปรชนิดนี้ว่าเป็นตัวแปรแบบ dynamic typing ตัวแปรที่ผ่านๆมาไม่ว่าจะเป็น int , float หรือ char ล้วนแล้วแต่เป็นแบบ static typing ทั้งสิ้นกล่าวคือเมื่อประกาศตัวแปรชนิดนั้นแล้วจะไม่สามารถเปลี่ยนเป็นชนิด อื่นได้อื่น แต่ตัวแปรแบบ dynamic typing จะต่างออกไป เพราะความพิเศษของมันอยู่ตรงที่ ตัวแปรชนิดนี้สามารถเปลี่ยนเป็นอ็อบเจกต์ได้ทุกชนิด พูดอีกอย่างก็คือตัวแปรนี้ใช้แทนอ็อบเจกต์ใดๆก็ได้ ลองดูตัวอย่างโปรแกรมต่อไปนี้

     

    Program 7.1

    Ractangle.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    #import <Foundation/Foundation.h>
     
    @interface Rectangle : NSObject
    {
        float height;
        float width;
        float area;
    }
    -(float) area;
    -(void) setHeigh:(float)h width:(float)w;
     
    @end

     

    Ractangle.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    #import "Rectangle.h"
     
    @implementation Rectangle
    -(float) area
    {
        return width * height;
    }
    -(void) setHeigh:(float)h width:(float)w
    {
        height = h;
        width = w;
    }
    @end

     

    Triangle.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    #import <Foundation/Foundation.h>
     
    @interface Triangle : NSObject
    {
        float height;
        float base;
        float area;
    }
    -(float) area;
    -(void) setHeigh:(float)h base:(float)b;
    @end

     

    Triangle.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    #import "Triangle.h"
     
    @implementation Triangle
    -(float) area
    {
        return 0.5 * base * height;
    }
     
    -(void) setHeigh:(float)h base:(float)b
    {
        height = h;
        base = b;
    }
    @end

     

    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
    
    #import <Foundation/Foundation.h>
    #import "Triangle.h"
    #import "Rectangle.h"
     
    int main(int argc, const char * argv[])
    {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
     
        Triangle *tri = [[Triangle alloc] init];
        Rectangle *rec =[[Rectangle alloc] init];
     
        float area = 0;
        id obj;
     
        [tri setHeigh:10 base:20];
        [rec setHeigh:10 width:20];
     
        obj = tri;
        area = [obj area];
        NSLog(@"Tri area:%f" , area);
     
        obj = rec;
        area = [obj area];
        NSLog(@"Rec area:%f" , area);
     
        [tri release];
        [rec release];
     
        [pool drain];
        return 0;
    }

     

    Program 7.1 Output

    Tri area: 100 
Rec area: 200

    โปรแกรมที่ได้เขียนไป เริ่มด้วยการประกาศคลาส Rectangle , Triangle และส่วนของเมนโปรแกรมก็ประกาศออบเจ็กต์ของคลาสทั้งสองนั่นก็คือ tri และ rect นอกจากนี้เราได้ประกาศตัวแปร area และตัวแปรชนิด id โดยให้ชื่อว่า obj ดังนั้นตัวแปร obj นี้ก็จะสามารถใช้เป็นออบเจ็กต์ใดๆก็ได้ จากตัวอย่างโค้ดของโปรแกรมเราในบรรทัดที่ 18 ได้กำหนดค่า obj ให้เท่ากับ tri

     

    obj = tri;

     

    เมื่อตัวแปร obj ถูกกำหนดให้เป๊นอ็อบเจกต์ tri ซึ่งเป็น instance ของคลาส Triangle ฉะนั้นแล้วเราก็สามารถที่จะเรียกเมธอดใดๆของ Triangle ผ่านทาง obj ได้เช่นกันเพราะถือว่าเป็นอ็อบเจกต์เดียวกัน แม้ว่าตัวแปร obj จะไม่ได้ประกาศว่าเป็นออบเจ็กต์ของคลาส Triangle ก็ตาม ดังนั้นการส่งเมสเซจหา obj

     

    area = [obj area];

     

    ก็เท่ากับเป็นการส่งเมสเซจไปยังอ็อบเจกต์ tri

     

    area = [tri area];

     

    เพราะเราได้กำหนดให้ obj เป็นอ็อบเจกต์ tri นั่นเอง และเมื่อสิ้นสุดการทำงานบรรทัดนี้ ตัวแปร obj ก็ได้ถูกเปลี่ยนให้เป็น rec ซึ่งเป็นอ็อบเจกต์ของคลาส Rectangle

     

    obj = rec;
    area = [obj area];

     

    การที่ทำแบบนี้ได้ ก็เพราะว่าตัวแปร obj สามารถเปลี่ยนไปเป็นอ็อบเจกต์ชนิดใดๆก็ได้ ไม่ได้ถูกจำกัดไว้กับคลาสใดคลาสหนึ่ง จากนั้นก็เรียกเมธอด area เพื่อคำนวนของค่าพื้นที่ของสี่เหลี่ยมได้อย่างถูกต้อง แล้วโปรแกรมรู้ได้อย่างไรว่าเมื่อส่งเมสเซจ area ไปยัง obj จะเป็นการส่งเมสเซจของคลาสไหนระหว่าง Rectangle และ Triangle เนื่องจากคลาสทั้งสองมีเมธอดชื่อเดียวกัน ? คำตอบก็คือเพราะว่าภาษา Objective-C เป็นภาษาแบบ dynamic runtime การตัดสินใจว่าจะส่งเมสเซจไปยังอ๊อบเจกต์ใดๆไม่ได้เกิดขึ้นตอนคอมไพล์ (Compile Time) แต่โปรแกรมจะตัดสินใจในช่วงที่กำลังทำงาน (Run time) ลักษณะการทำงานแบบนี้ศัพท์ทางเทคนิคเรียกว่า dynamic binding

     

    Selector

    อย่างที่ทราบกันอยู่แล้วว่าการเรียกใช้เมธอดของอ็อบเจกต์จะใช้วิธีการส่ง message หาอ็อบเจกต์นั้น (บางครั้งหนังสือเล่มนี้อาจจะเขียนว่า เรียกเมธอด แต่ขอให้เข้าใจว่าคือการส่งเมสเซจ) การส่งเมสเซจไม่ใช่การเรียกเมธอดหรือฟังก์ชั่นโดยตรงเหมือนภาษา C , C++ หรือ Java  แต่ในภาษา Objective-C จะเลือกเมธอดให้ทำงานโดยอิงกับสิ่งที่เรียกว่า selector  ถ้าจะอธิบายให้เห็นภาพ selector อาจจะเปรียบเสมือนซองจดหมาย ที่ด้านในมีคำสั่งการทำงาน เมื่อผู้รับเปิดซองก็ทำตามคำสั่งนั้น หากไม่สามารถทำตามคำสั่งนั้นได้ ก็จะเกิดข้อผิดพลาด invalid selector นั่นเอง การประกาศตัวแปรที่เป็น selector ทำได้ด้วยการประกาศให้เป็นตัวแปรชนิด SEL ดังเช่นตัวอย่าง

     

     SEL mySelector = @selector(dosomeThing);

     

    จากโค้ดได้ประกาศ selector ชื่อ mySelector โดยกำหนดให้เป็นเมธอด dosomeThing ในกรณีที่เมธอดนั้นรับพารามิเตอร์เราต้องเพิ่มเครื่องหมาย : ต่อท้าย เช่น

     

    -(void) doSomeThing:(int) value;
    -(void) doSomeThing:(int) value andOther:(int) other;

     

    ก็ต้องประกาศ selector ดังนี้

     

    SEL selectorA = @selector(doSomeThing:);
    SEL selectorB = @selector(doSomeThing:andOther:);

     

    เบื้องหลังกลไกลการทำงานของเมสเซจคือเมื่อโปรแกรมถูกคอมไพล์ โค้ดส่วนที่เป็นการส่งเมสเซจ เช่น

     

     [objA doSomeThing:10];

     

    จะถูกแปลงให้เป็นโค้ดคล้ายการเรียกฟังชั่นคำสั่งภาษา C ดังนี้

     

    objc_msgSend(b , @selector(doSomeThing:), 100);

     

    นอกจากการนี้แล้วเราสามารถให้อ็อบเจกต์ของเราทำงานตาม selector ที่กำหนดได้ด้วยเมธอด performSelector: และยังใช้เมธอด respondsToSelector: สอบถามอ็อบเจกต์ได้ด้วยว่าสามารถทำงานตาม selector ที่กำหนดได้หรือไม่ เพื่อให้เข้าใจการใช้ selector มากขึ้น เราจะเขียนโปรแกรมทดลองการใช้งาน selector กันสักโปรแกรม โดยโปรแกรมของเราจะยังใช้คลาส Triangle และ Rectangle จากโปรแกรม 7.1 และให้เพิ่มคลาส Circle ในโปรเจคใหม่ดังนี้

     

    Program 7.2

    Circle.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    #import <Foundation/Foundation.h>
    @interface Circle : NSObject 
    {
        float area;
        float radius;
    }
    -(float) area;
    -(void) setRadius:(float) r;
    -(void) printData;
     
    @end

     

    Circle.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    #import "Circle.h"
     
    @implementation Circle
     
    -(float) area
    {
        return M_PI * radius * radius;
    }
    -(void) setRadius:(float) r
    {
        radius = r;
    }
    -(void) printData
    {
        NSLog(@"Circle radius: %f area: %f", radius, [self area]);
    }
     
    @end

     

    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
    34
    35
    
    #import <Foundation/Foundation.h>
    #import "Triangle.h"
    #import "Rectangle.h"
    #import "Circle.h"
     
    int main(int argc, const char * argv[])
    {
     
        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
     
        SEL setValue = @selector(setHeigh:width:);
        SEL printData = @selector(printData);
     
        Circle* cir = [[Circle alloc] init];
        Triangle* tri = [[Triangle alloc] init];
        Rectangle* rect = [[Rectangle alloc] init];
     
        [cir setRadius:10];
        [tri setHeigh:10 base:10];
     
        objc_msgSend(rect , setValue, 20 , 10);
     
        if ( [tri respondsToSelector:printData])
            [tri performSelector:printData];
     
        if ( [rect respondsToSelector:printData])
            [rect performSelector:printData];
     
        if ( [cir respondsToSelector:printData])
            [cir performSelector:printData];
     
        [pool drain];
     
        return 0;
    }

     

    เมนโปรแกรมได้ประกาศ selector คือ setValue และ print หลังจากนั้นก็ประกาศอ็อบเจกต์ของคลาสทั้งสาม ต่อมาเราได้กำหนดค่าให้กับกับวงกลม cir มีรัศมี 10 และกำหนดค่าให้สามเหลี่ยม tri มีความกว้าง 10 ยาว 10 ส่วนในบรรทัดที่ 22 เป็นการกำหนดค่าให้กับอ็อบเจกต์เช่นเดียวกันแต่ใช้ฟังชั่น objc_msgSend เพื่อทำการส่งเมสเสจ setValue ให้กับอ็อบเจกต์ rect พร้อมกับค่าอีกสองค่าคือ 20 และ 10 ตามลำดับ ซึ่งโค้ดบรรทัดนี้จะเหมือนกับการเขียนโค้ด

     

    [rect setHeigh:20 base:10];

     

    ตั้งแต่บรรทัดที่ 24 จะเป็นโค้ดที่ใช้ในการถามอ็อบเจกต์นั้นว่ารองรับ selector ที่กำหนดให้ได้หรือไม่ ในกรณีที่รองรับก็ให้ selector นั้นทำงาน เมื่อกลับไปดูคลาส Triangle และ Rectangle จะพบว่าไม่มีเมธอด printData ดังนั้นแล้ว tri และ rect จะไม่สามารถทำตาม selector ที่ให้ได้ โปรแกรมจึงแสดงผลดังนี้

     

    Program 7.2 Output

    Circle radius: 10.000000 area: 314.159271

     

    การใช้ selector สำหรับบทนี้อาจจะยังไม่เห็นประโยชน์มากนัก แต่ในบทหลังๆเราจะได้เห็นการประยุกต์ใช้อีกหลายตัวอย่าง และจะได้เห็นความสามารถของความเป็น dynamic runtime ในภาษา Objective-C มากขึ้น

     

     

     

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

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

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