Block底層實現(xiàn)分析02-__block使用

注:分析參考 MJ底層原理班 內(nèi)容,本著自己學(xué)習(xí)原則記錄

本文使用的源碼為objc4-723

轉(zhuǎn) C++ 使用的命令 :
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

1 block 內(nèi)部不能修改基本數(shù)據(jù)類型 auto 變量分析

1.1 轉(zhuǎn)碼后從函數(shù)作用域和值捕獲邏輯分析

1.2 static 的基本數(shù)據(jù)變量可以在 block 內(nèi)修飾分析

1.3 全局變量當(dāng)然能在 block 內(nèi)被修改

  • 因為全局變量的全局特性:全局訪問,所以可以隨處修改


2 __block修飾 auto 變量

2.1 一般情況我們都不想改變基本數(shù)據(jù)類型的 auto 變量類型,但還是希望能夠在 block 內(nèi)部修改 auto 變量的值

  • 也就是說不想使用static修飾(這樣會改變變量內(nèi)存位置,static修飾時會使變量從棧區(qū)變成數(shù)據(jù)區(qū),變量的聲明周期被無限延長)
  • 也不會將基本數(shù)據(jù)類型的 auto 變量類型 轉(zhuǎn)為 全局變量類型

2.2 使用__block修飾 auto 變量

  • 可以讓auto變量在 block 內(nèi)被修改


  • __block不能修飾靜態(tài)變量(static)


  • __block不能修飾全局變量、靜態(tài)變量(static)

2.3 __block修改 auto變量的轉(zhuǎn)碼分析

  1. 編譯器會將__block變量包裝成一個對象
  2. __block變量的 age 對象內(nèi)存分析
源碼對象 內(nèi)存結(jié)構(gòu) 備注

3 __block修飾的 age 及 block 外面訪問的 age 對比

3.1 這些 age 都是同一個 age 嗎?

3.2 通過打印和源碼分析

3.2.1 從打印上看很明顯,后兩個(2、3) age 地址相同;
3.2.2 從源碼上看,2和3 的 age 訪問方式都是age.__forwarding->age,被
__block修飾后的 age 它是__Block_byref_age_0類型結(jié)構(gòu)體
3.2.3 從源碼上看,第1個打印 age 地址并不是我們期待的,因為打印出來的這個 age 地址是__Block_byref_age_0類型結(jié)構(gòu)體內(nèi)int age成員變量的地址值

3.3 通過內(nèi)存地址疊加分析

3.3.1 執(zhí)行__block int age = 10;代碼后,age 變量即變成__Block_byref_age_0類型的結(jié)構(gòu)體,那么此時 age 的地址就是該結(jié)構(gòu)體第一個成員變量的地址,age 結(jié)構(gòu)體的地址 等于 __isa 指向的值

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

3.3.2 而后續(xù)的(2、3)訪問的 age,都是通過age.__forwarding->age方式訪問,訪問的 age 實際是結(jié)構(gòu)體內(nèi)部的 int age變量,那么 age成員變量地址應(yīng)該是結(jié)構(gòu)體起始地址加上age 成員變量前的其他成員量占的內(nèi)存字節(jié)數(shù)之和

3.3.3 通過底層轉(zhuǎn)換形式,再次打印 age 結(jié)構(gòu)體地址

通過上圖方式,我們已經(jīng)能夠正確地獲取到 age 結(jié)構(gòu)體的地址,那么驗證一下,age 成員變量地址,是不是等于 age 結(jié)構(gòu)體地址加上age 成員變量之前的其他變量所占內(nèi)存字節(jié)之和呢?(答案當(dāng)然是肯定啦)即:0x100604600 + (8+8+4+4) = 0x100604618

image.png

4 __block的內(nèi)存管理

我們可以從Block底層實現(xiàn)分析01的第6點知道,當(dāng) block 內(nèi)部訪問的變量是對象類型或被__block修飾的基本變量類型時,block 的結(jié)構(gòu)體中 Desc 結(jié)構(gòu)體內(nèi)會多出兩個用于處理對象引用問題的函數(shù)成員變量,如下:

重點區(qū)別在struct __main_block_desc_0的結(jié)構(gòu)體成員上

4.1 訪問基本 auto 變量 block 轉(zhuǎn) C++后源碼

4.2 訪問對象類型的 auto 變量的 block 轉(zhuǎn) C++后源碼

訪問對象類型的 auto 變量struct __main_block_desc_0的結(jié)構(gòu)體成員比訪問`基本 auto 變量的多了兩個成員

void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);

4.3 內(nèi)存管理函數(shù)的實現(xiàn)

它們的作用就是用來處理對象類型的 auto 變量引用計數(shù)問題

4.4 訪問__block修飾的 auto 變量的 block 轉(zhuǎn) C++后源碼

4.5 分別對比block 訪問__weak對象、__block對象默認(rèn) strong 對象內(nèi)存管理

  1. OC 測試代碼


  2. 轉(zhuǎn)C++后


block(指定都是堆block,應(yīng)為只有堆block才會引用對象)內(nèi)部訪問的對象類型,會根據(jù)對應(yīng)的強(qiáng)弱修飾符__strong/__weak,調(diào)用對應(yīng)的函數(shù)_Block_object_assign進(jìn)行內(nèi)存處理

  • strong 修飾的,block 會強(qiáng)引用對象
  • weak 修飾,block 不會強(qiáng)引用對象
  • __block修飾,block 會強(qiáng)引用該變量對象

4.6 __block的內(nèi)存管理總結(jié)

  1. 當(dāng)block在棧上時,并不會對__block變量產(chǎn)生強(qiáng)引用

  2. 當(dāng)block被copy到堆時

  • 會調(diào)用block內(nèi)部的copy函數(shù)
  • copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
  • _Block_object_assign函數(shù)會對__block變量形成強(qiáng)引用(retain)
  1. 當(dāng)block從堆中移除時
  • 會調(diào)用block內(nèi)部的dispose函數(shù)
  • dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
  • _Block_object_dispose函數(shù)會自動釋放引用的__block變量(release)

4.7 __block修飾的對象age 變量,仍然是存在于棧上,棧上變量,堆上的block,如何關(guān)聯(lián)引用?

  • 實際上,當(dāng) block 被 copy 到堆上時,其訪問的__block變量也會被 copy 到堆上,如下情況圖解
  1. 將 block copy到堆



  2. 廢棄 block



5 block內(nèi)部訪問 對象類型的auto變量__block變量的內(nèi)存管理總結(jié)

  1. 當(dāng)block在棧上時,對它們都不會產(chǎn)生強(qiáng)引用

  2. 當(dāng)block拷貝到堆上時,都會通過copy函數(shù)來處理它們
    __block變量(假設(shè)變量名叫做a)

_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
  1. 對象類型的auto變量(假設(shè)變量名叫做p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
  1. 當(dāng)block從堆上移除時,都會通過dispose函數(shù)來釋放它們
    __block變量(假設(shè)變量名叫做a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
  1. 對象類型的auto變量(假設(shè)變量名叫做p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

6 __block__forwarding指針

下述訪問 age 的情況:



不知道你會不會奇怪,為什么訪問的 age 是通過age->__forwarding->age方式訪問?為什么要繞一大個彎來獲取呢?
其實這個與變量的棧堆拷貝有關(guān)的,從第4.7點中我們知道,__block修飾的變量會隨 block 的棧堆位置變化而相應(yīng)地發(fā)生變化,如下圖示:

  • 將 block copy到堆



那在__block變量被拷貝時候,__forwarding指針也會相應(yīng)發(fā)生變化

  • 證據(jù)1:同樣的age->__forwarding->age訪問方式,1的情況是,__block變量age結(jié)構(gòu)體 還在空間,所以內(nèi)部的 age 成員變量地址是棧地址樣式(比較長??),而2和3時候,__block變量 age 已經(jīng) copy 到控件,所以對應(yīng)的 age 成員變量地址變?yōu)槎训刂窐邮?比較短??)

你同樣可以打印一個局部int height變量地址,與其他打印的地址進(jìn)行對比,同在堆或棧的變量或?qū)ο蟮牡刂凡粫嗖钐h(yuǎn)(后續(xù)有機(jī)會,會詳細(xì)分析堆棧內(nèi)存結(jié)構(gòu)相關(guān)知識)

  • 證據(jù)2,在 MRC 環(huán)境下,下面圖中代碼足以證明 age 結(jié)構(gòu)體的forwarding 指針會如前面__forwarding變化圖那樣,在 age結(jié)構(gòu)體被堆棧block 捕獲后,其值會發(fā)生對應(yīng)的變化

7 被__block修飾的對象類型

7.1 被__block修飾的基本數(shù)據(jù)類型變量,在底層會將基本變量包裝成成一個結(jié)構(gòu)體對象

7.2 那被__block修飾的對象類型呢?

7.3 對象類型會被包裝成 __Block_byref_person_0類型 block,block__block personperson之間的關(guān)系

7.4 總結(jié):被__block修飾的對象類型內(nèi)存管理

  1. 當(dāng)__block變量在棧上時,不會對指向的對象產(chǎn)生強(qiáng)引用

  2. 當(dāng)__block變量被copy到堆時

  • 會調(diào)用__block變量內(nèi)部的copy函數(shù)
  • copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
  • _Block_object_assign函數(shù)會根據(jù)所指向?qū)ο蟮男揎椃?code>__strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用
  • 注意:這里僅限于ARC時會retain,MRC時不會retain
  1. 如果__block變量從堆上移除
  • 會調(diào)用__block變量內(nèi)部的dispose函數(shù)
  • dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
    _Block_object_dispose函數(shù)會自動釋放指向的對象(release

8 驗證7.4總結(jié)

8.1 首先將環(huán)境調(diào)至 MRC,執(zhí)行下述代碼,理解清楚:棧 block堆 block,棧的__block對象,和堆的__block對象的獲取,可以參考上述第6點去理解

這里就不再用文字詳細(xì)敘述了(用文字分析思考及邏輯變化過程需要太多篇幅了??,如有不懂,歡迎微信QQ討論,底部評論不會及時回復(fù),而且評論區(qū)不能貼圖解析很麻煩)
Demo:BlockTest-__block-對象類型01

8.2 驗證:當(dāng)__block變量在棧上時,不會對指向的對象產(chǎn)生強(qiáng)引用

  • MRC 環(huán)境


當(dāng) Person 第一次執(zhí)行完 release 操作時,即銷毀,證明 __block person 結(jié)構(gòu)體并沒有強(qiáng)引用 person。
Demo:BlockTest-__block-對象類型02

8.3 驗證:當(dāng)__block變量被copy到堆時

  1. MRC 環(huán)境
  • 通過對棧 block 進(jìn)行 copy 操作,轉(zhuǎn)為堆 block,堆 block 內(nèi)捕獲的 __block person 結(jié)構(gòu)體也會被 copy 到堆上,但是__block person 結(jié)構(gòu)體不會對 person 對象進(jìn)行強(qiáng)引用


Demo:BlockTest-__block-對象類型03

  1. ARC 環(huán)境
  • __strong


  • __weak


Demo:BlockTest-__block-對象類型04

前面測試用的代碼,開始沒想過要保存的,后面為了方便自己調(diào)試和截圖就補(bǔ)上一點點 demo 代碼了,不過沒有也沒關(guān)系,截圖也已經(jīng)非常清晰,自己敲一遍好了,理解更深刻。


文/Jacob_LJ(簡書作者)
PS:如非特別說明,所有文章均為原創(chuàng)作品,著作權(quán)歸作者所有,轉(zhuǎn)載需聯(lián)系作者獲得授權(quán),并注明出處,所有打賞均歸本人所有!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容