自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

iOS 開發(fā)—探秘 Block 原理

移動開發(fā) iOS
很多block原理性的文章都比較老,里面講的一些知識已經(jīng)過時,這里用新版的iOS SDK再梳理一遍block原理,也是和大家一起對已有知識做一次復習。

1.概述

在iOS開發(fā)中,block大家用的都很熟悉了,是iOS開發(fā)中閉包的一種實現(xiàn)方式,可以對一段代碼邏輯進行封裝,使其可以像數(shù)據(jù)一樣被傳遞、存儲、調(diào)用,并且可以保存相關(guān)的上下文狀態(tài)。

很多block原理性的文章都比較老,里面講的一些知識已經(jīng)過時,這里用新版的iOS SDK再梳理一遍block原理,也是和大家一起對已有知識做一次復習。

2.內(nèi)存布局

block本質(zhì)上可以理解為結(jié)構(gòu)體,對于結(jié)構(gòu)體的內(nèi)存布局,先用一張圖來表示一下,圖中字段順序按照布局的先后順序:

  • isa:block也有isa,從內(nèi)存結(jié)構(gòu)上也屬于對象,isa指向的是block的類對象,類對象例如__NSMallocBlock__,后續(xù)文章會講到;
  • flags:用于存儲一些標志位信息,例如是否捕獲外部變量;
  • reserved:系統(tǒng)保留字段,后續(xù)可能會用于一些編譯優(yōu)化標志位,或者存儲一些臨時變量的處理;
  • invoke:函數(shù)指針,指向了block要執(zhí)行的函數(shù)地址,也就是block代碼塊對應的函數(shù)地址;
  • descriptor(現(xiàn)在叫desc):指向block_desc_0,包含block大小、捕獲的外部變量布局信息、增加引用計數(shù)和銷毀的相關(guān)函數(shù)指針;
  • variables:block捕獲的外部變量。

圖片圖片

3.類型

由于block也是對象,可以通過class方法獲取到其類型,也就是類對象。block有下面三種類型:

  • __NSGlobalBlock__,沒有訪問auto變量的block,訪問static變量是沒問題的。這種類型的變量并沒有什么意義,如果不需要用到auto變量,寫成方法就可以滿足需求;
  • __NSStackBlock__,在MRC環(huán)境下,訪問了auto變量,會默認被放在棧區(qū)。需要手動copy到堆區(qū),ARC環(huán)境下會在訪問auto變量后,會自動拷貝到堆區(qū);
  • __NSMallocBlock__,由開發(fā)者自己管理內(nèi)存,不會由系統(tǒng)來釋放。

block的分配主要是在三個區(qū)域,堆區(qū)、棧區(qū)、全局區(qū),全局區(qū)的數(shù)據(jù)存儲在數(shù)據(jù)段。

block在不同的場景會存在不同的內(nèi)存區(qū)域中,在MRC中創(chuàng)建一個block首先是在__NSStackBlock__內(nèi)存中的,然后我們使用copy方法將block拷貝到__NSMallocBlock__內(nèi)存中進行內(nèi)存管理。后來在ARC中系統(tǒng)已經(jīng)幫我們做好了copy的操作,創(chuàng)建的block會自動copy到__NSMallocBlock__內(nèi)存中,堆區(qū)的block也有引用計數(shù)的概念。如果這個block中沒有用到任何外部參數(shù),系統(tǒng)會將這個block存放在__NSGlobalBlock__內(nèi)存中。

圖片圖片

并且block也有繼承關(guān)系,以下面TestBlock的實例來說,其父類是__NSGlobalBlock__,所有block的父類是NSBlock,并且NSBlock繼承自NSObject類。在更早一些的iOS系統(tǒng)中,__NSGlobalBlock__和NSBlock之間,還會有一層__NSGlobalBlock的關(guān)系(后面沒有下劃線)。

圖片圖片

4.轉(zhuǎn)換C++

下面,我們通過clang命令將block轉(zhuǎn)為結(jié)構(gòu)體,來分析下其具體實現(xiàn)。雖然這并不是最終運行在iOS系統(tǒng)上的代碼,其等于一種中間表現(xiàn)形式,后續(xù)編譯鏈接優(yōu)化才會形成運行在手機上的ipa包,但對于我們了解block的實現(xiàn)原理有很大幫助。

4.1轉(zhuǎn)換命令

xcrun是Xcode用于查找和執(zhí)行相關(guān)命令行的工具集,可以更好的執(zhí)行clang命令,減少報錯。

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc [源文件路徑] -o [目標文件路徑]

clang命令有下面這些關(guān)鍵參數(shù):

  • -fobjc-arc:如果項目是ARC或者ARC和MRC混編的環(huán)境,需要通過此參數(shù)修飾,表示按ARC的方式進行轉(zhuǎn)換,如果不需要ARC環(huán)境可以忽略;
  • -x objective-c++:此參數(shù)上面沒用,如果包含Objective++源文件的時候,需要用到此參數(shù),以確保clang可以區(qū)分OC和C++代碼;
  • -rewrite-objc:告訴clang以C++的方式重寫出來,包含的上層代碼,clang會以底層代碼的方式進行展現(xiàn);
  • [目標文件路徑]:非必傳參數(shù),不傳的話默認在當前目錄生成一個同名的cpp文件,例如main.m對應main.cpp。

4.2轉(zhuǎn)換示例

下面在main.m中實現(xiàn)了一個很簡單的block,并且沒有捕獲任何外部變量,通過clang命令查看C++代碼,觀察block的具體實現(xiàn)原理。

圖片圖片

轉(zhuǎn)換后將C++源文件拉到最下面,可以看到main函數(shù)以及TestBlock的實現(xiàn),main函數(shù)中有很多轉(zhuǎn)義代碼,刪掉后梳理邏輯會更清晰。

圖片圖片

5.結(jié)構(gòu)體

5.1基礎結(jié)構(gòu)

轉(zhuǎn)換后的代碼看著比較復雜,但我們只看關(guān)鍵信息,__main_block_impl_0構(gòu)造函數(shù)也可以去掉,整理后就是下面三個結(jié)構(gòu)體。在不包含外部變量和__block的前提下,block結(jié)構(gòu)體各個字段就這么簡單,關(guān)鍵就是isa、Block_size、FuncPtr這三個。

圖片圖片

我們也可以打印block結(jié)構(gòu)體相關(guān)字段,但由于block的結(jié)構(gòu)體并沒有聲明在某個.h文件中,所以需要我們講clang轉(zhuǎn)換后的結(jié)構(gòu)體粘到對應的文件中,做顯示聲明。隨后用__bridge的方式,將block對象橋接為自己聲明的結(jié)構(gòu)體,即可打印對應字段。

圖片圖片

結(jié)構(gòu)體中impl.FuncPtr存儲的就是回調(diào)函數(shù)地址,從地址可以看出是一個虛擬地址,block結(jié)構(gòu)體都存儲在堆區(qū)。

圖片圖片

5.2調(diào)用部分

看完block結(jié)構(gòu)體的定義,我們來到main函數(shù)中,看block的實現(xiàn)和調(diào)用轉(zhuǎn)換后是什么樣的。將main函數(shù)中block相關(guān)的轉(zhuǎn)換都去掉,結(jié)果如紅圈部分。本質(zhì)上就是兩步,第一步是調(diào)用__main_block_impl_0的結(jié)構(gòu)體構(gòu)造函數(shù),第二步是調(diào)用結(jié)構(gòu)體的函數(shù)指針。

圖片圖片

第一行main函數(shù)中調(diào)用的構(gòu)造方法,是__main_block_impl_0結(jié)構(gòu)體聲明的C++構(gòu)造函數(shù),因為我們創(chuàng)建的是一個最簡單block,可以看到block的存儲區(qū)域是在stack棧區(qū)的。即main函數(shù)調(diào)用完,block生命周期就會結(jié)束。

圖片圖片

__main_block_impl_0構(gòu)造函數(shù)有兩個參數(shù),第一個紅圈部分就是傳入函數(shù)指針地址,函數(shù)對應的就是block內(nèi)部的實現(xiàn)代碼。第二個參數(shù)是__main_block_desc_0_DATA結(jié)構(gòu)體,其定義為__main_block_desc_0,并且默認實現(xiàn)第一個參數(shù)傳0,第二個參數(shù)是block結(jié)構(gòu)體的大小,結(jié)構(gòu)體為__main_block_impl_0 block自身的結(jié)構(gòu)體大小。第三個參數(shù)有默認值,可以不傳。

圖片圖片

__main_block_desc_0結(jié)構(gòu)體是一種緊湊型的寫法,在聲明__main_block_desc_0結(jié)構(gòu)體后,緊接著聲明了一個名為__main_block_desc_0_DATA的變量,變量類型為靜態(tài)變量,并且實現(xiàn)了初始化相關(guān)代碼。

圖片圖片

在執(zhí)行block的代碼位置,可以看到并不是block->impl.FuncPtr的方式調(diào)用,而是直接block->FuncPtr的方式調(diào)用,中間少了一步。

嚴謹些來說應該加上impl,但不加也不會出問題。這是因為,如果看未刪除轉(zhuǎn)換代碼的原始clang代碼,可以看到block是被轉(zhuǎn)換為__block_impl的,也就是說被當做__block_impl看待的。如果再結(jié)合__main_block_impl_0的結(jié)構(gòu)體定義來看,__block_impl在成員變量的第一位,所以訪問FuncPtr是沒有問題的,只要不訪問Desc就是可以的。

6.外部變量

6.1值類型

如果在block的調(diào)用中加一個外部變量,那結(jié)構(gòu)體將會是怎樣的?

圖片圖片

通過clang命令可以可以看到,轉(zhuǎn)換后的__main_block_impl_0中增加了一個同名字段,這很簡單沒必要過多解釋。在__main_block_impl_0構(gòu)造函數(shù)中傳入,通過冒號后的初始化列表對value參數(shù)進行初始化。

圖片圖片

后面?zhèn)鲄⒑褪褂茫投际墙Y(jié)構(gòu)體賦值和取值邏輯,很簡單。

圖片圖片

6.2值傳遞

下面這種寫法,在block的使用中很容易踩坑。在block中使用value參數(shù),并且打印value參數(shù),發(fā)現(xiàn)結(jié)果為1,而不是2。

圖片圖片

通過C++源碼我們可以看到,這是因為如果block引用的外部變量是值類型,會采取直接復制值的方式,而不是指針引用。

圖片圖片

想解決這個問題也很簡單,通過__block修飾一下值類型,即可實現(xiàn)block內(nèi)value的值和外部value參數(shù)統(tǒng)一。

圖片圖片

6.3靜態(tài)變量

我們看一下,如果捕獲的是一個static修飾的靜態(tài)變量,其結(jié)構(gòu)體會是什么實現(xiàn)。

圖片圖片

轉(zhuǎn)換為C++代碼后,可以看到原來的值傳遞變成了地址傳遞,__main_block_impl_0中value的引用是指針引用,在main函數(shù)中將value的地址傳入。如果被static修飾的本身就是一個對象,對象是通過指針引用的,在block的結(jié)構(gòu)體中就是兩個星號引用。也就是NSObject **obj。

圖片圖片

正是由于靜態(tài)變量地址傳遞的實現(xiàn),在block內(nèi)可以對靜態(tài)變量直接進行更改,而無需用__block進行修飾。

圖片圖片

6.4全局變量

如果把value改為全局變量,結(jié)構(gòu)體會有什么變化呢?

圖片圖片

因為全局變量的作用域很大,所以并不需要block進行單獨持有即可訪問,結(jié)構(gòu)體并不會新增字段。

圖片圖片

6.5對象類型變量

如果block中引用的是對象,而不是基礎數(shù)據(jù)類型,結(jié)構(gòu)體會是什么定義呢?

圖片圖片

執(zhí)行clang命令,執(zhí)行完成后結(jié)構(gòu)體是下圖的,下面代碼去掉了轉(zhuǎn)換,以及整理過代碼??梢钥吹蕉嗔藘蓚€函數(shù)指針,__main_block_copy_0和__main_block_dispose_0。

以copy的實現(xiàn)__main_block_copy_0為例,執(zhí)行后會調(diào)用Block_object_assign的實現(xiàn),在實現(xiàn)中系統(tǒng)會根據(jù)person的引用方式,__strong、__weak、__unsafe_unretained,是強引用還是弱引用,調(diào)用對應的內(nèi)存管理方法。

__main_block_dispose_0函數(shù)在block從堆區(qū)移除的時候被調(diào)用,調(diào)用dispose時會調(diào)用實現(xiàn)Block_object_dispose函數(shù),函數(shù)中會根據(jù)person的引用方式,進行對應的減少引用計數(shù)或釋放操作。

copy和dispose兩個函數(shù)都有一個3的參數(shù),這個參數(shù)是一個標志位,表示外部變量類型。這里是BLOCK_FIELD_IS_OBJECT表示一個對象類型,也有BLOCK_FIELD_IS_WEAK表示weak引用的變量,BLOCK_FIELD_IS_BLOCK表示block類型的變量等。

圖片圖片


責任編輯:武曉燕 來源: 搜狐技術(shù)產(chǎn)品
相關(guān)推薦

2013-06-04 15:41:31

iOS開發(fā)移動開發(fā)block

2009-06-15 15:57:21

Spring工作原理

2017-03-07 09:45:43

iOSBlock開發(fā)

2023-06-07 15:25:19

Kafka版本日志

2025-02-08 08:10:00

2013-07-19 12:52:50

iOS中BlockiOS開發(fā)學習

2023-02-22 07:04:05

自動機原理優(yōu)化實踐

2011-08-08 18:11:45

IOS 4Block UIActionShe

2024-02-27 22:31:00

Feign動態(tài)代理核心

2010-08-09 08:48:46

File APIWeb

2009-11-04 15:54:20

Portlet入門企業(yè)門戶

2014-03-07 13:23:23

百度面試iOS

2009-08-25 13:48:01

Java EE架構(gòu)企業(yè)級應用

2010-08-27 10:41:41

iPhone核心應用程序

2013-07-19 14:00:13

iOS中BlockiOS開發(fā)學習

2013-07-19 14:35:59

iOS中BlockiOS開發(fā)學習

2010-02-26 17:54:54

python

2009-11-06 16:10:54

ClosureJavaScript開Google

2023-12-07 08:07:47

Node流程代碼

2023-11-30 22:06:43

點贊
收藏

51CTO技術(shù)棧公眾號