consumed這個單詞我并不能給出很準確的翻譯警检,在這篇文章中,我把Consumed parameters稱為耗用參數(shù),它在OC中有著獨特的應用場景邦尊。
在https://clang.llvm.org/docs/AutomaticReferenceCounting.html#id7這份文檔中瘩缆,講解了ARC方面的知識关拒,我對Consumed parameters這一個小模塊有很大的疑問,因此在網(wǎng)上查了一些資料,雖然有了一個大概的了解着绊,但是還是有一些不太清楚的地方谐算。
我們先來看一個例子,這個例子來源于上邊的那份文檔:
void foo(__attribute((ns_consumed)) id x);
- (void) foo: (id) __attribute((ns_consumed)) x;
我們可以用__attribute((ns_consumed))
來修飾某個函數(shù)或者方法的參數(shù)归露,但這只是表面上的看法洲脂,實際上,它并不是只作用于它修飾的某個參數(shù)剧包,而是作用于整個函數(shù)或方法恐锦。
它的一個限制是,只能修飾可retain的對象指針類型疆液,比如id
, Class
等等一铅,不能修飾int *
這樣的類型。
上邊的兩行代碼表示foo
被標記為consumed堕油。意味著該函數(shù)的被調(diào)用者希望得到一個+1 retain count的對象潘飘。聲明了這個屬性后,當傳入一個參數(shù)時掉缺,在函數(shù)調(diào)用前卜录,ARC會把對該參數(shù)做一次retain操作,在該函數(shù)結(jié)束后再對該參數(shù)做一次release操作眶明,這一過程很像函數(shù)對局部變量的操作暴凑。
到這里就產(chǎn)生了第一個疑問?
為什么要對傳入的參數(shù)做retain赘来,在結(jié)束時又release掉现喳?
這個跟參數(shù)的生命周期有關(guān),我們在函數(shù)中使用了參數(shù)犬辰,當然希望能夠得到這個參數(shù)的所有權(quán)嗦篱,并且希望該參數(shù)一直存活著。這個內(nèi)容我會在下邊的內(nèi)容中給出一定的解釋幌缝。在上邊的文檔中有這樣一段話:
Rationale
This formalizes direct transfers of ownership from a caller to a callee.
The most common scenario here is passing the self parameter to init, but it is
useful to generalize. Typically, local optimization will remove any extra retains
and releases: on the caller side the retain will be merged with a +1 source, and on
the callee side the release will be rolled into the initialization of the parameter.
這段話指出灸促,上邊的操作直接從調(diào)用者到被調(diào)用者轉(zhuǎn)移了所有權(quán),最常見的一個場景就是傳遞self
參數(shù)到init
方法之中涵卵,這個內(nèi)容將是本文最重要的內(nèi)容浴栽。一般來說,局部的優(yōu)化會移除任何額外的retain和release操作轿偎,這句話的意思是說典鸡,在函數(shù)中,某些局部變量不一定都會十分嚴格的按照retain/release原則來進行操作坏晦。調(diào)用端將會進行一些必要的合并操作萝玷,而被調(diào)用端也會對參數(shù)做一些額外的操作嫁乘。
到這里,有了第二個疑問球碉?
在ARC中蜓斧,為什么self
在init
方法中是一個consumed parameter?
這個問題我之前是不知道的睁冬,它來源于這個提問挎春。init
方法被標記為ns_consumes_self
。ns_consumes_self
說明在方法中遵循上邊講的原則豆拨,在方法調(diào)用之前先把self
做retain操作搂蜓,結(jié)束時做release操作。
User *user = [[User alloc] init];
這是一行非常簡單的代碼辽装,在調(diào)用了alloc
后就創(chuàng)建了一個User對象帮碰,這個可以在這篇回答中獲得證據(jù)。返回的對象的retain count等于1拾积,大家應該記得殉挽,凡是通過alloc/new/copy.etc
生成的對象,retain count都會+1拓巧,那么在這里的init
方法中:
self = [super init];
if (self) { ... }
return self;
self
首先被init
的調(diào)用者做了一次retain操作斯碌,此時它的retain count為1,執(zhí)行完self = [super init];
后肛度,它的retain count為2傻唾,直到init
返回后,self
做了一次release操作承耿,此時它的retain count為1冠骄。**這就完美保證了self
在方法中是一直存活的,也保證了能夠返回一個retain count為1的對象加袋。
有興趣可以翻看這個提問中的回答的部分凛辣,那哥們說的很詳細,再說一點职烧,在以前的MRC時代扁誓,代碼可以這樣寫:
- (NSView *)view {
//explicit retain-autorelease of +1 variable is +2 -> +1, guaranteed access or nil.
return [[_view retain]autorelease];
}
為了正確返回某個對象,先retain再release蚀之。
因此在使用consumed的時候蝗敢,需要注意一下幾點:
- 保證方法的接收者不能為null,因為在方法被調(diào)用之前足删,參會會做retain操作寿谴,這樣就帶來了內(nèi)存泄漏的問題
- 傳遞的參數(shù)的個數(shù)不能大于方法能夠動態(tài)處理的個數(shù),否則可能引起未知的后果
- 謹慎處理靜態(tài)類型的問題
何為靜態(tài)類型壹堰,何為動態(tài)類型拭卿?
A *a = [A new];
B *b = a;
那么b的靜態(tài)類型就是B,這個類型是由編譯器決定的贱纠,而A則是它的動態(tài)類型峻厚,由運行時決定。
我發(fā)現(xiàn)ASDisplayKit的源碼極其復雜谆焊,估計要花相當多的時間來解讀了惠桃。不能放棄,加油辖试。