問題
簡(jiǎn)單介紹 ARC 以及 ARC 實(shí)現(xiàn)的原理幌甘。
考查點(diǎn)
我記得在剛接觸iOS的時(shí)候?qū)@個(gè)ARC和MRC就討論頗深硼控,認(rèn)為ARC是對(duì)程序員的一種福利,讓我們節(jié)省了大量的代碼请契,那么ARC是什么呢咳榜?
ARC 是蘋果在 WWDC 2011 提出來的技術(shù),因此很多新入行的同學(xué)可能對(duì)此技術(shù)細(xì)節(jié)并不熟悉姚糊。但是贿衍,雖然 ARC 極大地簡(jiǎn)化了我們的內(nèi)存管理工作,但是引用計(jì)數(shù)這種內(nèi)存管理方案如果不被理解救恨,那么就無法處理好那些棘手的循環(huán)引用問題贸辈。所以,這道面試題其實(shí)是考查同學(xué)對(duì)于 iOS 程序內(nèi)存管理的理解深度肠槽。
答案
自動(dòng)的引用計(jì)數(shù)(Automatic Reference Count 簡(jiǎn)稱 ARC)擎淤,是蘋果在 WWDC 2011 年大會(huì)上提出的用于內(nèi)存管理的技術(shù)。
引用計(jì)數(shù)(Reference Count)是一個(gè)簡(jiǎn)單而有效的管理對(duì)象生命周期的方式秸仙。當(dāng)我們創(chuàng)建一個(gè)新對(duì)象的時(shí)候嘴拢,它的引用計(jì)數(shù)為 1,當(dāng)有一個(gè)新的指針指向這個(gè)對(duì)象時(shí)寂纪,我們將其引用計(jì)數(shù)加 1席吴,當(dāng)某個(gè)指針不再指向這個(gè)對(duì)象是,我們將其引用計(jì)數(shù)減 1捞蛋,當(dāng)對(duì)象的引用計(jì)數(shù)變?yōu)?0 時(shí)孝冒,說明這個(gè)對(duì)象不再被任何指針指向了,這個(gè)時(shí)候我們就可以將對(duì)象銷毀拟杉,回收內(nèi)存庄涡。由于引用計(jì)數(shù)簡(jiǎn)單有效,除了 Objective-C 語言外搬设,微軟的 COM(Component Object Model )穴店、C++11(C++11 提供了基于引用計(jì)數(shù)的智能指針 share_prt) 等語言也提供了基于引用計(jì)數(shù)的內(nèi)存管理方式。
引用計(jì)數(shù)這種內(nèi)存管理方式雖然簡(jiǎn)單拿穴,但是手工寫大量的操作引用計(jì)數(shù)的代碼不但繁瑣泣洞,而且容易被遺漏。于是蘋果在 2011 年引入了 ARC默色。ARC 顧名思義斜棚,是自動(dòng)幫我們填寫引用計(jì)數(shù)代碼的一項(xiàng)功能。
ARC 的想法來源于蘋果在早期設(shè)計(jì) Xcode 的 Analyzer 的時(shí)候该窗,發(fā)現(xiàn)編譯器在編譯時(shí)可以幫助大家發(fā)現(xiàn)很多內(nèi)存管理中的問題弟蚀。后來蘋果就想,能不能干脆編譯器在編譯的時(shí)候酗失,把內(nèi)存管理的代碼都自動(dòng)補(bǔ)上义钉,帶著這種想法,蘋果修改了一些內(nèi)存管理代碼的書寫方式(例如引入了 @autoreleasepool 關(guān)鍵字)后规肴,在 Xcode 中實(shí)現(xiàn)了這個(gè)想法捶闸。
ARC 的工作原理大致是這樣:當(dāng)我們編譯源碼的時(shí)候,編譯器會(huì)分析源碼中每個(gè)對(duì)象的生命周期拖刃,然后基于這些對(duì)象的生命周期删壮,來添加相應(yīng)的引用計(jì)數(shù)操作代碼。所以兑牡,ARC 是工作在編譯期的一種技術(shù)方案央碟,這樣的好處是:
編譯之后,ARC 與非 ARC 代碼是沒有什么差別的均函,所以二者可以在源碼中共存亿虽。實(shí)際上,你可以通過編譯參數(shù) -fno-objc-arc 來關(guān)閉部分源代碼的 ARC 特性苞也。
相對(duì)于垃圾回收這類內(nèi)存管理方案洛勉,ARC 不會(huì)帶來運(yùn)行時(shí)的額外開銷,所以對(duì)于應(yīng)用的運(yùn)行效率不會(huì)有影響如迟。相反收毫,由于 ARC 能夠深度分析每一個(gè)對(duì)象的生命周期,它能夠做到比人工管理引用計(jì)數(shù)更加高效殷勘。例如在一個(gè)函數(shù)中此再,對(duì)一個(gè)對(duì)象剛開始有一個(gè)引用計(jì)數(shù) +1 的操作,之后又緊接著有一個(gè) -1 的操作劳吠,那么編譯器就可以把這兩個(gè)操作都優(yōu)化掉引润。
但是也有人認(rèn)為,ARC 也附帶有運(yùn)行期的一些機(jī)制來使 ARC 能夠更好的工作痒玩,他們主要是指 weak 關(guān)鍵字淳附。weak 變量能夠在引用計(jì)數(shù)為 0 時(shí)被自動(dòng)設(shè)置成 nil,顯然是有運(yùn)行時(shí)邏輯在工作的蠢古。我通常并沒有把這個(gè)算在 ARC 的概念當(dāng)中奴曙,當(dāng)然,這更多是一個(gè)概念或定義上的分歧草讶,因?yàn)槌_ weak 邏輯之外洽糟,ARC 核心的代碼都是在編譯期填充的。
作者:優(yōu)雅地小男子
高級(jí)解析
前言
本文的ARC特指Objective C的ARC,并不會(huì)講解其他語言坤溃。另外拍霜,本文涉及到的原理部分較多,適合有一定經(jīng)驗(yàn)的開發(fā)者薪介。
什么是ARC祠饺?
ARC的全稱Auto Reference Counting. 也就是自動(dòng)引用計(jì)數(shù)。那么汁政,為什么要有ARC呢道偷?
我們從C語言開始。使用C語言編程的時(shí)候记劈,如果要在堆上分配一塊內(nèi)存勺鸦,代碼如下
`//分配內(nèi)存(malloc/calloc均可)`
`int * array = calloc(10, sizeof (int));`
`//釋放內(nèi)存`
`free(array);1234512345`
C是面向過程的語言(Procedural programming),這種內(nèi)存的管理方式簡(jiǎn)單直接目木。但是换途,對(duì)于面向?qū)ο缶幊蹋@種手動(dòng)的分配釋放毫無疑問會(huì)大大的增加代碼的復(fù)雜度嘶窄。
于是怀跛,OOP的語言引入了各種各樣的內(nèi)存管理方法,比如Java的垃圾回收和Objective C的引用計(jì)數(shù)柄冲。關(guān)于垃圾回收和飲用計(jì)數(shù)的對(duì)比吻谋,可以參見Brad Larson的這個(gè)SO回答。
Objective C的引用計(jì)數(shù)理解起來很容易现横,當(dāng)一個(gè)對(duì)象被持有的時(shí)候計(jì)數(shù)加一漓拾,不再被持有的時(shí)候引用計(jì)數(shù)減一,當(dāng)引用計(jì)數(shù)為零的時(shí)候戒祠,說明這個(gè)對(duì)象已經(jīng)無用了骇两,則將其釋放。
引用計(jì)數(shù)分為兩種:
手動(dòng)引用計(jì)數(shù)(MRC)
自動(dòng)引用計(jì)數(shù)(ARC)
在iOS開發(fā)早期姜盈,編寫代碼是采用MRC的
`// MRC代碼`
`NSObject * obj = [[NSObject alloc] init]; ``//引用計(jì)數(shù)為1`
`//不需要的時(shí)候`
`[obj release] ``//引用計(jì)數(shù)減1`
`//持有這個(gè)對(duì)象`
`[obj retain] ``//引用計(jì)數(shù)加1`
`//放到AutoReleasePool`
`[obj autorelease]``//在auto release pool釋放的時(shí)候低千,引用計(jì)數(shù)減1`
雖說這種方式提供了面向?qū)ο蟮膬?nèi)存管理接口,但是開發(fā)者不得不花大量的時(shí)間在內(nèi)存管理上馏颂,并且容易出現(xiàn)內(nèi)存泄漏或者release一個(gè)已被釋放的對(duì)象示血,導(dǎo)致crash。
再后來救拉,Apple對(duì)iOS/Mac OS開發(fā)引入了ARC难审。使用ARC,開發(fā)者不再需要手動(dòng)的retain/release/autorelease. 編譯器會(huì)自動(dòng)插入對(duì)應(yīng)的代碼亿絮,再結(jié)合Objective C的runtime告喊,實(shí)現(xiàn)自動(dòng)引用計(jì)數(shù)麸拄。
比如如下ARC代碼:
`NSObject * obj;`
`{`
`obj = [[NSObject alloc] init]; ``//引用計(jì)數(shù)為1`
`}`
`NSLog(@``"%@"``,obj);`
等同于如下MRC代碼
`NSObject * obj;`
`{`
`obj = [[NSObject alloc] init]; ``//引用計(jì)數(shù)為1`
`[obj relrease]`
`}`
`NSLog(@``"%@"``,obj);`
在Objective C中,有三種類型是ARC適用的:
block
objective 對(duì)象黔姜,id, Class, NSError*等
由attribute((NSObject))標(biāo)記的類型拢切。
像double *,CFStringRef等不是ARC適用的,仍然需要手動(dòng)管理內(nèi)存地淀。
Tips: 以CF開頭的(Core Foundation)的對(duì)象往往需要手動(dòng)管理內(nèi)存失球。
屬性所有權(quán)
最后,我們?cè)诳纯碅RC中常見的所有權(quán)關(guān)鍵字帮毁,
assign對(duì)應(yīng)關(guān)鍵字__unsafe_unretained, 顧名思義,就是指向的對(duì)象被釋放的時(shí)候豺撑,仍然指向之前的地址烈疚,容易引起野指針。
copy對(duì)應(yīng)關(guān)鍵字__strong,只不過在賦值的時(shí)候聪轿,調(diào)用copy方法爷肝。
retain對(duì)應(yīng)__strong
strong對(duì)應(yīng)__strong
unsafe_unretained對(duì)應(yīng)__unsafe_unretained
weak對(duì)應(yīng)__weak。
其中陆错,__weak和__strong是本文要講解的核心內(nèi)容灯抛。
ARC的內(nèi)部實(shí)現(xiàn)
ARC背后的引用計(jì)數(shù)主要依賴于這三個(gè)方法:
retain 增加引用計(jì)數(shù)
release 降低引用計(jì)數(shù),引用計(jì)數(shù)為0的時(shí)候音瓷,釋放對(duì)象对嚼。
autorelease 在當(dāng)前的auto release pool結(jié)束后,降低引用計(jì)數(shù)绳慎。
在Cocoa Touch中纵竖,NSObject協(xié)議中定義了這三個(gè)方法,由于Cocoa Touch中杏愤,絕大部分類都繼承自NSObject(NSObject類本身實(shí)現(xiàn)了NSObject協(xié)議)靡砌,所以可以“免費(fèi)”獲得NSObject提供的運(yùn)行時(shí)和ARC管理方法,這就是為什么適用OC開發(fā)iOS的時(shí)候珊楼,你的類要繼承自NSObject通殃。
既然ARC是引用計(jì)數(shù),那么對(duì)應(yīng)一個(gè)對(duì)象厕宗,內(nèi)存中必然會(huì)有一個(gè)地方來存儲(chǔ)這個(gè)對(duì)象的引用計(jì)數(shù)画舌。iOS的Runtime是開源的,在這里可以下載到全部的代碼媳瞪,我們通過源代碼一探究竟骗炉。
我們從retain入手,
`- (id)retain {`
`return` `((id)self)->rootRetain();`
`}`
`inline id objc_object::rootRetain()`
`{`
`if` `(isTaggedPointer()) ``return` `(id)``this``;`
`return` `sidetable_retain();`
`}`
所以說,本質(zhì)上retain就是調(diào)用sidetable_retain蛇受,再看看sitetable_retain的實(shí)現(xiàn):
`id objc_object::sidetable_retain()`
`{`
`//獲取table`
`SideTable& table = SideTables()[``this``];`
`//加鎖`
`table.lock();`
`//獲取引用計(jì)數(shù)`
`size_t& refcntStorage = table.refcnts[``this``];`
`if` `(! (refcntStorage & SIDE_TABLE_RC_PINNED)) {`
`//增加引用計(jì)數(shù)`
`refcntStorage += SIDE_TABLE_RC_ONE;`
`}`
`//解鎖`
`table.unlock();`
`return` `(id)``this``;`
`}`
到這里句葵,retain如何實(shí)現(xiàn)就很清楚了,通過SideTable這個(gè)數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)引用計(jì)數(shù)。我們看看這個(gè)數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn):
可以看到乍丈,這個(gè)數(shù)據(jù)結(jié)構(gòu)就是存儲(chǔ)了一個(gè)自旋鎖剂碴,一個(gè)引用計(jì)數(shù)map。這個(gè)引用計(jì)數(shù)的map以對(duì)象的地址作為key轻专,引用計(jì)數(shù)作為value忆矛。到這里,引用計(jì)數(shù)的底層實(shí)現(xiàn)我們就很清楚了请垛。
存在全局的map催训,這個(gè)map以地址作為key,引用計(jì)數(shù)的值作為value宗收。
再來看看release的實(shí)現(xiàn):
`SideTable& table = SideTables()[``this``];`
`bool do_dealloc = ``false``;`
`table.lock();`
`//找到對(duì)應(yīng)地址的`
`RefcountMap::iterator it = table.refcnts.find(``this``);`
`if` `(it == table.refcnts.end()) { ``//找不到的話漫拭,執(zhí)行dellloc`
`do_dealloc = ``true``;`
`table.refcnts[``this``] = SIDE_TABLE_DEALLOCATING;`
`} ``else` `if` `(it->second < SIDE_TABLE_DEALLOCATING) {``//引用計(jì)數(shù)小于閾值,dealloc`
`do_dealloc = ``true``;`
`it->second |= SIDE_TABLE_DEALLOCATING;`
`} ``else` `if` `(! (it->second & SIDE_TABLE_RC_PINNED)) {`
`//引用計(jì)數(shù)減去1`
`it->second -= SIDE_TABLE_RC_ONE;`
`}`
`table.unlock();`
`if` `(do_dealloc && performDealloc) {`
`//執(zhí)行dealloc`
`((void(*)(objc_object *, SEL))objc_msgSend)(``this``, SEL_dealloc);`
`}`
`return` `do_dealloc;`
release的到這里也比較清楚了:查找map混稽,對(duì)引用計(jì)數(shù)減1采驻,如果引用計(jì)數(shù)小于閾值,則調(diào)用SEL_dealloc
Autorelease pool
上文提到了匈勋,autorelease方法的作用是把對(duì)象放到autorelease pool中礼旅,到pool drain的時(shí)候,會(huì)釋放池中的對(duì)象洽洁。舉個(gè)例子
`__weak NSObject * obj;`
`NSObject * temp = [[NSObject alloc] init];`
`obj = temp;`
`NSLog(@``"%@"``,obj); ``//非空`
|
放到auto release pool中痘系,
`__weak NSObject * obj;`
`@autoreleasepool {`
`NSObject * temp = [[NSObject alloc] init];`
`obj = temp;`
`}`
`NSLog(@``"%@"``,obj); ``//null`
可以看到,放到自動(dòng)釋放池的對(duì)象是在超出自動(dòng)釋放池作用域后立即釋放的诡挂。事實(shí)上在iOS 程序啟動(dòng)之后碎浇,主線程會(huì)啟動(dòng)一個(gè)Runloop,這個(gè)Runloop在每一次循環(huán)是被自動(dòng)釋放池包裹的璃俗,在合適的時(shí)候?qū)Τ刈舆M(jìn)行清空奴璃。
對(duì)于Cocoa框架來說,提供了兩種方式來把對(duì)象顯式的放入AutoReleasePool.
NSAutoreleasePool(只能在MRC下使用)
@autoreleasepool {}代碼塊(ARC和MRC下均可以使用)
那么AutoRelease pool又是如何實(shí)現(xiàn)的呢城豁?
我們先從autorelease方法源碼入手
`//autorelease方法`
`- (id)autorelease {`
`return` `((id)self)->rootAutorelease();`
`}`
`//rootAutorelease 方法`
`inline id objc_object::rootAutorelease()`
`{`
`if` `(isTaggedPointer()) ``return` `(id)``this``;`
`//檢查是否可以優(yōu)化`
`if` `(prepareOptimizedReturn(ReturnAtPlus1)) ``return` `(id)``this``;`
`//放到auto release pool中苟穆。`
`return` `rootAutorelease2();`
`}`
`// rootAutorelease2`
`id objc_object::rootAutorelease2()`
`{`
`assert(!isTaggedPointer());`
`return` `AutoreleasePoolPage::autorelease((id)``this``);`
`}`
可以看到,把一個(gè)對(duì)象放到auto release pool中唱星,是調(diào)用了AutoreleasePoolPage::autorelease這個(gè)方法雳旅。
我們繼續(xù)查看對(duì)應(yīng)的實(shí)現(xiàn):
`public: static inline id autorelease(id obj)`
`{`
`assert(obj);`
`assert(!obj->isTaggedPointer());`
`id *dest __unused = autoreleaseFast(obj);`
`assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);`
`return` `obj;`
`}`
`static inline id *autoreleaseFast(id obj)`
`{`
`AutoreleasePoolPage *page = hotPage();`
`if` `(page && !page->full()) {`
`return` `page->add(obj);`
`} ``else` `if` `(page) {`
`return` `autoreleaseFullPage(obj, page);`
`} ``else` `{`
`return` `autoreleaseNoPage(obj);`
`}`
`}`
`id *add(id obj)`
`{`
`assert(!full());`
`unprotect();`
`id *ret = next; ``// faster than `return next-1` because of aliasing`
`*next++ = obj;`
`protect();`
`return` `ret;`
`}`
到這里,autorelease方法的實(shí)現(xiàn)就比較清楚了间聊,
autorelease方法會(huì)把對(duì)象存儲(chǔ)到AutoreleasePoolPage的鏈表里攒盈。等到auto release pool被釋放的時(shí)候,把鏈表內(nèi)存儲(chǔ)的對(duì)象刪除哎榴。所以型豁,AutoreleasePoolPage就是自動(dòng)釋放池的內(nèi)部實(shí)現(xiàn)僵蛛。
__weak與__strong
用過block的同學(xué)一定寫過類似的代碼:
`__weak typeSelf(self) weakSelf = self;`
`[object fetchSomeFromRemote:^{`
`__strong typeSelf(weakSelf) strongSelf = weakSelf;`
`//從這里開始用strongSelf`
`}];`
那么,為什么要這么用呢迎变?原因是:
block會(huì)捕獲外部變量充尉,用weakSelf保證self不會(huì)被block被捕獲,防止引起循環(huán)引用或者不必要的額外生命周期衣形。
用strongSelf則保證在block的執(zhí)行過程中驼侠,對(duì)象不會(huì)被釋放掉。
首先__strong和__weak都是關(guān)鍵字谆吴,是給編譯器理解的倒源。為了理解其原理,我們需要查看它們編譯后的代碼纪铺,使用XCode相速,我們可以容易的獲得一個(gè)文件的匯編代碼。
比如鲜锚,對(duì)于Test.m文件,當(dāng)源代碼如下時(shí):
`#import "Test.h"`
`@implementation Test`
`- (void)testFunction{`
`{`
`__strong NSObject * temp = [[NSObject alloc] init];`
`}`
`}`
`@end`
轉(zhuǎn)換后的匯編代碼如下:
`Ltmp3:`
`.loc 2 15 37 prologue_end ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:37`
`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 36 is_stmt 0 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:36`
`ldr x1, [x8]`
`.loc 2 15 36 discriminator 1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:36`
`bl _objc_msgSend`
`mov x8, ``#0`
`add x9, sp, ``#8 ; =8`
`.loc 2 15 29 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:15:29`
`str x0, [sp, ``#8]`
`Ltmp4:`
`.loc 2 16 5 is_stmt 1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:16:5`
`mov x0, x9`
`mov x1, x8`
`bl _objc_storeStrong`
`.loc 2 17 1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:17:1`
`ldp x29, x30, [sp, ``#32] ; 8-byte Folded Reload`
`add sp, sp, ``#48 ; =48`
`ret`
`Ltmp5:`
即使你不懂匯編苫拍,也能很輕易的獲取到調(diào)用順序如下
`_objc_msgSend ``// alloc`
`_objc_msgSend ``// init`
`_objc_storeStrong ``// 強(qiáng)引用`
在結(jié)合Runtime的源碼芜繁,我們看看最關(guān)鍵的objc_storeStrong的實(shí)現(xiàn)
`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]; }`
我們?cè)賮砜纯確_weak. 將Test.m修改成為如下代碼,同樣我們分析其匯編實(shí)現(xiàn)
`.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本身實(shí)現(xiàn)的核心就是以下兩個(gè)方法
_objc_initWeak
_objc_destroyWeak
我們通過Runtime的源碼分析這兩個(gè)方法的實(shí)現(xiàn):
<false></false>
`id objc_initWeak(id *location, id newObj)`
`{`
`//省略....`
`return` `storeWeak (location, (objc_object*)newObj);`
`}`
`void objc_destroyWeak(id *location)`
`{`
`(void)storeWeak (location, nil);`
`}`
所以骏令,本質(zhì)上都是調(diào)用了storeWeak函數(shù),這個(gè)函數(shù)內(nèi)容較多垄提,主要做了以下事情
獲取存儲(chǔ)weak對(duì)象的map榔袋,這個(gè)map的key是對(duì)象的地址,value是weak引用的地址铡俐。
當(dāng)對(duì)象被釋放的時(shí)候凰兑,根據(jù)對(duì)象的地址可以找到對(duì)應(yīng)的weak引用的地址,將其置為nil即可审丘。
這就是在weak背后的黑魔法吏够。
總結(jié)
這篇文章屬于想到哪里寫到哪里的類型,后邊有時(shí)間了在繼續(xù)總結(jié)ARC的東西吧滩报。