成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

iOS 開發—探秘 Block 原理

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

1.概述

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

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

2.內存布局

block本質上可以理解為結構體,對于結構體的內存布局,先用一張圖來表示一下,圖中字段順序按照布局的先后順序:

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

圖片圖片

3.類型

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

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

block的分配主要是在三個區域,堆區、棧區、全局區,全局區的數據存儲在數據段。

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

圖片圖片

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

圖片圖片

4.轉換C++

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

4.1轉換命令

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

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

clang命令有下面這些關鍵參數:

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

4.2轉換示例

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

圖片圖片

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

圖片圖片

5.結構體

5.1基礎結構

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

圖片圖片

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

圖片圖片

結構體中impl.FuncPtr存儲的就是回調函數地址,從地址可以看出是一個虛擬地址,block結構體都存儲在堆區。

圖片圖片

5.2調用部分

看完block結構體的定義,我們來到main函數中,看block的實現和調用轉換后是什么樣的。將main函數中block相關的轉換都去掉,結果如紅圈部分。本質上就是兩步,第一步是調用__main_block_impl_0的結構體構造函數,第二步是調用結構體的函數指針。

圖片圖片

第一行main函數中調用的構造方法,是__main_block_impl_0結構體聲明的C++構造函數,因為我們創建的是一個最簡單block,可以看到block的存儲區域是在stack棧區的。即main函數調用完,block生命周期就會結束。

圖片圖片

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

圖片圖片

__main_block_desc_0結構體是一種緊湊型的寫法,在聲明__main_block_desc_0結構體后,緊接著聲明了一個名為__main_block_desc_0_DATA的變量,變量類型為靜態變量,并且實現了初始化相關代碼。

圖片圖片

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

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

6.外部變量

6.1值類型

如果在block的調用中加一個外部變量,那結構體將會是怎樣的?

圖片圖片

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

圖片圖片

后面傳參和使用,就都是結構體賦值和取值邏輯,很簡單。

圖片圖片

6.2值傳遞

下面這種寫法,在block的使用中很容易踩坑。在block中使用value參數,并且打印value參數,發現結果為1,而不是2。

圖片圖片

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

圖片圖片

想解決這個問題也很簡單,通過__block修飾一下值類型,即可實現block內value的值和外部value參數統一。

圖片圖片

6.3靜態變量

我們看一下,如果捕獲的是一個static修飾的靜態變量,其結構體會是什么實現。

圖片圖片

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

圖片圖片

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

圖片圖片

6.4全局變量

如果把value改為全局變量,結構體會有什么變化呢?

圖片圖片

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

圖片圖片

6.5對象類型變量

如果block中引用的是對象,而不是基礎數據類型,結構體會是什么定義呢?

圖片圖片

執行clang命令,執行完成后結構體是下圖的,下面代碼去掉了轉換,以及整理過代碼。可以看到多了兩個函數指針,__main_block_copy_0和__main_block_dispose_0。

以copy的實現__main_block_copy_0為例,執行后會調用Block_object_assign的實現,在實現中系統會根據person的引用方式,__strong、__weak、__unsafe_unretained,是強引用還是弱引用,調用對應的內存管理方法。

__main_block_dispose_0函數在block從堆區移除的時候被調用,調用dispose時會調用實現Block_object_dispose函數,函數中會根據person的引用方式,進行對應的減少引用計數或釋放操作。

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

圖片圖片


責任編輯:武曉燕 來源: 搜狐技術產品
相關推薦

2013-06-04 15:41:31

iOS開發移動開發block

2009-06-15 15:57:21

Spring工作原理

2017-03-07 09:45:43

iOSBlock開發

2023-06-07 15:25:19

Kafka版本日志

2025-02-08 08:10:00

2013-07-19 12:52:50

iOS中BlockiOS開發學習

2024-02-27 22:31:00

Feign動態代理核心

2023-02-22 07:04:05

自動機原理優化實踐

2010-08-09 08:48:46

File APIWeb

2009-11-04 15:54:20

Portlet入門企業門戶

2011-08-08 18:11:45

IOS 4Block UIActionShe

2010-02-26 17:54:54

python

2009-11-06 16:10:54

ClosureJavaScript開Google

2010-08-27 10:41:41

iPhone核心應用程序

2014-03-07 13:23:23

百度面試iOS

2009-08-25 13:48:01

Java EE架構企業級應用

2013-04-17 10:06:55

Google GlasMirror API

2011-06-28 10:42:38

Windows 8開發部門DevX

2023-12-07 08:07:47

Node流程代碼

2023-11-30 22:06:43

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美一区2区三区3区公司 | av在线天天 | 一区二区三区欧美 | 成人精品在线观看 | 欧美专区在线观看 | 日本免费黄色一级片 | 啪啪精品 | 久热爱| 国产精品福利网站 | 日本福利片 | 欧美jizzhd精品欧美巨大免费 | 成人av片在线观看 | 国产欧美精品一区二区色综合朱莉 | 国产毛片毛片 | 天天综合成人网 | 久久婷婷国产麻豆91 | 国产精产国品一二三产区视频 | 日韩在线欧美 | 精品欧美一区免费观看α√ | 99在线免费视频 | 羞视频在线观看 | 国产美女永久免费无遮挡 | 亚洲一区视频在线 | 欧美亚洲国产日韩 | 国产精品毛片一区二区在线看 | 91精品久久久久久久久久 | 国产一极毛片 | 天天色天天射天天干 | 国产小视频在线观看 | 欧美色综合天天久久综合精品 | 精品国产91 | 看片地址 | 国产在线一区二区三区 | 欧美精品一区二区三区在线播放 | 色播av| 免费视频一区二区 | 国产精品欧美一区喷水 | 狠狠操av| 欧美日韩1区 | 黄色av免费网站 | 久久久久久99|