iOS copy那些事儿

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Q52077987/article/details/81943505

自定义对象copy

copy是NSObject定义的一个方法,任何继承自NSObject的类实例都可以调用这个方法,但是前提是实现了copyWithZone:方法,因为copy会调用这个方法,如果没有实现,将引起Crash。

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

The returned object is implicitly retained by the sender, who is responsible for releasing it. The copy returned is immutable if the consideration “immutable vs. mutable” applies to the receiving object; otherwise the exact nature of the copy is determined by the class.

zone这个参数已经被OC忽略了,因此不用管它。对于自定义的对象来说,copy返回什么全由自己决定。可以创建一个新的对象,也可以返回self;返回对象中的指针类型的属性是指向同一个还是复制一份也是由自己决定的。

注意:自定义类实现copyWithZone:要首先调用基类的copyWithZone:

A subclass version of the copyWithZone: method should send the message to super first, to incorporate its implementation, unless the subclass descends directly from NSObject
——来自NSObject的copy方法解释

和copy相关的还有一个NSCopying协议,这个协议只有一个实例方法:copyWithZone:,方法签名和上边那个copyWithZone: 的一样。一个类如果继承自NSObject,只要实现了copyWithZone:方法,不用显示的遵守NSCopying协议,就可以实现copy。NSCopying协议的存在是为了面向接口编程提供的约束条件。

NSObject还有一个与copy相关的类方法:

+ (id)copyWithZone:(struct _NSZone *)zone;

This method exists so class objects can be used in situations where you need an object that conforms to the NSCopying protocol. For example, this method lets you use a class object as a key to an NSDictionary object. You should not override this method.

注意,这是一个类方法。它的作用是让一个类可以作为NSDictionary的Key:

Person *p = [[Person alloc] init];
NSDictionary *dict = @{[p class]: @"1234"};

因此,它相当于是Class实现了NSCopying协议。

Foundation中可变不可变对象的copy

分为两部分研究,非容器研究NSString和NSMutableString;容器研究NSArray和NSMutableArray。分别研究copy和mutableCopy对内存的操作。

扫描二维码关注公众号,回复: 2974400 查看本文章

NSString & NSMutableString

1)

NSString *str1 = @"ddddd";
NSString *str2 = [str1 copy];
NSLog(@"str1 address %p", str1);
NSLog(@"str2 address %p", str2);

/* output
...test[1665:112618] str1 address 0x10dc27068
...test[1665:112618] str2 address 0x10dc27068
*/

NSString对象是一个不可变对象,对其copy返回自己。因为不可变嘛,所以没必要复制任何东西。

2)

NSString *str1 = @"ddddd";
NSMutableString *mutStr = [str1 mutableCopy];
NSLog(@"str1 address %p", str1);
NSLog(@"mutStr address %p", mutStr);

/* output 
...test[1712:114683] str1 address 0x104ca4068
...test[1712:114683] mutStr address 0x60c000249000
*/

NSString对象的mutableCopy返回了不同的地址,复制了对象,因为mutable的要修改嘛。

3)

NSString *str1 = @"ddddd";
NSMutableString *mutStr = [str1 mutableCopy];
NSString *str3 = [mutStr copy];
NSString *str4 = [mutStr mutableCopy];

NSLog(@"mutStr address %p", mutStr);
NSLog(@"str3 address %p", str3);
NSLog(@"str4 address %p", str4);

/* output
...test[1712:114683] mutStr address 0x60c000249000
...test[1712:114683] str3 address 0xa000064646464645
...test[1712:114683] str4 address 0x60c000248dc0
*/

NSMutableString对象的copy返回了一个不可变对象,也会复制对象,因为mutable对象以后要修改,不能影响copy出来的不可变对象嘛;NSMutableString对象的mutableCopy也要复制对象,因为mutableCopy出来是不同的对象,修改操作不能互相影响。

结论就是,只有不可变对象copy为不可变对象的时候,才会优化;其他情况都会复制内存。

NSArray & NSMutableArray

1)

Person *p = [[Person alloc] init];
p.name = @"ww";
p.age = 10;

NSArray *arr1 = @[p];
NSArray *arr2 = [arr1 copy];
NSMutableArray *mutArr = [arr1 mutableCopy];
NSArray *arr3 = [mutArr mutableCopy];
NSArray *arr4 = [mutArr copy];
NSLog(@"arr1 address %p", arr1);
NSLog(@"arr2 address %p", arr2);
NSLog(@"mutArr address %p", mutArr);
NSLog(@"arr3 address %p", arr3);
NSLog(@"arr4 address %p", arr4);

/*
...test[3021:164517] arr1 address 0x604000203f00
...test[3021:164517] arr2 address 0x604000203f00
...test[3021:164517] mutArr address 0x604000056ce0
...test[3021:164517] arr3 address 0x604000057040
...test[3021:164517] arr4 address 0x604000203ff0
*/

这个实验可以看到NSArray,NSMutableArray的copy和mutableCopy机制和NSString,NSMutableString是一样的。

再进一步,对于保存的对象p是否copy呢?Person在copyWithZone:中返回了一个新的对象。

@implementation Person

- (id)copyWithZone:(NSZone *)zone{
    Person *p = [[Person alloc] init];
    p.name = [self.name copy];
    p.age = self.age;

    return p;
}

@end

打印数组中第一个对象的地址:

NSLog(@"arr1[0] address %p", arr1[0]);
NSLog(@"arr2[0] address %p", arr2[0]);
NSLog(@"mutArr[0] address %p", mutArr[0]);
NSLog(@"arr3[0] address %p", arr3[0]);
NSLog(@"arr4[0] address %p", arr4[0]);
/* output
...test[3098:168499] arr1[0] address 0x60400003faa0
...test[3098:168499] arr2[0] address 0x60400003faa0
...test[3098:168499] mutArr[0] address 0x60400003faa0
...test[3098:168499] arr3[0] address 0x60400003faa0
...test[3098:168499] arr4[0] address 0x60400003faa0
*/

发现对象根本没有被复制(也可以在copyWithZone:中打断点看是否被调用),然后会不会是写时复制呢?

p.name = @"kkk";
NSLog(@"arr1 person name: %@", [arr1[0] name]);
NSLog(@"arr2 person name: %@", [arr2[0] name]);
NSLog(@"arr3 person name: %@", [arr3[0] name]);
NSLog(@"arr4 person name: %@", [arr4[0] name]);
NSLog(@"mutArr person name: %@", [mutArr[0] name]);

/*
...test[3168:171399] arr1 person name: kkk
...test[3168:171399] arr2 person name: kkk
...test[3168:171399] arr3 person name: kkk
...test[3168:171399] arr4 person name: kkk
...test[3168:171399] mutArr person name: kkk
*/

也不是。

因此结论就是容器的copy或者mutableCopy根本不会copy保存的内容,所以即使那些没有实现copyWithZone:的对象也照样可以保存到容器内。

这一点写代码的时候尤其要记住,比如你在函数中对NSMutableArray做了一个copy再返回给调用方,并不表示之后你可以随意修改NSMutableArray中的对象。如果你没有明确的copy一个对象,这个对象的修改就会影响所有使用它的地方。

所以,所谓的”深拷贝“,”浅拷贝“都不是严格的说法,到底多深才是深拷贝?不可变对象copy函数返回自身就根本没有copy,仅仅是一个优化罢了。

那么如果想让容器中的对象也复制一份,怎么办呢?使用NSArray的initWithArray: copyItems:方法。

NSArray *arr5 = [[NSArray alloc] initWithArray:arr1 copyItems:YES];
NSLog(@"arr1[0] address %p", arr1[0]);
NSLog(@"arr5[0] address %p", arr5[0]);
/* output
...test[3359:181703] arr1[0] address 0x60c000226ee0
...test[3359:181703] arr5[0] address 0x60800023b740
*/

注意如果使用了这个API,但是容器中保存的对象没有实现copyWithZone:,会发生Crash。这个API是通过保存对象的copyWithZone:方法复制对象的,所以复制的效果(能复制几层)还得看具体的实现。

猜你喜欢

转载自blog.csdn.net/Q52077987/article/details/81943505