block
簡介Block塊是封裝工做單元的對象,是能夠在任什麼時候間執行的代碼段。其本質上是可移植的匿名函數,能夠做爲方法和函數的參數傳入,能夠從方法和函數中返回。—(翻譯自官方文檔)markdown
塊是對C語言的一種擴展,它並未做爲標準的ANSI C所定義的部分,而是有蘋果公司添加到語言中的。塊看起來更像是函數,能夠給塊傳遞參數,塊也能夠具備返回值。函數
block
類型__NSGlobalBlock__
全局block,存儲在全局區block
,block
沒有入參,沒有block
內部代碼塊只是簡單的打印沒有引用外界變量,則此時的block
類型是__NSGlobalBlock__
__NSStackBlock__
棧區block__NSStackBlock__
,外界變量處理以後底層會對block
進行拷貝從棧區拷貝到堆區。在ARC下,編譯器作了不少的優化,每每看不到本質,上面的代碼輸出結果找不到,由於編譯器對__NSStackBlock__
自動進行了copy操做。改成MRC
就能夠看到不論是否對變量進行處理都會打印__NSStackBlock__
固然也能夠用改成MRC方法: Build Settings 裏面的Automatic Reference Counting改成NO。源碼分析
__weak
修飾block
,不作強引用同樣不會copy
因此此時打印的block
仍是棧區block
__NSMallocBlock__
堆區blockblock
底層拷貝以後變成堆區block
block
本質__main_block_impl_0
函數地址賦值給了block
此時咱們再看__main_block_impl_0
的結構 block
本質其實就是一個結構體,結構體中又存在着isa
,也能夠說block
本質就是一個OC對象,一個封裝了函數調用以及函數調用環境的OC對象FuncPtr
,並將block
做爲入參傳遞block
如何捕獲變量上文分析本質的時候寫了一個簡單的block沒有任何入參,也沒有使用外界變量,再寫一個使用外界變量的block
,看看底層block
如何使用這些外界變量的,在外界定義一個變量a,而後在代碼塊中打印a,查看編譯後的代碼:
優化
block
結構體中多了一個同名的參數,初始化的時候將外界的變量賦值給這個同名變量,其中局部變量會生成一個變量進行值拷貝,全局變量不捕獲變量直接使用外界變量,靜態變量是是有一個同名變量指針,是指針拷貝__block
原理block
代碼塊中直接修改外界沒有使用__block
修飾的變量時回報以下錯誤 此時咱們在使用
__block
修飾發現沒有報錯而且修改爲功 經過clang查看編譯後的代碼
發現編譯後
a
此時取得是地址不僅僅是簡單的數值而且被轉換成了__Block_byref_a_0
類型 經過編譯後的代碼發現
__Block_byref_a_0
是一個結構體,而後block
的結構體同時也多了一個__Block_byref_a_0
類型的指針因此底層a
是指針複製也就是和外界的a
變量指向了同一片內存地址,因此此時使用__block
修飾的變量可以修改爲功ui
block
真正的類型Block_layout
block
的時候首先會走到objc_retainBlock
方法中去如圖objc_retainBlock
,發現objc_retainBlock
方法裏面會跳轉到_Block_copy
方法中去,此時斷點繼續往下跟發現第一次會走進libobjc.A.dylib
庫中的_Block_copy
方法,方法內部呢又跳轉了一個_Block_copy
方法,一樣的繼續往下跟發現走進了libsystem_blocks.dylib
庫中的_Block_copy
方法,此時咱們再衝源碼中找block
的真正類型block
的真正類型是Block_layout
Block_layout
源碼探索flags
標識說明
BLOCK_DEALLOCATING
,釋放標記,-般經常使用BLOCK_NEEDS_FREE
作位與操做,一同傳入Flags
,告知該block
可釋放。BLOCK_REFCOUNT_MASK
,存儲引用計數的值;是一個可選用參數BLOCK_NEEDS_FREE
,低16是否有效的標誌,程序根據它來決定是否增長或是減小引用計數位的值;BLOCK_HAS_COPY_DISPOSE
,是否擁有拷貝輔助函數(a copy helper function);BLOCK_IS_GC
,是否擁有block
析構函數;BLOCK_IS_GLOBAL
,標誌是不是全局block;BLOCK_HAS_SIGNATURE
,與BLOCK_USE_STRET
相對,判斷當前block
是否擁有一個簽名。用於 runtime
時動態調用。descriptor
說明:Block_descriptor_1
是必選的Block_descriptor_2
和 Block_descriptor_3
都是可選的Block_descriptor_2
和Block_descriptor_3
的構造函數 Block_descriptor_1
的內存地址平移得來block
三層拷貝分析注意:能出發三層拷貝的狀況是,在block中捕獲用__block修飾的對象的時候出發spa
_Block_copy
block
從棧區拷貝到堆區 block
是否須要釋放,須要則不拷貝block
是則不拷貝aBlock
拷貝到result
result
_Block_byref_copy
__block
修飾的時候拷貝成Block_byref
結構體 從下文中的_Block_object_assign
源碼分析知道若是是__block
修飾的對象會交給_Block_byref_copy
去處理此時咱們再看_Block_byref_copy
的源碼 __block
修飾的變量在block
中可以直接修改對應的值,我看再看編譯後的代碼__block
修飾的對象轉換成__Block_byref_person_0
類型再看__Block_byref_person_0
結構體 __Block_byref_id_object_copy
和__Block_byref_id_object_dispose
,暫時不知道多的這兩個函數是怎麼用的,這時候咱們再回到源碼先看Block_byref
結構體 Block_byref
結構中是沒有這兩個函數的,可是Block_byref_2
恰好存在着兩個函數分別是copy
和dispose
,再看_Block_byref_copy
函數的源碼發現若是存在copy
和dispose
方法的時候會有調用copy
方法copy
的實現 _Block_object_assign
方法,此時傳入的就是普通的對象交給系統arc去處理,而後作一個指針拷貝,至此這一層拷貝就找到了_Block_object_assign
__main_block_copy_0
和__main_block_dispose_0
方法裏面的實現分別調用的是_Block_object_assign
和__main_block_dispose_0
,衝源碼中也能夠知道這兩個方法分別對應着Block_descriptor_2
中的copy
和dispose
此時咱們再看_Block_object_assign
的源碼實現 _Block_copy
去處理_Block_byref_copy
去處理該方法的源碼分析可看面內容block
循環引用block
,而後還有個屬性name
若是在block
中使用name
,那麼在block
中就會只有當前這個對象(上文中也分析過了,此狀況,block
內部結構體會添加一個一樣的對象作持有當前對象,底層傳入普通對象會進行指針拷貝而且引用計數加一)這就形成了相互持有循環引用。block
中須要使用self
中的屬性直接使用__weak
就能夠了,這樣兩個指針都指向同一片內存地址可是使用__weak
修飾不會形成引用計數加一。block
中又嵌套block
的狀況 __weak typeof(self) weakSelf = self;
self.tdBlock = ^(void){
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
self.tdBlock();
複製代碼
這個狀況外面同樣的使用__weak
去修飾,可是在第一個block
裏面在使用__strong
去修飾。應爲__strong
修飾的變量在代碼塊中是個局部變量,因此block
代碼執行完成以後會自動釋放,因此不會形成循環引用,爲何要在block
中在使用__strong
修飾呢?主要是應爲裏面的block
是一個延時操做若是不是用__strong
修飾那麼執行完析構函數weakSelf
就變成nil
了,因此在嵌套的block
,中拿到的weakSelf
,就是nil,此時在使用__strong
是爲了延長weakSelf
聲明週期,讓其在嵌套的block
執行完成以後再銷燬__block
修飾是利用了在block
內部能夠修改變量的值的屬性,一樣的在定義block
的時候self
的引用計數會加一,可是在使用完成以後能夠吧變量置爲nil
,那麼self
的引用計數又會減一,因此不會形成循環引用。(注意:這種方法block必需要調用若是不調用變量沒法置空同樣會形成循環引用)self
就會被做爲臨時變量壓棧進來,因此就不會形成持有,也就不會形成循環引用(函數參數是存在棧區的,是由編譯器自動分配和釋放的)OC
是隻能單繼承的語言,可是它是基於運行時的機制,因此能夠經過NSProxy
來實現 僞多繼承,填補了多繼承的空白NSProxy
和 NSObject
是同級的一個類,也能夠說是一個虛擬類,只是實現了NSObject
的協議NSProxy
實際上是一個消息重定向封裝的一個抽象類,相似一個代理人,中間件,能夠經過繼承它,並重寫下面兩個方法來實現消息轉發到另外一個實例- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel