block的實(shí)質(zhì)

1.什么是block

block是將函數(shù)及其執(zhí)行上下文封裝起來(lái)的對(duì)象,是一段代碼塊,是一個(gè)結(jié)構(gòu)體送爸,里面有isa指針指向自己的類(global malloc stack),有desc結(jié)構(gòu)體描述block的信息占遥,__forwarding指向自己或堆上自己的地址幼东,如果block對(duì)象截獲變量颖系,這些變量也會(huì)出現(xiàn)在block結(jié)構(gòu)體中。最重要的block結(jié)構(gòu)體有一個(gè)函數(shù)指針忆蚀,指向block代碼塊矾利。block結(jié)構(gòu)體的構(gòu)造函數(shù)的參數(shù)懊悯,包括函數(shù)指針,描述block的結(jié)構(gòu)體梦皮,自動(dòng)截獲的變量(全局變量不用截獲)炭分,引用到的__block變量。(__block對(duì)象也會(huì)轉(zhuǎn)變成結(jié)構(gòu)體)

block代碼塊在編譯的時(shí)候會(huì)生成一個(gè)函數(shù)剑肯,函數(shù)第一個(gè)參數(shù)是前面說(shuō)到的block對(duì)象結(jié)構(gòu)體指針捧毛。執(zhí)行block,相當(dāng)于執(zhí)行block里面__forwarding里面的函數(shù)指針。

2.什么是block調(diào)用

block調(diào)用即是函數(shù)的調(diào)用

3.__block修飾符

一般情況下,對(duì)被截獲變量進(jìn)行賦值操作需添加__block修飾符
__block不能修飾全局變量、靜態(tài)變量(static)

{
   NSMutableArray *array = nil;
   void(^Block)(void) = ^{
           array = [NSMutableArray array];
   }
   Block();
}
是否存在問(wèn)題?
需要在array聲明處添加__block修飾符
__block int multiplier = 6;
    int(^Block)(int) = ^int(int num)
    {
        return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"result is %d", Block(2));

   結(jié)果為8
__block修飾的變量變成了對(duì)象
  • _ _block 這個(gè)修飾符做了什么操作呢眷蜈?就是讓block內(nèi)部可以訪問(wèn)自動(dòng)變量
    __weak將int類型的數(shù)據(jù)轉(zhuǎn)換成了一個(gè)__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為0竿秆,既然有isa指針,那么說(shuō)明這個(gè)結(jié)構(gòu)體也是一個(gè)對(duì)象,__forwarding存儲(chǔ)的是__Block_byref_i_0的地址值,flags為0泞辐,size為Block_byref_i_0的內(nèi)存大小,i是真正存儲(chǔ)變量值的地方,是通過(guò)__Block_byref_i_0結(jié)構(gòu)體的指針__forwarding讀取和修改的變量i.

  • 為什么要通過(guò)__forwarding轉(zhuǎn)一下呢竞滓,而不是直接讀取i
    這是因?yàn)楫?dāng)我們調(diào)用block的時(shí)候咐吼,block可能存在于棧中可能存在于堆中


__block修飾后的底層實(shí)現(xiàn):

1.__block將int i進(jìn)行包裝,包裝成一個(gè)__Block_byref_i_0結(jié)構(gòu)體對(duì)象商佑,結(jié)構(gòu)體中的i是存儲(chǔ)i的int值的锯茄;
2.當(dāng)我們?cè)赽lock內(nèi)修改或訪問(wèn)該對(duì)象時(shí),是通過(guò)該對(duì)象的__forwarding去找對(duì)應(yīng)的結(jié)構(gòu)體再找對(duì)應(yīng)的屬性值茶没,這是因?yàn)開_forwarding在不同情況下指向不同的地址肌幽,防止只根據(jù)單一的一個(gè)內(nèi)存地址出現(xiàn)變量提前釋放無(wú)法訪問(wèn)的情況。
那么我們就明白為什么可以修改__block修飾的自動(dòng)變量了抓半,__block修飾下的i不再是int類型而變成一個(gè)對(duì)象(對(duì)象p)喂急,我們block內(nèi)部訪問(wèn)和修改的是這個(gè)對(duì)象內(nèi)部的一個(gè)屬性,并不是這個(gè)對(duì)象琅关,所以是可以修改訪問(wèn)的煮岁。只不過(guò)這個(gè)轉(zhuǎn)化為對(duì)象的內(nèi)部過(guò)程封裝起來(lái)不讓開發(fā)者看到,所以就給人的感覺是可以修改auto變量也就是修改時(shí)是int i涣易。

4.block的內(nèi)存管理

//全局block
_NSConcreteGlobalBlock
//棧block
_NSConcreteStackBlock
//堆block
_NSConcreteMallocBlock
說(shuō)明.jpeg



  • 為什么捕獲局部變量而不捕獲全局變量?
    全局變量:整個(gè)項(xiàng)目都可以訪問(wèn)冶伞,block調(diào)用的時(shí)候可以直接拿到訪問(wèn)新症,不用擔(dān)心變量被釋放的情況;
    局部變量:則不同响禽,局部變量是有作用域的徒爹,如果blcok調(diào)用的時(shí)候blcok已經(jīng)被釋放了荚醒,就會(huì)出現(xiàn)嚴(yán)重的問(wèn)題,所以為了避免這個(gè)問(wèn)題block需要捕獲需要的局部變量隆嗅。(比如我們局部變量和block都卸載了viewDidLoad方法界阁,但是我在touchesBegan方法中調(diào)用block,這個(gè)時(shí)候局部變量早就釋放了胖喳,所以block要捕獲局部變量)

  • 為什么auto變量是捕獲的值泡躯,而靜態(tài)變量是捕獲的地址呢?
    自動(dòng)變量和靜態(tài)變量存儲(chǔ)的區(qū)域不同丽焊,兩者釋放時(shí)間也不同较剃。
    自動(dòng)變量:存放在棧中的,創(chuàng)建與釋放是由系統(tǒng)設(shè)置的技健,隨時(shí)可能釋放掉写穴。
    靜態(tài)變量:存儲(chǔ)在全局存儲(chǔ)區(qū)的,生命周期和app是一樣的雌贱,不會(huì)被銷毀啊送。
    所以對(duì)于隨時(shí)銷毀的自動(dòng)變量肯定是把值拿進(jìn)來(lái)保存了,如果保存自動(dòng)變量的地址欣孤,那么等自動(dòng)變量釋放后我們根據(jù)地址去尋值肯定會(huì)發(fā)生懷內(nèi)存訪問(wèn)的情況删掀,而靜態(tài)變量因?yàn)轫?xiàng)目運(yùn)行中永遠(yuǎn)不會(huì)被釋放,所以保存它的地址值就完全可以了导街,等需要用的時(shí)候直接根據(jù)地址去尋值披泪,就能找到。

  • 為什么靜態(tài)變量和全局變量同樣不會(huì)被銷毀搬瑰,為什么一個(gè)被捕獲地址一個(gè)則不會(huì)被捕獲呢款票?
    靜態(tài)變量和全局變量因?yàn)閮烧咴L問(wèn)方式不同造成的
    全局變量:整個(gè)項(xiàng)目都可以拿來(lái)訪問(wèn),所以某個(gè)全局變量在全局而言是唯一的(也就是全局變量不能出現(xiàn)同名的情況泽论,即使類型不同也不行艾少,否則系統(tǒng)不知道你具體訪問(wèn)的是哪一個(gè))
    靜態(tài)變量:則不是,全局存儲(chǔ)區(qū)可能存儲(chǔ)著若干個(gè)名為type的靜態(tài)變量翼悴。
    所以這就導(dǎo)致了訪問(wèn)方式的不同缚够,比如說(shuō)有個(gè)block,內(nèi)部有一個(gè)靜態(tài)變量和一個(gè)全局變量鹦赎,那么在調(diào)用的時(shí)候系統(tǒng)可以直接根據(jù)全局變量名去全局存儲(chǔ)區(qū)查找就可以找到谍椅,名稱是惟一的,所以不用捕獲任何信息即可訪問(wèn)古话。而靜態(tài)變量而不行雏吭,全局存儲(chǔ)區(qū)可能存儲(chǔ)著若干個(gè)名為type的靜態(tài)變量,所以blcok只能根據(jù)內(nèi)存地址去區(qū)分調(diào)用自己需要的那個(gè)

  • block的copy操作


  • 棧上的block copy之后,MRC環(huán)境下是否會(huì)引起內(nèi)存泄漏?
    是的,copy操作之后,堆上的block沒(méi)有額外的成員變量指向它,正如我們和alloc對(duì)象后,沒(méi)有進(jìn)行relese,造成內(nèi)存泄漏

5.block的底層結(jié)構(gòu)

通過(guò)clang命令將oc代碼轉(zhuǎn)換成c++代碼(如果遇到_weak的報(bào)錯(cuò)是因?yàn)開weak是個(gè)運(yùn)行時(shí)函數(shù)陪踩,所以我們需要在clang命令中指定運(yùn)行時(shí)系統(tǒng)版本才能編譯):

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main.cpp
-(void)viewDidLoad{
    [super viewDidLoad];
    int i = 1;
    void(^block)(void) = ^{
        NSLog(@"%d",i);
    };
    block();
}

轉(zhuǎn)換成c++代碼如下:

//block的真實(shí)結(jié)構(gòu)體
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  int i;
    //構(gòu)造函數(shù)(相當(dāng)于OC中的init方法 進(jìn)行初始化操作) i(_i):將_i的值賦給i flags有默認(rèn)值杖们,可忽略
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//封存block代碼的函數(shù)
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
  int i = __cself->i; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_3g_7t9fzjm91xxgdq_ysxxghy_80000gn_T_ViewController_c252e7_mi_0,i);
    }

//計(jì)算block需要多大的內(nèi)存
static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

//viewDidLoad方法
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    //定義的局部變量i
    int i = 1;
    //定義的blcok底部實(shí)現(xiàn)
    void(*block)(void) = &__ViewController__viewDidLoad_block_impl_0(
                                            __ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, i));
    //block的調(diào)用
    bloc->FuncPtr(block);
}

可看出悉抵,定義的block實(shí)際上就是一直指向結(jié)構(gòu)體_ViewController_viewDidLoad_block_impl_0的指針(將一個(gè)_ViewController_viewDidLoad_block_impl_0結(jié)構(gòu)體的地址賦值給了block變量)。

_ViewController_viewDidLoad_block_impl_0包含以下幾個(gè)部分:

  • impl
  • Desc : 存儲(chǔ)兩個(gè)參數(shù)reserved和Block_size摘完,并且reserved賦值為0而Block_size則存儲(chǔ)著__ViewController__viewDidLoad_block_impl_0的占用空間大小姥饰。最終將desc結(jié)構(gòu)體的地址傳入__ViewController__viewDidLoad_block_impl_0中賦值給Desc。所以Desc的作用是記錄Block結(jié)構(gòu)體的內(nèi)存大小孝治。
  • 引用的局部變量
  • 構(gòu)造方法

其中impl包含:

  • isa指針列粪,存放結(jié)構(gòu)體的內(nèi)存地址,存儲(chǔ)著&_NSConcreteStackBlock地址荆秦,可以暫時(shí)理解為其類對(duì)象地址篱竭,block就是_NSConcreteStackBlock類型的
  • Flags:這個(gè)用不到 有默認(rèn)值
  • FuncPtr:block代碼塊地址,存儲(chǔ)著viewDidLoad_block_func_0函數(shù)的地址步绸,也就是block代碼塊的地址掺逼。所以當(dāng)調(diào)用block的時(shí)候,bloc->FuncPtr(block);是直接調(diào)用的FuncPtr方法瓤介。


    簡(jiǎn)化圖.png

6.循環(huán)引用問(wèn)題

循環(huán)引用也是block中一個(gè)常見的問(wèn)題吕喘,什么是循環(huán)引用呢?
從block捕獲對(duì)象變量的過(guò)程中可看出刑桑,block在堆中的時(shí)候會(huì)根據(jù)變量自己的修飾符來(lái)進(jìn)行強(qiáng)引用或者弱引用氯质,假設(shè)block對(duì)person對(duì)象進(jìn)行強(qiáng)引用,而person如果對(duì)block也進(jìn)行強(qiáng)引用的話祠斧,那就形成了循環(huán)引用闻察,person對(duì)象和block都有強(qiáng)指針指引著,使它們得不到釋放琢锋。
解決方法:
__weak和__unsafe_unretained
相同點(diǎn):表示的是對(duì)象的一種弱引用關(guān)系
不同點(diǎn):__weak修飾的對(duì)象被釋放后辕漂,指向?qū)ο蟮闹羔槙?huì)置空,也就是指向nil,不會(huì)產(chǎn)生野指針
__unsafe_unretained修飾的對(duì)象被釋放后吴超,指針不會(huì)置空钉嘹,而是變成一個(gè)野指針,那么此時(shí)如果訪問(wèn)這個(gè)對(duì)象的話鲸阻,程序就會(huì)Crash跋涣,拋出BAD_ACCESS的異常。

block可以給NSMutableArray中添加元素嗎鸟悴,需不需要添加__block陈辱?

不需要,因?yàn)樵赽lock塊中僅僅是使用了array的內(nèi)存地址遣臼,往內(nèi)存地址中添加內(nèi)容性置,并沒(méi)有修改arry的內(nèi)存地址,因此array不需要使用__block修飾也可以正確編譯揍堰。

blcok為什么能回調(diào)聲明時(shí)的代碼塊呢鹏浅?

因?yàn)閛c調(diào)用”block()” 實(shí)際就是這句block->FuncPtr(block);,因?yàn)閎lcok->FuncPtr保存的就是__main_block_func_0函數(shù)屏歹。
總結(jié)block聲明的時(shí)候保存了__main_block_impl_0地址隐砸,而__main_block_impl_0則保存了函數(shù)體,block的類型蝙眶,和blcok的結(jié)構(gòu)體大小季希,最后block回調(diào)的時(shí)候block->FuncPtr(block)就是調(diào)用了__main_block_impl_0中保存的函數(shù)__main_block_func_0.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市幽纷,隨后出現(xiàn)的幾起案子式塌,更是在濱河造成了極大的恐慌,老刑警劉巖友浸,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件峰尝,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡收恢,警方通過(guò)查閱死者的電腦和手機(jī)武学,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)伦意,“玉大人火窒,你說(shuō)我怎么就攤上這事⊥匀猓” “怎么了熏矿?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)离钝。 經(jīng)常有香客問(wèn)我票编,道長(zhǎng),這世上最難降的妖魔是什么奈辰? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任栏妖,我火速辦了婚禮,結(jié)果婚禮上奖恰,老公的妹妹穿的比我還像新娘吊趾。我一直安慰自己,他們只是感情好瑟啃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布论泛。 她就那樣靜靜地躺著,像睡著了一般蛹屿。 火紅的嫁衣襯著肌膚如雪屁奏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天错负,我揣著相機(jī)與錄音坟瓢,去河邊找鬼勇边。 笑死,一個(gè)胖子當(dāng)著我的面吹牛折联,可吹牛的內(nèi)容都是我干的粒褒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼诚镰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼奕坟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起清笨,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤月杉,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后抠艾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苛萎,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年跌帐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了首懈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谨敛,死狀恐怖究履,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脸狸,我是刑警寧澤最仑,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站炊甲,受9級(jí)特大地震影響泥彤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卿啡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一吟吝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颈娜,春花似錦剑逃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至同仆,卻和暖如春萤捆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工俗或, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留市怎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓蕴侣,卻偏偏與公主長(zhǎng)得像焰轻,于是被迫代替她去往敵國(guó)和親臭觉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昆雀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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