深淺拷貝的不同
淺拷貝是對對象發出一個retain操作,將指針賦值給新的集合對象,如圖1
淺拷貝:
NSArray *shallowCopyArray = [someArray copyWithZone:nil];
NSDictionary *shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:NO];
其中這些方法不侷限於數組,也適用於array對象,其中copy默認調用的是copyWithZone,返回的是不可變的對象,mutalbeCopy默認調用mutalbeCopyWithZone,返回可變對象
深拷貝:
集合方法:initWithDicotonary copyItems:YES是進行深拷貝,它使每個集合內的元素接受copyWithZone消息,其中copyWithZone是淺拷貝,他只進行一層深拷貝
NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];
如果需要進行純深拷貝(多層次深拷貝),應該用如下方法,需要遵循NSCoding協議:
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
拷貝和可變性:
拷貝一個集合會對內部元素的可變性有影響,每個方法的影響都稍微不同,具體如下:
- copyWithZone,返回不可變的首層對象(數組的話默認由消息的發送者進行retainCount+1新的array和舊的array,是首地址的拷貝),所有更深層次的可變性保持不變
- initWithDictionary: copyItems:NO 返回可變的首層對象,所有更深層次的可變性保持不變
- initWithDictionary: copyItems:YES返回可變的首層對象,下一層不可變(因爲對每個內部對象發送了一次copyWithZone方法,如果爲實現此方法將會報runtimeError),所有更深層次的可變性保持不變
- 純深拷貝所有可變性在任意層上保持不變
//copy 默認調用copyWithZone返回的是不可變的對象,mutableCopy 默認調用mutableCopyWithZone返回可變對象;二者都會對地址進行改變,查看NSObject API有解析
NSMutableString* im=[[NSMutableString alloc]initWithString:@"Inner_MM"];
NSString* isx=@"Inner_YYY";
//測試基本數據類型的copy 裏面默認調用copyWithZone,產生新的副本
NSString* test=[im copy];
NSLog(@"im %ld",[im retainCount]);NSLog(@"test %ld",[test retainCount]);
NSLog(@"im %p",im);NSLog(@"test %p",test);
[im appendString:@"TEST"];//元數據改變新數據不變
NSLog(@"im %@",im);NSLog(@"test %@",test);
NSArray* a=[NSArray arrayWithObjects:im,isx, nil];//集合對象
//arr1的內容是 a(數組--裏面有可變字符串im和不可變字符串isx) m(可變字符串) s(不可變字符串)
NSMutableString* m=[[NSMutableString alloc]initWithString:@"MM"];//retainCount1
NSString* s=@"YYY";
NSArray* arr1=[NSArray arrayWithObjects:a,m,s, nil];//m retainCount2加入數組加1;s(不可變字符串比較特殊)是不可變的永遠都是-1
//retainCount是針對內存地址空間的計數器(arr1和arr2地址相同),與具體類別無關--個人理解
NSLog(@"arr1 %ld",[arr1 retainCount]);
NSLog(@"arr1 %p",arr1);
NSArray* arr2=[arr1 copyWithZone:nil];//copyWithZone返回的是消息的接受者
NSLog(@"copyWithZone a %ld",[a retainCount]);//2未改變
//arr1 和 arr2的retainCount均爲2,所以是對arr的指針進行了retain(Copy的副本是隱士的被髮送方retain--官方文檔解釋:NSCopying裏面),他的釋放應由發送方來進行釋放
NSLog(@"copyWithZone arr1 %ld",[arr1 retainCount]);
NSLog(@"copyWithZone arr2 %ld",[arr2 retainCount]);
NSLog(@"copyWithZone arr2 %p",arr2);
//m的引用計數器和地址均相同,因此他們是同一個,所以此處是淺拷貝,但是m的retainCount是2
NSLog(@"copyWithZone arr1 m %ld",[[arr1 objectAtIndex:1]retainCount]);
NSLog(@"copyWithZone arr2 m %ld",[[arr2 objectAtIndex:1]retainCount]);
NSLog(@"copyWithZone arr1 m %p",[arr1 objectAtIndex:1]);
NSLog(@"copyWithZone arr2 m %p",[arr2 objectAtIndex:1]);
//initWithItem copyItems:NO 因爲自己alloc地址肯定不一樣,內容一樣,因爲進行了一次淺拷貝,內部數據進行retain+1
NSArray* arr3=[[NSArray alloc]initWithArray:arr1 copyItems:NO];
NSLog(@"initWithArray copyItems:NO a %ld",[a retainCount]);//3發送改變
NSLog(@"initWithArray copyItems:NO arr1 %ld",[arr1 retainCount]);
NSLog(@"initWithArray copyItems:NO arr3 %ld",[arr3 retainCount]);
NSLog(@"initWithArray copyItems:NO arr3 %p",arr3);
[im appendString:@"WW"];
[m appendString:@"3"];
NSLog(@"initWithArray copyItems:NO a %@",[arr1 objectAtIndex:0]);
NSLog(@"initWithArray copyItems:NO a %@",[arr3 objectAtIndex:0]);
NSLog(@"initWithArray copyItems:NO m %@",[arr1 objectAtIndex:1]);
NSLog(@"initWithArray copyItems:NO m %@",[arr3 objectAtIndex:1]);
NSLog(@"initWithArray copyItems:NO m %ld",[[arr1 objectAtIndex:1]retainCount]);//2+1=3
NSLog(@"initWithArray copyItems:NO m %ld",[[arr3 objectAtIndex:1]retainCount]);
//initWithItem copyItems:YES 因爲自己alloc地址肯定不一樣,如果是集合對象或自定義對象內容一樣,普通對象不一樣
NSArray* arr4=[[NSArray alloc]initWithArray:arr1 copyItems:YES];
NSLog(@"initWithArray copyItems:YES a %ld",[a retainCount]);//4
NSLog(@"initWithArray copyItems:YES arr1 %ld",[arr1 retainCount]);
NSLog(@"initWithArray copyItems:YES arr4 %ld",[arr4 retainCount]);
NSLog(@"initWithArray copyItems:YES arr4 %p",arr4);
[im appendString:@"FF"];
[m appendString:@"4"];
NSLog(@"initWithArray copyItems:YES a %@",[arr1 objectAtIndex:0]);
NSLog(@"initWithArray copyItems:YES a %@",[arr4 objectAtIndex:0]);
NSLog(@"initWithArray copyItems:YES m %@",[arr1 objectAtIndex:1]);
NSLog(@"initWithArray copyItems:YES m %@",[arr4 objectAtIndex:1]);
NSLog(@"initWithArray copyItems:YES m %ld",[[arr1 objectAtIndex:1]retainCount]);
NSLog(@"initWithArray copyItems:YES m %ld",[[arr4 objectAtIndex:1]retainCount]);//-1
NSLog(@"initWithArray copyItems:YES im %ld",[im retainCount]);//此時爲2原因在於加入a的時候+1,此後一直沒變,init不會對數組內數組元素髮消息
//總結:1,copy裏面默認調用copyWithZone,二者可以認爲基本相同,如果自定義子類繼承父類後有額外的變量,需要重寫此方法,並且首先調用父類方法();2,copyWithZone是對數組指針進行retainCount+1;initWithArray copyItems:NO對數組內部數據進行+1(要區分是對數組+1還是裏面的內容+1);3,initWithArray copyItems:YES,對數組內的每個元素接收一次copyWithZone方法(可查看 NSArray),因此,如果內部元素是數組類型還是淺複製即retainCount+1,而基本類型則是重新Copy一個新副本
//注:copy產生的是不可變的,所以arr4裏面的index1輸出的retainCount是-1;數組的操作和其他集合類似