iOS - block原理解讀(一)

前言

block在網(wǎng)絡(luò)上的文章也比較多,
本文將開發(fā)中block使用細(xì)節(jié)和block實(shí)現(xiàn)原理結(jié)合起來,
加上個人的理解,
幫助大家更好地理解block和使用block。

問題

  • block的意義
  • block為什么不能修改外部變量?這里的外部變量又指的是什么?
  • 被block引用的對象,引用計數(shù)為何+=2?
  • 循環(huán)引用究竟是為何引起的?
  • __block 又是什么原理?
  • block與copy

等等

block產(chǎn)生的意義

程序始終都要遵循逐行執(zhí)行的原則,
而block 可以理解為 邏輯觸發(fā)執(zhí)行
定時器 可以理解為 時間觸發(fā)執(zhí)行

舉個有點(diǎn)意思的例子:

背景:我現(xiàn)在身處異世界,我有很多酷炫的技能
我現(xiàn)在有這樣一個技能,發(fā)動這個技能,我可以在當(dāng)前這個地方留一個分身并安排好任務(wù),然后我可以繼續(xù)做我自己的事情了,等我再次使用這個技能時,我的分身將開始處理這項(xiàng)任務(wù),處理完成后,我的分身將會消失。

隱含的問題:給分身安排具體任務(wù)的時候,這個任務(wù)在未來是否能夠完成是未知的,因?yàn)槲覀儾恢牢磥頃l(fā)生什么,
代碼亦是如此

進(jìn)一步理解:程序是嚴(yán)密而又真實(shí)的,所以并不存在什么高科技呀,黑魔法呀~
block其實(shí)就是用程序?qū)崿F(xiàn)了代碼緩存和對象緩存,相應(yīng)的對象緩存在block對象中,
執(zhí)行block,就是把緩存的代碼執(zhí)行一遍,而相應(yīng)的對象的狀態(tài),可能會因?yàn)閳?zhí)行完緩存下來的代碼而發(fā)生變化。

到此,希望你對block會產(chǎn)生了那么一點(diǎn)點(diǎn)的興趣~

依舊還是要從源碼說起


強(qiáng)調(diào):以下講的變量a 默認(rèn)指的是 自動變量auto,不是對象類型


#import <UIKit/UIKit.h>

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        int a = 0;
        void (^block)(void) = ^{
            NSLog(@"%d",a);
        };
        
       block();

        return 0;
    }
}

將其翻譯成底層c++文件, 一點(diǎn)一點(diǎn)看
main函數(shù)里的代碼

int a = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

這是一大串什么?
不要著急,我們從上到下,從左往右進(jìn)行說明。
首先,block初始化這一行

// 原代碼
void (^block)(void) = ^{
       NSLog(@"%d",a);
 };
// 翻譯成c++代碼
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
// 簡化后
block =  &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,a);
  1. void (*block)(void) 函數(shù)指針,參數(shù)void,返回值void

  2. ((void (*)()) 上面函數(shù)指針的類型,作用是強(qiáng)轉(zhuǎn)

  3. &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a)) 分解來看

分解第一步:__main_block_impl_0 組成

__main_block_impl_0是一個結(jié)構(gòu)體,包含一個構(gòu)造函數(shù)和三個變量
一個普通的結(jié)構(gòu)體類型,一個結(jié)構(gòu)體指針類型,
還有一個和外部的變量a,名稱一樣,類型一樣。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
分解第二步:__main_block_impl_0 的 構(gòu)造函數(shù)

第一個參數(shù)void *fp,傳入的是 &__main_block_func_0

__main_block_func_0是一個靜態(tài)函數(shù),
它的參數(shù)又是__main_block_impl_0這個結(jié)構(gòu)體指針

即 FuncPtr 存儲的是 __main_block_func_0 靜態(tài)函數(shù)地址

impl.FuncPtr = fp;
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_lb_tby1gwds2fnb89dzkf4cq3xh0000gn_T_main_9bc6d9_mi_0,a);
}

是不是快要繞暈了呢?

在靜態(tài)函數(shù)__main_block_func_0內(nèi),
先是獲取__main_block_impl_0結(jié)構(gòu)體內(nèi)的變量a,然后打印出來。

從這一點(diǎn)可以看出 靜態(tài)函數(shù)的作用就是寫在block內(nèi)部的代碼的容器和入口。

而__main_block_impl_0結(jié)構(gòu)體內(nèi)的變量a的值來自外部,是在結(jié)構(gòu)體的構(gòu)造函數(shù)內(nèi)進(jìn)行了賦值操作。

得到如下結(jié)論:

獲取到的基礎(chǔ)變量的值在block初始化的時候已經(jīng)確定了,
block外部的變量a在后續(xù)無論做什么操作,都不會影響block內(nèi)部保存的變量a
這就是在block內(nèi)部直接修改自動變量會報錯的原因,
如果這里直接允許修改了,在目前條件下,也僅僅能做到block內(nèi)部的變量a進(jìn)行重新賦值操作,和外部變量a沒有關(guān)系,產(chǎn)生歧義。

示例圖.png
當(dāng)然,使用__block修飾的自動變量可以進(jìn)行修改,那么又是什么原理呢?我們先繼續(xù)解讀當(dāng)前的源碼

第二個參數(shù) &__main_block_desc_0
__main_block_desc_0靜態(tài)結(jié)構(gòu)體存儲的是block的基礎(chǔ)信息

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

第三個參數(shù)為我們用到的外部的變量a,賦值給結(jié)構(gòu)體內(nèi)的變量a

第四個參數(shù)沒有傳,默認(rèn)為0 (輔助變量,可以忽略,不會影響block的理解)

分解第三步:__main_block_impl_0 中的 __block_impl

存儲的block的信息,相應(yīng)的參數(shù)在構(gòu)造函數(shù)內(nèi)進(jìn)行了賦值操作

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

最后 block初始化代碼和執(zhí)行代碼放在一起看

void (^block)(void) = ^{
       NSLog(@"%d",a);
 };
// c++代碼
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
// 簡化
block =  &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a)

block();
// c++代碼
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
// 簡化
block->FuncPtr(block);

block->FuncPtr 指的就是 __main_block_func_0這個函數(shù)的地址,參數(shù)為block本身

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_lb_tby1gwds2fnb89dzkf4cq3xh0000gn_T_main_19f603_mi_0,a);
 }

串起來再看block構(gòu)造

block初始化:block 通過 __main_block_impl_0結(jié)構(gòu)體構(gòu)造函數(shù)進(jìn)行初始化,同時生成__main_block_func_0靜態(tài)函數(shù),并將其地址以及其他相關(guān)信息儲存在__block_impl這個結(jié)構(gòu)體成員變量中。
其中,__block_impl這個結(jié)構(gòu)體成員變量是__main_block_impl_0的首地址。

block調(diào)用:block指針指向的是__main_block_impl_0 的首地址,即__block_impl的地址,所以可以強(qiáng)轉(zhuǎn)為(__block_impl *)類型,并訪問其成員FuncPtr,指向的是靜態(tài)函數(shù)地址,并傳入?yún)?shù)__main_block_impl_0,也就是block自己。

名稱 類型 是否隨block內(nèi)容改變 生成順序 說明
__block_impl 結(jié)構(gòu)體 NO 1 底層結(jié)構(gòu)體,屬于__main_block_impl_0成員
__main_block_impl_0 結(jié)構(gòu)體 YES 2 緩存變量/對象,主結(jié)構(gòu)體
__main_block_func_0 靜態(tài)函數(shù) YES 2 緩存代碼,地址存放在__block_impl中

該緩存代碼指的是:

緩存代碼一詞,代碼的意思.png

總結(jié):
將外部變量/對象的信息緩存在__main_block_impl_0中,
將代碼緩存在靜態(tài)函數(shù)中,
靜態(tài)函數(shù)在緩存代碼的時候需要用到外部變量/對象的信息
執(zhí)行block就是執(zhí)行了該靜態(tài)函數(shù)


如果到此有不理解的地方可能c++基礎(chǔ)較薄弱,百度一下輔助查看


最后

本文說明了block的用意,揭開了黑魔法的初級面紗,細(xì)講了block基礎(chǔ)源碼,解釋了基礎(chǔ)類型的變量為何不能在block內(nèi)部直接修改

后續(xù)會借此基礎(chǔ)之上,繼續(xù)解讀目錄中的問題

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

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

  • 前言 Blocks是C語言的擴(kuò)充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,881評論 0 23
  • 面試題 block的原理是怎樣的?本質(zhì)是什么? __block的作用是什么?有什么使用注意點(diǎn)? block的屬性修...
    xx_cc閱讀 14,030評論 10 77
  • 《Objective-C高級編程》這本書就講了三個東西:自動引用計數(shù)、block、GCD,偏向于從原理上對這些內(nèi)容...
    WeiHing閱讀 10,105評論 10 69
  • 年前賀歲檔電影預(yù)售時我只買了《瘋狂的外星人》(預(yù)售票房第一位),完全忽略了《流浪地球》。 看了《瘋狂的外星人》,略...
    珊言三語閱讀 508評論 1 5
  • 我穿越時空來到一個完全陌生的城市,那是一個死氣沉沉的完全沒有生氣的城市,城市里有很多人,但是很奇怪大家都是黑白色的...
    盈昃閱讀 451評論 0 0

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