iOS 探索copy strong 和NSCopying NSMutableCopying

请尊重作者码字的辛苦😁:原创转载请注明出处

自定义类

故事来源于最近项目中有一个对接ERP功能,在编辑一些信息后突然放弃返回到上一个界面,再进入发现上一次编辑信息在没保存的情况依然生效了。分析:原有的信息肯定是从上一个页面带过来的,第一反应是修改的是同一个对象。

解决:如果要避免这种情况,可以在编辑界面属性用copy修饰如:

@property (nonatomic, copy) ContactPersonModel *ContactPersonModel;
copy_原对象.png
copy_后对象.png

这样无论你在编辑界面怎么修改都不会影响上层数据。
我就知道你不信我改为strong修饰对比:

@property (nonatomic, strong) ContactPersonModel *ContactPersonModel; 
strong_原对象.png
strong_后对象.png

通过以上的图对比很直观的就知道发生了什么问题,同时请注意这是由自己定义的类通过copy和strong修饰可以达到不同的效果,以下观点是本人的理解。

copy:会生成一个新对象,新的内存地址,但是新对象里面的内容还是指向原来的内容,新对象retainCount为1,旧对象不会发生改变。

strong: 源对象和副本都指向同一个地址,也就是这个对象的retainCount加1了,修改会影响源对象。

这里不得不提下在开发中NSString的使用。在不可变的情况下无论你是用copy还是strong修饰都是指向同一个地址,并且引用计数都会加1;如果是可变的NSMutableString情况下用copy修饰的属性,源对象和副本指向不同的地址,不会影响源对象,副本对象引用计数为1,类型为不可变的NSString;用strong修饰源对象和副本指向同一个地址,类型为NSMutableString,所以修改副本对象会影响到源对象。

@property (nonatomic, copy) NSString *copiedString;
@property (atomic, copy)    NSString *atomicCopyString;
@property (nonatomic, strong) NSString *strongString;
@property (atomic, strong)   NSString *atomicStrongString;
//不可变string
NSString *string = @"leefenghy";
self.copiedString = string;
self.atomicCopyString = string;
self.strongString = string;
 self.atomicStrongString = string;
//可变string
NSMutableString *string = [[NSMutableString alloc] initWithString:@"leefenghy"];
self.copiedString = string;
self.atomicCopyString = string;
self.strongString = string;
 self.atomicStrongString = string;

对于assign和weak这里就不讨论了,这两个东西使用场景很明显也很好理解。

解决这个问题还有另外一种思路,自定义类实现NSCopying和NSMutableCopying也可以达到目的,我这里选择的是NSCopying,给自己的类实现NSMutableCopying总感觉怪怪的😶。

- (id)copyWithZone:(NSZone *)zone
{
    ContactPersonModel *model = [[[self class] allocWithZone:zone] init];
   // 这里self其实就要被copy的那个对象,很显然要自己赋值给新对象,所以这里可以控制copy的属性
    model.personID = self.personID;
    model.appellation = self.appellation;
    model.appellationStr = self.appellationStr;
    model.name = self.name;
    model.enName = self.enName;
    model.mail = self.mail;
    model.tel = self.tel;
    model.remark = self.remark;
    model.remove = self.remove;
    model.seleted = self.seleted;
    return model;
}

使用: addVC.ContactPersonModel = [personModel copy];
这样自定义类就有和系统Foundation类一样有了copy功能了。
由上面代码看出实现- (id)copyWithZone:(NSZone *)zone时,类alloc init重新分配了内存空间。
最终效果:

model.png

mode_copying.png

model_mutableCopy.png

系统Foundation类浅拷贝和深拷贝

  • 官方的解释图胜过千言万语
There are two kinds of object copying: shallow copies and deep copies. 
The normal copy is a shallow copy that produces a new collection that shares ownership of the objects with the original. 
Deep copies create new objects from the originals and add those to the new collection. 
This difference is illustrated by Figure:

翻译:对象拷贝有两种方式:浅拷贝和深拷贝。浅拷贝并不会拷贝对象本身,仅仅是拷贝指向对象的指针;深拷贝是直接拷贝整个对象内存到另一块内存中(内容拷贝)。


官方解释图.png
  • Shallow Copies
官方注释:
There are a number of ways to make a shallow copy of a collection. When you create a shallow copy,   
the objects in the original collection are sent a retain message and the pointers are copied to the new
collection. 

翻译:集合类型有多种浅拷贝方式,当你创建一个浅拷贝的集合时,集合里面的对象会发送一个retain消息同时对象的指针会拷贝到这个新的集合。

  浅拷贝:发送retain消息,引用计数+1,同时指针被拷贝到新的集合。
  • Deep Copies
There are two ways to make deep copies of a collection. You can use the collection’s equivalent of
initWithArray:copyItems: with YES as the second parameter. If you create a deep copy of a collection in 
this way, each object in the collection is sent a copyWithZone: message. If the objects in the collection 
have adopted the NSCopying protocol, the objects are deeply copied to the new collection, which is 
then the sole owner of the copied objects. If the objects do not adopt the NSCopying protocol, 
attempting to copy them in such a way results in a runtime error. However, copyWithZone: produces a 
shallow copy. This kind of copy is only capable of producing a one-level-deep copy. If you only need a 
one-level-deep copy. you can explicitly call for one as in 方式一.

翻译:集合的深拷贝有两种方式:initWithArray:copyItems:将第二参数设置为YES即可为深拷贝。如果你用这种方式建立一个深拷贝集合,集合里面的每个对象都会发送copyWithZone:消息。如果集合里面的对象都遵守了NSCopying协议,这些对象都会深拷贝到这个新的集合,如果集合里面的对象没有遵守NSCopying协议用这种方式深拷贝,会发生运行时报错。不管怎么样copyWithZone:这种拷贝方式只能提供one-level-deep copy单层深拷贝。

方式一:one-level-deep copy
NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];
方式二: A true deep copy

将集合进行归档(archive),然后解档(unarchive),如:

NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
          [NSKeyedArchiver archivedDataWithRootObject:oldArray]];
集合的单层深复制 (one-level-deep copy)
看到这里,有同学会问:如果在多层数组中,对第一层进行内容拷贝,其它层进行指针拷贝,
这种情况是属于深复制,还是浅复制?对此,苹果官网文档有这样一句话描述:

This kind of copy is only capable of producing a one-level-deep copy. 
If you only need a one-level-deep copy。你可以采用方式一。

If you need a true deep copy, such as when you have an array of arrays, you can archive and then 
unarchive the collection, provided the contents all conform to the NSCoding protocol。你可以采用方式二。

从文中可以看出,苹果认为这种复制不是真正的深复制,而是将其称为单层深复制(one-level-deep copy)。
因此,网上有人对浅复制、深复制、单层深复制做了概念区分。

浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制。

深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有一层是深复制。

完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都是对象复制。

当然,这些都是概念性的东西,没有必要纠结于此。只要知道进行拷贝操作时,被拷贝的是指针还是内容即可。

总结

自定义对象copy和mutableCopy

遵守NSCopying或者NSMutableCopying协议,符合以上浅拷贝和深拷贝基本原则。

系统对象的copy与mutableCopy方法

不管是集合类对象,还是非集合类对象,接收到copymutableCopy消息时都遵守以下原则:

  • copy返回immutable对象(不可改变对象),所以对copy返回的对象调用mutable对象接口就会crash。
  • mutableCopy返回的是mutable对象(可变对象)。
非集合类对象的copy与mutableCopy

非集合类对象经常使用的有NSString、NSNumber、NSValue、NSDate、NSData、NSCache...
copy:

 NSString *string = @"leefenghy";
 NSString *stringCopy = [string copy];
 NSMutableString *mutableString = [string mutableCopy];

通过查看内存:string和stringCopy的地址是一样的,是指针拷贝;mutableString地址不一样,是内容拷贝。

string-copy-mutableCopy.png

mutableCopy:

NSMutableString *mutString = [[NSMutableString alloc] initWithString:@"leefenghy"];
NSString *stringCopy = [mutString copy];
NSMutableString *mutStringCopy = [mutString copy];
NSMutableString *stringMutCopy = [mutString mutableCopy];
[mutStringCopy appendString:@"github"];

mutString-mutableCopy-coy.png

由上图可知:mutString是一个可变string,stringCopy和mutStringCopy的地址是一样的(都是不可变的),其他的都不一样。我猜想系统对可变对象和不可变对象储存位置不一样,应该是分开来管理。实际NSMutableString *mutStringCopy = [mutString copy];这样操作后是一个不可变对象,应该存储在静态区。所以执行[mutStringCopy appendString:@"github"];程序会crash-[NSTaggedPointerString appendString:]: unrecognized selector sent to instance 0xa020004402da5659,因为不可变对象调用了可变对象接口。

非集合对象:

对immutable对象copy是指针的拷贝,mutableCopy都是内容拷贝;对mutable对象copy后是一个immutable对象和mutable存储在不同的位置,mutableCopy是内容拷贝。

集合类对象的copy与mutableCopy

集合类对象经常使用的有:NSArray、NSDictionary、NSIndexPath、NSSet...
immutable copy:

NSArray *array = @[@[@"ll",@"lee"],@[@"jane",@"kang"]];
NSArray *arrayCopy = [array copy];
NSMutableArray *arrayMutCopy = [array mutableCopy];

immutable-copy.png

由图可知:array、arrayCopy的地址相同,arrayMutCopy和array地址是不同的,说明copy执行了指针拷贝,mutableCopy执行了内容拷贝。但是这里要指出的arrayMutableCopy内容拷贝仅仅是对array对象拷贝(one-level-deep copy)。array、arrayCopy、arrayMutCopy里面的元素还是执行的指针拷贝
mutable copy:

NSMutableArray *array = [[NSMutableArray alloc] initWithArray:@[@[[NSMutableString stringWithString:@"ll"],@"lee"],@[@"jane",@"kang"]]];
NSArray *arrayCopy = [array copy];
NSMutableArray *mutableArrayCopy = [array copy];
NSMutableArray *mutableArrayMutableCopy = [array mutableCopy];
[mutableArrayCopy addObject:@"sark"];

mutabe-mutableCopy.png

由图可知:array、arrayCopy、mutableArrayCopy、mutableArrayMutableCopy的内存地址都不一样,都是内容拷贝,但是对象里面的元素都是指针拷贝。
[mutableArrayCopy addObject:@"sark"];执行这段代码同样会crash,因为此时mutableArrayCopy 是一个NSArrayI类型是不可变的,没有addObject:这个方法。

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

推荐阅读更多精彩内容