栈与堆
首先,我们需要知道:为什么只有Objective-C对象需要进行内存管理,而其它非对象类型(如基本数据类型)不需要我们来管理呢?
因为: Objective-C 的对象在内存中是以堆的方式分配空间的
- 堆里面的内存是动态分配的,所以也就需要程序员手动的去添加内存、回收内存
- OC对象存放于堆里面(堆内存要程序员手动回收,也就是 release )
- 非OC对象一般放在栈里面(栈内存会被系统自动回收)
例如:
|
|
我们知道OC对象一般都是指针形式,大神ibireme在 Objective-C 中的类和对象中提到:
凡是首地址是*isa的struct指针,都可以被认为是objc中的对象。运行时可以通过isa指针,查找到该对象是属于什么类(Class)。
下图可以清楚的表示 OC 中的堆栈分配:
assign 与 weak
基础知识提要
@property 声明的属性,会自动创建 getter,setter 方法。 属性中申明的关键字主要有三大类:
- readwrite/readonly: 是否生成 setter 方法, 默认 readwrite
- assign/weak/strong/copy/retain: setter 方法中如何传递所有权等内存管理策略, ARC 环境默认为 strong
- nonatomic/atomic:是否加线程锁(并不能完全控制线程访问,最好事是自己手动底层加锁,默认 atomic
NSString 的引用计数LLVM是有优化的,并不是简单根据设定的关键字来判断。本文只是为了方便易懂,以这个类作为例子。
assign
如果设置 assign ,在 setter 方法中是这样的:
|
|
也就是说成员变量 _name 并没有获得 name 所指的对象的所有权,原来对象 @“Amywushu” 的 retainCount 不会改变。一旦 name 被释放,_carName 也就不再指向一个合法的位置,出现指针悬空,如下图所示,相当于 _carName (图中为 carName )指向的 name,但实际上 _carName 是指向OC对象的,这里只是为了方便理解。
assign/weak 的区别
ARC 中 OC 对象已经不再使用 assign 了,而是使用 weak ,两者的区别在: weak 弱引用所指的对象没有被任何strong指针指向,那么就将被销毁,所有指向这个对象的 weak 指针也将被置为 nil 。而 assign 不会被置为 nil 。但对于非 OC 对象来讲,因为其存储空间在栈上,由系统管理内存,所以一般还是使用 assign。
unsafe_unretained
unsafe_unretained 的语义与 assign 类似,相当于用于 OC 对象类型的 assign 。使用这个关键字主要出于性能考虑,因为 weak 对性能有一些影响,因此对性能要求高的地方可以考虑使用 unsafe_unretained 替换 weak 。比如 YYModel 的实现,为了追求更高的性能,其中大量使用 unsafe_unretained 作为变量标识符。
retain 与 strong
知识点:
MRR内存管理基本原则
- 为创建的所有对象设置所有权
- 应使用retain方法获取对象(你尚未拥有)的所有权
- 当不再使用某个对象时,必须放弃其所有权
- 不能放弃不归你所有的对象的所有权
|
|
——引用自[精通Objective-C]内存管理
如果设置 retain ,在 setter 方法中是这样的:
|
|
也就是说,retain/strong 会在 setter 方法中,对传入的对象 "Amywushu"
进行引用计数 +1 的操作。简单来说,就是会拥有传入对象 "Amywushu"
的所有权,而不是像 assign/weak 一样,依赖于传入的对象指针 name
,而并非拥有实际所有权。
相当于一个保险柜拥有两把钥匙,变量解除所有权 (release) ,也就相当于归还钥匙。当两把钥匙都被归还之后,这个保险柜(对象)也就会被释放。只要拥有该对象的所有权(至少有一把钥匙没有归还),这个对象就不会被释放。
如图所示,_carName
和 name
均拥有对 Amywushu
这个字符串对象的所有权,该对象的引用计数变为 1+1=2 。
strong 是在 iOS 引入 ARC 的时候引入的关键字,是retain的一个可选的替代。 strong 跟 retain 的意思相同并产生相同的代码,但是语意上更好更能体现对象的关系。
关于 strong 和 weak 还有什么不清楚的话,可以学习 onevcat 的手把手教你ARC——iOS/Mac开发ARC入门和使用这篇文章,讲得非常清晰易懂。
copy 与 mutableCopy
copy 、mutableCopy 与 strong 的区别在于,深拷贝时,示例变量对于传入对象的副本拥有所有权,而不是对象本身;浅拷贝时,则没有区别。
如果设置 copy ,在 setter 方法中是这样的:
|
|
如上图所示,原来对象的 retainCount 不变,新 copy 出来的对象副本的 retainCount=1 ,原对象引用计数不变,两者 copy 之后互不相关,这是深拷贝。
- 深拷贝与浅拷贝
深拷贝: 是对内存空间的拷贝,也就是这里的 copy(数组等类型例外)
浅拷贝: 是对内存地址的拷贝,也就是上面的 retain/strong,以及 copy 的某些情况
深拷贝和浅拷贝比较复杂,我的另一篇文章有具体讲解copy 与 mutableCopy(传说中的深浅拷贝,这里只是简要提及一下。
注意! 如果是 copy 的是一个 NSArray 呢? 比如:
|
|
这个时候,系统的确是为 array2 开辟了一块内存空间,但是我们要知道的是, array2 中的每个元素,,只是 copy 了指向 array 中相对应元素的指针,这是“单层深拷贝”.
一般来说,不要将 copy 用到 NSMutableString ,NSMutableArray ,NSMutableDictionary 等可变对象上,除非有特别的需求。
例如:
|
|
运行则会报出错误 signal SIGABRT
我们明明在代码里用的 NSMutableArray 这个可变数组,为什么错误里会说是在向 NSArray 这个不可变数组,调用 addObject: 这个方法呢?( __NSArrayI 表示的是 NSArray 类型)。
是因为在 self.mutableArray_copy = mutableArray;
这一句的时候,会调用 mutableArray_copy 的 setter 方法, copy 属性默认 setter 方法是这样写的:
|
|
setter 方法里调用的是 copy,而不是 mutableCopy ,也就是说拷贝过来的是不可变的 NSArray 类型。那么 NSMutableArray 的添加元素等方法自然就不能使用了。
解决方法为:将 copy 改为 strong ;或者重写 setter 方法,将 copy 改为 mutableCopy 。
copy 现在都比较少用,一般用于 NSString 。因为父类指针可以指向子类对象,NSMutableNSString 是 NSString 的子类,使用 strong 的话虽然 NSString 是不可变对象,但是它传入的值可能会是 NSMutableString 可变对象,如果这个可变对象的内容在其他地方被修改了,那 NSString 指针所指的对象也随之改变了,而其本身可能对此毫不知情。因此一般用 copy 。
nonull nullable null_resettable
这三个属性关键字是 WWDC2015 中介绍的 OC 新特性,与 Swift 中的 ? 和 ! 类似。
- nonull:该属性不能为 nil ,必须有值。
- nullable:表示可选的,可以为 nil。
- null_resettable:表示 setter 方法是 nullable ,可以为 nil;而 getter 方法是nonull ,必须有值。
总结
总的来说,就是内存处理方式不一样,assign/weak 相当于借用传入的指针变量来指向对象(实际上并不是,可以这样理解),retain/strong 相当于不同的指针变量指向同一个对象,copy 则是在内存里复制了一个对象并指向它。读写属性以及原子性比较简单,不再赘述。
关于 atomic 的锁机制,以及 ARC 机制到底如何进行的内存管理,之后会进一步学习。敬请期待。
2016.12.07 补充:
新增博文: ARC 是如何进行内存管理的
Reference
[1] Objective-C 内存管理——你需要知道的一切 https://segmentfault.com/a/1190000004943276
[2] @property属性关键字详解 http://www.wugaojun.com/blog/2015/07/25/at-propertyshu-xing-guan-jian-zi-xiang-jie/
[3] Objective-C 的自动引用计数(ARC) https://hran.me/archives/objective-c-automatic-reference-counting.html
[4] Objective-c 内存管理的历史和参考资料 http://www.pchou.info/ios/2015/06/05/oc-memory-management.html