这是我的第一篇文章,有什么不对的地方还烦请大家指出啦,总觉得做开发很有必要做笔记,方便记录自己的所得也能与大家探讨,但是之前一直比较懒所以无所作为,那接下来的时间一起好好努力吧各位加油加油。
1.总起
1.1 了解内存管理
了解内存管理的最普通的方式是考虑所有权。如果Manny对Jack曾说过alloc,retain或者copy,则表示Manny已经宣称对Jack的所有权。多个对象可以同时拥有Jack,但每个对象只负责正确地管理自己对Jack的所有权,最终释放Jack是每个Jack所有者的责任,而Jack的非所有者永远不需要释放Jack。只要所有拥有Jack所有权的对象都以这种方式执行,Jack就不会泄漏也不会有任何指向Jack的指针留下来摇晃。
1.2 相关知识点
-
野指针:指针变量没有进行初始化或指向的空间已经释放。
- 使用野指针调用对象方法,会报异常,程序崩溃。
- 通常在调用完release方法后,把保存对象指针的地址清空(不是销毁对象,引用计数为0时才会调用dealloc销毁对象),赋值为nil,但OC中没有空指针异常,所以[nil retain]调用方法不会有异常。
-
内存泄漏:
-
在ARC自动引用计数模式下,造成内存泄漏的情况:
- 如Person *person = [Person new];(对象指针提前赋值nil或者清空,⚠️提前)在栈区中的person已经释放,而堆区new产生的对象还没有释放,就会造成内存泄漏。
-
在MRC手动引用计数模式下,造成内存泄漏的情况:
- 没有配对释放,不符合内存管理原则。
- 对象指针提前赋值nil或者清空,导致release不起作用。
-
-
僵尸对象:堆中已经被释放的对象(retainCount=0)。
-
空指针:指针赋值为空(nil)。
提醒
一个变量的名称,包括实例变量,只是一个指针。当你向该指针发送消息时,你实际上就是通过该指针将消息发送到它指向的对象。内存管理的规则是关于对象的原则,而不是关于名称、引用或指针的规则。你不能递增或递减一个指针的保留计数,因为没有这个东西。指针所占用的内存是自动管理的(而且很小)。内存管理所关注的是指针所指向的对象。
2.MRC
2.1 实现原理
Objective-C对象中保存着引用计数这一整数值。调用alloc或者retain方法后,引用计数+1。调用release后,引用计数-1。引用计数为0时,调用dealloc方法废弃对象。
2.2 相关操作
对象操作 | Objective-C方法 | 引用计数 |
---|---|---|
生成并持有对象 | alloc/new/copy/mutablecopy | 1 |
持有对象 | retain | +1 |
释放对象 | release | -1 |
废弃对象 | dealloc | 0 |
2.3 内存管理的思考方式
-
自己生成的对象,自己持有
NSObject *obj = [[NSObject alloc] init]; [obj retain]; NSLog(@"obj - %lu",[obj retainCount]);复制代码
-
非自己生成的对象,自己也能持有
id obj = [NSMutableArray array]; [obj retain]; NSLog(@"obj - %lu",[obj retainCount]);复制代码
-
不再需要自己持有的对象时释放
NSObject *obj = [[NSObject alloc] init];//自己生成自己持有 [obj retain]; NSObject *obj2 = [obj retain];//非自己生成,自己持有 [obj release]; [obj2 release]; NSLog(@"obj - %lu",[obj retainCount]);复制代码
-
非自己持有的对象自己无法释放
id obj = [NSMutableArray array]; [obj release];复制代码
提醒
MRC下对象的引用计数可以通过[object retainCount]方法获得。
2.4 NSAutoreleasePool
autorelease故名思议就是自动释放,看上去很像ARC,但其实更类似于C语言中的局部变量,也就是说,超出变量作用域的时候将自动被废弃,但这里与C语言不同的是,编程人员可以手动设置其作用域。 autorelease的具体使用方法如下:
- 生成并持有NSAutoreleasePool对象
- 调用已分配的autorelease方法
- 废弃NSAutoreleasePool对象
NSAutoreleasePool对象的生命周期相当于C语言变量的作用域,对于所有调用过autorelease方法的对象,在废弃NSAutoreleasePool对象时,都将对对象统一调用release方法,代码如下所示:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init] ;id obj = [ [NSObject alloc]init];[obj autorelease] ;[pool drain];复制代码
提醒
在Cocoa框架中,如果不是使用alloc/new/copy/mutablecopy这几个方法返回的对象,其余方法返回的对象都将自动注册到NSAutoreleasePool中,id array = [NSMutableArray arrayWithCapacity:10];其实也就等同于:id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
3.ARC
ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所做的只不过是在代码编译时为你自动在何时的位置插入release或者autorelease,减少了开发的工作量。但我们有时仍需要四种所有权修饰符来配合ARC来进行内存管理。
3.1 四种所有权修饰符
-
__strong:强引用,持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil(这里先创建一个指针,将新的值retain一次,将指针动态指向新的值,并将旧的值release一次)。
NSObject *obj = [[NSObject alloc]init]; //他们是等价的 NSObject __strong *obj = [[NSObject alloc]init];复制代码
-
__weak:弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针.避免循环引用,会将对象注册到autoreleasepool(既不保留新值,也不释放旧值,动态地将指针指向新的值,如果这个值刚被dealloc,就会将指针更新为一个nil指针)。
-
unsafe_unretained:相当于assign。直接赋值。引用计数不变。他会发生野指针现象。所以不安全。不像weak,当指向的对象为空的时候,将指针置为nil。
-
_autoreleasing:将对象赋值给附有 _ autoreleasing 修饰符的变量等同于ARC 无效时调用对象的autorelease方法。
@autoreleasepool { id __autoreleasing obj = [[NSObject alloc] init]; }复制代码
3.2 底层实现
-
__strong底层实现
-
alloc/new/copy/mutablecopy情况下
//alloc为例的模拟底层代码为: id __Strong obj = [[NSObject alloc] init]; id obj = objc_msgSend(NSobject, @selector(alloc)); objc_msgSend(obj, @selector(init)); objc_release(obj); //编译器自动帮我们加入了release,来释放对象复制代码
-
非alloc/new/copy/mutablecopy情况下
//array为例的模拟底层代码为: id obj = objc_msgSend(NSMutableArray, @selector(array)); objc_retainAutoreleasedReturnValue(obj); objc_release(obj); //objc_retainAutoreleasedReturnValue(obj)是主要用于最优化程序运行 //array方法的底层模拟为: id obj = objc_msgSend(NSmutableArray,@selector(alloc)); objc_msgSend(obj,@selector(init)); return objc_autorealeaseReturnValue(obj);复制代码
objc_autorealeaseReturnValue(obj)与objc_retainAutoreleasedReturnValue(obj)是成对的,objc_autorealeaseReturnValue(obj)会把对象注册到autorealeasepool中,但是如果它检测到方法执行列表中出现objc_retainAutoreleasedReturnValue(obj)方法,那么就不会将返回的对象注册到autorealeasepool,而是直接传递到方法和函数的调用方。这样直接传递可以达到最优化。
-
-
__weak底层实现
-
__weak源码
id __weak obj1 = obj; //模拟代码 id obj1; obj1 = 0; objc_storeWeak(&obj1,obj); objc_destoryWeak(&obj1); 等同于objc_storeWeak(&obj1,0);复制代码
objc_storeWeak(&obj1,obj)函数将第二个参数的赋值对象的地址作为键值,将第一个参数的附有__weak修饰符的变量的地址注册到weak表中,如果第二个参数为0,则把变量的地址从weak中删除。一个键值可以注册多个变量的地址由此可见,如果大量的weak变量,则会消耗CPU资源,所以weak 只用来避免循环引用。
-
__weak与@autoreleasepool
id __weak obj1 = obj; NSLog(@"%@",obj1); //模拟代码 id obj1; objc_initWeak(&obj1,obj); id temp = objc_loadWeakRetained(&obj1); objc_autorelease(temp); NSLog(@"%@",temp); objc_destoryWeak(&obj1);复制代码
所以在@autorealeasepool块结束前可以放心使用weak修饰变量
-
__autoreleasing底层实现
将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效时,调用对象的autorelease方法。
-
使用alloc/new/copy/mutableCopy时
@autoreleasepool { id __autoreleasing obj = [[NSObject alloc] init]; } //模拟代码 id pool = objc_autoreleasePoolPush(); id obj = objc_msgSend(NSObject, @selector(alloc)); objc_msgSend(obj, @selector(init)); objc_autorelease(obj); objc_autoreleasPoolPop(pool);复制代码
-
使用alloc/new/copy/mutableCopy以外的方法时
@autoreleasepool { id __autoreleasing obj = [NSMutableArray array]; } // 模拟代码 id pool = objc_autoreleasePoolPush(); id obj = objc_msgSend(NSMutableArray,@selector(array)); objc_retainAutoreleasedReturnValue(obj); objc_autorelease(obj); objc_autoreleasPoolPop(pool); // 虽然 obj 持有对象的方法变为 objc_retainAutoreleasedReturnValue, 但是将 obj 所引用的对象注册到 autoreleasepool 中的方法并没有改变复制代码
关于@autoreleasepool,在ARC下应该使用@autoreleasepool而不是NSAutoreleasePool,@autoreleasepool的具体实现将在之后的文章中继续探讨。
-
-
3.3 使用规则
- 不能使用retain/release/retainCount/autorelease
- 不重载dealloc(如果是释放对象内存以外的处理,是可以重载该函数的,但是不能调用[super dealloc])
- 不能使用NSAllocateObject, NSDeallocateObject
- 不能在C结构体中使用对象指针
- id与void *间的如果cast时需要用特定的方法(__bridge关键字)
- 不能使用NSAutoReleasePool,而需要使用@autoreleasepool块
- 不能使用区域(NSZone)
4.属性的内存管理
ObjC2.0引入了@property,提供成员变量访问方法、权限、环境、内存管理类型的声明,下面主要说明ARC中属性的内存管理。属性的参数分为三类,基本数据类型默认为(atomic,readwrite,assign),对象类型默认为(atomic,readwrite,strong),其中第三个参数就是该属性的内存管理方式修饰,修饰词可以是以下之一:
-
assign
直接赋值,一般用来修饰基本数据类型。当然也可以修饰ObjC对象,但是不推荐,因为被assign修饰的对象释放后,指针还是指向释放前的内存,在后续操作中可能会导致内存问题引发崩溃。
@property (nonatomic, assign) NSInteger count;复制代码
-
retain
retain和strong一样,都用来修饰ObjC对象,使用set方法赋值时,实质上是会先保留新值,再释放旧值,再设置新值,避免新旧值一样时导致对象被释放的的问题。
//MRC写法如下 - (void)setCount:(NSObject *)count { [count retain]; [_count release]; _count = count; } //ARC对应写法 - (void)setCount:(NSObject *)count { _count = count; }复制代码
-
copy
一般用来修饰String、Dict、Array等需要保护其封装性的对象,尤其是在其内容可变的情况下,因此会拷贝(深拷贝)一份内容給属性使用,避免可能造成的对源内容进行改动。使用set方法赋值时,实质上是会先拷贝新值,再释放旧值,再设置新值。实际上,遵守NSCopying的对象都可以使用copy,当然,如果你确定是要共用同一份可变内容,你也可以使用strong或retain。
-
weak
ARC新引入修饰词,可代替assign,比assign多增加一个特性(置nil)。weak和strong一样用来修饰ObjC对象。使用set方法赋值时,实质上不保留新值,也不释放旧值,只设置新值。
@property (weak) id
delegate;复制代码 -
strong
ARC新引入修饰词,可代替retain,ARC一般都写strong。
Person *per = [[Person alloc] init];
self.person = per;
如果是strong,对象的retainCount为2,如果为weak,对象的retainCount为1。
-
unsafe_unretained
等价于assign,可以用来修饰数据类型和OC对象,但是不会使计数器加1,且对象销毁时也不会将对象指向nil,容易造成野指针错误。
5.block的内存管理
OC中使用block必须自己管理内存,错误的内存管理将导致循环引用等内存泄漏问题,这里主要说明在ARC下block声明和使用的时候需要注意的两点:
-
如果你使用@property去声明一个block的时候,一般使用copy来进行修饰(当然也可以不写,编译器自动进行copy操作),尽量不要使用retain。
@property (nonatomic, copy) void(^block)(NSData * data);复制代码
-
block会对内部使用的对象进行强引用,因此在使用的时候应该确定不会引起循环引用,当然保险的做法就是添加弱引用标记。
__weak typeof(self) weakSelf = self;复制代码