為什么回頭再看這些基礎(chǔ)知識呢智什,因為覺得好記性不如爛筆頭榨乎。以前知其然不知所以然的東西,為了實現(xiàn)功能而實現(xiàn)功能而沒有時間歸納總結(jié)负饲,覺得還是用文章的形式記錄下來很好堤魁,好了,閑話不多說返十,回歸正題妥泉,當(dāng)然同類文章比比皆是,但自己寫一寫跟看一看的感覺會不太一樣洞坑,畢竟也算是自己的體會之一吧盲链。
拷貝,就是復(fù)制對象迟杂」粽矗拷貝分兩種,分別是淺拷貝排拷、深拷貝侧漓。
-
淺拷貝
不會產(chǎn)生新的對象,指針指向的地址沒有改變监氢,也就是沒有創(chuàng)建新的內(nèi)存空間布蔗,即復(fù)制指針。
-
深拷貝
會產(chǎn)生新的對象浪腐,指針指向的地址會改變纵揍,也就是創(chuàng)建了新的內(nèi)存空間,即內(nèi)容拷貝牛欢。
那么copy和mutableCopy有什么區(qū)別呢
-
copy
不可變拷貝骡男,對于不可變的Foundation框架的常見類,是不創(chuàng)建新內(nèi)存空間的傍睹,對于可變的Foundation框架的常見類以及自定義的對象隔盛,會開辟一份內(nèi)存空間給新對象犹菱。新對象都是不可變的。
-
mutableCopy
可變拷貝吮炕,其拷貝過程就是在內(nèi)存中重新開辟一塊區(qū)域腊脱,將對象復(fù)制一份放到這個區(qū)域。新對象是可以改變的龙亲,而且新對象的改變對原對象是沒有影響的陕凹。
我們來以例子說明:
一、NSString
不可變字符串
NSString *str = @"test";
NSString *copyStr = [string copy];
NSMutableString *mutableCopyStr = [string mutableCopy];
NSLog(@"str:%@,%p",str,str);
NSLog(@"copyStr:%@,%p,%d",copyStr,copyStr,copyStr == str);
NSLog(@"mutableCopyStr:%@,%p,%d",muCopyStr,mutableCopyStr,mutableCopyStr == str);
打遇:
str:test,0x10145b098
copyStr:test,0x10145b098,1
muCopyStr:test,0x7feb9355aed0,0
可變字符串
NSMutableString *str = [[NSMutableString alloc]initWithString:@"test"];
NSString *copyStr = [str copy];
NSMutableString *mutableCopyStr = [str mutableCopy];
NSLog(@"str:%@,%p",str,str);
NSLog(@"copyStr:%@,%p,%d",copyStr,copyStr,copyStr == str);
NSLog(@"mutableCopyStr:%@,%p,%d",mutableCopyStr,mutableCopyStr,mutableCopyStr == str);
打佣虐摇:
str:test,0x7fbd90f0f640
copyStr:test,0xa000000747365744,0
muCopyStr:test,0x7fbd90f0be70,0
由此可見,對于不可變字符串拂盯,copy不會產(chǎn)生新的對象佑女,mutableCopy卻是會產(chǎn)生新的對象;對于可變字符串谈竿,copy和mutableCopy都是產(chǎn)生新的對象团驱,只是copy產(chǎn)生一個不可變的新對象,而mutableCopy產(chǎn)生一個可變的新對象空凸。
我們再來看看這個例子
NSString *str =@"test";
NSString *copyStr = [str copy];
NSMutableString *mutableCopyStr = [str mutableCopy];
str = @"test0";
//重新給Str賦值嚎花,實際重新給Str開辟一個內(nèi)存空間,因此這一操作后實際這三個對象都是不一樣的
NSLog(@"str:%@,%p",str,str);
NSLog(@"copyStr:%@,%p,%d",copyStr,copyStr,copyStr == str);
NSLog(@"mutableCopyStr:%@,%p,%d",mutableCopyStr,mutableCopyStr,mutableCopyStr == str);
str:test0,0x10745e218
copyStr:test,0x10745e098,0
mutableCopyStr:test,0x7fa15beab330,0
二呀洲、NSArray
NSArray和NSstring都是Foundation類的一種紊选,所以情況都是一樣
不可變數(shù)組
NSArray *arr =@[@"test"];
NSArray *copyArr = [arr copy];
NSMutableArray *mutableCopyArr = [arr mutableCopy];
NSLog(@"arr:%@,%p",arr,arr);
NSLog(@"copyArr:%@,%p,%d",copyArr,copyArr,copyArr == arr);
NSLog(@"mutableCopyArr:%@,%p,%d",mutableCopyArr,mutableCopyArr,mutableCopyArr == arr);
打印:
arr:(
test
),0x7fdc4161bbd0
copyArr:(
test
),0x7fdc4161bbd0,1
mutableCopyArr:(
test
),0x7fdc4161b160,0
可變數(shù)組
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"1",@"2",@"3",nil];
NSMutableArray *copyArr = [arr copy];
[copyArr removeLastObject];
Xcode崩潰
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI removeLastObject]: unrecognized selector sent to instance 0x7fa2da614120'
Foundation框架常用的類有:NSNumber两嘴、NSString丛楚、NSArray、NSDictionary憔辫、NSMutableArray趣些、NSMutableDictionay、NSMutableString等copy產(chǎn)生的對象時不可變的贰您,mutableCopy產(chǎn)生的對象時可變的
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"1",@"2",@"3",nil];
NSArray *copyArr = [arr copy];
NSMutableArray *mutableCopyArr = [arr mutableCopy];
arr[0] = @"0";
NSLog(@"arr:%@,%p",arr,arr);
NSLog(@"copyArr:%@,%p,%d",copyArr,copyArr,copyArr == arr);
NSLog(@"mutableCopyArr:%@,%p,%d",mutableCopyArr,mutableCopyArr,mutableCopyArr == arr);
[mutableCopyArr addObject:@"hello"];
NSLog(@"mutableCopyArr:%@,%p,%d",mutableCopyArr,mutableCopyArr,mutableCopyArr == arr);
打踊灯健:
arr:(
0,
2,
3
),0x7f94fbe12550
copyArr:(
1,
2,
3
),0x7ffc12611020,0
mutableCopyArr:(
1,
2,
3
),0x7f94fbe79f80,0
對于Foundation框架的常見類:
-
不可變:(NS*)
copy,淺拷貝锦亦,不產(chǎn)生新對象
mutableCopy舶替,深拷貝,產(chǎn)生新可變對象
-
可變:(NSMutable*)
copy杠园,深拷貝顾瞪,產(chǎn)生新不可變對象,
mutableCopy,深拷貝陈醒,產(chǎn)生新可變對象
另外提一點惕橙,我們比較提倡多用字面量語法創(chuàng)建這些類
例如:
NSMutableArray *arr = [@[] mutableCopy];
NSMutableDictionary *dict = [@{} mutableCopy];
三、自定義對象
先建一個Person類
@interface Person : NSObject
@property (nonatomic, copy)NSString *pid;
@property (nonatomic, copy)NSString *name;
@end
//初始化Person對象
Person *p = [[Person alloc] init];
p.name = @"joe";
p.pid = @"10";
Person *copyP = [p copy];
Xcode運行的話钉跷,不出意外肯定會報錯
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person copyWithZone:]: unrecognized selector sent to instance 0x7ff643641780'
原因就是沒有實現(xiàn)copyWithZone:這個方法
@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone;
@end
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
@end
command+左鍵可以得知這個方法是NSCopying里的協(xié)議方法弥鹦,那么Person要先遵循NSCopying方法
@interface Person : NSObject<NSCopying>
@property (nonatomic, copy)NSString *pid;
@property (nonatomic, copy)NSString *name;
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone{
return @"test";
}
//初始化一個Person對象
Person *p = [[Person alloc] init];
p.name = @"joe";
p.pid = @"10";
Person *copyP = [p copy];
NSLog(@"p = %p copyP = %p", p, copyP);
p = 0x7fab996acf50 copyP = 0x10ab5c088
可以得出copyWithZone:(NSZone *)zone 實際為新對象重新分配了內(nèi)存空間,由于返回是一個字符串爷辙,所以copyP實際是一個NSString類的對象
@implementation Person
- (id)copyWithZone:(NSZone *)zone{
//已給p分配了zone彬坏,就無需再alloc內(nèi)存空間
// Person *p = [[Person alloc]init];
//如果Person類會被繼承,那么將Person allocWithZone 改為[self class] allocWithZone:
// Person *p = [[Person allocWithZone:zone]init];
Person *p = [[[self class] allocWithZone:zone] init];
p.name = self.name;
p.pid = self.pid;
return p;
}
//初始化Person對象
Person *p = [[Person alloc] init];
p.name = @"joe";
p.pid = @"10";
Person *copyP = [p copy];
NSLog(@"p = %p copyP = %p", p, copyP);
NSLog(@"name = %@, pid = %@", copyP.name, copyP.pid);
NSLog(@"p.name地址:%p膝晾,copyP.name地址:%p",p.name,copyP.name);
p = 0x7fbfcb40e470 copyP = 0x7fbfcb418330
name = joe, pid = 10
p.name地址:0x10489d2a8栓始,copyP.name地址:0x10489d2a8
copyWithZone
為淺拷貝,自定義對象重寫該方法玷犹,當(dāng)進(jìn)行copy時為新對象分配了一個新的內(nèi)存地址混滔,mutableCopyWithZone
為深拷貝洒疚,也會為新對象分配一個新的內(nèi)存地址歹颓,所以當(dāng)[p copy]
時需要遵循 NSCopying
協(xié)議并重寫copyWithZone
方法,當(dāng)[p mutableCopy]
時需要遵循NSMutableCopying
并重寫mutableCopyWithZone
油湖,兩者的寫法也是一樣的巍扛。
- (id)mutableCopyWithZone:(NSZone *)zone{
Person *p = [[[self class] allocWithZone:zone] init];
p.name = self.name;
p.pid = self.pid;
return p;
}
當(dāng)子類繼承父類時,子類也會遵循父類的NSCopying協(xié)議乏德,這時可以不用再在子類里重寫copyWithZone方法撤奸。
但是有個缺陷,雖然我們可以在父類里使用runtime遍歷所有成員屬性列表喊括,然后賦值胧瓜,但其父類的屬性還是賦值不到。
@interface Person : NSObject<NSCopying>
@property (nonatomic, copy)NSString *pid;
@property (nonatomic, copy)NSString *name;
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone{
id obj = [[[self class]allocWithZone:zone]init];
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [self valueForKey:key];
[obj setValue:value forKey:key];
}
free(ivars);
return obj;
}
@interface Student : Person
@property (nonatomic, copy) NSString *age;
@end
//初始化Student對象
Student *s = [[Student alloc]init];
s.name = @"marry";
s.age = @"18";
Student *copyS = [s copy];
NSLog(@"s = %p copyS = %p", s, copyS);
NSLog(@"name = %@ age = %@", copyS.name, copyS.age);
打又J病:
s = 0x7fa83a608ce0 copyS = 0x7fa83a61a100
name = (null) age = 18
這里需要遍歷到父類的成員變量
- (id)copyWithZone:(NSZone *)zone{
id obj = [[[self class]allocWithZone:zone]init];
void (^getIvarsBlock)(Class) = ^(Class c){
unsigned int outCount;
Ivar * ivars = class_copyIvarList(c, &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [self valueForKey:key];
[obj setValue:value forKey:key];
NSLog(@"copy-----%@",key);
}
free(ivars);
};
Class c = [self class];
while (c) {
getIvarsBlock(c);
c = class_getSuperclass(c);
NSLog(@"class---%@",c);
if ( c == [NSObject class]) {
break;
}
}
return obj;
}
這時候再來看看屬性Strong和copy就容易理解了
例如
我們在Person類里再添加一條屬性
@interface Person : NSObject<NSCopying>
@property (nonatomic, copy)NSString *pid;
@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy) NSMutableArray *arr;
@end
//初始化Person對象
Person *p = [[Person alloc] init];
p.name = @"joe";
p.pid = @"10";
NSMutableArray *arr = [@[@"test"] mutableCopy];
p.arr = arr;
[arr addObject:@"hell0"];
Person *copyP = [p copy];
NSLog(@"arr:%p府喳, p.arr:%@ %p,\n copyP.arr:%@ %p",arr,p.sign,p.arr,copyP.arr,copyP.arr);
arr:0x7fddea4358a0蘑拯,p.arr:(
test
)钝满,0x7fddea43d0b0,
copyP.arr:(
test
)申窘,0x7fddea43d0b0
@property 本質(zhì)是:ivar(實例變量)弯蚜、存取方法(access method = getter + setter)
當(dāng)用copy修飾時,self.arr實際實現(xiàn)這個setter方法
- (void)setArr:(NSMutableArray *)arr{
_arr = [arr copy];
}
[p.arr addObject:@"hell0"];
必然會報錯誤
[__NSArrayI addObject:]: unrecognized selector sent to instance 0x7ff3b94ee1a0'
所以self.arr 仍是不可變的剃法,所以不要在@property添加可變屬性
那我們用strong修飾試試
@property (nonatomic, strong) NSMutableArray *arr;
//初始化Person對象
Person *p = [[Person alloc] init];
p.name = @"joe";
p.pid = @"10";
NSMutableArray *arr = [@[@"test"] mutableCopy];
p.arr = arr;
[arr addObject:@"hell0"];
Person *copyP = [p copy];
NSLog(@"arr:%p碎捺, p.arr:%@ %p,\n copyP.arr:%@ %p",arr,p.sign,p.arr,copyP.arr,copyP.arr);
arr:0x7fbbf8503cc0,p.arr:(
test,
hell0
)收厨,0x7fbbf8503cc0悍引,
copyP.arr:(
test,
hell0
),0x7fbbf8503cc0
實際用當(dāng)用strong修飾時帽氓,self.arr實現(xiàn)這個setter方法
- (void)setArr:(NSMutableArray *)arr{
_arr = arr
}
所以用strong修飾時趣斤,self.arr 為可變數(shù)組,地址沒有改變黎休,當(dāng)arr改變時浓领,self.arr也會改變。
上面我們也注意到势腮,copyWithZone: 產(chǎn)生的是淺復(fù)制联贩,所以這種方法只能產(chǎn)生第一層深復(fù)制,如果集合內(nèi)元素仍然是集合捎拯,則子集合內(nèi)元素不會被深復(fù)制泪幌,只對子集合內(nèi)元素指針進(jìn)行復(fù)制。
我們看看這個例子
NSMutableString * testStr = [NSMutableString stringWithString:@"test"];
NSArray *arr = @[testStr];
NSArray *copyArr = [arr copy];
NSMutableArray *mutableCopyArr = [arr mutableCopy];
NSArray *arr2 = [[NSArray alloc]initWithArray:arr copyItems:YES];
[testStr appendString:@" hello"];
NSLog(@" arr.firstObject = %p, \n copyArr.firstObject= %p, \n mutableCopyArr.firstObject= %p, \n arr2.firstObject =%p",arr.firstObject, copyArr.firstObject, mutableCopyArr.firstObject, arr2.firstObject);
NSLog(@"arr %@, \n copyArr %@, \n mutableCopyArr %@, \n arr2 %@",arr, copyArr, mutableCopyArr, arr2);
打印:
arr.firstObject = 0x7fbfd27069a0,
copyArr.firstObject = 0x7fbfd27069a0,
mutableCopyArr.firstObject = 0x7fbfd27069a0,
arr2.firstObject = 0xa000000747365744
arr (
"test hello"
),
copyArr (
"test hello"
),
mutableCopyArr (
"test hello"
),
arr2 (
test
)
使用initWithArray: copyItems:YES署照,同initWithDictionary copyItems:YES
只能深拷貝到第2層祸泪,如果還有更深層的,還是不能完全深拷貝
NSMutableString * testStr = [NSMutableString stringWithString:@"test"];
NSMutableArray *testArr = [NSMutableArray arrayWithObject:testStr];
NSArray *arr = @[testArr];
NSArray *copyArr = [arr copy];
NSMutableArray *mutableCopyArr = [arr mutableCopy];
NSArray *arr2 = [[NSArray alloc]initWithArray:arr copyItems:YES];
[testStr appendString:@" hello"];
NSLog(@" arr.firstObject = %p, \n copyArr.firstObject= %p, \n mutableCopyArr.firstObject =%p, \n arr2.firstObject =%p",arr.firstObject, copyArr.firstObject, mutableCopyArr.firstObject, arr2.firstObject);
NSLog(@"arr %@, \n copyArr %@, \n mutableCopyArr %@, \n arr2 %@",arr, copyArr, mutableCopyArr, arr2);
打咏ㄜ健:
arr.firstObject = 0x7fb50bf0cf60,
copyArr.firstObject = 0x7fb50bf0cf60,
mutableCopyArr.firstObject = 0x7fb50bf0cf60,
arr2.firstObject = 0x7fb50bf002f0
arr (
(
"test hello"
)
),
copyArr (
(
"test hello"
)
),
mutableCopyArr (
(
"test hello"
)
),
arr2 (
(
"test hello"
)
)
想實現(xiàn)完全深復(fù)制没隘,應(yīng)使用歸檔解檔。使用歸檔和解檔需要遵循NSCoding協(xié)議禁荸。
NSMutableString * testStr = [NSMutableString stringWithString:@"test"];
NSMutableArray *testArr = [NSMutableArray arrayWithObject:testStr];
NSArray *arr = @[testArr];
NSArray *copyArr = [arr copy];
NSMutableArray *mutableCopyArr = [arr mutableCopy];
NSArray *arr2 = [[NSArray alloc]initWithArray:arr copyItems:YES];
//完全深拷貝
NSArray *arr3 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:arr]];
[testStr appendString:@" hello"];
id firstObject1 = [arr firstObject][0];
id firstObject2 = [copyArr firstObject][0];
id firstObject3 = [mutableCopyArr firstObject][0];
id firstObject4 = [arr2 firstObject][0];
id firstObject5 = [arr3 firstObject][0];
NSLog(@" arr.firstObject.firstObject = %p, \n copyArr.firstObject.firstObject = %p, \n mutableCopyArr.firstObject.firstObject = %p, \n arr2.firstObject.firstObject = %p右蒲,arr3.firstObject.firstObject = %p",firstObject1, firstObject2, firstObject3, firstObject4,firstObject5);
NSLog(@"arr %@, \n copyArr %@, \n mutableCopyArr %@, \n arr2 %@,\n arr3 %@",arr, copyArr, mutableCopyArr,arr2,arr3);
打痈鲜臁:
arr.firstObject.firstObject = 0x7fd4d271e8d0, copyArr.firstObject.firstObject = 0x7fd4d271e8d0,
mutableCopyArr.firstObject.firstObject = 0x7fd4d271e8d0,
arr2.firstObject.firstObject = 0x7fd4d271e8d0瑰妄,
arr3.firstObject.firstObject = 0x7fd4d272b360
arr (
(
"test hello"
)
),
copyArr (
(
"test hello"
)
),
mutableCopyArr (
(
"test hello"
)
),
arr2 (
(
"test hello"
)
),
arr3 (
(
test
)
)
我們考慮深拷貝時映砖,如果集合的結(jié)構(gòu)元素不嵌套其他集合间坐,mutableCopy會復(fù)制所有內(nèi)容,當(dāng)集合元素還是集合啊央,就應(yīng)考慮歸檔解檔實現(xiàn)完全深拷貝眶诈。