iOS 進(jìn)階知識(shí)點(diǎn)整理--分析block

本文將圍繞以下幾個(gè)問(wèn)題分析block:

  1. block是不是函數(shù)指針饭望?如果不是它們有什么區(qū)別固额?

  2. block修改局部變量為什么必須要加__block修飾完残?

  3. block為什么會(huì)產(chǎn)生循環(huán)引用聋袋?應(yīng)該怎么避免音比?

要完全搞清楚這幾個(gè)問(wèn)題妖滔,首先我們需要搞清block的本質(zhì)是什么隧哮?

結(jié)論:在iOS中block本質(zhì)上就是一個(gè)oc對(duì)象,內(nèi)部同其他OC對(duì)象一樣有一個(gè)isa指針铛楣。block是一個(gè)將函數(shù)定義近迁、實(shí)現(xiàn)、調(diào)用封裝在一起的OC對(duì)象簸州;

第一步:先寫(xiě)一個(gè)簡(jiǎn)單的測(cè)試block

- (void)test {
    void(^block)(NSInteger, NSInteger) = ^(NSInteger a, NSInteger b){
        NSLog(@"block a = %ld, b = %ld",a, b);
    };
    block(10, 20);
}

用命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m 將OC文件轉(zhuǎn)換成c++代碼查看OC內(nèi)部實(shí)現(xiàn)結(jié)構(gòu)

static void _I_ViewController_test(ViewController * self, SEL _cmd) {
    void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger,                 NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA));
    ((void (*)(__block_impl *, NSInteger, NSInteger))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 20);
}

從上述代碼可以看到鉴竭,block的定義被轉(zhuǎn)換成了

void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA));

實(shí)際上是把函數(shù)__ViewController__test_block_impl_0的函數(shù)地址賦值給了block,\color{red}{注意:block與函數(shù)指針有一點(diǎn)相似岸浑,但block不是函數(shù)指針}

應(yīng)該在這段代碼之上我們可以看到__ViewController__test_block_impl_0其實(shí)是一個(gè)結(jié)構(gòu)體搏存,結(jié)構(gòu)如下:

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

該結(jié)構(gòu)體內(nèi)部包含了兩個(gè)變量implDesc和一個(gè)同名構(gòu)造函數(shù)__ViewController__test_block_impl_0矢洲,全局搜索__block_impl找到其結(jié)構(gòu)如下:

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

  • isa:類(lèi)型指針璧眠,這里代表block的類(lèi)型

  • Flags:這里從構(gòu)造函數(shù)的調(diào)用看到它有一個(gè)默認(rèn)值0,并未對(duì)其賦值读虏,也許在更深層有其它的用處

  • Reserved:應(yīng)該同F(xiàn)lags差不多在底層有使用吧

  • FuncPtr:函數(shù)指針责静,這里指向__ViewController__test_block_func_0函數(shù)

    到這里足以證明block就是一個(gè)OC對(duì)象,同樣有這一個(gè)指向它類(lèi)型的isa指針盖桥;結(jié)構(gòu)體__ViewController__test_block_impl_0的下方應(yīng)該就能看到另一個(gè)變量Desc其結(jié)構(gòu)如下:

    static struct __ViewController__test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __ViewController__test_block_desc_0_DATA = { 0, sizeof(struct __ViewController__test_block_impl_0)};
    
    

    __ViewController__test_block_desc_0包含了兩個(gè)變量灾螃,并在定義時(shí)創(chuàng)建了一個(gè)__ViewController__test_block_desc_0_DATA結(jié)構(gòu)體

  • reserved:字面意思保留,應(yīng)也是一個(gè)內(nèi)部使用字段揩徊,這里能看到為其賦值0

  • Block_size:sizeof()腰鬼,這里代表結(jié)構(gòu)體__ViewController__test_block_impl_0的內(nèi)存占用大小

void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA));

回看我們的block定義,__ViewController__test_block_impl_0調(diào)用時(shí)還傳入了一個(gè)__ViewController__test_block_func_0函數(shù)

static void __ViewController__test_block_func_0(struct __ViewController__test_block_impl_0 *__cself, NSInteger a, NSInteger b) {
  // NSLog(@"block a = %ld, b = %ld",a, b); OC block 內(nèi)部代碼
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_dm_tq28fyls0cq42nl6fdhyqzd40000gn_T_ViewController_939d4d_mi_0,a, b);
        }

從函數(shù)結(jié)構(gòu)就能看出該函數(shù)其實(shí)就是block{}里面的實(shí)現(xiàn)塑荒,因此我們得出block就是OC對(duì)象熄赡,內(nèi)部封裝了c函數(shù)定義、實(shí)現(xiàn)齿税、和函數(shù)調(diào)用彼硫;

1.那么block是不是函數(shù)指針?如果不是它們有什么區(qū)別?

綜上所述乌助,block不是函數(shù)指針溜在。函數(shù)指針是指向一段預(yù)先定義好的可執(zhí)行代碼,且該函數(shù)地址是在編譯時(shí)就已經(jīng)確定好的他托,函數(shù)內(nèi)只能訪(fǎng)問(wèn)全局變量。block是OC對(duì)象仆葡,可以在運(yùn)行時(shí)自由創(chuàng)建赏参,不同作用域內(nèi)創(chuàng)建的block對(duì)象的生命周期也不一樣,通常動(dòng)態(tài)創(chuàng)建的block都存儲(chǔ)在棧上沿盅,可以調(diào)用[block copy]將其拷貝到堆上延長(zhǎng)生命周期把篓,另外block對(duì)局部變量擁有讀的權(quán)限,對(duì)使用__block修飾的局部變量擁有讀寫(xiě)權(quán)限

2.block修改局部變量為什么必須要加__block修飾腰涧?
- (void)test {
 int j = 10;
 int g = 10;
 static int s = 10;
 __block int i = 10;
 void(^block)(NSInteger, NSInteger) = ^(NSInteger a, NSInteger b){
 i++;
 s++;
 self;
// j++; 報(bào)錯(cuò):Variable is not assignable (missing __block type specifier)
 NSLog(@"block a = %ld, b = %ld",a, b);
 NSLog(@"block i = %d, j = %d",i, j);
 };
 block(10, 20);
}

在原有test方法內(nèi)新增成員變量j韧掩、g,static變量s窖铡,__block變量i,再次執(zhí)行命令轉(zhuǎn)換為c++疗锐,結(jié)果如下:

static void _I_ViewController_test(ViewController * self, SEL _cmd) {
    int j = 10;
    int g = 10;
    static int s = 10;
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 10};
    void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA, &s, self, j, g, (__Block_byref_i_0 *)&i, 570425344));
    ((void (*)(__block_impl *, NSInteger, NSInteger))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 20);
}

可以看到前三個(gè)變量并沒(méi)有什么變化,只有__block 變量i被轉(zhuǎn)換成了__Block_byref_i_0結(jié)構(gòu)體:

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

__isa=(void*)0,__forwarding=(__Block_byref_i_0 *)&i指向i自身地址的指針费彼,__flags=0,__size=sizeof(__Block_byref_i_0),i=10為原來(lái)i的值滑臊,修改后block的實(shí)現(xiàn)變成如下:

struct __ViewController__test_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__test_block_desc_0* Desc;
  int *s;
  ViewController *self;
  int j;
  int g;
  __Block_byref_i_0 *i; // by ref
  __ViewController__test_block_impl_0(void *fp, struct __ViewController__test_block_desc_0 *desc, int *_s, ViewController *_self, int _j, int _g, __Block_byref_i_0 *_i, int flags=0) : s(_s), self(_self), j(_j), g(_g), i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

仔細(xì)看會(huì)發(fā)現(xiàn)在函數(shù)__ViewController__test_block_impl_0()后面還有這么一截: s(_s), self(_self), j(_j), g(_g), i(_i->__forwarding)這一截都是對(duì)結(jié)構(gòu)體__ViewController__test_block_impl_0內(nèi)的自定義變量進(jìn)行復(fù)制s(_s)等價(jià)于s = _s其他同理,結(jié)合改變后的block定義:

    void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA, &s, self, j, g, (__Block_byref_i_0 *)&i, 570425344));

看調(diào)用__ViewController__test_block_impl_0函數(shù)時(shí)傳入的參數(shù)((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA, &s, self, j, g, (__Block_byref_i_0 *)&i, 570425344)前兩個(gè)同修改前一樣前面有介紹箍铲,從第三個(gè)開(kāi)始分別為:靜態(tài)變量s的地址雇卷、當(dāng)前類(lèi)對(duì)象self、j和g傳入的都是int類(lèi)型數(shù)值颠猴、(__block變量轉(zhuǎn)換后)結(jié)構(gòu)體i的地址关划,最后那個(gè)數(shù)字對(duì)應(yīng)的是flags

那么,block修改局部變量為什么必須要加__block修飾翘瓮?

因?yàn)橹郏褂胈_block修飾后的變量在block內(nèi)部會(huì)創(chuàng)建一個(gè)相應(yīng)的結(jié)構(gòu)體,該結(jié)構(gòu)體內(nèi)保留了變量的值和地址春畔,block內(nèi)部還為結(jié)構(gòu)體定義了一個(gè)指針變量脱货,內(nèi)部對(duì)原來(lái)變量的讀寫(xiě)都是通過(guò)這個(gè)結(jié)構(gòu)體指針來(lái)實(shí)現(xiàn)的,所以就完成了對(duì)__block變量的修改律姨;更多細(xì)節(jié)

3.block為什么會(huì)產(chǎn)生循環(huán)引用振峻?應(yīng)該怎么避免?

通過(guò)以上內(nèi)容分析择份,我們知道block內(nèi)會(huì)對(duì)外部變量扣孟、對(duì)象產(chǎn)生一個(gè)引用關(guān)系,OC對(duì)象在沒(méi)有指定強(qiáng)弱引用的情況下荣赶,默認(rèn)是強(qiáng)引用凤价,那么一個(gè)對(duì)象如果間接或直接擁有block鸽斟,block內(nèi)部又擁有該對(duì)象那么就會(huì)產(chǎn)生循環(huán)引用

如圖1中object----(strong)---->object1----(strong)---->block----(strong)---->object就是一個(gè)間接性引用產(chǎn)生的循環(huán)引用

圖1

解決圖1中問(wèn)題其實(shí)只需將三條箭頭中任意一條改用弱引用就能打破循環(huán)保留問(wèn)題,但在實(shí)際開(kāi)發(fā)過(guò)程中我們調(diào)用block一般都不是及時(shí)性的利诺,所以如果object----(weak)---->object1或者object1----(weak)---->block當(dāng)我們?cè)谡{(diào)用block時(shí)可能對(duì)象已經(jīng)被釋放富蓄,代碼運(yùn)行會(huì)違背我們預(yù)期效果,即解決block循環(huán)引用問(wèn)題慢逾,我們推崇將block-->object外部改成弱引用立倍,block內(nèi)部在將弱引用對(duì)象轉(zhuǎn)強(qiáng)引用避免block內(nèi)部訪(fǎng)問(wèn)還沒(méi)結(jié)束object就被釋放問(wèn)題,如圖2:

圖2

參考文章:
iOS Block原理探究以及循環(huán)引用的問(wèn)題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末侣滩,一起剝皮案震驚了整個(gè)濱河市口注,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌君珠,老刑警劉巖寝志,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異策添,居然都是意外死亡材部,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)舰攒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)败富,“玉大人,你說(shuō)我怎么就攤上這事摩窃∈薅#” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵猾愿,是天一觀的道長(zhǎng)鹦聪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蒂秘,這世上最難降的妖魔是什么泽本? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮姻僧,結(jié)果婚禮上规丽,老公的妹妹穿的比我還像新娘。我一直安慰自己撇贺,他們只是感情好赌莺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著松嘶,像睡著了一般艘狭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,727評(píng)論 1 305
  • 那天巢音,我揣著相機(jī)與錄音遵倦,去河邊找鬼。 笑死官撼,一個(gè)胖子當(dāng)著我的面吹牛梧躺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播歧寺,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼燥狰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了斜筐?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蛀缝,失蹤者是張志新(化名)和其女友劉穎顷链,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體屈梁,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗤练,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了在讶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片煞抬。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖构哺,靈堂內(nèi)的尸體忽然破棺而出革答,到底是詐尸還是另有隱情,我是刑警寧澤曙强,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布残拐,位于F島的核電站,受9級(jí)特大地震影響碟嘴,放射性物質(zhì)發(fā)生泄漏溪食。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一娜扇、第九天 我趴在偏房一處隱蔽的房頂上張望错沃。 院中可真熱鬧,春花似錦雀瓢、人聲如沸枢析。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)登疗。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辐益,已是汗流浹背断傲。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留智政,地道東北人认罩。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像续捂,于是被迫代替她去往敵國(guó)和親垦垂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

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

  • 1. Block的底層結(jié)構(gòu) 以下是一個(gè)沒(méi)有參數(shù)和返回值的最簡(jiǎn)單的Block: 為了探索Block的底層結(jié)構(gòu)牙瓢,需要將...
    再好一點(diǎn)點(diǎn)閱讀 466評(píng)論 0 4
  • block.png iOS代碼塊Block 概述 代碼塊Block是蘋(píng)果在iOS4開(kāi)始引入的對(duì)C語(yǔ)言的擴(kuò)展,用來(lái)實(shí)...
    全棧農(nóng)民工閱讀 589評(píng)論 0 1
  • iOS代碼塊Block 概述 代碼塊Block是蘋(píng)果在iOS4開(kāi)始引入的對(duì)C語(yǔ)言的擴(kuò)展,用來(lái)實(shí)現(xiàn)匿名函數(shù)的特性,B...
    smile刺客閱讀 2,349評(píng)論 2 26
  • 人與人初識(shí)劫拗,都只會(huì)覺(jué)得對(duì)方眉眼可愛(ài)志趣相投,大抵是因?yàn)榇蠹疑钪谝挥∠蟮闹匾苑耍谑强偰茌p易化解心中不悅页慷,給旁...
    最近愛(ài)放空閱讀 179評(píng)論 0 0
  • dasfsad
    adamasp閱讀 74評(píng)論 0 1