美文网首页
浅拷贝与深拷贝

浅拷贝与深拷贝

作者: mtry | 来源:发表于2020-04-29 21:59 被阅读0次

写在前面

  • 浅拷贝:指向对象所在内存指针的拷贝,对象所在内存不变
  • 深拷贝:对象所在内存重新拷贝一份

在 iOS 中,实现 NSObject 对象的拷贝是通过调用 - (id)copy- (id)mutableCopy,这两个方法的内部实现如下:

- (id)copy {
    return [(id)self copyWithZone:nil];
}

- (id)mutableCopy {
    return [(id)self mutableCopyWithZone:nil];
}

copyWithZone:mutableCopyWithZone: 内部是没有实现的,分别对应 NSCopyingNSMutableCopying 协议

@protocol NSCopying

- (id)copyWithZone:(nullable NSZone *)zone;

@end

@protocol NSMutableCopying

- (id)mutableCopyWithZone:(nullable NSZone *)zone;

@end

小结:在 iOS 中,如果要实现 NSObject 对象的拷贝功能,就需要实现 NSCopyingNSMutableCopying 协议。

  • NSCopying 用于不可以变对象的拷贝,比如:NSData,NSArray
  • NSMutableCopying 用于可以变对象的拷贝,比如:NSMutabelArray,NSMutableDictionary

常见系统类

这里主要讨论:NSNumber,NSString,NSMutableString,NSArray,NSMutabelArray这几个主要类型,其他的类原理类似。

NSNumber

这里把NSNumber提出来,主要是大部分的NSNumber,在 64 位设备上大都是通过 Tagged Pointer 方式存储。Tagged Pointer 是通过把值存储在指针中,提升对小对象读取速度和节省内存的技术。因为已经没有内存的概念了,所有拷贝也不用常规的方式去思考了。

+ (void)test
{
    NSNumber *num1 = @(100);
    NSNumber *num2 = num1.copy;
    NSLog(@"num1:%p", num1);
    NSLog(@"num2:%p", num2);
}

/*
打印如下:
num1:0x837c70775f2c0953
num2:0x837c70775f2c0953
*/

可以发现通过 Tagged Pointer 存储的对象,拷贝是不生效的。在开发中主要会遇到 Tagged Pointer 优化的类有: NSString,NSIndexPath,NSManagedObjectID,NSDate

当然像 NSString 通过 Tagged Pointer 来存储,条件还是比较苛刻的,字符串的长度有要求,比如

+ (void)test
{
    NSString *str1 = [NSString stringWithFormat:@"a"]; // 或者 [@"a".mutableCopy copy]
    NSString *str2 = str1.copy;
    NSLog(@"str1:%@ %p", str1.class, str1);
    NSLog(@"str2:%@ %p", str2.class, str2);
}

/*
打印如下:
str1:NSTaggedPointerString 0xb50a158d9e7f2850
str2:NSTaggedPointerString 0xb50a158d9e7f2850
*/

这里也不深究了,总结起来就是通过 Tagged Pointer 存储的对象,拷贝是不起作用的。有兴趣的可以参考我的另一篇文章 Tagged Pointer

NSString 和 NSMutableString

除了上面讨论的 Tagged Pointer,不可变的字符串在内存中的存储也是比较特殊的。因为是一个常量,每一个不可变字符串一旦生成内存地址就固定了,所以拷贝也就没有意义了

+ (void)test
{
    NSString *str1 = @"我是字符串";
    NSString *str2 = str1.copy;
    NSLog(@"str1:%@ %p", str1.class, str1);
    NSLog(@"str2:%@ %p", str2.class, str2);
}

/*
打印如下:
str1:__NSCFConstantString 0x10979b050
str2:__NSCFConstantString 0x10979b050
*/

在不考虑这些特殊存储之外,下面再来思考 NSMutableString 对象的拷贝

+ (void)test
{
    NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"我是字符串"];
    NSMutableString *str2 = str1.copy; // str2 为一个不可以字符串
    NSMutableString *str3 = str1.mutableCopy;
    NSLog(@"str1:%@ %p %@", str1.class, str1, str1);
    NSLog(@"str2:%@ %p %@", str2.class, str2, str2);
    NSLog(@"str3:%@ %p %@", str3.class, str3, str3);
    
    [str1 appendString:@"++++"];
    
    NSLog(@"str1:%@ %p %@", str1.class, str1, str1);
    NSLog(@"str2:%@ %p %@", str2.class, str2, str2);
    NSLog(@"str3:%@ %p %@", str3.class, str3, str3);
}

/*
打印如下:
str1:__NSCFString 0x6000017d2160 我是字符串
str2:__NSCFString 0x6000017d2070 我是字符串
str3:__NSCFString 0x6000017d2190 我是字符串

str1:__NSCFString 0x6000017d2160 我是字符串++++
str2:__NSCFString 0x6000017d2070 我是字符串
str3:__NSCFString 0x6000017d2190 我是字符串
*/

可以发现 copymutableCopy 一个可以变字符串都是深拷贝;区别在于通过 copy 生成的是不可以字符串,mutableCopy 生成的是可变字符串。

注意:如果是符合 Tagged Pointer 存储的可变字符串,调用 copy,就会使用 Tagged Pointer 存储。

NSArray 和 NSMutabelArray

+ (void)test
{
    NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"a"];
    NSMutableString *str2 = [[NSMutableString alloc] initWithString:@"b"];
    NSMutableArray *arr1 = [NSMutableArray arrayWithArray:@[str1]];
    NSMutableArray *arr2 = arr1.mutableCopy;
    NSLog(@"arr1:%p %@", arr1, arr1);
    NSLog(@"arr2:%p %@", arr2, arr2);
    
    [str1 appendString:@"a"];
    [arr1 addObject:str2];
    
    NSLog(@"arr1:%p %@", arr1, arr1);
    NSLog(@"arr2:%p %@", arr2, arr2);
}

/*
打印如下:
arr1:0x600002335b00 (a)
arr2:0x600002335da0 (a)

arr1:0x600002335b00 (aa,b)
arr2:0x600002335da0 (aa)
*/

列表对象进行了深拷贝,不过列表中的对象没有拷贝。

接下观察 NSArray 的拷贝情况

+ (void)test
{
    NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"a"];
    NSArray *arr1 = @[str1];
    NSArray *arr2 = arr1.copy;
    NSLog(@"arr1:%p %@", arr1, arr1);
    NSLog(@"arr2:%p %@", arr2, arr2);
    
    [str1 appendString:@"a"];
    
    NSLog(@"arr1:%p %@", arr1, arr1);
    NSLog(@"arr2:%p %@", arr2, arr2);
}

/*
打印如下:
arr1:0x600002a46c40 (a)
arr2:0x600002a46c40 (a)

arr1:0x600002a46c40 (aa)
arr2:0x600002a46c40 (aa)
*/

地址都没有变,由于不可变的列表不能增加或删除元素,而且列表对象也不会改变,深拷贝一个列表也不能做什么,效果和不拷贝没啥差别。

接下来看可变列表和不可变列表之间的拷贝情况

+ (void)test
{
    NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"a"];
    NSMutableString *str2 = [[NSMutableString alloc] initWithString:@"b"];
    NSArray *arr1 = @[str1];
    NSMutableArray *arr2 = arr1.mutableCopy;
    NSArray *arr3 = arr2.copy;
    NSLog(@"arr1:%p %@", arr1, arr1);
    NSLog(@"arr2:%p %@", arr2, arr2);
    NSLog(@"arr3:%p %@", arr3, arr3);
    
    [str1 appendString:@"a"];
    [arr2 addObject:str2];
    
    NSLog(@"arr1:%p %@", arr1, arr1);
    NSLog(@"arr2:%p %@", arr2, arr2);
    NSLog(@"arr3:%p %@", arr3, arr3);
}

/*
打印如下:
arr1:0x600002857a40 (a)
arr2:0x60000267ea90 (a)
arr3:0x600002857a60 (a)

arr1:0x600002857a40 (aa)
arr2:0x60000267ea90 (aa,b)
arr3:0x600002857a60 (aa)
*/

可变列表和不可变列表之间的拷贝是深拷贝,同样列表中的元素不变。

如果想把列表中的元素也进行拷贝这需要使用 initWithArray:copyItems 方法

+ (void)test
{
    NSMutableString *str1 = [[NSMutableString alloc] initWithString:@"a"];
    NSMutableString *str2 = [[NSMutableString alloc] initWithString:@"b"];
    NSArray *arr1 = @[str1];
    NSArray *arr2 = [[NSArray alloc] initWithArray:arr1 copyItems:YES];
    NSMutableArray *arr3 = [[NSMutableArray alloc] initWithArray:arr1 copyItems:YES];;
    
    NSLog(@"arr1:%p %@", arr1, arr1);
    NSLog(@"arr2:%p %@", arr2, arr2);
    NSLog(@"arr3:%p %@", arr3, arr3);
    
    [str1 appendString:@"a"];
    [arr3 addObject:str2];
    
    NSLog(@"arr1:%p %@", arr1, arr1);
    NSLog(@"arr2:%p %@", arr2, arr2);
    NSLog(@"arr3:%p %@", arr3, arr3);
}

/*
打印如下:
arr1:0x600002202f80 (a)
arr2:0x600002202f60 (a)
arr3:0x600002e71350 (a)

arr1:0x600002202f80 (aa)
arr2:0x600002202f60 (a)
arr3:0x600002e71350 (a,b)
*/

initWithArray:copyItems 通过函数生成的新列表,会对列表中的调用 copymutableCopy,所有列表中的元素需要实现 NSCopyingNSMutableCopying 协议。

自定义类

有了上面的基础,可以根据自己的需求实现,自定义的类的拷贝了,比如:

@interface MTObject : NSObject<NSCopying>

@property (nonatomic, strong) NSMutableString *str;
@property (nonatomic, strong) NSMutableArray *arr;

@end

@implementation MTObject

- (id)copyWithZone:(nullable NSZone *)zone
{
    MTObject *copyObj = [[self class] allocWithZone:zone];
    copyObj.str = self.str.mutableCopy;
    copyObj.arr = [[NSMutableArray alloc] initWithArray:self.arr copyItems:YES]; //self.arr.mutableCopy 仅列表拷贝,列表中的元素不变
    return copyObj;
}

@end
+ (void)test
{
    NSMutableString *strInArr = @"1".mutableCopy;
    
    MTObject *obj1 = [MTObject new];
    obj1.str = @"a".mutableCopy;
    obj1.arr = [NSMutableArray array];
    [obj1.arr addObject:strInArr];
    
    MTObject *obj2 = obj1.copy;
    [obj1.str appendString:@"a"];
    [strInArr appendString:@"1"];
    
    NSLog(@"obj1:%p %p %@ %p %@", obj1, obj1.str, obj1.str, obj1.arr, obj1.arr);
    NSLog(@"obj2:%p %p %@ %p %@", obj2, obj2.str, obj2.str, obj2.arr, obj2.arr);
}

obj1:0x600001503fe0 0x600001b7a400 aa 0x600001b7a4f0 (11)
obj2:0x600001503ec0 0x600001b7a550 a 0x600001b7a520 (1)

总结

NSObject 对象如果需要调用 copymutableCopy 方法,NSObject 对象需要实现 NSCopyingNSMutableCopying 协议。

  1. 特殊对象的存储,没有拷贝的概念
    1. Tagged Pointer 方式存储
    2. 不可变的常量字符串
  2. 非集合类的普通对象的拷贝,一般都是对象那一层进行来深拷贝,而对象的成员变量是没有变。可以通过 NSCopying 协议,按照自己期望的方式去实现
  3. 集合类的普通对象的拷贝,也是只是集合对象进行来深拷贝,而集合的的对象是没有变的。不可以变的集合调用 copy 方法,不会有任何拷贝现象。

相关文章

  • JS中的深拷贝与浅拷贝

    知乎:js中的深拷贝和浅拷贝? 掘金: js 深拷贝 vs 浅拷贝 前言 首先深拷贝与浅拷贝只针对 Object,...

  • 认识js下的浅拷贝与深拷贝

    浅拷贝与深拷贝 首先深拷贝和浅拷贝只针对像 Object, Array 这样的复杂对象的。简单来说,浅拷贝只拷贝一...

  • iOS深拷贝(MutableCopy)与浅拷贝(Copy)的区别

    深拷贝和浅拷贝的概念 iOS中有深拷贝和浅拷贝的概念,那么何为深拷贝何为浅拷贝呢?浅拷贝:浅拷贝并不拷贝对象本身,...

  • Objective-C中的浅拷贝和深拷贝

    Objective-C中的浅拷贝和深拷贝IOS开发之深拷贝与浅拷贝(mutableCopy与Copy)详解iOS ...

  • 深拷贝和浅拷贝

    干货!深拷贝和浅拷贝的区别 深拷贝才是拷贝,浅拷贝就是Retain Copy与Retain的区别 Copy: 根据...

  • js浅拷贝、深拷贝

    前言 本文主要简单讲一下什么是浅拷贝、什么是深拷贝、深拷贝与浅拷贝的区别,以及怎么进行深拷贝和怎么进行浅拷贝。 一...

  • iOS 图文并茂的带你了解深拷贝与浅拷贝

    iOS 图文并茂的带你了解深拷贝与浅拷贝 iOS 图文并茂的带你了解深拷贝与浅拷贝

  • 深拷贝和浅拷贝

    1: iOS开发 深拷贝与浅拷贝 2: iOS 浅谈:深.浅拷贝与copy.strong 3: iOS开发——深...

  • [C++之旅] 15 深拷贝与浅拷贝

    [C++之旅] 15 深拷贝与浅拷贝 拷贝构造函数分为深拷贝和浅拷贝两种方式 浅拷贝只是将被拷贝的对象的成员直接赋...

  • 深拷贝VS浅拷贝

    深拷贝VS浅拷贝 本文主要对深拷贝&浅拷贝的解释及实现做一下简单记录。 之所以会有深拷贝与浅拷贝之分,是因为不同数...

网友评论

      本文标题:浅拷贝与深拷贝

      本文链接:https://www.haomeiwen.com/subject/yynnwhtx.html