iOS开发之内存管理的前世今生
2016-01-19 16:08
387 查看
内存管理一直是开发者们津津乐道的话题,iOS开发中的内存管理也当然也不例外。本文将对iOS开发中内存管理相关问题作较详细描述,从MRC、ARC到现在的Swift自动内存管理,就作者所了解的内容一一作介绍,欢迎拍砖给建议。
一、内存区域介绍
要管理内存,我们就必须要对应用程序运行在内存中的状态有所了解,需要知道哪些需要我们的应用程序去管理,哪些是由系统自动管理,而不需要我们操心。程序运行过程中使用到的可编程内存大致可以分为:
全局/静态存储区,全局变量和静态变量的存储区域
栈区,在函数执行过程中,函数内局部变量的存储单元可以在栈上创建,函数执行结束后这些存储单元自动被释放。
堆区,亦称动态分配区,由程序在运行过程中动态申请分配和管理的区,通常说的内存管理,基本是指对于这一内存区块的管理
常量区,存储程序运行过程中用到的各种常量,不允许修改
来段代码说明一下吧
二、iOS内存管理的黄金法则(Swift不适用,Swift自动管理内存)
谁创建谁释放
谁retain谁释放
一句话,谁让retainCount计数器增加,谁负责让它减少
1)Object-C中MRC内存管理的一些规则
A、使用alloc, new, copy或者mutableCopy等以及调用addObject等方法时,引用计数器+1,使用release时,引用计数器-1,当引用计数器为0时,对象被释放
B、Property Attributes包括retain和assign
retain,相当于ARC中的strong
assign,相当于ARC中的weak
2)Object-C中ARC内存管理的一些规则
A、同样使用alloc, new, copy或者mutableCopy等以及调用addObject等方法时,引用计数器+1,使用release时,引用计数器-1,当引用计数器为0时,对象被释放
B、Property Attributes除了包括MRC中的属性外,增加了
strong,强引用
weak,弱引用
三、那些内存管理中的坑
1)循环引用
相信这个坑是绝大多数开发人员都遇到过的坑。循环引用即A持有了B,B持有了A,导致无论是先释放A还是B,总是被对方持有,而导致双方始终无法释放的内存泄漏问题。来个网上多次用的代码例子吧,A和B的亲密关系:)
解决这种亲密关系导致的循环引用,采用弱引用即可,将上面class B的代码改成:
2) Block中的坑
要想知道坑在何处,首先得了解Block可以访问的变量范围,Block中可访问的变量范围有:
A、全局变量(包含在Block中声明的静态变量),Block可以直接访问
B、被当作参数传入Block块中的变量(类似函数参数)
C、和Block块属同一作用域的栈变量会被当作常量在Block中捕获
D、和Block块属同一作用域,但被__block修饰的变量会以引用的方式被Block捕获,且是可变的
E、在Block块中声明的变量如同函数中声明的变量一样
Block在访问对象变量(即类对象)时有两条隐含的retain对象规则,即:
A、如果访问类属性对象变量,则Block会强引用self,即retain一次类对象本身
B、如果访问了局部对象变量,则Block会强引用局部变量自身一次
由这两条规则,我们很容易就知道Block中循环引用的坑,代码如下:
破解Block中的循环引用,代码修改为如下:
3)闭包中循环引用
闭包中变量的访问范围及持有变了隐含规则同Block,此处直接上代码解释循环引用问题
代码修改为:
先看一下NSTimer中定义的函数声明及参数说明吧,然后再来解释
repeats参数被设置成YES时,target中的对象将永远不会被释放,只有调用invalidate方法之后才会释放target对象,从而释放接收处理target对象。看下面代码中的注释及输出结果比较
说明对象被正常释放
5)performSelector中的对象retain问题
函数的声明和参数就不赘述了,还是重点看看里面有关参数retain部分的解释吧,如图中红色线框标准部分:
只有当执行完成之后才会释放target和argument对象,它的执行前提条件是:1)时间到;2)满足指定的Loop Modes。因此在发起该方法的类销毁之前该方法不一定会被执行,因此就会存在内存泄漏的风险。能否在dealloc或deinit中释放呢?请看客考虑
释放该方法中retain的对象,系统也提供了对应的API,即
其他可能存在的循环引用或内存泄漏等与内存管理相关的内容,待发现后再一一补充吧,先到此为止。
一、内存区域介绍
要管理内存,我们就必须要对应用程序运行在内存中的状态有所了解,需要知道哪些需要我们的应用程序去管理,哪些是由系统自动管理,而不需要我们操心。程序运行过程中使用到的可编程内存大致可以分为:
全局/静态存储区,全局变量和静态变量的存储区域
栈区,在函数执行过程中,函数内局部变量的存储单元可以在栈上创建,函数执行结束后这些存储单元自动被释放。
堆区,亦称动态分配区,由程序在运行过程中动态申请分配和管理的区,通常说的内存管理,基本是指对于这一内存区块的管理
常量区,存储程序运行过程中用到的各种常量,不允许修改
来段代码说明一下吧
int a = 0; //全局初始化区 char *p1; //全局未初始化区 @implementation test - (void)test:(int)para { //para在栈上 int b; //栈 char s[] = "abc"; //栈 char *p2; //栈 char *p3 = "1234"; //1234在常量区,p3在栈上 static int c = 0; //全局(静态)初始化区 p1 = (char *)malloc(10); //分配来的10个字节的区域在堆区 } @end
二、iOS内存管理的黄金法则(Swift不适用,Swift自动管理内存)
谁创建谁释放
谁retain谁释放
一句话,谁让retainCount计数器增加,谁负责让它减少
1)Object-C中MRC内存管理的一些规则
A、使用alloc, new, copy或者mutableCopy等以及调用addObject等方法时,引用计数器+1,使用release时,引用计数器-1,当引用计数器为0时,对象被释放
B、Property Attributes包括retain和assign
retain,相当于ARC中的strong
assign,相当于ARC中的weak
2)Object-C中ARC内存管理的一些规则
A、同样使用alloc, new, copy或者mutableCopy等以及调用addObject等方法时,引用计数器+1,使用release时,引用计数器-1,当引用计数器为0时,对象被释放
B、Property Attributes除了包括MRC中的属性外,增加了
strong,强引用
weak,弱引用
三、那些内存管理中的坑
1)循环引用
相信这个坑是绝大多数开发人员都遇到过的坑。循环引用即A持有了B,B持有了A,导致无论是先释放A还是B,总是被对方持有,而导致双方始终无法释放的内存泄漏问题。来个网上多次用的代码例子吧,A和B的亲密关系:)
class A { let b: B init() { b = B() b.a = self } deinit { print("A deinit") } } class B { var a: A? deinit { print("B deinit") } }
解决这种亲密关系导致的循环引用,采用弱引用即可,将上面class B的代码改成:
class B { weak var a: A? //增加weak权限修饰符,弱化引用关系 deinit { print("B deinit") } }
2) Block中的坑
要想知道坑在何处,首先得了解Block可以访问的变量范围,Block中可访问的变量范围有:
A、全局变量(包含在Block中声明的静态变量),Block可以直接访问
B、被当作参数传入Block块中的变量(类似函数参数)
C、和Block块属同一作用域的栈变量会被当作常量在Block中捕获
D、和Block块属同一作用域,但被__block修饰的变量会以引用的方式被Block捕获,且是可变的
E、在Block块中声明的变量如同函数中声明的变量一样
Block在访问对象变量(即类对象)时有两条隐含的retain对象规则,即:
A、如果访问类属性对象变量,则Block会强引用self,即retain一次类对象本身
B、如果访问了局部对象变量,则Block会强引用局部变量自身一次
由这两条规则,我们很容易就知道Block中循环引用的坑,代码如下:
//规则一导致的循环引用 dispatch_async(queue, ^{ doSomethingWithObject(instanceVariable); //访问属性 }); //规则二导致的循环引用 id localVariable = instanceVariable; dispatch_async(queue, ^{ doSomethingWithObject(localVariable); //访问局部变量 });
破解Block中的循环引用,代码修改为如下:
__block id weakSelf = self; //MRC //__unsafe_unretained __block id weakSelf = self; //ARC dispatch_async(queue, ^{ doSomethingWithObject(weakSelf.instanceVariable); //访问属性 }); __block id localVariable = instanceVariable; //MRC //__unsafe_unretained __block id localVariable = instanceVariable //ARC dispatch_async(queue, ^{ doSomethingWithObject(localVariable); //访问局部变量 });
3)闭包中循环引用
闭包中变量的访问范围及持有变了隐含规则同Block,此处直接上代码解释循环引用问题
class A: NSObject { let name: String = "A" lazy var printName: () -> () = { print("A's name is \(self.name)") //此处自动retain一次self,导致循环引用 } deinit { print("A deinit") } } let instanceA: A = A() instanceA.printName() //此处只会打印出"A's name is A",不会打印出"A deinit"
代码修改为:
class A: NSObject { let name: String = "A" lazy var printName: () -> () = { [weak self] in if let weakSelf = self { print("A's name is \(weakSelf.name)") } } deinit { print("A deinit") } } let instanceA: A = A() instanceA.printName() //此处会打印出"A's name is A"和"A deist"说明循环引用已被打破,对象正常释放4)NSTimer中的对象retain问题
先看一下NSTimer中定义的函数声明及参数说明吧,然后再来解释
class func scheduledTimerWithTimeInterval(_ ti: NSTimeInterval, target aTarget: AnyObject, selector aSelector: Selector, userInfo userInfo: AnyObject?, repeats yesOrNo: Bool) -> NSTimer
repeats参数被设置成YES时,target中的对象将永远不会被释放,只有调用invalidate方法之后才会释放target对象,从而释放接收处理target对象。看下面代码中的注释及输出结果比较
class A: NSObject { var timer: NSTimer? override init() { super.init() self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "printName", userInfo: nil, repeats: true) } deinit { print("A deinit") } func printName() { print("name = A") } } //初始化一个对象,同时触发timer var instanceA: A? = A() instanceA = nil //此处即使置为nil,也不会释放对象instanceA,因为timer中还持有该对象,会不停的输出"name = A"下面增加一个特定条件下触发invalidate方法的功能,比如执行了3次之后就触发invalidate。
class A: NSObject { var timer: NSTimer? var times: Int = 0 override init() { super.init() self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "printName", userInfo: nil, repeats: true) } deinit { print("A deinit") } func printName() { if self.times >= 3 { self.timer?.invalidate() //这个invalidate为什么不写在deist函数里?看客可以想想 } print("name = A") self.times++ } }输出结果为:
说明对象被正常释放
5)performSelector中的对象retain问题
函数的声明和参数就不赘述了,还是重点看看里面有关参数retain部分的解释吧,如图中红色线框标准部分:
只有当执行完成之后才会释放target和argument对象,它的执行前提条件是:1)时间到;2)满足指定的Loop Modes。因此在发起该方法的类销毁之前该方法不一定会被执行,因此就会存在内存泄漏的风险。能否在dealloc或deinit中释放呢?请看客考虑
释放该方法中retain的对象,系统也提供了对应的API,即
- cancelPerformSelector:target:argument:
其他可能存在的循环引用或内存泄漏等与内存管理相关的内容,待发现后再一一补充吧,先到此为止。
相关文章推荐
- Ruby中Block和迭代器的使用讲解
- Ruby中使用Block、Proc、lambda实现闭包
- Ruby中的block、proc、lambda区别总结
- Lua的内存管理浅析
- 跟我学习JScript的Bug与内存管理
- 跟我学习javascript的垃圾回收机制与内存管理
- Lua返回一个Closures函数实例
- 深入理解JavaScript系列(16) 闭包(Closures)
- 深入探讨PHP中的内存管理问题
- linux 内存管理机制详细解析
- 解析PHP中的内存管理,PHP动态分配和释放内存
- javascript内存管理详细解析
- JavaScript内存管理介绍
- Cocos2d-x的内存管理总结
- 模拟实现C语言中的内存管理
- javascript错误的认识不用关心内存管理
- IOS中内存管理那些事
- Python深入学习之内存管理
- 全面解析Objective-C中的block代码块的使用
- 简单说说STL的内存管理