系列文章目录
iOS基础—Block
iOS基础—Protocol
iOS基础—KVC vs KVO
iOS网络—AFNetworking
iOS网络—NSURLSession
iOS内存管理—MRC vs ARC
iOS基础—Category vs Extension
iOS基础—多线程:GCD、NSThread、NSOperation
iOS基础—常用三方库:Masonry、SDWebImage
iOS基础—定时器:GCD、NSTimer、CADisplayLink
文章目录
一、iOS内存管理
首先,得明确内存管理的概念,内存管理指的是:
- 分配内存:为应用程序的变量、对象和数据结构在内存中分配空间。
- 使用内存:在程序运行过程中,正确地访问和操作已分配的内存。
- 释放内存:在不再需要某块内存时,归还给操作系统或内存池,避免内存泄漏和碎片化。
根据实现方式,内存管理可以分为手动内存管理和自动内存管理。
- 手动内存管理:由程序员显式地分配和释放内存,例如C语言中的malloc和free。
- 自动内存管理:由运行时系统自动管理内存的分配和回收,例如Java的垃圾回收机制、Objective-C的ARC(Automatic Reference Counting)等。
为什么需要管理内存?
- 预防内存泄漏:
内存泄漏指的是程序在运行期间未能适当地释放不再使用的内存,导致内存逐渐被耗尽,最终可能导致程序崩溃(Out of Memory)或系统性能下降。- 防止悬挂指针:
悬挂指针指的是指向已释放内存的指针,继续访问这些指针会导致不确定行为,可能引起程序崩溃或数据损坏。- 优化内存使用:
没有有效的内存管理,可能会出现内存碎片化,程序执行效率下降等问题。良好的内存管理能够减少碎片化,优化内存利用率,提升程序性能。- 提高程序稳定性:
通过有效的内存管理,可以减少由于内存问题导致的程序崩溃、非预期行为等,增强程序的稳定性和可靠性。- 简化开发过程:
自动内存管理,例如垃圾回收和ARC,能帮助开发者自动处理内存分配和回收,从而减少代码中的错误,简化开发过程。
需要程序员管理的是堆空间,动态分配的内存:
- 栈(Stack):由系统自动管理,用于存储局部变量、函数调用等,具有生命周期短、内存上下文优先的特点,先进先出。
- 堆(Heap):由程序员或运行时手动管理,通常用于动态内存分配,生命周期较长,随机访问和回收。
例如在c++中,可以利用RAII的思想来管理内存,在iOS中,是通过引用计数来完成内存管理的:
在iOS中,引用计数(Reference Counting)是管理内存的关键机制,它用于跟踪对象的引用次数,从而确定对象在什么时候该被释放。引用计数的核心思想是每个对象都有一个计数器,用于记录多少个地方引用了该对象。当引用计数变为零时,对象会被销毁以释放内存。
在Xcode中,我们可以设置iOS管理内存的方式:
二、MRC
手动引用计数(Manual Reference Counting)是开发者需要手动管理对象的引用计数。这包括显式调用retain、release和autorelease方法。
- alloc/init:分配并初始化对象,引用计数为1。
- retain:增加对象的引用计数。
- release:减少对象的引用计数。当引用计数变为0时,内存将被释放。
- autorelease:将对象添加到自动释放池,池被销毁时会调用release。
我们可以通过 retainCount 来获取对象的引用计数:
系统提供的对象,内存管理可能被优化过,所以引用计数打印是-1,官方文档也说明 retainCount 返回的引用计数可能是未定义的,所以用自定义对象来进行实验。
1.alloc/new/copy/mutableCopy
在苹果规定中,使用 alloc/new/copy/mutableCopy 创建返回的对象归调用者所有:
Person *p1 = [[Person alloc] init]; // p1 引用计数为1
Person *p2 = [Person new]; // p2 引用计数为1
Person *p3 = [p1 copy]; // p3 引用计数为1
Person *p4 = [p1 mutableCopy]; // p4 引用计数为1
2.retain
在MRC环境下,retain 方法用于显式增加对象的引用计数。这通常在你希望在多个地方引用一个对象,且每个地方都希望确保对象在自己使用期间不被释放的情况下使用。
另外,当我们使用可变集合类(例如NSMutableArray, NSMutableSet, NSMutableDictionary等)的addObject:方法时,该方法会对添加的对象调用retain,以便持有(retain)该对象。这是集合类的一般行为,用于确保集合内部持有对其对象的强引用,从而防止对象被意外释放。
MRC下 @property 的修饰符:
- 如果在 @property 声明中使用 retain 修饰符,系统会自动为你生成相应的 getter 和 setter 方法,这些方法会包含内存管理代码以便正确处理引用计数。然而,你仍然需要自己在 dealloc 方法中手动释放对象。
- 如果在 @property 后边加上 assign,系统就不会帮我们生成set方法内存管理的代码,仅仅只会生成普通的getter/setter方法,默认什么都不写就是assign。assign 修饰符通常用于基本数据类型(如 int,float 等)以及不需要引用计数管理的对象。
3.release
我们持有一个对象,如果在不需要继续使用该对象时,我们需要对其进行释放(release)。引用计数的持有与释放应该是成对出现的,否则会出现内存泄漏。另外,我们也不能访问某个已经被释放的对象,否则会导致程序崩溃。
4.autorelease
- 在Objective-C中,autorelease是一种用于对象内存管理的方法,特别是在手动引用计数(Manual Reference Counting, MRC)环境中。它主要用于延迟释放对象,即在当前自动释放池(autorelease pool)退出时释放对象,而不是在调用autorelease方法后立即释放。
- 自动释放池(autorelease pool)是一个临时容器,用于存储带有自动释放消息的对象。通常在每个事件循环或程序主运行循环中使用。对象被发送autorelease消息后,被添加到当前的自动释放池中,当自动释放池被销毁时,每个池中的对象都会收到一个release消息。
自动释放池的工作原理
- 加入池中:当一个对象调用 autorelease 方法时,它会被加入当前线程的自动释放池中。
- 池的销毁:当自动释放池销毁时,池中的对象会收到 release 消息,从而适当减小它们的引用计数。
有两种方式创建和管理自动释放池:
- 使用 NSAutoreleasePool(适用于旧代码或不在@autoreleasepool块内的情况):
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 代码块...
[pool drain];
- 使用 @autoreleasepool 块(推荐的现代方式):
@autoreleasepool {
// 代码块...
}
在执行复杂操作或大量循环时,由于创建了大量的临时对象,这些对象会被添加到自动释放池中。如果池内对象没有被及时释放,可能会导致内存使用增加甚至内存溢出。所以,我们可以在每次循环内创建自动释放池。每次循环迭代结束时,临时对象将被释放,从而避免累积过多未释放的对象导致内存使用激增。
三、ARC
1.__strong
在 ARC(自动引用计数)环境下,Objective-C 提供了几种不同的内存管理修饰符以帮助开发者控制对象的生命周期。其中,__strong 是默认的所有权修饰符。
__strong 的作用:
- 强引用:__strong 修饰的对象在引用存在期间,引用计数会增加,确保对象不会被销毁。
- 自动管理内存:在超出变量作用域时,编译器会自动释放__strong引用计数的对象,无需开发者手动管理内存。
ARC 的工作原理:
ARC(自动引用计数)在编译时会根据代码中的引用情况自动插入 retain 和 release 调用,具体来说:
- 持有对象: 当一个对象被 __strong 修饰符的变量引用时,ARC 插入 retain。
- 释放对象: 当 __strong 修饰符的变量超出作用域或被赋其他值时,ARC 插入 release
2.__weak
__weak 在Objective-C中用来声明一个对象的弱引用,即对该对象的引用不会增加其引用计数。当所引用的对象的引用计数降为0时,会被系统自动销毁,并且所有指向它的弱引用会被自动设置为nil。这在避免循环引用(尤其是在对象之间相互引用的情况下)方面非常有用。
__weak 的作用:
- 弱引用:不会增加引用计数,也不会阻止对象被销毁。
- 自动设置为nil:所引用的对象被销毁时,弱引用会自动设置为nil,防止出现悬挂指针(指向已被销毁对象的指针)。
weak的实现机制:
- 初始化弱引用: 当一个变量以__weak修饰符声明时,系统会在内部记录对象的弱引用。
- 对象销毁时通知: 当一个对象的所有强引用被释放,即将被销毁时,系统会将通知所有指向它的弱引用变量,并将它们设置为nil。
下面是一个循环引用的例子:
用Xcode的 memory graph 来查看对象之间的循环引用,它们无法释放会导致内存泄漏:
用 weak 修饰属性,让属性持有对象的弱引用,就可以正常释放了:
另外需要注意的是,如果一个变量被weak修饰,那么这个变量不能持有对象示例,对象创建完立即被释放,并且编译器会发出警告:
3.__unsafe_unretained
在Objective-C中,除了__strong和__weak,还有另一种所有权修饰符__unsafe_unretained。__unsafe_unretained与__weak类似,它不会增加对象的引用计数,但两者有一个重要的区别:当对象被释放时,__unsafe_unretained指针不会自动设置为nil,从而可能导致悬挂指针(dangling pointer),这会导致访问已释放对象时出现崩溃或未定义行为。