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

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

Objective-C : Copying Object (ตอนที่2)

    Shallow and Deep copy

    ตัวแปร fruits_A นั้นก๊อบปี้ fruits_B มา นั่นก็หมายถึงว่ามันใช้เป็นคนละอ็อบเจ็ก ดังนั้นมันก็ควรจะไม่เกี่ยวกัน ซึ่งก็ถูกต้องแล้ว ดังเช่นในโปรแกรมที่ 12.2 ที่เราได้ลบสมาชิก fruits_B ออกไปก็ไม่เกี่ยวกับ fruits_A แล้วปัญหาที่เกิดขึ้นของโปรแกรม 12.3 นั้นคืออะไร ? ปัญหาที่เกิดขึ้นจริงๆคืออาร์เรย์เป็นคนละตัวกัน แต่สมาชิกของอาร์เรย์ทั้งสองเป็นตัวเดียวกัน เพราะเนื่องจากว่าตัวแปรที่ fruit_A นั้นเก็บไว้มันเป็นเพียง pointer ที่ชี้ไปยัง apple , orange , mango นั่นก็หมายความว่าสมาชิกในอาเรย์ของ fruits_A และ fruits_B ไม่ได้ถูกสร้างขึ้นมาใหม่ แต่เป็นเพียงแค่การ copy reference หรือเก็บ memory address มาแค่นั้น ฉะนั้นการแก้ไขสมาชิกใน fruits_B ย่อมมีผลกระทบโดยตรงต่อสมาชิก fruits_B เพื่อให้เข้าใจมากขึ้นลองพิจารณาภาพประกอบต่อไปนี้

     

    จากรูปจะเห็นว่า fruit_A และ fruit_B นั้นเป็นคนละอ็อบเจ็กกัน แต่สมาชิกภายในกลับชี้ไปยังตำแหน่งสตริงเดียวกัน สิ่งที่เกิดขึ้นลักษณะนี้เรียกว่า shallow copy หรือถ้าหากเราต้องการจะก๊อบปี้อ็อบเจ็กที่เราเขียนขึ้นมาเอง ดังเช่น

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    #import "MacBook.h"
     
    int main(int argc, const char * argv[])
    {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
     
        MacBook *macbookA = [[MacBook alloc] init]; 
     
        MacBook *macbookB = [macbookA copy];
     
        [macbookA release];
        [pool drain];
        return 0;
    }

     

    เมื่อคอมไพล์โปรแกรมจะแจ้ง error บอกว่าไม่เจอ copyWithZone: (เมธอดนี้จะถูกเรียกโดยเมธอด copy อีกที)

     

    *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[MacBook copyWithZone:]: unrecognized selector sent to instance 0x100108a80′
    *** First throw call stack:

     

    แล้วเราจะทำอย่างไรเพื่อที่จะให้อาร์เรย์ทั้งสองและสมาชิกแยกขาดกัน หรือทำอย่างให้เราสามารถก๊อบปี้อ็อบเจ็กที่เราสร้างขึ้นเองได้ ? การแก้ปัญหาก็คือเราต้องทำ deep copy  แต่เราต้องเขียนโค้ดในส่วนนี้เอง ไม่เหมือนกับเมธอด copy และ mutable ที่สามารถเรียกใช้ได้เลย วิธีการคือเราจะเขียนโค้ดของโปรโตคอล <NSCopying>  สำหรับคลาสของอ็อบเจ็กนั้นๆ ดังเช่น ตัวอย่างโปรแกรมต่อไปนี้ (คลาส Product จากโปรแกรม 8.7)

     

    Program 12.4

    product.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    #import <Foundation/Foundation.h>
     
    @interface Product : NSObject <NSCopying>
    {
        NSMutableString* _name;
        float _price;
    }
     
    -(void) setName:(NSString*) name andPrice:(float) price;
     
    @end

     

    ในส่วนของ interface เราได้เพิ่ม <NSCopying> เพื่อประกาศว่าเราจะเขียนแคทิกกอรีนี้ หลังจากนั้นก็ต้องเขียนเมธอดที่แคทิกอรี่บังคับนั่นคือ copyWithZone: (ถ้าดูจาก error ก่อนหน้านี้ เราก็พอจะคาดเดาได้ว่า ต้องเขียนเมธอด copyWithZone)

     

    product.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
    38
    39
    40
    41
    42
    43
    44
    45
    
    #import "Product.h"
     
    @implementation Product
     
    -(id) init
    {
        self = [super init];
        if( self != nil)
        {
            _name = [[NSMutableString alloc] init];
            _price = 0;
        }
        return self;
    }
    -(void) setName:(NSString*) name
    {
        if(_name != nil)
            [_name release];
        _name = [name copy];
    }
     
    -(id) copyWithZone: (NSZone *) zone
    {
        Product* newProduct = [[Product allocWithZone:zone] init];
        [newProduct setName:_name andPrice:_price];
        return newProduct;
    }
     
    -(void) setName:(NSString*) name andPrice:(float) price
    {
        [_name setString:name];
        _price = price;
    }
     
    -(NSString*) description
    {
        return [NSString stringWithFormat:@"%@ %.2f",_name,_price ];
    }
     
    -(void) dealloc
    {
        [_name release];
        [super dealloc];
    }
    @end

     

    เมื่อดูโค้ดของ copyWithZone: เราได้ประกาศอ็อบเจ็กขึ้นมาใหม่ ด้วยเมธอด allocWithZone: และส่งพารามิเตอร์ zone ที่ได้รับเข้ามา พารามิเตอร์ zone นี้เป็นตัวกำหนดขอบเขตพื้นส่วนของหน่วยความจำ โดยปกติเราไม่ต้องไปยุ่งเกี่ยวกับ memory zone เลย เว้นแต่ในกรณีที่เราต้องการจะกำหนดขอบเขตพื้นที่หน่วยความจำเอง เช่น เราต้องการให้สมาชิกของคลาสจองพื้นที่ในบริเวณเดียวกัน ซึ่งอาจจะเขียนโค้ดส่วนของ init ได้ดังตัวอย่างต่อไปนี้

     

    -(id)init {
        if (self = [super init]) {
            obj = [[otherClass allocWithZone:[self zone]] init];
        }
     
        return self;
    }

     

    ดังนั้นเมื่อเราส่ง zone เป็นพารามิเตอร์ ก็หมายถึงว่าเราต้องการจะให้อ็อบเจ็กใหม่นี้ อยู่ในส่วนเดียวกันกับอ็อบเจ็กที่เราต้องการจะก๊อบปี้นั่นเอง เมื่อเราสร้างอ็อบเจ็กใหม่แล้วจากนั้นก็เรียกเมธอด setName:andPrice: เพื่อกำหนดค่าต่างๆของอ็อบเจ็ก และถ้าหากสังเกตดูจะพบว่าเมธอดที่ส่งค่ากลับเป็นอ็อบเจ็กที่เคยเขียนมาต้อง เรียก autorelease แต่เมธอดนี้กลับไม่ต้องเรียก นั่นก็เพราะว่า เมธอด initWithZone: นี้ ถูกเรียกโดย copy ซึ่งจะเพิ่มค่า retain count อยู่แล้ว เพื่อให้เป็นไปตามกฎการนับจำนวน retain เราจึงไม่ต้อง release ในเมธอดนี้ เพราะผู้เรียกจะเป็นคนสั่ง release เอง

     

    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
    
    #import <Foundation/Foundation.h>
    #import "Product.h"
     
    int main(int argc, const char * argv[])
    {
     
        @autoreleasepool {
     
            Product* apple = [[Product alloc] init];
            Product* samsung;
     
            [apple setName:@"Computer" andPrice:100];
     
            samsung = [apple copy];
     
            NSLog(@"%@",apple);
            NSLog(@"%@",samsung);
     
            [apple release];
            [samsung release];
     
        }
        return 0;
    }

    Program 12.4 Output

    Computer 100
    Computer 100

     

    จากโค้ดของโปรแกรม แสดงให้เห็นว่าอ็อบเจ็ก samsung ก็อปปี้ apple ได้โดยใช้เมธอด copy และอ็อบเจ็กที่ได้ก็เหมือนกันทุกประการ และโปรแกรมก็ทำงานได้สมบูรณ์ แต่อย่างไรก็ตามถ้าหากเราซับคลาส Product เมธอด copy นี้ก็จะถูก inherited ไปด้วย นั่นหมายความว่าเมื่อเราเรียกเมธอด copy ด้วยคลาสใหม่ เราจะไม่ได้คลาสใหม่แต่จะได้ Product แทน การแก้ไขอาจจะทำได้โดยเปลี่ยนโค้ด เพื่อให้แน่ใจว่าเมธอด copy จะสร้างอ็อบเจ็กที่เป็นซับคลาสไม่ใช่คลาสแม่ ซึ่งมีโค้ดเป็นดังนี้

     

    id newProduct = [[[self class] allocWithZone: zone] init];

     

    หรือเราอาจจะเขียน copyWithZone: ของซับคลาสขึ้นมาใหม่ก็ได้ แต่เราควรเรียกเมธอด copy ของ super ก่อนด้วยเพื่อให้แน่ใจว่าตัวแปรต่างๆในคลาสแม่ก๊อบปี้อย่างถูกต้อง

     

    Copy NSArray

    ในกรณีที่เราใช้ NSArray หรือ NSMutableArray และต้องการจะก๊อบปปี้สมาชิกทั้งหมดมาด้วย เราสามารถใช้เมธอด initWithArray:copyItem: เพื่อช่วยให้เราก๊อบปี้อาร์เรย์ได้อย่างง่ายดาย ดังเช่นตัวอย่าง โปรแกรมต่อไปนี้

     

    Program 12.5

    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 <Foundation/Foundation.h>
     
    int main(int argc, const char * argv[])
    {
     
        @autoreleasepool {
     
            NSMutableString* apple = [NSMutableString stringWithString:@"Apple"];
            NSMutableString* mango = [NSMutableString stringWithString:@"Mango"];
            NSMutableString* orange = [NSMutableString stringWithString:@"Orange"];
     
            NSMutableArray* fruits_A = [NSMutableArray arrayWithObjects:apple
                                        ,mango,orange, nil];
     
            NSArray* fruits_B = [[NSArray alloc] initWithArray:fruits_A copyItems:YES];
     
            [[fruits_A objectAtIndex:0] appendString:@"123"];
     
            NSLog(@"A: %@",fruits_A);
            NSLog(@"B: %@",fruits_B);
     
            [fruits_B release];
     
        }
        return 0;
    }

     

    Program 12.5 Output

    A: (
        Apple,
        Mango,
        Orange
    )

    B: (
        Apple123,
        Mango,
        Orange
    )

     

    ในบรรทัดที่ 16 จะเห็นว่าเราได้เรียกใช้เมธอด initWithArray:copyItem: และส่งพารามิเตอร์ fruit_A  และ YES ตามลำดับ หลังจากนั้นก็แก้ไขสมาชิกตัวแรกของอาร์เรย์ B โดยเพิ่ม “123” ต่อท้ายเข้าไป เมื่อคอมไพล์โปรแกรมก็จะเห็นว่าอาร์เรย์ทั้งสองไม่ได้เกี่ยวข้องกันแล้ว

     

    Setter/Getter & Copy Attribute

    เมื่อไหร่ก็ตามที่เราเขียน setter หรือ getter เราควรจะพิจารณาถึงวิธีการเก็บค่าของตัวแปรว่ารับเข้ามาแบบใด เพื่อที่จะได้จัดการกับตัวแปรนั้นได้อย่างถูกต้อง เช่นสมมติว่าเขียน เมธอด setter ดังนี้

     

    -(void) setName:(NSString*) name
    {
        _name = name;
    }
     
    -(void) setPrice:(float) price
    {
        _price = price;
    }

     

    เมธอด setPrice: สามารถใช้การให้ค่าได้โดยตรง เพราะเป็น primitive type แต่ในกรณี setName นั้นไม่สามารถทำแบบนี้ได้ เพราะจากที่เราได้เรียนรู้ไปแล้วว่า การให้ค่าแบบนี้เป็นการให้ _name ชี้ไปยังตำแหน่งเดียวกับพารามิเตอร์ name ดังนั้นแล้วสิ่งที่ควรจะแก้ไข ก็คือเปลี่ยนไปใช้เมธอด copy ดังนี้

     

    -(void) setName:(NSString*) name
    {
        _name = [name copy];
    }

     

    แต่อย่างไรก็ตามการไขแบบนี้ก็ยังจะเกิดปัญหาอยู่เพราะเนื่องจากเมื่อ เรียกใช้เมธอด copy สิ่งที่จะเกิดขึ้นก็คือ _name จะเพิ่มค่า retain count และเราต้อง release เพื่อให้จำนวน retain นั้นเท่ากัน ดังนั้นแล้ว เราอาจจะแก้ไขเปลี่ยนโค้ดให้เป็น

     

    -(void) setName:(NSString*) name
    {
        if(_name != nil)
            [_name release];
        _name = [name copy];
    }

     

    จากโค้ดเราตรวจสอบก่อนว่าตัวแปร _name มีค่าเก่าอยู่หรือไม่ ถ้ามีก็ให้ release ก่อนแล้วถึงจะ copy ค่าใหม่

    ในบทที่ 7 เราได้พูดถึง copy arrtibute ของ property กันไปบ้าง ถ้าหากเราประกาศพร๊อพเพอร์ตี้แบบ copy เช่น

     

    @property (copy,nonatomic) NSString *name;

     

    และเมื่อคอมไพล์เลอร์สร้างโค้ดจาก @synthesize โค้ดจะรูปแบบเดียวกันกับเมธอด setName ที่ได้แก้ไขไป

    ในบทนี้เราได้เรียนรู้การก๊อบปี้อ็อบเจ็ก และได้ทำความเข้าใจเพิ่มเกี่ยวกับ shallow copy , deep copy รวมไปถึงความเข้าใจเกี่ยวกับการใช้ attribute แบบ copy ในบทต่อไปเราจะได้เรียนรู้กับ Archiving ซึ่งเป็นการรวมข้อมูลจากหลายอ็อบเจ็กมาไว้ในรูปแบบเดียว

     

     

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

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

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