【GeekBand】数组array中的深拷贝和浅拷贝

讨论主题

下列代码中:为什么用实例变量初始化的可变数组添加对象元素成功,而用self.属性的方式初始化的可变数组添加对象元素时会报错?

@property(nonatomic,copy)NSMutableArray *mutableArray;
@end
@implementation  Test
-(void)viewDidLoad{
_mutableArray=[NSMutableArray array];//1.不报错
self.mutableArray = [NSMutableArray array];//2.报错
}
@end

讨论内容

精华回复

张志华

应该是不会崩溃的,你在调用self.之前有没有用@property声明,之后声明之后才回有get.set方法,不然会找不到selector,我觉得你的崩溃可能不是这个原因
错误提示是你给不可变数组发送啦了可变数组才能接受的消息,addobject,c应该是copy
用这个意义,属性用self.处理后成了不可变数组

煎饼虾
但是mutable的不是要用copy的么

Thor@2x
我怎么觉得copy是为了让 不可变的属性不要误传成可变的变量呢?mutable array 是个 array,你给声明array的属性赋值mutable版本是可以的,但mutable版本的变量一变,你array属性指向的变量跟着就变了

Thor@2x
报错是你要在 addobject的时候

张志华
不同的语义是为了表达不同意思存在的,并不是百分百固定的搭配,copy之后变成复制一个不可变数组在大多数场景下更适用一些,不知道能不用属性的语义声明一个可变的复制

Thor@2x
由于声明了copy,_版本赋值是个mutable array,self版本赋值是个array,可变复制感觉主要场景是生成不可变的可变版本,通常是执行一段逻辑时有用,声明属性的话,可能并不常用,直接声明成可变似乎就可以了

葛伟Will

好像这个文章解释的说的通
https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8A%EF%BC%89.md#3-%E6%80%8E%E4%B9%88%E7%94%A8-copy-%E5%85%B3%E9%94%AE%E5%AD%97
copy + atomic 或者 strong + nonatomic

风早
我在xcode7下设置为strong后下划线数组名和点语法都可以添加元素,good

**杨武 **

哦,是这个呀,没事儿别瞎 (copy) ,网上现在流行的 mutable string 要 copy 也纯属瞎搞,所有的 collection copy 出的都是不可变的版本,可变的是 mutableCopy。当初设计这个接口的人想法挺奇怪的

张志华
我之前不知道在哪里看到过不可变版本的内存占用更小,拷贝出来的对象是不可变的,有需要的时候自己更改,倒也不失为一种合理的设计

**杨武 **

multable string property 要 copy 的理论是,赋值的代码以后也不能用原来的引用继续修改了,这样后续的代码如果无意识重用这个变量做了点别的,也不会有影响。这是为了小概率的错误行为付了不必要的代价,copy 成本比加个引用计数高多了。
不论不可变的版本有什么好处,mutable 的东东 copy 出来的就应该是 mutable 的,Foundation 的这个实现事质上变更了 copy 的语义,应该做成 copy + immutableCopy 才符合直觉。不过已经这样这么多年了,将就着用吧。

煎饼虾
那为什么这样就行,老师不要见怪,因为我_和self. 搞得不是太清楚,有点混乱

Thor@2x
这么一说确实是,现在copy的语义是immutableCopy,和mutableCopy对应

**杨武 **

property 申明并不是什么魔法,只是相当于编译器帮你写了 get/set 方法,而 _arr 则绕过了 get/set

葛伟Will
是说_arr = arr 这里 setter 是直接赋值self.arr = 这里就是等于号,就是 copy 对象,所以变成了 NSArray,不可变。这样理解对吗?

李建忠

Property就是用来封装一些copy,指针所有权的。
直接用实例变量赋值,就是指针直接赋值,当然没有copy啦
@上海-葛伟 正解

Thor@2x
好兴师动众呢~~感觉不少第三方库已经全力向Swift迁移了,说OC版本都不维护了。。。然而swift课程还没看完。。。

李建忠

老师都喜欢看到高质量问题
大家好好玩玩Swift里面的数组和字符串,肯定还会有不少好问题
其实 OC把 数组分成 可变和不可变,特别还让它们成为子父类, 是非常脑残的设计。好处没捞到什么,带来问题一大堆。Swift终于不那么脑残了

**Tinyfool ‍培强 **

可变不可变还是为了多线程优化,swift其实就是自动可识别,其实还是分得,oc时代自动分不够容易,主要是llvm进步的结果

张志华
可变不可变还是为了多线程优化这怎么说?

**Tinyfool ‍培强 **
不可变的是线程自动安全的,swift是因为静态分析器足够强大了

Thor@2x
个人觉得可变和不可变的设计,一方面是和“值传递”vs“引用传递”类似的对本地变量的安全保障;另一方面是内存分配的性能优化吧

张志华
因为就是不考虑变化,所有其他线程对之操作所涉及的安全机制都可以不需要?

**Tinyfool ‍培强 **
对啊

李建忠

字符串做可变和不可变 区分是很有必要的。主要是做了共享优化。多线程安全只是一个附带的好处

**Tinyfool ‍培强 **

不可变得特别快,其实跟内存分配当然有关系了,不过不可变和可变在很多语言都有,基本上就是多线程开始流行开始有的

**杨武 **
其实当年 Delphi 的 string 是 copy on right 的,感觉还蛮好的。关键还是编译器要自己写呀

**Tinyfool ‍培强 **
p也有它的问题,跟大家都不一样,走别的API就要做类型转换
基本上我的看法是llvm的很多改进催生了swift
s和oc本质是一样的
但是固化了和自动化了很多oc的习惯
当然未来s会有它自己的路

李建忠
大家看看今年WWDC上的几个swift讲座,就明白即便LLVM这批人对 OC语言层面的一些设计也是忍好久了。
OC底层和Swift的一样,只能说问题一样,底层实现(LLVM)一样, 但给 程序员的 实现路径很不一样

Tinyfool ‍培强
说一样是在说,底层构建一样,设计思路延续,学一门新语言首先要找它和你熟悉的语言的一样的部分
这样你事半功倍既可以开始了,所以我拿起swift就可以写
看了一两个小时文档就可以写项目了,而不一样是从语言的设计理念,具体语法上的不同去看
现在初学者最怕的是一上来就说这东西怎么完全不同啊

李建忠

比如copy这个问题,在OC里面用 property 修饰符表达。但copy本质上是跟随类型的,什么样的类型,拥有什么样的copy语义。Swift就把这个纠正过来了

**Tinyfool ‍培强 **

当年很多人学oc的问题就在于此
现在学s也是,至于这些细节就是你需要慢慢的一点一点的在使用中学习中去掌握的,还是那句话我其实懒得讲技术,前面也算是鸡汤吧,技术的东西看书吧,听人讲不如自己多看多钻研然后再去讨论

李建忠

当然 一样的是,两个语言,你都要理解 什么是深拷贝,浅拷贝,栈拷贝,堆拷贝...这些根本性的东西是一样的。不会因语言变化而变化

葛伟Will
Swift提供新的接口也就相当于提供新的解决方案吧新的解决问题的方法,这个非常好。

**Tinyfool ‍培强 **
拿arc和可变不可变来说吧,这两件事情上为什么说s和oc是一样的
arc语法虽然接近gc,但是性能和优化点还是引用技术那种,可变不可变也类似,现在虽然不用你显式的声明了,但是llvm还是会自动识别是可变还是不可变,不是说语言里面没有了
就说当你需要避免可变带来的问题的时候
你还是要注意你怎么使用你的变量

Thor@2x
呃。。。swift里面。。。好像也有可变性的吧。。。let和var...

**Tinyfool ‍培强 **
也就说语法上简单了,不代表这两个概念没意义了,那是显示模式
现在有自动识别了显式声明,你现在写简单程序你可以理解为关系不大

**杨武 **
let/var 控制的是变量,而不是变量引用的对象。

**Tinyfool ‍培强 **

但是写性能有关的程序你还是要能理解自己
嗯,细节
就像oc以前一般不需要你懂什么叫做pool,但是写有大量的对象创造和毁灭的场合的时候,你自己不会操作pool,性能马上就垮了
语法是一个语言里面最简单的部分,也是我最少谈的部分

**杨武 **
说到语法,Swift 最不舒服的 2 点:let 和 ( ) ,简直不忍直视。也不知道 const 和 ${} 怎么招惹 Chris Lattner 了

**Tinyfool ‍培强 **
当然也是阻止初学者学新语言最主要的部分

**Tinyfool ‍培强 **
哈哈,let我喜欢啊

**杨武 **
想起了 Basic 是吧

**Tinyfool ‍培强 **
不是啊,很像一句话啊

Thor@2x
感觉上,一个语言社区的氛围对初学者的判断影响很大,直接形成了洗脑式的刻板印象。。。

Tinyfool ‍培强
我早记不得basic了,社区肯定是最重要的东西

**杨武 **
如果不分 const/var 倒是挺好的,其它语言也有用 let 的,没有这么难受

**Tinyfool ‍培强 **

在我看来技术大家都差不多,关键看谁有比较好的社区了

Thor@2x

于是乎初学者一上来就会受到社区的渲染,在经验并不丰富的时候就会觉得:C++好复杂,Java适合大型系统,PHP就是意大利面条,Python简洁优雅,Ruby的开发效率甩其他语言几条街。。。

**杨武 **
话说,这个说法基本靠谱

Thor@2x

因此初学者大概也是奔着一开始的社区宣传来的吧~从疯狂地爱上某个语言或者某种技术,直到渐渐发现每种东西都有自己独特的优势和一大堆问题。。。

**杨武 **

《七周七语言》+《七周七数据库》包解毒

FuckWisdom
感谢各位老师热情讨论[憨笑]感恩节福利[鼓掌]

李建忠
大家要多积极问好问题

我的内容总结

1.理解_实例变量和self.属性之间的区别

这里来结合讨论主题来写一写
之所以@property(nonatomic,copy)NSMutableArray *mutableArray;用self.mutableArray无法添加元素,而_mutableArray可以,是因为属性多执行这样一段代码:

//这里我就不科普点语法那些事了
-(void)setMutableArray:(NSMutableArray *)mutableArray
{
    if(mutableArray != nil){
        _mutableArray = [mutableArray copy];
    }
}

这里暂时先不解释copy对于可变数组的作用.
先附上汇编代码作为最直观地证据,代码别傻傻地细看,主要看旁边的注释:

- (void)viewDidLoad {
    _mutableArray = @[@"1",@"2"];
    self.mutableArray =@[@"1",@"2"];
}
    0x102c26853 <+19>:  leaq   0x2926(%rip), %r8         ; @"'2'"
    0x102c2685a <+26>:  leaq   0x28ff(%rip), %r9         ; @"'1'"
    0x102c26861 <+33>:  movq   0x27b8(%rip), %r10        ; (void *)0x0000000105ea4070: __stack_chk_guard
    0x102c26868 <+40>:  movq   (%r10), %r10
    0x102c2686b <+43>:  movq   %r10, -0x8(%rbp)
    0x102c2686f <+47>:  movq   %rdi, -0x30(%rbp)
    0x102c26873 <+51>:  movq   %rsi, -0x38(%rbp)
    0x102c26877 <+55>:  movq   %r9, -0x18(%rbp)
    0x102c2687b <+59>:  movq   %r8, -0x10(%rbp)
    0x102c2687f <+63>:  movq   0x3932(%rip), %rsi        ; (void *)0x00000001038cf900: NSArray
    0x102c26886 <+70>:  movq   0x38c3(%rip), %rdi        ; "arrayWithObjects:count:"
    0x102c2688d <+77>:  movq   %rdi, -0x40(%rbp)
    0x102c26891 <+81>:  movq   %rsi, %rdi
    0x102c26894 <+84>:  movq   -0x40(%rbp), %rsi
    0x102c26898 <+88>:  callq  0x102c27810               ; symbol stub for: objc_msgSend
    0x102c2689d <+93>:  movq   %rax, %rdi
    0x102c268a0 <+96>:  callq  0x102c27828               ; symbol stub for: objc_retainAutoreleasedReturnValue
    0x102c268a5 <+101>: movq   -0x30(%rbp), %rcx
    0x102c268a9 <+105>: movq   0x3940(%rip), %rdx        ; ViewController._mutableArray
    0x102c268b0 <+112>: movq   (%rcx,%rdx), %rsi
    0x102c268b4 <+116>: movq   %rax, (%rcx,%rdx)
    0x102c268b8 <+120>: movq   %rsi, %rdi
    0x102c268bb <+123>: callq  0x102c2781c               ; symbol stub for: objc_release
    0x102c268c0 <+128>: movl   $0x2, %r11d
    0x102c268c6 <+134>: movl   %r11d, %ecx
    0x102c268c9 <+137>: leaq   -0x28(%rbp), %rax
    0x102c268cd <+141>: leaq   0x28ac(%rip), %rdx        ; @"'2'"
    0x102c268d4 <+148>: leaq   0x2885(%rip), %rsi        ; @"'1'"
->  0x102c268db <+155>: movq   -0x30(%rbp), %rdi
    0x102c268df <+159>: movq   %rsi, -0x28(%rbp)
    0x102c268e3 <+163>: movq   %rdx, -0x20(%rbp)
    0x102c268e7 <+167>: movq   0x38ca(%rip), %rdx        ; (void *)0x00000001038cf900: NSArray
    0x102c268ee <+174>: movq   0x385b(%rip), %rsi        ; "arrayWithObjects:count:"
    0x102c268f5 <+181>: movq   %rdi, -0x48(%rbp)
    0x102c268f9 <+185>: movq   %rdx, %rdi
    0x102c268fc <+188>: movq   %rax, %rdx
    0x102c268ff <+191>: callq  0x102c27810               ; symbol stub for: objc_msgSend
    0x102c26904 <+196>: movq   %rax, %rdi
    0x102c26907 <+199>: callq  0x102c27828               ; symbol stub for: objc_retainAutoreleasedReturnValue
    0x102c2690c <+204>: movq   %rax, %rcx
    0x102c2690f <+207>: movq   0x3842(%rip), %rsi        ; "setMutableArray:"

看不懂没关系,再附上我精心准备的解释图吧,更加直观。

2.深入理解数组中的深拷贝和浅拷贝

第一层理解:

  • 如果对一个不可变容器复制,copy是指针复制,即浅拷贝。
  • 如果对一个可变容器复制,copy是对象复制,即深拷贝。
- (void)viewDidLoad { 
    NSArray *array = [NSArray array];
    NSArray *array2 = [array copy];
    NSLog(@"%p and %p",array,array2);
    
    NSMutableArray *marray = [NSMutableArray array];
    NSMutableArray *marray2 = [marray copy];
    NSLog(@"%p and %p",marry,marray2);
}

输出结果如下:

2015-12-02 16:05:14.503 Sample[1357:290815] 0x7f8240d032f0 and 0x7f8240d032f0
2015-12-02 16:05:14.503 Sample[1357:290815] 0x7f8240c20bd0 and 0x7f8240c054f0

用一张内存图来说明这段代码的关系

第二层理解:

  • 如果是对可变容器copy,是对象复制,即深拷贝,但拷贝出的是一个不可变容器。
  • 如果是对可变容器mutableCopy才符合正确地copy语义,也是对象复制,即深拷贝,这次拷贝出的是一个可变容器。
- (void)viewDidLoad {
    NSMutableArray *array = [NSMutableArray array];
    NSLog(@"%@",[array class]);
    [array addObject:@"Panda"];
    
    NSMutableArray *array2 = [array mutableCopy];
    NSLog(@"%@",[array2 class]);
    [array2 addObject:@"Lion"];     //成功
    
    NSMutableArray *array3 = [array copy];
    NSLog(@"%@",[array3 class]);
    [array3 addObject:@"Lion"];     //报错
}

2015-12-03 15:31:39.773 Sample[1782:244030] __NSArrayM
2015-12-03 15:31:39.773 Sample[1782:244030] __NSArrayM
2015-12-03 15:31:39.773 Sample[1782:244030] __NSArrayI

可以发现array3的类型是_NSArrayI,即NSArrayInmuatble
说起来很绕,放一张内存图来说一说

第三层理解:

  • 上述的深拷贝其实还不是完全深拷贝,因为第二层的图可以发现mutableCopy的数组仍然共享同样的数组元素。
  • 完全深拷贝即是对数组元素同样的拷贝的真正深拷贝。
- (void)viewDidLoad {
    NSMutableArray *marray = [NSMutableArray array];
    [marray addObject:@"Panda"];
    NSMutableArray *marray2 = [marray mutableCopy];//一般深拷贝
    NSMutableArray* marray3 = [NSKeyedUnarchiver unarchiveObjectWithData:
                                [NSKeyedArchiver archivedDataWithRootObject: marray]];//完全深拷贝
    NSLog(@"1:%p \n 2:%p \n 3:%p",marray[0],marray2[0],marray3[0]);
}

输出结果如下:

2015-12-02 16:34:22.437 Sample[1428:314672] 1:0x1067e8150
2:0x1067e8150
3:0xa000061646e61505

再发图感受一下:

3.Swift中的容器对象
//copy-on-write
var array1=[1,2,3,4,5]
var array4=array1
array1[0]=10
array1.append(20)
print(array1)
print(array4)

Swift中的数组使用的是copy-on-write技术,即数组写入元素,copy一份新的对象。


WeChat_1449054355.jpeg

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

推荐阅读更多精彩内容