谈谈浅拷贝和深拷贝

为什么回头再看这些基础知识呢,因为觉得好记性不如烂笔头。以前知其然不知所以然的东西,为了实现功能而实现功能而没有时间归纳总结,觉得还是用文章的形式记录下来很好,好了,闲话不多说,回归正题,当然同类文章比比皆是,但自己写一写跟看一看的感觉会不太一样,毕竟也算是自己的体会之一吧。

拷贝,就是复制对象。拷贝分两种,分别是浅拷贝、深拷贝。

  • 浅拷贝

    不会产生新的对象,指针指向的地址没有改变,也就是没有创建新的内存空间,即复制指针。

  • 深拷贝

    会产生新的对象,指针指向的地址会改变,也就是创建了新的内存空间,即内容拷贝。

那么copy和mutableCopy有什么区别呢

  • copy

    不可变拷贝,对于不可变的Foundation框架的常见类,是不创建新内存空间的,对于可变的Foundation框架的常见类以及自定义的对象,会开辟一份内存空间给新对象。新对象都是不可变的。

  • mutableCopy

    可变拷贝,其拷贝过程就是在内存中重新开辟一块区域,将对象复制一份放到这个区域。新对象是可以改变的,而且新对象的改变对原对象是没有影响的。

我们来以例子说明:

一、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不会产生新的对象,mutableCopy却是会产生新的对象;对于可变字符串,copy和mutableCopy都是产生新的对象,只是copy产生一个不可变的新对象,而mutableCopy产生一个可变的新对象。

我们再来看看这个例子

NSString  *str =@"test"; 
NSString *copyStr = [str copy];
NSMutableString *mutableCopyStr = [str mutableCopy];
   
str = @"test0";
//重新给Str赋值,实际重新给Str开辟一个内存空间,因此这一操作后实际这三个对象都是不一样的
    
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类的一种,所以情况都是一样

不可变数组

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

可变数组

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产生的对象时不可变的,mutableCopy产生的对象时可变的


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,浅拷贝,不产生新对象

    • mutableCopy,深拷贝,产生新可变对象

  • 可变:(NSMutable*)

    • copy,深拷贝,产生新不可变对象,

    • mutableCopy,深拷贝,产生新可变对象

另外提一点,我们比较提倡多用字面量语法创建这些类

例如:

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'

原因就是没有实现copyWithZone:这个方法

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

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

command+左键可以得知这个方法是NSCopying里的协议方法,那么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 实际为新对象重新分配了内存空间,由于返回是一个字符串,所以copyP实际是一个NSString类的对象

@implementation Person
- (id)copyWithZone:(NSZone *)zone{
   //已给p分配了zone,就无需再alloc内存空间
   // 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为浅拷贝,自定义对象重写该方法,当进行copy时为新对象分配了一个新的内存地址,mutableCopyWithZone为深拷贝,也会为新对象分配一个新的内存地址,所以当[p copy]时需要遵循 NSCopying 协议并重写copyWithZone方法,当[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;
}

当子类继承父类时,子类也会遵循父类的NSCopying协议,这时可以不用再在子类里重写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);

打印:

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 本质是:ivar(实例变量)、存取方法(access method = getter + setter)

当用copy修饰时,self.arr实际实现这个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

实际用当用strong修饰时,self.arr实现这个setter方法

- (void)setArr:(NSMutableArray *)arr{
    _arr = arr
}

所以用strong修饰时,self.arr 为可变数组,地址没有改变,当arr改变时,self.arr也会改变。

上面我们也注意到,copyWithZone: 产生的是浅复制,所以这种方法只能产生第一层深复制,如果集合内元素仍然是集合,则子集合内元素不会被深复制,只对子集合内元素指针进行复制。

我们看看这个例子

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"
    )
)

想实现完全深复制,应使用归档解档。使用归档和解档需要遵循NSCoding协议。

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
    )
)

我们考虑深拷贝时,如果集合的结构元素不嵌套其他集合,mutableCopy会复制所有内容,当集合元素还是集合,就应考虑归档解档实现完全深拷贝。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,601评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,367评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,249评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,539评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,967评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,929评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,098评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,825评论 0 207
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,609评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,796评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,282评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,603评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,277评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,159评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,959评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,079评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,874评论 2 277

推荐阅读更多精彩内容