本文的 ARC 特指 Objective C 的 ARC,并不会讲解其他语言。另外,本文涉及到的原理部分较多,适合有一定经验的开发者。
ARC 的全称 Auto Reference Counting. 也就是自动引用计数。那么,为什么要有 ARC 呢?
我们从 C 语言开始。使用 C 语言编程的时候,如果要在堆上分配一块内存,代码如下
- //分配内存(malloc/calloc均可)
- int*array=calloc(10,sizeof(int));//释放内存
- free(array);
C 是面向过程的语言(Procedural programming),这种内存的管理方式简单直接。但是,对于面向对象编程,这种手动的分配释放毫无疑问会大大的增加代码的复杂度。
于是,OOP 的语言引入了各种各样的内存管理方法,比如 Java 的垃圾回收和 Objective C 的引用计数。关于垃圾回收和饮用计数的对比,可以参见 Brad Larson 的这个 SO 回答。
Objective C 的引用计数理解起来很容易,当一个对象被持有的时候计数加一,不再被持有的时候引用计数减一,当引用计数为零的时候,说明这个对象已经无用了,则将其释放。
引用计数分为两种:
在 iOS 开发早期,编写代码是采用 MRC 的
- // MRC代码
- NSObject * obj = [[NSObject alloc] init]; //引用计数为1
- //不需要的时候
- [obj release] //引用计数减1
- //持有这个对象
- [obj retain] //引用计数加1
- //放到AutoReleasePool
- [obj autorelease] //在auto release pool释放的时候,引用计数减1
虽说这种方式提供了面向对象的内存管理接口,但是开发者不得不花大量的时间在内存管理上,并且容易出现内存泄漏或者 release 一个已被释放的对象,导致 crash。
再后来,Apple 对 iOS/Mac OS 开发引入了 ARC。使用 ARC,开发者不再需要手动的
. 编译器会自动插入对应的代码,再结合 Objective C 的 runtime,实现自动引用计数。
- retain/release/autorelease
比如如下 ARC 代码:
- NSObject* obj;
- {
- obj = [[NSObjectalloc] init];//引用计数为1}NSLog(@"%@",obj);
等同于如下 MRC 代码
- NSObject* obj;
- {
- obj = [[NSObjectalloc] init];//引用计数为1[obj relrease]
- }NSLog(@"%@",obj);
在 Objective C 中,有三种类型是 ARC 适用的:
像
,
- double *
等不是 ARC 适用的,仍然需要手动管理内存。
- CFStringRef
Tips: 以 CF 开头的(Core Foundation)的对象往往需要手动管理内存。
最后,我们在看看 ARC 中常见的所有权关键字,
对应关键字
- assign
, 顾名思义,就是指向的对象被释放的时候,仍然指向之前的地址,容易引起野指针。
- __unsafe_unretained
对应关键字
- copy
, 只不过在赋值的时候,调用
- __strong
方法。
- copy
对应
- retain
- __strong
对应
- strong
- __strong
对应
- unsafe_unretained
- __unsafe_unretained
对应
- weak
。
- __weak
其中,
和
- __weak
是本文要讲解的核心内容。
- __strong
ARC 背后的引用计数主要依赖于这三个方法:
增加引用计数
- retain
降低引用计数,引用计数为 0 的时候,释放对象。
- release
在当前的 auto release pool 结束后,降低引用计数。
- autorelease
在 Cocoa Touch 中,
协议中定义了这三个方法,由于 Cocoa Touch 中,绝大部分类都继承自
- NSObject
(
- NSObject
类本身实现了
- NSObject
协议),所以可以 "免费" 获得
- NSObject
提供的运行时和 ARC 管理方法,这就是为什么适用 OC 开发 iOS 的时候,你的类要继承自
- NSObject
。
- NSObject
既然 ARC 是引用计数,那么对应一个对象,内存中必然会有一个地方来存储这个对象的引用计数。iOS 的 Runtime 是开源的,在这里可以下载到全部的代码,我们通过源代码一探究竟。
我们从 retain 入手,
- - (id)retain {return((id)self)->rootRetain();
- }inline idobjc_object::rootRetain()
- {if(isTaggedPointer())return(id)this;returnsidetable_retain();
- }
所以说,本质上 retain 就是调用
,再看看
- sidetable_retain
的实现:
- sitetable_retain
- idobjc_object::sidetable_retain()
- {//获取tableSideTable& table = SideTables()[this];//加锁table.lock();//获取引用计数size_t& refcntStorage = table.refcnts[this];if(! (refcntStorage & SIDE_TABLE_RC_PINNED)) {//增加引用计数refcntStorage += SIDE_TABLE_RC_ONE;
- }//解锁table.unlock();return(id)this;
- }
到这里,retain 如何实现就很清楚了,通过
这个数据结构来存储引用计数。我们看看这个数据结构的实现:
- SideTable
- typedef objc: :DenseMap,
- size_t,
- true > RefcountMap;
- struct SideTable {
- spinlock_t slock;
- RefcountMap refcnts;
- weak_table_t weak_table;
- //省略其他实现...
- };
可以看到,这个数据结构就是存储了一个自旋锁,一个引用计数 map。这个引用计数的 map 以对象的地址作为 key,引用计数作为 value。到这里,引用计数的底层实现我们就很清楚了。
存在全局的 map,这个 map 以地址作为 key,引用计数的值作为 value。
再来看看 release 的实现:
- SideTable& table = SideTables()[this];booldo_dealloc =false;
- table.lock();//找到对应地址的RefcountMap::iterator it = table.refcnts.find(this);if(it == table.refcnts.end()) {//找不到的话,执行delllocdo_dealloc =true;
- table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
- }else if(it->second < SIDE_TABLE_DEALLOCATING) {//引用计数小于阈值,deallocdo_dealloc =true;
- it->second |= SIDE_TABLE_DEALLOCATING;
- }else if(! (it->second & SIDE_TABLE_RC_PINNED)) {//引用计数减去1it->second -= SIDE_TABLE_RC_ONE;
- }
- table.unlock();if(do_dealloc && performDealloc) {//执行dealloc((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
- }returndo_dealloc;
release 的到这里也比较清楚了:查找 map,对引用计数减 1,如果引用计数小于阈值,则调用
- SEL_dealloc
上文提到了,autorelease 方法的作用是把对象放到 autorelease pool 中,到 pool drain 的时候,会释放池中的对象。举个例子
- __weak NSObject * obj;
- NSObject * temp = [[NSObject alloc] init];
- obj = temp;
- NSLog(@"%@", obj); //非空
放到 auto release pool 中,
- __weak NSObject* obj;
- @autoreleasepool {NSObject* temp = [[NSObjectalloc] init];
- obj = temp;
- }NSLog(@"%@",obj);//null
可以看到,放到自动释放池的对象是在超出自动释放池作用域后立即释放的。事实上在 iOS 程序启动之后,主线程会启动一个 Runloop,这个 Runloop 在每一次循环是被自动释放池包裹的,在合适的时候对池子进行清空。
对于 Cocoa 框架来说,提供了两种方式来把对象显式的放入 AutoReleasePool.
(ARC 和 MRC 下均可以使用)
- @autoreleasepool {}代码块
那么 AutoRelease pool 又是如何实现的呢?
我们先从
方法源码入手
- autorelease
- //autorelease方法- (id)autorelease {return((id)self)->rootAutorelease();
- }//rootAutorelease 方法
- inline idobjc_object::rootAutorelease()
- {if(isTaggedPointer())return(id)this;//检查是否可以优化
- if(prepareOptimizedReturn(ReturnAtPlus1))return(id)this;//放到auto release pool中。
- returnrootAutorelease2();
- }// rootAutorelease2
- idobjc_object::rootAutorelease2()
- {
- assert(!isTaggedPointer());returnAutoreleasePoolPage::autorelease((id)this);
- }
可以看到,把一个对象放到 auto release pool 中,是调用了
这个方法。
- AutoreleasePoolPage::autorelease
我们继续查看对应的实现:
- public:static inline idautorelease(idobj)
- {
- assert(obj);
- assert(!obj->isTaggedPointer());id*dest __unused = autoreleaseFast(obj);
- assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);returnobj;
- }static inline id*autoreleaseFast(idobj)
- {
- AutoreleasePoolPage *page = hotPage();if(page && !page->full()) {returnpage->add(obj);
- }else if(page) {returnautoreleaseFullPage(obj, page);
- }else{returnautoreleaseNoPage(obj);
- }
- }id*add(idobj)
- {
- assert(!full());
- unprotect();id*ret = next;// faster than `return next-1` because of aliasing*next++ = obj;
- protect();returnret;
- }
到这里,
方法的实现就比较清楚了,
- autorelease
autorelease 方法会把对象存储到
的链表里。等到 auto release pool 被释放的时候,把链表内存储的对象删除。所以,AutoreleasePoolPage 就是自动释放池的内部实现。
- AutoreleasePoolPage
- __weak与__strong
用过 block 的同学一定写过类似的代码:
- __weaktypeSelf(self) weakSelf =self;
- [object fetchSomeFromRemote:^{
- __strongtypeSelf(weakSelf) strongSelf = weakSelf;//从这里开始用strongSelf}];
那么,为什么要这么用呢?原因是:
保证 self 不会被 block 被捕获,防止引起循环引用或者不必要的额外生命周期。
- weakSelf
首先
和
- __strong
都是关键字,是给编译器理解的。为了理解其原理,我们需要查看它们编译后的代码,使用 XCode,我们可以容易的获得一个文件的汇编代码。
- __weak
比如,对于
文件,当源代码如下时:
- Test.m
- #import"Test.h"
- @implementation Test- (void)testFunction{
- {
- __strong NSObject* temp = [[NSObjectalloc] init];
- }
- }@end
转换后的汇编代码如下:
- Ltmp3:
- .loc 2 15 37prologue_end; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:37ldr x9, [x9]
- ldr x1, [x8]movx0, x9
- bl _objc_msgSend
- adrp x8, L_OBJC_SELECTOR_REFERENCES_.2@PAGEaddx8, x8, L_OBJC_SELECTOR_REFERENCES_.2@PAGEOFF.loc 2 15 36is_stmt0 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:36ldr x1, [x8].loc 2 15 36discriminator1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:36bl _objc_msgSendmovx8,#0
- addx9, sp,#8 ; =8
- .loc 2 15 29 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:29str x0, [sp,#8]
- Ltmp4:
- .loc 2 16 5is_stmt1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:16:5
- movx0, x9movx1, x8
- bl _objc_storeStrong.loc 2 17 1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:17:1ldp x29, x30, [sp,#32] ; 8-byte Folded Reload
- addsp, sp,#48 ; =48
- ret
- Ltmp5:
即使你不懂汇编,也能很轻易的获取到调用顺序如下
- _objc_msgSend // alloc
- _objc_msgSend // init
- _objc_storeStrong // 强引用
在结合 Runtime 的源码,我们看看最关键的 objc_storeStrong 的实现
- void objc_storeStrong(id * location, id obj) {
- id prev = *location;
- if (obj == prev) {
- return;
- }
- objc_retain(obj); * location = obj;
- objc_release(prev);
- }
- id objc_retain(id obj) {
- return [obj retain];
- }
- void objc_release(id obj) { [obj release];
- }
我们再来看看
. 将 Test.m 修改成为如下代码,同样我们分析其汇编实现
- __weak
- .loc 2 15 35 prologue_end;
- /Users/hl / Desktop / OCTest / OCTest / Test.m: 15 : 35 ldr x9,
- [x9] ldr x1,
- [x8] mov x0,
- x9 bl _objc_msgSend adrp x8,
- L_OBJC_SELECTOR_REFERENCES_.2@PAGE add x8,
- x8,
- L_OBJC_SELECTOR_REFERENCES_.2@PAGEOFF.loc 2 15 34 is_stmt 0;
- /Users/hl / Desktop / OCTest / OCTest / Test.m: 15 : 34 ldr x1,
- [x8].loc 2 15 34 discriminator 1;
- /Users/hl / Desktop / OCTest / OCTest / Test.m: 15 : 34 bl _objc_msgSend add x8,
- sp,
- #24; = 24.loc 2 15 27;
- /Users/hl / Desktop / OCTest / OCTest / Test.m: 15 : 27 mov x1,
- x0.loc 2 15 27 discriminator 2;
- /Users/hl / Desktop / OCTest / OCTest / Test.m: 15 : 27 str x0,
- [sp, #16];
- 8 - byte Folded Spill mov x0,
- x8 bl _objc_initWeak.loc 2 15 27;
- /Users/hl / Desktop / OCTest / OCTest / Test.m: 15 : 27 ldr x1,
- [sp, #16];
- 8 - byte Folded Reload.loc 2 15 27 discriminator 3;
- /Users/hl / Desktop / OCTest / OCTest / Test.m: 15 : 27 str x0,
- [sp, #8];
- 8 - byte Folded Spill mov x0,
- x1 bl _objc_release add x8,
- sp,
- #24 Ltmp4: .loc 2 16 5 is_stmt 1;
- /Users/hl / Desktop / OCTest / OCTest / Test.m: 16 : 5 mov x0,
- x8 bl _objc_destroyWeak.loc 2 17 1;
- /Users/hl / Desktop / OCTest / OCTest / Test.m: 17 : 1 ldp x29,
- x30,
- [sp, #48];
- 8 - byte Folded Reload add sp,
- sp,
- #64; = 64 ret
可以看到,
本身实现的核心就是以下两个方法
- __weak
- _objc_initWeak
- _objc_destroyWeak
我们通过 Runtime 的源码分析这两个方法的实现:
- id objc_initWeak(id * location, id newObj) {
- //省略....
- return storeWeak < false
- /*old*/
- ,
- true
- /*new*/
- ,
- true
- /*crash*/
- > (location, (objc_object * ) newObj);
- }
- void objc_destroyWeak(id * location) { (void) storeWeak < true
- /*old*/
- ,
- false
- /*new*/
- ,
- false
- /*crash*/
- > (location, nil);
- }
所以,本质上都是调用了
函数,这个函数内容较多,主要做了以下事情
- storeWeak
引用的地址。
- weak
引用的地址,将其置为 nil 即可。
- weak
这就是在
背后的黑魔法。
- weak
这篇文章属于想到哪里写到哪里的类型,后边有时间了在继续总结 ARC 的东西吧。
来源: http://blog.csdn.net/hello_hwc/article/details/70045334