iOS底層原理 - 窺探Block的本質(一)

通過窺探Block的底層實現(xiàn)苦银,解答以下問題

1.Block底層數(shù)據(jù)結構是什么,本質是什么
2.Block與其所訪問的外部變量的關系
3.Block的內(nèi)存管理

Block的本質是什么?是函數(shù)单绑?代碼塊嘱函?OC對象?

簡單起見列另,我們在main.m文件中寫一個沒有任何參數(shù)訪問的簡單的Block芽腾,通過分析其源碼窺探Block的本質
定義一個簡單的Block

typedef void(^MyBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyBlock block;
        block = ^{
            NSLog(@"this is block");
        };
    }
    return 0;
}

我們知道OC是基于C/C++實現(xiàn)的,接下來我們通過命令行xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名main.m文件轉化成對應的.cpp文件页衙,窺探Block的本質摊滔。

第一步:在.cpp文件中檢索int main(int argc, const char * argv[])定位main函數(shù)位置阴绢,從而找到我們寫的代碼

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        MyBlock block;
        block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    }
    return 0;
}

為了方便閱讀我們將上述源碼中的核心部分((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))中的強制類型轉換去掉,可以得到&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)艰躺,到此可以清楚的看到這里調用了一個函數(shù)__main_block_impl_0(...)呻袭,傳入了兩個參數(shù)__main_block_func_0__main_block_desc_0_DATA证舟。并將函數(shù)返回值的地址賦值給了block拣展。接下來我們分別分析函數(shù)及這兩個參數(shù)。

首先我們看下函數(shù)__main_block_impl_0
.cpp文件中進一步檢索函數(shù)__main_block_impl_0

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

通過上述源碼咽白,可以看到__main_block_impl_0(...)是結構體struct __main_block_impl_0的構造函數(shù)页响。也就是說Block的本質就是結構體struct __main_block_impl_0

繼續(xù)檢索傳入的參數(shù)__main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d9b536_mi_0);
}

可以看出這就是我們寫的NSLog(@"this is block");即block的實現(xiàn)部分篓足。

繼續(xù)檢索__main_block_desc_0_DATA

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)};

分析上述源碼可知,這是一個存儲了結構體struct __main_block_impl_0大小信息等描述信息的結構體闰蚕。

分析完入?yún)⒑笳煌希覀冊僖淮螌⒛抗饣氐浇Y構struct __main_block_impl_0

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

進一步分析其成員變量,其中struct __main_block_desc_0我們上邊已經(jīng)看過了没陡。接下來我們檢索成員變量struct __block_impl

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

可與看到一個很熟悉的指針isa指針涩哟,我們知道oc對象的基本特點就是有一個isa指針,看到這你可能會猜想block本身是否也是一個oc對象呢盼玄?
答案是肯定的贴彼,block本質的確是一個oc對象,下面我們將通過其他方式驗證這一點埃儿。我們知道幾乎所有的oc對象都繼承自NSObject锻弓,嘗試打印Block的父類。

NSLog(@"block class : %@", [block class]);
NSLog(@"block super class : %@", class_getSuperclass([block class]));
NSLog(@"block super super class : %@", class_getSuperclass(class_getSuperclass([block class])));
NSLog(@"block super super super class : %@", class_getSuperclass(class_getSuperclass(class_getSuperclass([block class]))));

// 輸出log
block class : __NSGlobalBlock__
block super class : __NSGlobalBlock
block super super class : NSBlock
block super super super class : NSObject

從上述log可以看出蝌箍,block本身的確是一個oc對象青灼,且其繼承自NSObject

Block對基礎數(shù)據(jù)類型的捕獲

定義三種基本類型變量,并在Block中訪問他們

int globalInt = 1; // 全局變量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyBlock block;
        static int staticInt = 5; // 靜態(tài)變量
        int autoInt = 10; // 自動變量(本質上是auto int autoInt = 10;省略了關鍵字auto妓盲,通常我們定義的都是這種變量)
        block = ^{
            NSLog(@"staticInt = %d", staticInt);
            NSLog(@"autoInt = %d", autoInt);
            NSLog(@"globalInt = %d", globalInt);
        };
        block();
    }
    return 0;
}

通過xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名得到對應的.cpp文件杂拨。
.cpp文件中檢索int main(int argc, const char * argv[])定位main函數(shù)

int globalInt = 1;
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        MyBlock block;
        static int staticInt = 5;
        int autoInt = 10;
        block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticInt, autoInt));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

觀察上述源碼,可以看到在構建block的結構體struct __main_block_impl_0時悯衬,比之前多傳了兩個參數(shù)&staticIntautoInt弹沽,但是并沒有傳globalInt,即:static變量傳了地址(地址傳遞)筋粗,局部auto變量傳了值(值傳遞)策橘,全局變量沒有傳遞。
接下來我們?nèi)?code>struct __main_block_impl_0中看下這些參數(shù)的處理

Block對基礎數(shù)據(jù)類型的捕獲.png

觀察上述代碼娜亿,可以看到結構體struct __main_block_impl_0中多了兩個成員變量int *staticInt;int autoInt;丽已,并且構造函數(shù)__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staticInt, int _autoInt, int flags=0) : staticInt(_staticInt), autoInt(_autoInt)將外邊傳過來的_staticInt_autoInt分別賦值給了int *staticInt;int autoInt;也就是說Block分別對靜態(tài)變量和自動變量進行了地址捕獲和值捕獲

在看下__main_block_func_0__main_block_desc_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *staticInt = __cself->staticInt; // 取結構體中的int型指針變量staticInt給局部int型指針變量staticInt
  int autoInt = __cself->autoInt; // 取結構體中int型變量autoInt給局部int型變量autoInt

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_7c19b4_mi_0, (*staticInt)); // 打印局部int型指針staticInt指向值
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_7c19b4_mi_1, autoInt);//打印局部變量autoInt的值
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_7c19b4_mi_2, globalInt);//打印全局變量globalInt的值
        }

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)};

觀察上述代碼可知__main_block_desc_0沒有變化

小結

  • Block不會捕獲全局變量
  • Block會對靜態(tài)變量進行地址捕獲
  • Block會對自動變量進行值捕獲

那么Block為什么這么做呢买决,認真思考下就會發(fā)現(xiàn)這是一個很合乎情理的設計沛婴,對于全局變量由于它是全局的泻蚊,本身就可以在任意地方訪問,所以Block自然不需要捕獲它秒旋,就像函數(shù)中訪問全局變量不需要將其當參數(shù)傳進去一樣尘奏;對于局部靜態(tài)變量炫加,首先我們想象一種使用場景铺然,Block及局部靜態(tài)變量是在一個普通函數(shù)中的赋铝,并且Block作為函數(shù)返回值使用革骨,在這種情況下助隧,因為出了函數(shù)我們就無法訪問到局部變量了筑凫,所以首先Block肯定要對這個靜態(tài)局部變量進行捕獲巍实,再者我們知道靜態(tài)變量不會隨著大括號即函數(shù)的結束而銷毀膝昆,所以Block只需要對其進行地址捕獲即可挠唆。對于普通的自動局部變量谒麦,由于其會被銷毀,所以需要對其進行值捕獲

Block對OC對象的捕獲

為了演示耻蛇,我們先創(chuàng)建一個類LJPerson

@interface LJPerson : NSObject

@property (copy, nonatomic) NSString    *name;
@property (assign, nonatomic) int       age;

@end

和基礎變量一樣,我們也創(chuàng)建三種不同的LJPerson變量刁赦,并在Block中訪問他們

typedef void(^MyBlock)(void);

LJPerson *globalPerson; // 全局變量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyBlock block;
        
        globalPerson = [[LJPerson alloc] init];
        globalPerson.name = @"globalPerson";
        globalPerson.age = 1;
        
        static LJPerson *staticPerson; // 靜態(tài)變量
        staticPerson = [[LJPerson alloc] init];
        staticPerson.name = @"staticPerson";
        staticPerson.age = 10;
        
        LJPerson *autoPerson = [[LJPerson alloc] init]; // 自動變量
        autoPerson.name = @"autoPerson";
        autoPerson.age = 20;
        
        block = ^{
            NSLog(@"globalPerson = %@", globalPerson.name);
            NSLog(@"staticPerson = %@", staticPerson.name);
            NSLog(@"autoPerson = %@", autoPerson.name);
        };
        block();
    }
    return 0;
}

同樣的生成對應的.cpp文件,并檢索main函數(shù)int main(int argc, const char * argv[])和結構體struct __main_block_impl_0

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        MyBlock block;

        globalPerson = ((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LJPerson"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)globalPerson, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d7ed57_mi_0);
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)globalPerson, sel_registerName("setAge:"), 1);

        static LJPerson *staticPerson;
        staticPerson = ((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LJPerson"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)staticPerson, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d7ed57_mi_1);
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)staticPerson, sel_registerName("setAge:"), 10);

        LJPerson *autoPerson = ((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LJPerson"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)autoPerson, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d7ed57_mi_2);
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)autoPerson, sel_registerName("setAge:"), 20);

        block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticPerson, autoPerson, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  LJPerson **staticPerson; // 地址捕獲
  LJPerson *autoPerson; // 值捕獲
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, LJPerson **_staticPerson, LJPerson *_autoPerson, int flags=0) : staticPerson(_staticPerson), autoPerson(_autoPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

觀察上述源碼波闹,可知Block對OC變量的捕獲機制和基礎數(shù)據(jù)類型一樣酝豪。即:Block對變量的捕獲方式只取決于其是全局變量、靜態(tài)變量精堕、還是自動變量孵淘。
那么Block對OC類型變量的處理與基礎數(shù)據(jù)類型變量的處理究竟有什么不同呢?
檢索Block的成員變量struct __main_block_desc_0

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

對比之前的源碼歹篓,可以發(fā)現(xiàn)這里多了兩個函數(shù)copy瘫证,dispose揉阎。
觀察結構體對象__main_block_desc_0_DATA的入?yún)ⅲ芍?code>copy背捌、dispose函數(shù)分別對應參數(shù)__main_block_copy_0毙籽、__main_block_dispose_0,檢索函數(shù)__main_block_copy_0毡庆、__main_block_dispose_0

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->staticPerson, (void*)src->staticPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->autoPerson, (void*)src->autoPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->staticPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->autoPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

觀察函數(shù)兩個函數(shù)坑赡,可以看到__main_block_copy_0內(nèi)部分別對staticPersonautoPerson對象各調用了一次_Block_object_assign方法么抗,__main_block_dispose_0內(nèi)部分別對staticPerson毅否、autoPerson各調用了一次_Block_object_dispose方法。也就是說Block除了捕獲OC對象的外蝇刀,還會對OC對象做內(nèi)存管理螟加。

小尾巴

到此我們已經(jīng)解決了開篇中的第1、2個問題吞琐,并且知道了Block會對OC對象做內(nèi)存管理捆探,那么在后續(xù)的文章里我們將繼續(xù)探索Block是如何進行內(nèi)存管理的以及當我們用__weak__block修飾變量時究竟是在做些什么

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末顽分,一起剝皮案震驚了整個濱河市徐许,隨后出現(xiàn)的幾起案子施蜜,更是在濱河造成了極大的恐慌卒蘸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缸沃,死亡現(xiàn)場離奇詭異修械,居然都是意外死亡,警方通過查閱死者的電腦和手機肯污,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門蹦渣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人柬唯,你說我怎么就攤上這事∈纾” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵书在,是天一觀的道長拆又。 經(jīng)常有香客問我,道長遏乔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任凉翻,我火速辦了婚禮捻激,結果婚禮上,老公的妹妹穿的比我還像新娘垃杖。我一直安慰自己丈屹,他們只是感情好调俘,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布彩库。 她就那樣靜靜地躺著先蒋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪竞漾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天鳞仙,我揣著相機與錄音叨襟,去河邊找鬼。 笑死,一個胖子當著我的面吹牛爹梁,可吹牛的內(nèi)容都是我干的提澎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼积糯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了看成?” 一聲冷哼從身側響起跨嘉,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎祠乃,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琴拧,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡嘱支,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赢织。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馍盟。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡茧吊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搓侄,到底是詐尸還是另有隱情,我是刑警寧澤芯侥,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站柱查,受9級特大地震影響,放射性物質發(fā)生泄漏研乒。R本人自食惡果不足惜淋硝,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望竿报。 院中可真熱鬧,春花似錦继谚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捂襟。三九已至,卻和暖如春葬荷,著一層夾襖步出監(jiān)牢的瞬間纽帖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工扒吁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留室囊,地道東北人雕崩。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓盼铁,卻偏偏與公主長得像尝偎,于是被迫代替她去往敵國和親饶火。 傳聞我的和親對象是個殘疾皇子鹏控,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容