當前位置:編程學習大全網 - 編程語言 - iOC中block下的__block、__Strong、__weak

iOC中block下的__block、__Strong、__weak

兩個對象相互持有,這樣就會造成循環引用,如下圖所示

圖中,對象A持有對象B,對象B持有對象A,相互持有,最終導致兩個對象都不能釋放。

最普通的情況,由於block會對block中的對象進行持有操作, 就相當於持有了其中的對象 ,而如果此時block中的對象又持有了該block,則會造成循環引用。如下:

調用以上三句均會造成循環引用,因為不管是通過 self.blockString 還是 _blockString ,或是函數調用 [self doSomething] , 因為只要 block中用到了對象的屬性或者函數,block就會持有該對象而不是該對象中的某個屬性或者函數。

當有someObj持有self對象,此時的關系圖如下。

當someObj對象release self對象時,self和myblock相互引用,retainCount都為1,造成循環引用

解決:

使用 __weak 修飾self,使其在block中不被持有,打破循環引用。開始狀態如下

當someObj對象釋放self對象時,Self的retainCount為0,走dealloc,釋放myBlock對象,使其retainCount也為0。

其實以上循環引用的情況很容易發現,因為此時Xcode就會報警告。而發生在多個對象間的時候,Xcode就檢測不出來了,這往往就容易被忽略。

解決方法:

將objA對象weak,使其不在block中被持有

註:以上使用 __weak 打破循環的方法只在ARC下才有效,在MRC下應該使用 __block

或者,在block執行完後,將block置nil,這樣也可以打破循環引用

這樣做的缺點是,block只會執行壹次,因為block被置nil了,要再次使用的話,需要重新賦值。

在開發工程中,發現壹些同學並沒有完全理解循環引用,以為只要有block的地方就會要用__weak來修飾對象,這樣完全沒有必要,以下幾種block是不會造成循環引用的。

大部分GCD方法

因為self並沒有對GCD的block進行持有,沒有形成循環引用。目前我還沒碰到使用GCD導致循環引用的場景,如果某種場景self對GCD的block進行了持有,則才有可能造成循環引用。

block並不是屬性值,而是臨時變量

這裏因為block只是壹個臨時變量,self並沒有對其持有,所以沒有造成循環引用

看下面例子,有這種情況,如果不只是ClassA持有了myBlock,ClassB也持有了myBlock。

當ClassA被someObj對象釋放後

此時,ClassA對象已經被釋放,而myBlock還是被ClassB持有,沒有釋放;如果myBlock這個時被調度,而此時ClassA已經被釋放,此時訪問的ClassA將是壹個nil對象(使用 __weak 修飾,對象釋放時會置為nil),而引發錯誤。

另壹個常見錯誤使用是,開發者擔心循環引用錯誤(如上所述不會出現循環引用的情況),使用 __weak 。比如

此時導致doSomething直接無法執行,因為 block作為參數傳給dispatch_async時,系統會將block拷貝到堆上,而且block會持有block中用到的對象 ,因為dispatch_async並不知道block中對象會在什麽時候被釋放,為了確保系統調度執行block中的任務時其對象沒有被意外釋放掉, dispatch_async必須自己retain壹次對象(即self),任務完成後再release對象(即self) 。但這裏使用 __weak ,使dispatch_async沒有增加self的引用計數,這使得在系統在調度執行block之前,self可能已被銷毀,但系統並不知道這個情況,導致block執行時訪問已經被釋放的self,而達不到預期的結果。

理解這點很重要,這是許多使用 __weak,__stong 的由來,實際的過程原理與block實現有關,下文會補充,這裏先記住這點。

註:如果是在MRC模式下,使用 __block 修飾self,則此時block訪問被釋放的self,則會導致crash。 如下:

運行結果

解決方法:

對於這種場景,就不應該使用 __weak 來修飾對象,讓dispatch_after對self進行持有,保證block執行時self還未被釋放。

還有壹種場景,在block執行開始時self對象還未被釋放,而執行過程中,self被釋放了,此時訪問self時,就會發生錯誤。

對於這種場景,應該在block中對 對象使用 __strong 修飾,使得在block期間對 對象持有,block執行結束後,解除其持有。這也就是為什麽許多使用block的地方內外要

註:此方法只能保證在block執行期間對象不被釋放,如果對象在block執行執行之前已經被釋放了,該方法也無效。

講到這裏不得不提及壹下 __block 。承接前面的例子,均是在block中使用其他對象的方法。實際應用中會有在block使用變量,或其他對象屬性的情況。如下:

亦或是

無論是在block中修改外部變量、或是對其他熟悉的操作。因為block在其他線程中執行,取值時是進行copy操作,如果是有self等引用則不會有問題,如果是外部變量或臨時聲明的對象,在block做處理操作時則要考慮,是否希望改變原來的值。這裏的__block起到了原來c語言中&取地址,傳遞地址的作用。

這裏的作用機理和block的機理有關,block本身的核心邏輯是C中的匿名方法,因為C語言中方法是可以作為屬性來傳遞的,OC中將block需要使用的方法快作為匿名方法的熟悉,封裝在壹個結構體中。而這句__block則是將對於需要傳遞內存的屬性/對象壹同封裝在這個結構體中。

不過我個人覺得,這種臨時變量需要在block中進行處理,然後希望同時影響外部的需求。我認為在業務需求上是很少用到的,大家想避免還是很容易。

1.要註意block本身持有情況,及block內部的持有情況,尤其是類似“ block myBlock”等已經在類內部全局聲明持有的block

2.block本身並不是屬性值,而是臨時變量,故不要壹味的在weak中使用weak

3.在block外部聲明的臨時變量,需要在block內部繼續使用時,要用__strong聲明,來防止在進入block前,已經被釋放掉了。

4.如果有臨時變量/對象需要放到block中處理,且希望是Strong傳遞而非Copy傳遞則用 __block 修飾

小聲bb:講了這麽多,其實很簡單,遇到block,先看block是否全局引用了,如果全局引用了,則其內部的self等必然要weak處理。二要看內部是否使用了臨時變量/對象的方法,如果使用了則要考慮是否需要使用strong防止對象過程釋放。__block就是給臨時變量,修改隱性的copy為strong。

本篇中許多內容引用自

/p/492be28d63c4

感謝?作者@HK_Hank

block原理部分大家感興趣可以看

/a/1190000018779727

/p/221d0778dcaa

/p/00a7ee0177ea

附上項目中常用宏,供大家參考使用

使用大致如下

  • 上一篇:C語言和匯編語言的區別是什麽?
  • 下一篇:變換角度,寫出個性化的作文:正交變換x=py具體步驟
  • copyright 2024編程學習大全網