偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

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

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

1.概述

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

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

2.內(nèi)存布局

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

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

圖片圖片

3.類型

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

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

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

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

圖片圖片

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

圖片圖片

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

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

4.1轉(zhuǎn)換命令

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

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

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

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

4.2轉(zhuǎn)換示例

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

圖片圖片

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

圖片圖片

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

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

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

圖片圖片

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

圖片圖片

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

圖片圖片

5.2調(diào)用部分

看完block結(jié)構(gòu)體的定義,我們來(lái)到main函數(shù)中,看block的實(shí)現(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ù),因?yàn)槲覀儎?chuàng)建的是一個(gè)最簡(jiǎn)單block,可以看到block的存儲(chǔ)區(qū)域是在stack棧區(qū)的。即main函數(shù)調(diào)用完,block生命周期就會(huì)結(jié)束。

圖片圖片

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

圖片圖片

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

圖片圖片

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

嚴(yán)謹(jǐn)些來(lái)說(shuō)應(yīng)該加上impl,但不加也不會(huì)出問(wèn)題。這是因?yàn)?,如果看未刪除轉(zhuǎn)換代碼的原始clang代碼,可以看到block是被轉(zhuǎn)換為_(kāi)_block_impl的,也就是說(shuō)被當(dāng)做__block_impl看待的。如果再結(jié)合__main_block_impl_0的結(jié)構(gòu)體定義來(lái)看,__block_impl在成員變量的第一位,所以訪問(wèn)FuncPtr是沒(méi)有問(wèn)題的,只要不訪問(wèn)Desc就是可以的。

6.外部變量

6.1值類型

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

圖片圖片

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

圖片圖片

后面?zhèn)鲄⒑褪褂?,就都是結(jié)構(gòu)體賦值和取值邏輯,很簡(jiǎn)單。

圖片圖片

6.2值傳遞

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

圖片圖片

通過(guò)C++源碼我們可以看到,這是因?yàn)槿绻鸼lock引用的外部變量是值類型,會(huì)采取直接復(fù)制值的方式,而不是指針引用。

圖片圖片

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

圖片圖片

6.3靜態(tài)變量

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

圖片圖片

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

圖片圖片

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

圖片圖片

6.4全局變量

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

圖片圖片

因?yàn)槿肿兞康淖饔糜蚝艽螅圆⒉恍枰猙lock進(jìn)行單獨(dú)持有即可訪問(wèn),結(jié)構(gòu)體并不會(huì)新增字段。

圖片圖片

6.5對(duì)象類型變量

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

圖片圖片

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

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

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

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

圖片圖片


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

2013-06-04 15:41:31

iOS開(kāi)發(fā)移動(dòng)開(kāi)發(fā)block

2009-06-15 15:57:21

Spring工作原理

2017-03-07 09:45:43

iOSBlock開(kāi)發(fā)

2023-06-07 15:25:19

Kafka版本日志

2025-02-08 08:10:00

2013-07-19 12:52:50

iOS中BlockiOS開(kāi)發(fā)學(xué)習(xí)

2023-02-22 07:04:05

自動(dòng)機(jī)原理優(yōu)化實(shí)踐

2024-02-27 22:31:00

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

2011-08-08 18:11:45

IOS 4Block UIActionShe

2010-08-09 08:48:46

File APIWeb

2009-11-04 15:54:20

Portlet入門(mén)企業(yè)門(mén)戶

2010-02-26 17:54:54

python

2009-11-06 16:10:54

ClosureJavaScript開(kāi)Google

2014-03-07 13:23:23

百度面試iOS

2009-08-25 13:48:01

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

2010-08-27 10:41:41

iPhone核心應(yīng)用程序

2013-07-19 14:00:13

iOS中BlockiOS開(kāi)發(fā)學(xué)習(xí)

2013-07-19 14:35:59

iOS中BlockiOS開(kāi)發(fā)學(xué)習(xí)

2023-12-07 08:07:47

Node流程代碼

2023-11-30 22:06:43

點(diǎn)贊
收藏

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