iOS内存管理(1)-基本概念整理

主要内容:

  1. 内存区域划分
  2. 内存管理/引用计数
  3. MRC手动管理引用计数
  4. ARC自动引用计数
  5. 内存泄漏问题
  6. 野指针问题
一、内存区域划分

程序在分配内存的时,主要分为:栈区堆区静态区常量区代码区

内存区域具体说明
栈区存放局部变量的值,系统自动分配和释放;
特点:容量小,速度快,有序
堆区存放通过malloc系列函数或new操作符分配的内存,如对象;
一般由程序员分配和释放,如果不释放,则出现内存泄露;
特点:容量大,速度慢,无序;
静态区存放全局变量和静态变量(包括静态局部变量和静态全局变量);
当程序结束时,系统回收;
常量区存放常量的内存区域;
程序结束时,系统回收;
代码区存放二进制代码的区域

从上述分类上看,我们在开发过程中主要涉及的是堆上内存的管理。通常,我们创建一个对象的代码如下:

NSObject *obj = [[NSObject alloc] init]; 

上述代码创建了一个 NSObject类型的指针 obj和一个 NSObject类型的对象。obj指针存在栈上,而其指向的对象则是在堆上。这种对象也称之为堆对象

二、内存管理/引用计数

无论是MRC还是ARC环境,Objective-C都采用引用计数来管理内存;每个对象都有一个引用计数器,任何时候指向对象的指针个数和对象的引用计数相等,当一个对象的引用计数为0的时候将会被释放;

OC管理内存涉及到对象的"生成""持有""释放"MRC需要调用对应的方法来管理引用计数,而ARC则是自动管理引用计数,无需再调用这些内存管理的方法。虽然两者管理内存的形式不同,但是它们都遵循相同的内存管理规律,内容如下:

  1. 自己生成的对象,自己所持有;
  2. 非自己生成的对象,自己也能持有;
  3. 不再需要自己持有对象时释放;
  4. 非自己持有的对象无法释放;
三、MRC手动管理引用计数

MRC,即手动管理引用计数。当我们通过 allocretain等方法持有对象后,也必须有相应的 release或者 autorelease将其释放。总结对象操作与Objective-C内存方法对应关系如下:

对象操作OC方法
生成并持有对象以alloc/new/copy/mutableCopy等名称开头方法
持有对象retain方法
释放对象release方法
废弃对象dealloc方法
1.自己生成的对象,自己所持有/非自己生成对象,不持有
id obj = [[NSObject alloc] init];  //自己生成并持有对象
id obj1 = [NSMutableArray array];  //取得非自己生成的对象,但不持有对象

OC中使用 allocnewcopymutableCopy这些名称开头的方法意味着自己生成对象并持有,否则就是非自己生成的对象不持有。如上源码,使用NSObject类的alloc类方法就能自己生成并持有对象,指向生成并持有对象的指针被赋值给了obj

通过自定义方法来理解这两种创建对象方法的区别(系统方法也是类似的实现),测试代码如下:

//以alloc开头的方法
- (id)allocObject {
    id obj = [[NSObject alloc] init];
    return obj;
}

- (id)object {
    id obj = [[NSObject alloc] init];
    [obj autorelease];  //用该方法,可以使取得的对象存在,但是自己不持有对象
    return obj;
}

autorelease即自动释放,对象已经加入自动释放池,所以获取对象并不持有;涉及到的自动释放池的内容会在后续详细总结。

注意:生成并持有对象的的方法一定是驼峰拼写来命名的方法,如 allocallocMyObject等方法;相反 allocatemutableCopyed就不属于这类方法;

2.非自己生成的对象,自己也能持有
 id obj1 = [NSMutableArray array];  //取得非自己生成的对象,但不持有对象
 [obj retain];                      //通过retain方法,持有了对象

源代码中, NSMutableArray类对象被赋值给变量 obj,但是变量 obj自己不持有该对象。使用 retain方法后可以持有对象。

3.不再需要自己持有对象时释放

自己持有的对象,一旦不需要,持有者有义务释放该对象,释放对象使用release方法。

id obj = [[NSObject alloc] init]; //自己生成并持有对象
[obj release];                    //释放自己持有的对象
NSLog(@"%@",obj);                 //已经释放,再次使用会崩溃

虽然指向对象的指针依然保留在变量obj中,看似可以访问,但对象一经释放就绝不可再访问。

4.非自己持有的对象无法释放

在应用程序中释放非自己持有的对象就会造成崩溃,使用代码演示如下:

//情况1:释放完不再需要的对象后再次释放,访问了已经废弃的对象而崩溃!
id obj = [[NSObject alloc] init];
[obj release];
[obj release];

//情况2:取得自己并不持有的对象对其释放,释放了非自己持有的对象而崩溃!
id obj = [[NSMutableArray array];
[obj release];
四、ARC自动引用计数

ARC(Automic Reference Counting),即自动引用计数;这是iOS5推出的新特性,iOS4.3也支持ARC,只是不能使用weakARC不再需要使用类似 retainrelease的操作来持有或者释放对象,从而大大提高了开发效率;

1.ARC使用条件
  1. Xcode4.2或以上版本;
  2. 使用LLVM编辑器3.0或以上版本;
  3. Xcode编译器选项中设置ARC有效;
2.ARC基本原理
  1. ARC下的编译器会在代码编译阶段合适的位置,自动加入 retain/release/autorelease的操作;
  2. ARC的规则:只要还有一个强引用指针指向对象,对象就会保存在内存中;
  3. ARC中使用 strongweak关键字来修饰对象; strong表示强引用,对应MRC下的 retainweak表示弱引用,对应原来的 assign,不同的是当对象被释放的时候,对象 weak指针自动赋值为 nil,从而不会引发野指针错误;
3.ARC所有权修饰符

ARC有效时,OC处理id类型和对象类型必须附加所有权修饰符。所有权修饰符一共有四种:

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

__strong修饰符:

__strongid类型和对象类型默认的所有权修饰,表示对对象的"强引用";当对象没有任何一个强引用指向它的时候,对象将被释放。

__weak修饰符:

  1. __weak__strong修饰符的作用相反,表示弱引用,不会增加引用计数;
  2. 当对象被释放后,所有指向它的弱引用都会被置为nil,这样避免了野指针问题;
  3. __weak修饰符常用于解决循环引用问题;
  4. __weak只能用于iOS5以上版本,更早的版本只能使用 __unsafe_unretained修饰符。

__unsafe_unretained修饰符:

  1. __unsafe_unretained提供弱引用,与 __weak作用类似;
  2. __unsafe_unretained不能在对象释放后自动置为 nil,易产生野指针问题
  3. __unsafe_unretained可用于iOS5之前版本,为兼容ARC弱引用而引入;

__autoreleasing修饰符:

将对象赋值给附有 __autoreleasing修饰符的变量,等同于在MRC下调用对象的 autorelease方法,即对象被注册到 autoreleasepool;

ARC环境不能使用 NSAutoreleasePool类也不能调用 autorelease方法,代替它们实现对象自动释放的是 @autoreleasepool块__autoreleasing修饰符;两种环境下的使用情况类比如下图:

图片

如图所示, @autoreleasepool块替换了 NSAutoreleasePoool类对象的生成、持有及废弃这一过程。而附有 __autoreleasing修饰符的变量替代了 autorelease方法,将对象注册到了 autoreleasepool;

但事实上,显式使用 __autoreleasing修饰符的情况非常少见,这主要是因为ARC的很多情况下,即使是不显式的使用 __autoreleasing,也能实现对象被注册到释放池中。换句话来说,ARC环境下对象会被自动加入释放池的情况包括以下几种情况:

  1. 编译器会进行优化,检查方法名是否以 alloc/new/copy/mutableCopy开始,如果不是则自动将返回对象注册到 Autoreleasepool;
  2. 访问附有 __weak修饰符的变量时,实际上必定要访问注册到 Autoreleasepool的对象,即会自动加入 Autoreleasepool;
  3. id的指针或对象的指针(id*NSError **),在没有显式地指定修饰符时候,会被默认附加上 __autoreleasing修饰符,加入 Autoreleasepool;
4.ARC属性修饰符

ARC中的所有权修饰与属性修饰符存在着对应关系,如果不一致还会引起编译错误。总结两者的对应关系如下:

属性修饰符所有权修饰符
assign_unsafeunretained
copy__strong(但是赋值的是被复制的对象)
retain__strong
strong__strong
unsafe_unretained_unsafeunretained
weak__weak

以上各种属性只有 copy不是简单的赋值,它赋值的是通过 NSCopying接口的 copyWithZone:方法复制赋值源生成的对象。

5.ARC管理内存的规则
  1. 不能使用retain/release/retainCount/autorelease内存管理方法;
  2. 不能使用NSAllocateObject/NSDeallocateObject方法;
  3. 必须遵守内存管理的方法命名规则;
  4. 不能显式调用dealloc方法,如[super dealloc];
  5. 使用@autoreleasepool块代替NSAutoreleasePool;
  6. 不能使用区域(NSZone);
  7. 对象类型变量不能作为C语言结构体(struct/union)的成员;
  8. 显式转换idvoid *
6.必须遵守内存管理的方法命名规则

MRC下,用于对象生成/持有的方法必须遵守alloc、new、copy、mutableCopy的命名规则。以这些名称开始的方法在返回对象时,必须返回给调用方所应当持有的对象。这在ARC环境下的规则一样。只是ARC下关于init开发的方法规则要更加严格了:

  1. 必须是实例方法,且返回对象;
  2. 返回对象应该是id类型或该方法声明类的对象,抑或该类的超类或子类;
  3. 该返回类型不注册到autoreleasepool上;
  4. 基本上,init方法只是对alloc方法返回值的对象进行初始化处理并返回对象;
7.显式转换id和void *

情况1:__bridge转换

/*
MRC代码下,将id变量直接强制转换void*正常,但ARC下报错
id obj = [[NSObject alloc] init];
void *p = obj;
*/
 
//ARC下代码使用__bridge实现单纯的赋值转换
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)(obj);

总结:

  1. __bridge只能实现Objective-C对象和Core Foundation对象的相互转换,但是不修改对象(内存)管理权;
  2. 所以,其安全性与赋值给__unsafe_unretained修饰符相近,甚至会更低。如果管理时不注意赋值对象的所有者,就容易产生野指针错误导致程序崩溃。

情况2:__bridge_retained转换

id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)(obj);
    
/*相当于MRC代码:
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
*/

总结:

  1. __bridge_retained转换可使要转换的变量也持有所赋值的对象,后续需要使用CFRelease或者相关方法来释放对象;
  2. 此操作类似于retain。上述代码中变量obj和变量p同时持有对象。

情况3:__bridge_transfer转换

id obj = (__bridge_transfer id)p;
    
/*相当于MRC代码:
id obj = id(p)
[obj retain];
[(id)p release];
*/

总结:

  1. __bridge_transfer转换提供与__bridge_retained相反的动作,被转换的变量所持有的对象在该变量被赋值给转换的目标后随之释放。
  2. 此操作与release相似。
五、内存泄漏问题
  1. 内存泄露就是本该废弃的对象在超出其生命周期后继续存在;
  2. 内存泄露可导致内存浪费、程序运行速度减慢甚至系统崩溃等严重后果;

总结常见的内存泄露的异常情况如下:

  1. BlockdelegateNSTimer使用不当,造成循环引用;
  2. OC对象不当使用,如CoreFoundation方式申请的内存,忘记释放,需要freerelease方法;
  3. 第三方框架不当使用,如AFNetworking循环引用(未使用单例或者没有调用销毁NSURLSession的方法;
  4. 自定义长时间执行任务的线程,却没有添加自动释放池;
#define WS(weakSelf)   __weak __typeof(&*self)weakSelf = self; // 弱引用

#define ST(strongSelf)  __strong __typeof(&*self)strongSelf = weakSelf; //使用这个要先声明weakSelf
六、野指针问题

野指针指针就是指向一个已经删除对象或者访问受限内存区域的指针;

注意:野指针不是nil指针,而是指向”垃圾”内存(不可用内存)的指针;

总结ARC下常见的野指针异常情况如下:图片

---End---
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值