iOS开发之内存管理的前世今生
内存管理一直是开发者们津津乐道的话题,iOS开发中的内存管理也当然也不例外。本文将对iOS开发中内存管理相关问题作较详细描述,从MRC、ARC到现在的Swift自动内存管理,就作者所了解的内容一一作介绍,欢迎拍砖给建议。
一、内存区域介绍
要管理内存,我们就必须要对应用程序运行在内存中的状态有所了解,需要知道哪些需要我们的应用程序去管理,哪些是由系统自动管理,而不需要我们操心。程序运行过程中使用到的可编程内存大致可以分为:
- 全局/静态存储区,全局变量和静态变量的存储区域
- 栈区,在函数执行过程中,函数内局部变量的存储单元可以在栈上创建,函数执行结束后这些存储单元自动被释放。
- 堆区,亦称动态分配区,由程序在运行过程中动态申请分配和管理的区,通常说的内存管理,基本是指对于这一内存区块的管理
- 常量区,存储程序运行过程中用到的各种常量,不允许修改
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谁释放
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: Binit() {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; //ARCdispatch_async(queue, ^{doSomethingWithObject(weakSelf.instanceVariable); //访问属性});__block id localVariable = instanceVariable; //MRC//__unsafe_unretained __block id localVariable = instanceVariable //ARCdispatch_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] inif 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 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")}}//初始化一个对象,同时触发timervar instanceA: A? = A()instanceA = nil //此处即使置为nil,也不会释放对象instanceA,因为timer中还持有该对象,会不停的输出"name = A" 下面增加一个特定条件下触发invalidate方法的功能,比如执行了3次之后就触发invalidate。
class A: NSObject {var timer: NSTimer?var times: Int = 0override 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部分的解释吧,如图中红色线框标准部分:
- cancelPerformSelector:target:argument: 其他可能存在的循环引用或内存泄漏等与内存管理相关的内容,待发现后再一一补充吧,先到此为止。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
