讨论主题
下列代码中:为什么用实例变量初始化的可变数组添加对象元素成功,而用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一份新的对象。