1、系統物件的複製
不管是集合類物件,還是非集合類物件,接收到copy
和mutableCopy
訊息時,都遵循以下準則:
copy
返回immutable
物件;所以,如果對copy返回值使用mutable
物件介面就會crash;mutableCopy
返回mutable
物件;
下圖詳細闡述了NSString
、NSMutableString
、NSArray
、NSMutableArray
、NSDictionary
、NSMutableDictionary
分別呼叫copy
與mutableCopy
方法後的結果:
1.1 非集合類物件的copy與mutableCopy
系統非集合類物件指的是NSString
,NSNumber
之類的物件。下面看一下非集合類NSString
物件拷貝的例子:
copy
1 2 3 4 5 6 7 |
NSString *string = @"origin"; NSString *stringCopy = [string copy]; NSMutableString *stringMCopy = [string mutableCopy]; NSLog(@"%p", string); NSLog(@"%p", stringCopy); NSLog(@"%p", stringMCopy); |
1 2 3 |
2016-03-10 17:32:04.479 Homework[21715:2353641] 0x1000d0ea0 2016-03-10 17:32:04.481 Homework[21715:2353641] 0x1000d0ea0 2016-03-10 17:32:04.481 Homework[21715:2353641] 0x17006fe40 |
通過檢視記憶體,可以看到stringCopy
和string
的地址是一樣,進行了指標拷貝;而stringMCopy
的地址和string
不一樣,進行了內容拷貝。
mutableCopy
1 2 3 4 5 6 7 8 9 10 |
NSMutableString *string = [NSMutableString stringWithString: @"origin"]; //copy NSString *stringCopy = [string copy]; NSMutableString *mStringCopy = [string copy]; NSMutableString *stringMCopy = [string mutableCopy]; NSLog(@"%p", string); NSLog(@"%p", stringCopy); NSLog(@"%p", mStringCopy); NSLog(@"%p", stringMCopy); |
1 2 3 4 |
2016-03-10 17:34:11.486 Homework[21728:2354359] 0x17426f800 2016-03-10 17:34:11.487 Homework[21728:2354359] 0x174230600 2016-03-10 17:34:11.487 Homework[21728:2354359] 0x1742306e0 2016-03-10 17:34:11.487 Homework[21728:2354359] 0x174267240 |
1 2 3 4 |
//change value [mStringCopy appendString:@"mm"]; //crash [string appendString:@" origion!"]; [stringMCopy appendString:@"!!"]; |
crash的原因就是copy
返回的物件是immutable
物件。
1.2 集合類物件的copy與mutableCopy
集合類物件是指NSArray
、NSDictionary
、NSSet
之類的物件。下面看一下集合類NSArray
物件使用copy
和mutableCopy
的一個例子:
copy
1 2 3 4 5 6 7 |
NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]]; NSArray *copyArray = [array copy]; NSMutableArray *mCopyArray = [array mutableCopy]; NSLog(@"%p", array); NSLog(@"%p", copyArray); NSLog(@"%p", mCopyArray); |
1 2 3 |
2016-03-10 17:53:40.113 Homework[21775:2358227] 0x17403f040 2016-03-10 17:53:40.114 Homework[21775:2358227] 0x17403f040 2016-03-10 17:53:40.114 Homework[21775:2358227] 0x174247e60 |
可以看到copyArray
和array
的地址是一樣的,而mCopyArray
和array
的地址是不同的。說明copy操作進行了指標拷貝,mutableCopy進行了內容拷貝。但需要強調的是:此處的內容拷貝,僅僅是拷貝array
這個物件,array
集合內部的元素仍然是指標拷貝。這和上面的非集合immutable
物件的拷貝還是挺相似的,那麼mutable
物件的拷貝會不會類似呢?我們繼續往下,
mutableCopy
1 2 3 4 5 6 7 |
NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil]; NSArray *copyArray = [array copy]; NSMutableArray *mCopyArray = [array mutableCopy]; NSLog(@"%p", array); NSLog(@"%p", copyArray); NSLog(@"%p", mCopyArray); |
1 2 3 |
2016-03-10 17:54:39.114 Homework[21782:2358605] 0x170058e40 2016-03-10 17:54:39.115 Homework[21782:2358605] 0x170058ed0 2016-03-10 17:54:39.115 Homework[21782:2358605] 0x170058ea0 |
檢視記憶體,如我們所料,copyArray
、mCopyArray
和array
的記憶體地址都不一樣,說明copyArray
、mCopyArray
都對array
進行了內容拷貝。
2、自定義物件的複製
使用copy
和mutableCopy
複製物件的副本使用起來確實方便,那麼我們自定義的類是否可呼叫copy
與mutableCopy
方法來複制副本呢?我們先定義一個Person
類,程式碼如下:
1 2 3 4 5 |
@interface Person : NSObject @property (nonatomic, assign) NSInteger age; @property (nonatomic, copy) NSString *name; @end |
然後嘗試呼叫Person
的copy
方法來複制一個副本:
1 2 3 4 |
Person *person1 = [[Person alloc] init];//建立一個Person物件 person1.age = 20; person1.name = @"張三"; Person *person2 = [person1 copy];//複製副本 |
執行程式,將會發生崩潰,並輸出以下錯誤資訊:
1 |
[Person copyWithZone:]: unrecognized selector sent to instance 0x608000030920 |
上面的提示:Person
找不到copyWithZone:
方法。我們將複製副本的程式碼換成如下:
1 |
Person *person2 = [person1 mutableCopy];//複製副本 |
再次執行程式,程式同樣崩潰了,並輸出去以下錯誤資訊:
1 |
[Person mutableCopyWithZone:]: unrecognized selector sent to instance 0x600000221120 |
上面的提示:Person
找不到mutableCopyWithZone:
方法。
大家可能會覺得疑惑,程式只是呼叫了copy
和mutableCopy
方法,為什麼會提示找不到copyWithZone:
與mutableCopyWithZone:
方法呢?其實當程式呼叫物件的copy
方法來複制自身時,底層需要呼叫copyWithZone:
方法來完成實際的複製工作,copy
返回實際上就是copyWithZone:
方法的返回值;mutableCopy
與mutableCopyWithZone:
方法也是同樣的道理。
那麼怎麼做才能讓自定義的物件進行copy
與mutableCopy
呢?需要做以下事情:
1 2 |
1.讓類實現NSCopying/NSMutableCopying協議。 2.讓類實現copyWithZone:/mutableCopyWithZone:方法 |
所以讓我們的Person
類能夠複製自身,我們需要讓Person
實現NSCopying
協議;然後實現copyWithZone:
方法:
1 2 3 4 5 |
@interface Person : NSObject @property (nonatomic, assign) NSInteger age; @property (nonatomic, copy) NSString *name; @end |
1 2 3 4 5 6 7 8 9 10 11 |
#import "Person.h" @implementation Person - (id)copyWithZone:(NSZone *)zone { Person *person = [[[self class] allocWithZone:zone] init]; person.age = self.age; person.name = self.name; return person; } @end |
執行之後發現我們實現了物件的複製:
同時需要注意的是如果物件中有其他指標型別的例項變數,且只是簡單的賦值操作:person.obj2 = self.obj2
,其中obj2
是另一個自定義類,如果我們修改obj2
中的屬性,我們會發現複製後的person
物件中obj2
物件中的屬性值也變了,因為對於這個物件並沒有進行copy
操作,這樣的複製操作不是完全的複製,如果要實現完全的複製,需要將obj2
對應的類也要實現copy,然後這樣賦值:person.obj2 = [self.obj2 copy]
。如果物件很多或者層級很多,實現起來還是很麻煩的。如果需要實現完全複製同樣還有另有一種方法,那就是歸檔:
1 |
Person *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:person1]]; |
這樣我們就實現了自定義物件的複製,需要指出的是如果重寫copyWithZone:
方法時,其父類已經實現NSCopying
協議,並重寫過了copyWithZone:
方法,那麼子類重寫copyWithZone:
方法應先呼叫父類的copy
方法複製從父類繼承得到的成員變數,然後對子類中定義的成員變數進行賦值:
1 2 3 4 5 6 |
- (id)copyWithZone:(NSZone *)zone { id obj = [super copyWithZone:zone]; //對子類定義的成員變數賦值 ... return obj; } |
關於mutableCopy
的實現與copy
的實現類似,只是實現的是NSMutableCopying
協議與mutableCopyWithZone:
方法。對於自定義的物件,在我看來並沒有什麼可變不可變的概念,因此實現mutableCopy
其實是沒有什麼意義的,在此就不詳細介紹了。
3、定義屬性的copy指示符
如下段程式碼,我們在定義屬性的時候使用了copy
指示符:
1 2 3 4 |
#import @interface Person : NSObject @property (nonatomic, copy) NSMutableString *name; @end |
使用如下程式碼來進行測試:
1 2 3 |
Person *person1 = [[Person alloc] init];//建立一個Person物件 person1.name = [NSMutableString stringWithString:@"蘇小妖"]; [person1.name appendString:@"123"]; |
執行程式會崩潰,並且提示以下資訊:
1 |
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:' |
這段錯誤提示不允許修改person
的name
屬性,這是因為程式定義name
屬性時使用了copy
指示符,該指示符置頂呼叫setName:
方法時(通過點語法賦值時,實際上是呼叫對應的setter
方法),程式實際上會使用引數的副本對name
實際變數複製。也就是說,setName:
方法的程式碼如下:
1 2 3 |
- (void)setName:(NSMutableString *)name { _name = [name copy]; } |
copy
方法預設是複製該物件的不可變副本,雖然程式傳入的NSMutableString
,但程式呼叫該引數的copy
方法得到的是不可變副本。因此,程式賦給Person
物件的name例項變數的值依然是不可變字串。
注意:定義合成getter
、setter
方法時並沒有提供mutableCopy
指示符。因此即使定義例項變數時使用了可變型別,但只要使用copy
指示符,例項變數實際得到的值總是不可變物件。
參考文章:
iOS之物件複製
iOS的深複製與淺複製