OC基礎-拷貝

一、概念理解

拷貝是將原有的對象拷貝一份,獲得一個新的對象。

copy和retain的區別:

如果retain一個對象,對象始終只有一個,修改的只是對象中的計數器,增加一個引用,多個引用指向同一內存空間,如果其中一個引用修改了對象的屬性,則對象的屬性發生改變。

如果copy一個對象,將獲得一個新的對象,對新對象的[mutable]copy操作,和原對象沒有關係。


拷貝分淺拷貝和深拷貝:

例如一個數組,包含多個對象

淺拷貝:拷貝一個新的數組對象,數組的元素所指向的內存空間和原來的對象所指向的內存空間是相同的。

深拷貝:拷貝一個新的數組對象,數組的元素所指向的內存空間也是原對象的元素所指向的內存空間的拷貝。


二、拷貝協議

1.NSCopying協議

 @protocol NSCopying
 -(id)copyWithZone:(NSZone *)zone;
 @end

任何拷貝功能都需要實現NSCopying協議。也就是說,如果想要使用copy方法,就必須實現該方法。該方法在調用copy方法的時候觸發。

系統有些類已經實現了該協議,如:

NSString、NSMutableString、NSArray、NSMutableArray、NSDictionary、NSMutableDictionary

對於這些類的對象,我們可以直接使用copy方法

2.NSMutableCopying協議

@protocol NSMutableCopying
 -(id)mutableCopyWithZone:(NSZone *)zone;
 @end

該協議和上面的類似,區別在於一個可變。任何需要調用mutableCopy方法,都必須實現該方法。使用的情況很少。

系統有些類已經實現了該協議,同上,我們可以直接使用。

對於自定義的類,基本上都是隻使用NSCopying協議,NSMutableCopying協議基本不用。


3.系統中已經實現了拷貝協議的一些類

不可變對象,通過copy,新對象不可變

不可變對象,通過mutableCopy,新對象可變

可變對象,通過copy,新對象不可變

可變對象,通過mutableCopy,新對象可變

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //NSString調用mutableCopy獲得的新對象是可變的
        NSString * s = [NSString stringWithFormat:@"hello"];
        NSString * s1 = [s copy];
        NSMutableString * s2 = [s mutableCopy];
        [s2 appendString:@" world!"];
        
        NSLog(@"s1 is %@", s1);
        NSLog(@"s2 is %@", s2);
        
        NSMutableString * ms = [NSMutableString stringWithFormat:@"happy"];
        
        //NSMutableString調用copy獲得的是不可變字符串
        NSMutableString * ms1 = [ms copy];//ms1不能使用appendString
        NSMutableString * ms2 = [ms mutableCopy];
        [ms2 appendString:@" girls!"];
        
        NSLog(@"ms1 is %@", ms1);
        NSLog(@"ms2 is %@", ms2);
    }
    return 0;
}
輸出結果:

2015-10-15 04:28:00.155 拷貝[1448:21914] s1 is hello
2015-10-15 04:28:00.156 拷貝[1448:21914] s2 is hello world!
2015-10-15 04:28:00.156 拷貝[1448:21914] ms1 is happy
2015-10-15 04:28:00.157 拷貝[1448:21914] ms2 is happy girls!

三、自定義類實現拷貝協議

現在假設自己創建一個類,需要調用copy方法,如何去做呢?下面是一個例子關於自定義類如何實現拷貝協議。

QFCar.h

#import <Foundation/Foundation.h>

@interface QFCar : NSObject <NSCopying,NSMutableCopying>
{
    NSString * _name;
    int _year;
}
@property(nonatomic, copy)NSString * name;
@property(nonatomic, assign)int year;

@end

創建一個類QFCar,實現了NSCoyping和NSMutableCopying協議。聲明瞭兩個屬性,一個表示車的品牌,一個表示車上市年份。並對這些屬性使用@property。其實NSMutableCopying協議的做法和NSCopying協議的做法是一樣的,一般使用NSCopying完全足夠了,這裏爲了全面理解,也實現了該協議。

QFCar.m

#import "QFCar.h"

@implementation QFCar

@synthesize name = _name;
@synthesize year = _year;

-(void)setName:(NSString *)name{
    if(_name != name){
        [_name release];
        _name = [name copy];
    }
}
//實現NSObject的方法,在調用[car copy]的時候觸發
-(id)copyWithZone:(NSZone *)zone{
  //1.新分配一塊內存空間,創建一個新的對象
    QFCar * c = [[[[self class] allocWithZone:zone] init] autorelease];
 //2.設置屬性
    c.name = self.name;
    c.year = self.year;
//3.返回新的對象
    return c;
}

//實現NSObject的方法,在調用[car mutablecopy]時觸發
-(id)mutableCopyWithZone:(NSZone *)zone{
    QFCar * c = [[[[self class] allocWithZone:zone] init] autorelease];
    c.name = self.name;
    c.year = self.year;
    return c;
}

//當調用%@輸出該類的對象時,觸發該方法。
-(NSString *)description{
    return [NSString stringWithFormat:@"Car name %@ year %d", self.name, self.year];
}

@end
在QFCar.h頭文件中,我們實現了NSCopying協議和NSMutableCopying協議,在QFCar.m文件中,我們就可以實現copyWithZone方法和mutableCopyWithZone方法,這兩個方法是NSObject的方法,我們只有在頭文件中實現了協議,才能實現該方法。

在上面的代碼中,可見copy和mutableCopy的實現是一樣的,一般我們使用copy就足夠了。

main.m

#import <Foundation/Foundation.h>
#import "QFCar.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        QFCar * car = [[QFCar alloc] init];
        car.name = @"wow";
        car.year = 2015;
        QFCar * car1 = [car copy];
        car1.name = @"qq";
        car1.year = 2014;
        //NSLog(@"car1 %@ is %d", car1.name, car1.year);
        NSLog(@"car1 is %@", car1);
        
        QFCar * car2 = [car mutableCopy];
        car2.name = @"qr";
        car2.year = 2013;
        //NSLog(@"car2 %@ is %d", car2.name, car2.year);
        NSLog(@"car2 is %@", car2);
        
        NSLog(@"car is %@", car);
    }
    return 0;
}
輸出結果:

2015-10-15 05:07:38.694 拷貝[1937:34383] car1 is Car name qq year 2014
2015-10-15 05:07:38.696 拷貝[1937:34383] car2 is Car name qr year 2013
2015-10-15 05:07:38.696 拷貝[1937:34383] car is Car name wow year 2015
Program ended with exit code: 0
由輸出結果可以看出:

1.對子定義的類的對象,copy和mutableCopy是一樣的。

2.使用copy後,新對象的屬性的改變和原對象沒有關係。


四、深拷貝

1.概念

對於一些有多級子對象的拷貝,纔有深拷貝和淺拷貝之分。

淺拷貝只拷貝最上層的對象,子對象不做任何拷貝,子對象和原子對象指向同一內存空間。

深拷貝會層層拷貝,獲得的新對象和原對象完全不同。下面是一個數組的深拷貝示意圖:

2.實現深拷貝的方法

有兩種實現深拷貝的方法:

1⃣️使用copy協議來實現深拷貝

2⃣️使用歸檔來實現深拷貝


五、使用拷貝協議進行深拷貝

以系統默認實現深拷貝的類爲例。NSString類是簡單對象,無神拷貝。NSArray和NSDictionary有深拷貝,實現深拷貝的方法如下:

- (id) initWithArray: (NSArray *) array copyItems: (BOOL)flag;

- (id) initWithDictionary: (NSDictionary *) otherDictionary copyItems: (BOOL)flag;

調用該方法時,將flag開關打開。

1.NSArray淺拷貝

#import <Foundation/Foundation.h>
#import "QFCar.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray * _carList = [[NSMutableArray alloc] init];
        for(int i = 0; i < 10; i++){
            QFCar * c = [[QFCar alloc] init];
            c.name = [NSString stringWithFormat:@"WOW730%d",i];
            c.year = 2006+i;
            [_carList addObject:c];
        }
        //淺拷貝
        NSMutableArray * arr2 = [[NSMutableArray alloc] initWithArray:_carList];
        [[arr2 objectAtIndex:0] setName:@"QQ2103"];
        [[arr2 objectAtIndex:0] setYear:2103];
        
        NSLog(@"arr2 is %@", arr2);
        NSLog(@"carList is %@", _carList);
    }
    return 0;
}
輸出結果:

2015-10-15 05:58:32.507 拷貝[2950:59616] arr2 is (
    "Car name QQ2103 year 2103",
    "Car name WOW7301 year 2007",
    "Car name WOW7302 year 2008",
    "Car name WOW7303 year 2009",
    "Car name WOW7304 year 2010",
    "Car name WOW7305 year 2011",
    "Car name WOW7306 year 2012",
    "Car name WOW7307 year 2013",
    "Car name WOW7308 year 2014",
    "Car name WOW7309 year 2015"
)
2015-10-15 05:58:32.509 拷貝[2950:59616] carList is (
    "Car name QQ2103 year 2103",
    "Car name WOW7301 year 2007",
    "Car name WOW7302 year 2008",
    "Car name WOW7303 year 2009",
    "Car name WOW7304 year 2010",
    "Car name WOW7305 year 2011",
    "Car name WOW7306 year 2012",
    "Car name WOW7307 year 2013",
    "Car name WOW7308 year 2014",
    "Car name WOW7309 year 2015"
)
Program ended with exit code: 0
由輸出結果可見,淺拷貝只是拷貝了外層對象,子對象並沒有拷貝,子對象和原對象指向同一內存空間,所以新對象的子對象修改了屬性,原對象對應的子對象的屬性也發生變化。

2.NSArray深拷貝

#import <Foundation/Foundation.h>
#import "QFCar.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray * _carList = [[NSMutableArray alloc] init];
        for(int i = 0; i < 10; i++){
            QFCar * c = [[QFCar alloc] init];
            c.name = [NSString stringWithFormat:@"WOW730%d",i];
            c.year = 2006+i;
            [_carList addObject:c];
        }
        
        NSMutableArray * arr3 = [[NSMutableArray alloc] initWithArray:_carList copyItems:YES];
        [[arr3 objectAtIndex:0] setName:@"QQ2103"];
        [[arr3 objectAtIndex:0] setYear:2103];
        NSLog(@"arr3 is %@", arr3);
        NSLog(@"carList is %@", _carList);
    }
    return 0;
}
輸出結果:

2015-10-15 06:18:43.250 拷貝[3413:70327] arr3 is (
    "Car name QQ2103 year 2103",
    "Car name WOW7301 year 2007",
    "Car name WOW7302 year 2008",
    "Car name WOW7303 year 2009",
    "Car name WOW7304 year 2010",
    "Car name WOW7305 year 2011",
    "Car name WOW7306 year 2012",
    "Car name WOW7307 year 2013",
    "Car name WOW7308 year 2014",
    "Car name WOW7309 year 2015"
)
2015-10-15 06:18:43.251 拷貝[3413:70327] carList is (
    "Car name WOW7300 year 2006",
    "Car name WOW7301 year 2007",
    "Car name WOW7302 year 2008",
    "Car name WOW7303 year 2009",
    "Car name WOW7304 year 2010",
    "Car name WOW7305 year 2011",
    "Car name WOW7306 year 2012",
    "Car name WOW7307 year 2013",
    "Car name WOW7308 year 2014",
    "Car name WOW7309 year 2015"
)
Program ended with exit code: 0
由輸出結果可以看出,深拷貝所獲得的新對象和原來的對象完全沒有關係,對新對象的子對象做任何修改都不會影響原對象。
備註:

在使用copy協議進行深拷貝時,外層對象和子對象都需要拷貝,所以都需要實現NSCopying協議,否則會報錯。在上面的例子中,NSMutableArray已由系統實現了NSCopying協議,QFCar自定義類在前面的代碼中也實現了NSCopying協議。


六、使用歸檔協議進行深拷貝

歸檔協議NSCoding的兩個方法:歸檔和解檔

-(void)encodeWithCoder: (NSCoder *) aCoder;

- (id) initWithCoder: (NSCoder *) aDecoder;

QFcar.h

#import <Foundation/Foundation.h>

@interface QFCar : NSObject <NSCoding>
{
    NSString * _name;
    int _year;
}
@property(nonatomic, copy)NSString * name;
@property(nonatomic, assign)int year;

@end
使用了歸檔協議,就無須使用copy協議了,兩種協議任選一種,都可以實現深拷貝,都需要熟練。

QFcar.m

#import "QFCar.h"

@implementation QFCar

@synthesize name = _name;
@synthesize year = _year;

//使用@property和@synthesize會自動實現setter和getter方法,但是如果覺得某個setter或者getter方法需要做一些初始化操作,可以自己重寫。
-(void)setName:(NSString *)name{
    //系統默認的寫法
    /*
    if(_name != name){
        [_name release];
        _name = [name copy];
    }
     */
    _name = @"china";
}

//當調用%@輸出該類的對象時,觸發該方法。
-(NSString *)description{
    return [NSString stringWithFormat:@"Car name %@ year %d", self.name, self.year];
}

//NSCoding歸檔協議的兩個方法
//存檔
-(void) encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:self.name forKey:@"Name"];
    [aCoder encodeInt:self.year forKey:@"Year"];
    //這裏存檔的key可以任意,解檔的key和存檔的key保持一致。
}
//解檔
-(id)initWithCoder:(NSCoder *)aDecoder{
    self = [super init];
    if(self){
        self.name = [aDecoder decodeObjectForKey:@"Name"];
        self.year = [aDecoder decodeIntForKey:@"Year"];
    }
    return self;
}

@end
存檔的過程,就是以鍵值對形式,將對象轉換成二進制數據,鍵爲自定義的鍵,值就是對象的二進制數據。

解檔時,根據鍵,將二進制數據還原成對象。

main.m

#import <Foundation/Foundation.h>
#import "QFCar.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray * _carList = [[NSMutableArray alloc] init];
        for(int i = 0; i < 10; i++){
            QFCar * c = [[QFCar alloc] init];
            c.name = [NSString stringWithFormat:@"WOW730%d",i];
            c.year = 2006+i;
            [_carList addObject:c];
        }
        //1.將數組轉換成二進制數據
        NSData * data = [NSKeyedArchiver archivedDataWithRootObject: _carList];
        //2.將二進制數據還原成原來的數組
        NSMutableArray * arr = [NSKeyedUnarchiver unarchiveObjectWithData: data];
        [[arr objectAtIndex:0] setName:@"QQ2103"];
        [[arr objectAtIndex:0] setYear:2103];
        
        NSLog(@"arr is %@", arr);
        NSLog(@"carList is %@", _carList);
        
        //崩潰的原因是因爲QFCar沒有實現歸檔協議
        
    }
    return 0;
}
輸出結果;

2015-10-15 06:44:48.202 拷貝[4028:84044] arr is (
    "Car name china year 2103",
    "Car name china year 2007",
    "Car name china year 2008",
    "Car name china year 2009",
    "Car name china year 2010",
    "Car name china year 2011",
    "Car name china year 2012",
    "Car name china year 2013",
    "Car name china year 2014",
    "Car name china year 2015"
)
2015-10-15 06:44:48.204 拷貝[4028:84044] carList is (
    "Car name china year 2006",
    "Car name china year 2007",
    "Car name china year 2008",
    "Car name china year 2009",
    "Car name china year 2010",
    "Car name china year 2011",
    "Car name china year 2012",
    "Car name china year 2013",
    "Car name china year 2014",
    "Car name china year 2015"
)
Program ended with exit code: 0
由上面的結果可於看出,使用歸檔協議也實現了深拷貝。


上面的例子中,無論是使用copy協議還是歸檔協議,都是針對只有一層子對象的對象進行深拷貝,如果是對多層次的對象進行深拷貝,實現原理是一樣的,需要層層深入,操作還是挺複雜的,但按照原理來,也不會出錯。

@詩未冷學習博客



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章