细节决定成败,iOS移动开发发展已经多年,相信内存管理这个概念已经被说烂了,但是相信对于很多小伙伴来说,只是了解了内存管理的大概知识体系,对于一些边缘知识点掌握的并不是很好,边缘不代表不重要.
互联网环境寒冬将至,为了应对目前市场越来越高的需求,iOS开发不得不去提升自己的能力去匹配更高的需求,当每个人都学会了的时候,你比别人更好的体现就是你会的内容更深入,进入正题,下面是总结的一些内存管理方面的使用注意事项.
-
内存管理方法名命名规则
-
alloc/new/copy/mutable
以上名称开头的方法名,返回自己生成并持有的对象,如果没有引用的变量则会释放并废弃.// 示例 // main.m实现如下 #import <Foundation/Foundation.h> #import "CFObject.h" int main(int argc, const char * argv[]) { @autoreleasepool { id __weak obj; @autoreleasepool { obj = [CFObject newObject]; NSLog(@"%@", obj); } NSLog(@"%@", obj); } return 0; } //CFObject.h中做函数声明 // CFObject.m实现如下 + (id)allocObject { return [[CFObject alloc] init]; } + (id)newObject { return [[CFObject alloc] init]; } // 打印结果为null null /** 结果分析: 由于obj指针为__weak弱指针,所以并不持有生成的对象,编译器判断没有持有当前对象的变量,所以在newObject方法执行结束后将对象释放并废弃,obj此时指向nil,第一个log打印null 第二个log打印时obj也不再指向对象,所以obj指针指向nil,打印结果为null */
你可能会说,这不就是内存管理最基本的原则吗,不用了就释放.这进阶什么了,刚学的时候就已经理解了.别急,继续看下面的例子
// 在CFObject.m中增加一个createObject方法,返回实例对象 + (id)createObject { return [[CFObject alloc] init]; } // main.m中和上一个示例一样调用并打印 int main(int argc, const char * argv[]) { @autoreleasepool { id __weak obj; @autoreleasepool { obj = [CFObject createObject]; NSLog(@"%@", obj); } NSLog(@"%@", obj); } return 0; } /** 猜一下打印结果是什么,还是null, null 吗? 结果是 : <CFObject: 0x102805ef0> 和 null 为什么返回实例的实现都一样,更换了方法名就打印不一样了呢,这就引出了第二条命名规则 */
-
非第一条提到的名称命名的方法名
除了第一条以外命名的方法名,例如createObject NSMutablArray的 array方法 [NSMutablArray array],这一类的方法返回实例时并不持有对象,但是为了能达到使用对象的目的,使用了autorelease自动释放池管理对象内存.
```
// 所以上面示例中的createObject方法实现可以转换为返回autorelease 对象.类似以下实现(仅逻辑类比,并非真正实现)
+ (id)createObject {
return [[[CFObject alloc] init] autorelease];
}/** 结果分析 : 由于createObject返回实例对象被自动释放池管理,所以不会立马释放对象,当一个log打印时,当前对象存在,可以有打印结果, 当autoreleasepool{}结束作用域时,对象被释放,所以第二个打印为null. */ ```
-
ARC下第三条规则(引用自Objective-C高级编程一书中Page 52 内容)
init:init方法规则比alloc/new/copy/mutableCopy 还要严格,该方法:
1. 必须返回的是实例方法
2. 返回对象类型为id或者声明类的对象类型,或者是该类的父类或子类
3. 返回对象不注册到autoreleaspool上,只是对alloc返回的对象初始化并返回该对象,init方法源码中调用runtime的_objc_rootInit直接将返回的对象返回,未作处理.(参考runtime源码NSObject.mm的init方法实现)
-
-
内存管理修饰符详解
- 修饰符
- __strong : 强引用指针,用于ARC下对变量修饰并产生强引用持有赋值对象,默认环境下的修饰符,一般 id object 书写格式默认就是id __strong object.使用非常常见.
- __weak:弱引用指针,用于ARC下对变量修饰产生弱引用,不持有对象,常用于解决循环引用问题.当指向对象不存在时被置为nil.
- 不支持__weak的情景:
- 大多数重写了retain/release等内存管理方法的类,由于其自己实现了引用机制,而__weak需要用到一些runtime运行时库的函数,所以此种情况的类不支持__weak修饰,一般声明中会有提示,并且编译器编译时报错
- 实现了allowsWeakReference或者retainWeakReference方法并且返回NO的情况
- 当实现allowsWeakReference方法返回No时表示不支持弱引用,在赋值给__weak修饰的变量时,程序会异常终止
- 当实现retainWeakReference方法返回NO时,__weak修饰的指针变量会被置为nil.
// 还是用以上示例中的CFObject类实现方法如下 - (BOOL)retainWeakReference { return YES; } // main.m做以下测试 int main(int argc, const char * argv[]) { @autoreleasepool { id object = [[CFObject alloc] init]; id __weak obj = object; NSLog(@"%@", obj); } return 0; } /** 当return NO时,打印null; 当return YES时,打印对象 */
- __weak修饰的变量使用时会调用objc_loadWeakRetained()/objc_release()方法对做retain/release操作.大量使用__weak会产生性能损耗(大量做retain/release操作),
- 不支持__weak的情景:
- __autorelease
- C中动态数组不支持autorelease(参考OC高编 P65)
- autorelease修饰auto自动变量限制(仅限于局部变量,函数以及方法参数)
- 非显示使用__autorelease修饰也生效的两种情况:
- 第一种就是命名规则提到的第二条,虽然没显示的__autorelease修饰,但是编译器会做添加逻辑处理
- 类似id obj 是默认 __strong修饰一样,如果是对象的指针id *obj 或者NSObject ** object 这样,默认是__autorelease.示例就是当传入参数为对象指针类型,例如NSError的使用情景下,(该情况本人未通过代码验证,参考文章是OC 高级编程一书中的介绍)
NSError *error; // 声明 method:(NSError **)error; // 调用 method:&error // 此时method:(NSError **)error;声明时,可以看做是method:(NSError * __autorelease *)error;
- autorelease 和__strong 共同使用时优化方案;
当遇到命名规则中的第二条情况时,方法返回的对象需要做autorelease处理,如果对象赋值给__strong修饰的变量则又会做一个retain处理,示例如下:+ (id)createObject { return [[CFObject alloc] init]; } // main.m id obj = [CFObject createObject]; clang编译成c++代码之后, // CFObject.m + (id)createObject { id tem1 = ((id(*)(id, SEL))objc_msgSend)([NSObject class], @selector(alloc)); id tem2 = ((id(*)(id, SEL))objc_msgSend)(tem1, @selector(init)); return objc_autoreleaseReturnValue(id2); } // main.m id obj_tmp = ((id(*)(id, SEL))objc_msgSend)(self, @selector(obj)); id obj = objc_retainAutoreleasedReturnValue(obj_tmp); /** main.m中,编译器会在返回为autorelease对象赋值后自动插入objc_retainAutoreleasedReturnValue()方法, 此时先autorelease,然后再retain放到自动释放池对象的操作会显得很没效率. apple其实是对这个做了优化处理的,处理逻辑如下(参考OC 高级编程 P67): 1. 在类似命名规则第二条情况时,返回autorelease对象是通过函数objc_autoreleaseReturnValue()函数实现的. 使用到objc_autoreleaseReturnValue()函数时,编译器并不会决定立马将对象放入自动释放池,而是去查看调用方的执行命令列表,是否会有objc_retainAutoreleasedReturnValue()操作,如果没有,则按照正常的流程将对象放入自动释放池,如果有调用,则不再方式释放池,而是通过修改某个全局数据结构标志位的方式,当调动到objc_retainAutoreleasedReturnValue()方法时,会去检测该标志位,如果已经设置修改过,则不会再做retain操作, 通过修改标志位的方式效率比retain/release 会更高 */
- unsafe_unretain : 同__weak一样,都是弱引用,差异区别会放在下一知识点说明.
- __weak 和unsafe_unretain 修饰符的差异
- 空指针、野指针、悬垂指针的差异
空指针很好理解,就是指向NUll的指针,但是针对野指针(Wild pointer)和悬垂指针(Dangling pointer)则有不同的理解.
在某些语言中,是不区分野指针和悬垂指针的,因为会Wild pointer 会被导向至 Dangling pointer ;对悬垂指针的理解是指向了已经被释放的内存地址,但是指针未被置空.对野指针的理解是未初始化的指针或者是前面悬垂指针的情况,所以一般OC里也会说指向被释放内存地址的指针未野指针.对二者没有明显的区分. - 二者差异以及使用场景
__weak 和unsafe_unretained都是弱引用指针,区别在于__weak是ARC下的弱引用,对不存在ARC机制以前使用unsafe_unretained修饰,从名字也可以看出后者是不安全的,即当对象被释放废弃后,指针不会被置为nil,还指向原来的内存空间,此时使用有可能会导致程序异常.
- 空指针、野指针、悬垂指针的差异
- 修饰符
-
如何获取引用计数值
- 因为在ARC环境下不允许使用ratainCount(MRC支持),所以如何获取引用计数来调试内存管理呢,
- OC对象获取方式:_objc_rootRetainCount()
- C对象获取方式:CFGetRetainCout()
获取引用计数是苹果思想里面不推荐的,因为引用计数并不是非常明显的代表着内存管理的结果.例如autorelease下的引用计数,会默认在autoreleasepool pop时 做release操作,但是当你打印时还未执行pop操作就会导致和你认为的数值不符,我们应该换一种角度看待引用计数的思想,他并不是真的想让开发者根据数值去控制内存管理,真正的思想是根据是否存在强引用的思想去理解内存管理.通过成对的retain/release去控制引用.所以此处的引用计数只是作为debug的一个参考,不可太过依赖.
另外知其然,知其所以然.对于内存管理相关的源码分析会放在以后几章节详细说明.
还在学习的路上奔波,所以有的知识理解还存在很大误区,非常欢迎指出共同进步...
网友评论