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

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

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

    Property

    โดยปกติแล้วเมื่อเราประกาศคลาส ตัวแปรที่อยู่ภายในคลาส (instance variable) จะถูกกำหนดให้เป็นแบบ private หรือถูกปกปิดจากภายนอกโดยอัตโนมัติ เพราะฉะนั้นเราจึงต้องประกาศเมธอดเพื่อใช้ในการกำหนดค่าและแสดงค่าของตัวแปร ภายในคลาสหรือที่เรียกว่า setter/getter ดังตัวอย่าง

     

    Student.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    #import <Foundation/Foundation.h>
     
    @interface Student: NSObject{
        int _mathMidTerm;
        int _mathExamination;
    
    }
    -(void) setMathMidTerm:(int)score;
    -(void) setMathExamination:(int)score;
    -(int)  mathMidTerm;
    -(int)  mathExamination;
    @end

     

    Student.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
    
    #import <Student.hh>
    @implementation Student
     
    -(void) setMathMidTerm:(int)score
    {
        _mathMidTerm = score;
    }
     
    -(void) setMathExamination:(int)score
    {
        _mathExamination = score;
    }
     
    -(void) mathMidTerm
    {
        return _mathMidTerm;
    }
     
    -(void) mathExamination
    {
        return _mathExamination;
    }
     
    @end

     

    จากตัวอย่างจะพบว่าถ้าหากจะให้อ๊อบเจ็กต์อื่นใช้ instance variable ของคลาสได้นั้น เราต้องประกาศเมธอดถึงสองเมธอด นั่นคือเมธอธอดที่ใช้กำหนดค่าตัวแปร (setter) และเมธอดที่ใช้ขอค่าตัวแปร (getter) ยิ่งคลาสมีจำนวน instance variable มากเท่าไหร่จำนวนเมธอดก็จะเพิ่มมากตามไปด้วย โชคดีที่เราไม่ต้องทุ่มเทเวลากับการเขียนโค้ด setter/getter เพราะหลังจากที่ภาษา Objective-C ได้ปรับปรุงมาถึง Objective-C 2.0 ก็ได้เพิ่มความสามารถในการสร้าง setter/getter ให้กับ instance variable โดยอัตโนมัติ  วิธีการคือประกาศตัวแปรนั้นให้เป็นพร๊อพเพอร์ตี้ (property) ของคลาส โดยการเขียน @property (property directive) ในส่วน interface พร้อมกับกำหนดคุณสมบัติของพร๊อพเพอร์ตี้ (attribute) จากนั้นเขียน @synthesize (synthesize directive) ในส่วน implementation เพื่อบอกให้คอมไพเลอร์สร้างโค้ด getter/setter ให้อัตโนมัติ

     

    Student.h ( property )

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    #import <Foundation/Foundation.h>
    @interface Student: NSObject
    {
        int mathMidTerm;
        int mathExamination;
    }
    @property (assign,readwrite) int mathMidTerm;
    @property (assign,readwrite) int mathExamination;
    @end

     

    Student.m ( property )

    1
    2
    3
    4
    5
    6
    7
    8
    
    #import "Student.h"
     
    @implementation Student
     
    @synthesize mathMidTerm;
    @synthesize mathExamination;
     
    @end

     

    ส่วนการเรียกใช้ property ก็ไม่ได้ยุ่งยากอะไรเพียงแค่ใช้เครื่องหมายจุด . และตามด้วยชื่อของ property ดังเช่นตัวอย่าง

     

    manee.mathMidTerm = 25;
     
    int score = manee.mathMidTerm;

     

    Property Attribute

    จากโค้ดของคลาส Student ที่ผ่านมาเราได้ประกาศพร๊อพเพอร์ตี้ 2 ตัวคือ mathMidTerm และ mathExamination พร้อมกับกำหนดคุณสมบัติของพร๊อพเพอร์ตี้ให้เป็นแบบ assign , readwrite หมายความว่าเมื่อกำหนดค่าให้กับพร๊อพเพอร์ตี้นั้นจะเป็นการกำหนดค่าตัวแปร โดยตรง และพร๊อพเพอร์ตี้นี้อ่านและเขียนข้อมูลได้ นอกจากนี้ยังมี attribute อื่นๆดังนี้

     

    readwrite กำหนดให้พร๊อพเพอร์ตี้อ่านและเขียนข้อมูลได้
    readonly กำหนดให้พร๊อพเพอร์ตี้อ่านข้อมูลได้อย่างเดียว ไม่สามารถแก้ไขได้
    assign เมธอด getter/setter ที่ถูกสร้างขึ้นจะมีการทำการงานเหมือนกับการให้ค่าโดยตรง เช่น x = 20 ปกติจะใช้กับค่าที่เป็น primitive type เช่น int , float
    retain หากกำหนดพร๊อพเพอร์ตี้แบบนี้ setter ที่ถูกสร้างขึ้นจะ release ออบเจ็กต์เดิมก่อนและหลังจากนั้นจะ retain ออบเจ็กต์ใหม่ ลักษณะการทำงานเหมือนโค้ดตัวอย่างดังนี้

     

    -(void) setValue:(Student *)str
    {
        [student release];
        student = [str retain];
    }

     

    copy เมื่อกำหนดคุณสมบัติแบบ copy เมธอด setter ที่สร้างขึ้นจะ release ออบเจ็กต์เดิมและหลังจากนั้นจะ copy ออบเจ็กต์ใหม่ มีลักษณะการทำงานเหมือนโค้ดตัวอย่างดังนี้

     

    -(void) setValue:(Student *)str
    {
        [student release];
        student = [str copy];
    }

     

    nonatomic เมื่อโปรแกรมทำงานแบบ multithread (จะอธิบายเทรดอย่างละเอียดอีกครั้งในบทหลัง ) getter/setter ที่ถูกสร้างขึ้นจะไม่รับประกันว่าค่าที่ได้จะครบถ้วน เช่น thread A กำลังอ่านค่าและ thread B เข้ามาเปลี่ยนแปลงค่า อาจจะเกิดปัญหาว่าอ็อบเจกต์ที่ใช้งานหายหายไปก่อนที่ thread A จะได้รับ เนื่องจากการกระทำของ B


    atomic เมื่อโปรแกรมทำงานแบบ multithread จะไม่เกิดปัญหาเช่นเดียวกัน nonatomic เพราะเมธอด getter/setter ที่ถูกสร้างขึ้นจะรับประกันว่าการขอค่าจะได้รับครบถ้วนจาก getter หรือเมื่อกำหนดค่าก็จะกระทำผ่าน setter เสมอ อย่างไรก็ตาม atomic ไม่ได้หมายความว่า thread safe และข้อเสียของ atomic คือทำงานได้ช้ากว่า nonatomic
    เมื่อประกาศพร๊อพเพอร์ตี้จะมี attribute ที่ถูกกำหนดโดยอัตโนมัติไว้อยู่แล้วคือ atomic , assign , readwrite เราไม่ต้องเขียนโค้ด 3 attribute นี้ก็ได้ นอกจากนี้ในคอมไพล์เลอร์ LLVM รุ่นใหม่ ( มากับ XCode 4.2 ) ไม่จำเป็นต้องเขียน @synthesize ก็ได้เพราะคอมไพล์เลอร์จะสร้างให้อัตโนมัติ คุณสมบัติต่างๆของ attribute ยังไม่หมดเพียงเท่านี้ แต่เป็นส่วนที่เกี่ยวข้องกับ Automatic Reference Counting (ARC) ซึ่งจะอธิบายเพิ่มเติมในบทหลังๆ
    นอกจากนี้การประกาศพร๊อพเพอร์ตี้เราไม่จำเป็นต้องประกาศให้ชื่อของ พร๊อพเพอร์ตี้เป็นชื่อเดียวกับ instance variable ก็ได้ เพียงแค่เราต้องกำหนดให้พร๊อพเพอร์ตี้ให้เท่ากับตัวแปรที่เราต้องการ  ดังเช่นตัวอย่าง


    Student.h

    1
    2
    3
    4
    5
    6
    7
    8
    
    #import <Foundation/Foundation.h>
     
    @interface Student: NSObject
    {
        int _mathMidTermScore;
        int _mathExaminationScore;
    }
    @property (assign) int midTerm;

     

    Student.m

    1
    2
    3
    4
    5
    6
    7
    8
    
    #import "Student.h"
     
    @implementation Student
     
    @synthesize midTerm = _mathMidTermScore;
    @synthesize examination = _mathExaminationScore;
     
    @end

     

    ตัวอย่างโค้ดจะเห็นว่า เราได้กำหนดให้ชื่อของพร๊อพเพอร์ตี้ ไม่เหมือนกับชื่อของตัวแปร แต่ในส่วนของ synthesize เราได้กำหนดให้พร๊อพเพอร์ตี้เท่ากับตัวแปรที่ต้องการ

     

    @synthesize midTerm = _mathMidTermScore;

     

    ส่วนการเรียกใช้งานก็ยังคงเป็นรูปแบบเดิมไม่ได้มีอะไรพิเศษเพิ่มเติม

    manee.midTerm = 45;

     

    อย่างที่ได้กล่าวไปว่าการใช้ @property และ @synthesize นั้นเป็นการบอกให้คอมไพเลอร์สร้างเมธอดให้อัตโนมัติ ดังนั้นถ้าไม่ต้องการจะเรียกใช้ผ่านเครื่องหมายจุด  แต่ต้องการจะเรียกเมธอด setter และ getter ที่คอมไพลเลอร์สร้างให้ก็สามารถทำได้เช่นกัน ดังตัวอย่าง

     

    [manee setMidTerm:45];

     

    @dynamic

    ปกติเราจะใช้ @synthesize เพื่อบอกให้คอมไพล์เลอร์สร้างเมธอด getter/setter ให้กับเรา แต่ในบางครั้งเราไม่ต้องการจะให้คอมไพล์เลอร์สร้างให้ เราสามารถใช้ @dynamic บอกกับคอมไพล์เลอร์ว่าเราได้เตรียมเมธอด setter/getter ไว้ให้แล้ว เช่น จากโค้ด @synthesize

     

    @implementation Student
     
    @synthesize examination = _mathExamination;
     
    @end

     

    หากเปลี่ยนใช้ @dynamic ก็จะมีโค้ดดังนี้

     

    @implementation Student
     
    @dynamic examination;
     
    -(int) examination
    {
        return _mathExamination;
    }
    -(void) setExamination:(int)ex
    {
        _mathExamination = ex;
    }
    @end

     

    ถึงแม้ว่าเราต้องเขียนโค้ดเอง แต่ก็มีข้อดีคือหากเราต้องการความยืดหยุ่นในการจัดค่าด้วยตัวเอง และยังต้องการความสะดวกในการเขียนโค้ดแบบพร๊อพเพอร์ตี้เราก็สามารถใช้ @dynamic ช่วยจัดการปัญหาตรงนี้ได้ การใช้ @dynamic นี้จะพบเห็นได้บ่อยเมื่อใช้ Core Data

     

    Self Keyword

    self เป็น keyword เพื่ออ้างอิงถึงตัวเอง การใช้ self มักจะใช้เพื่อเรียกเมธอดภายในตัวคลาสเอง เช่น ถ้าหากเราต้องการจะคำนวนค่าเฉลี่ย ของคะแนนกลางภาคและปลายภาคก็อาจจะเพิ่มเมธอด getAverageScore:

     

    -(float) getAverageScore:(int)score
    {
        return ( _mathMidTerm + _mathExamination ) / 2.0;
    }

     

    ต่อมาเราต้องการจะเขียนเมธอด printAllScore: เพื่อใช้แสดงคะแนนกลางภาค ปลายภาค และคะแนนเฉลี่ย ตัวแปรต่างๆในคลาสเช่น _mathMidTerm และ _mathExamination เข้าใช้ได้โดยตรงอยู่แล้ว แต่ในกรณีคะแนนเฉลี่ยจะต่างออกไปเพราะเป็นค่าจากการคำนวน การเขียนโค้ดคำนวนใหม่ในเมธอด printAllScore: ก็ไม่ใช่เรื่องที่ดีนัก การแก้ปัญหาที่ดีกว่านั้นก็คือการเรียกใช้เมธอด getAverageScore: ของตัวคลาสเอง ดังเช่นตัวอย่าง

     

    -(void) printAllScores:(int)score
    {
        float avgScore = [self getAverageScore];
    
    NSLog(@"MidTerm: %i", _mathMidTerm);
        NSLog(@"MidTerm: %i", _mathExamination);
        NSLog(@"MidTerm: %f", avgScore);
    }

     

    นอกจากการเรียกเมธอดแล้วเรายังสามารถที่จะใช้ self เพื่ออ้างถึงพร๊อพเพอร์ตี้ได้เช่นกัน ดังตัวอย่าง

     

    self.midTerm = 20 ;

     

    มาถึงตรงนี้อาจจะเกิดข้อสงสัยว่า มันต่างอะไรกับการเรียกใช้ instance variable โดยตรงๆ เพราะยังไงก็สามารถเข้าถึงได้โดยตรงอยู่แล้ว ดังเช่น

     

    _mathMidTerm = 20 ;

     

    ถึงแม้ว่าสองวิธีการนี้จะเป็นการกำหนดค่ากับตัวแปรเหมือนกัน แต่การทำงานแตกต่างกัน เพราะถ้าหากเราใช้พร๊อพเพอร์ตี้จะเป็นการใช้เมธอด setter/getter ที่คอมไพเลอร์สร้างขึ้น (หรือเรากำหนดเอง) ส่วนอีกวิธีเป็นกำหนดค่าไปยังตัวแปรโดยตรง ถึงแม้ว่าเราจะกำหนดค่าโดยตรงให้กับตัวแปรได้ แต่ในบางกรณีเช่นการทำงานในแบบ multithread เราจำเป็นต้องกำหนดค่าผ่านพร๊อพเพอร์ตี้เพื่อไม่ให้เกิดปัญหาระหว่าง thread เป็นต้น

     

    More about Inheritance

    เนื้อหาในส่วนนี้จะอธิบายเพิ่มเติมเกี่ยวกับการสืบทอดคลาสของภาษา Objective-C เพิ่มเติม เพราะก่อนหน้านี้ได้อธิบายเกี่ยวกับการสืบทอดคลาสและ subclass เพียงคร่าว ยังไม่ได้ใช้ประโยชน์ของการสืบทอดแบบเต็มที่  จากตัวอย่างที่ผ่านๆมาการประกาศคลาสเราจะประกาศโดยการสืบทอดคลาส NSObject ซึ่งเป็นคลาสต้นแบบของทุกๆคลาส (Root Class) ดังเช่นโค้ดตัวอย่าง

     

    @interface Student: NSObject{
    }
    @end

     

    จริงๆแล้วเราไม่จำเป็นต้องใช้ NSObject เป็น root class ก็ได้ แต่ที่เราต้องใช้คลาสนี้ก็เพราะว่า NSObject เป็นคลาสพื้นฐานมีคำสั่งและเมธอดเบื้องต้นที่จำเป็นสำหรับทุกคลาส เช่นการจองหน่วยความจำ การเปรียบเทียบชนิดออบเจ็กต์ เป็นต้น

     

    จากนิยามของการสืบทอดในบทที่ 4 ได้บอกว่าคลาสที่เป็นต้นกำเนิดจะเรียกว่า superclass หรือ parent และเราจะเรียกคลาสที่สืบทอดว่า subclass หรือ child และเมื่อไหร่ก็ตามที่ประกาศคลาสใหม่โดยสืบทอดจากคลาสอื่น คลาสที่สร้างขึ้นมาใหม่จะมีคุณสมบัติเหมือนกับ superclass หรืออาจจะบอกได้ว่าคลาสที่สร้างขึ้นมาใหม่จะมีเมธอดเช่นเดียวกัน หรือมีตัวแปรในคลาสเหมือนกันกับ superclass เพื่อให้ง่ายต่อความเข้่าใจเราจะเขียนคลาสและโปรแกรมง่ายๆสักโปรแกรม

     

    ElectronicDevice.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    #import <Foundation/Foundation.h>
     
    @interface ElectronicDevice : NSObject
    {
        float price;
    }
    @property float price;
    -(void) printPrice;
    @end

     

    ElectronicDevice.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    #import "ElectronicDevice.h"
     
    @implementation ElectronicDevice
    @synthesize price;
     
    -(void) printPrice
    {
        NSLog(@"Price = %fn",price);
    }
     
    @end

     

    คลาส ElectronicDevice ที่ได้ประกาศไปมีตัวแปรแค่ตัวเดียวและก็มีเมธอดไว้สำหรับแสดงค่า price อย่างง่ายๆ ต่อไปให้ประกาศคลาสใหม่ขึ้นมาชื่อ Computer ดังตัวอย่าง

     

    Computer.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    #import <Foundation/Foundation.h>
    #import "ElectronicDevice.h"
    @interface Computer : ElectronicDevice
    {
        int speed;
    }
    @property int speed;
    -(void) printSpeed;
     
    @end

     

    Computer.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    #import "Computer.h"
     
    @implementation Computer
    @synthesize speed;
    -(void) printSpeed
    {
        NSLog(@"Speed = %d mHz",speed);
    }
    @end

     

    เมื่อดูโค้ดของคลาสคอมพิวเตอร์ เราได้ได้กำหนดให้คลาส Computer สืบทอดคลาส ElectronicDevice

     

    @interface Computer : ElectronicDevice

     

    คลาส Computer เราเรียกว่าคลาสลูก (sub class หรือ child) ส่วน ElectronicDevice เรียกว่าคลาสแม่ (super class หรือ parent) คลาสลูกจะได้รับคุณสมบัติทุกประการเหมือนคลาสแม่ นั่นหมายความว่าคลาส Computer นั้นมีเมธอดและสมาชิกในคลาสเช่นเดียวกันกับคลาส ElectronicDevice

     

    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
    
    #import "ElectronicDevice.h"
    #import "Computer.h"
     
    int main (int argc, const char *argv[])
    {    
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];     
     
        ElectronicDevice *device = [[ElectronicDevice alloc] init];
        Computer *computer = [[Computer alloc] init];
     
        NSLog(@"Device");
        device.price = 5000;
        [device printPrice];
     
        NSLog(@"Computer");
        computer.price = 12000;
        computer.speed = 450;
        [computer printSpeed];
        [computer printPrice];
     
        [computer release];
        [device release];    
     
        [pool drain];
        return 0;
    }

     

    Program 7.3 Output

    Device 
price 5000.0
    
Computer
    speed 450 mHz
    price 12000.0

     

    จากโค้ดบรรทัดที่ 19 จะเห็นว่า computer นั้นสามารถเรียกใช้ printPrice ของคลาสแม่ได้เลย รวมถึงการใช้ตัวแปรต่างๆภายในคลาสก็ทำได้เช่นกัน โปรดจำไว้ว่าการ inheritance จะเป็นลักษณะบนลงล่าง ดังนั้นคลาสที่ทำการสืบทอดจะมีเมธอดและตัวแปรต่างๆของคลาสแม่มาด้วยเสมอ เช่น Computer ก็จะได้ทุกๆอย่างจากคลาส ElectronicDevice รวมถึง NSObject เพราะเนื่องจาก ElectronicDevice ได้สืบทอด NSObject มาอีกที ฉะนั้นแล้วการสืบทอดถึงแม้จะมีข้อดีมากมาย แต่ก็มีข้อเสียคือคลาสจะมีขนาดใหญ่ขึ้นเรื่อยๆ

     

     

     

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

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

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