iOS Block底層解析二

一、__block 的解析

  • 接上一篇《iOS Block底層解析一》,在block中我們要修改局部變量(自動局部變量,自動局部類對象)前面都加__block修飾 自動是auto的意思,就是我們寫代碼蘋果自動給我們生成的 比如:int a = 10;其實是 auto int a = 10; 下面就直接說局部變量吧,這么麻煩的叫法膈應人

  • 為啥局部變量在block里面不能直接修改,從上一篇的分析我們知道我們傳進去的局部變量,相當于函數(shù)的參數(shù),你在函數(shù)里面改參數(shù),外面的局部變量會變嗎?例如:

void test8(int a) {
    a++;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int b = 10;
        test8(b);
        NSLog(@"%d",b);      
    }
    return 0;
}
打印結(jié)果: 
2020-04-13 21:12:24.922403+0800 blockTest[3434:163752] 10
  • 類對象的屬性修改要不要加__block呢? 其實不用的,跟NSMutableArray等 增、刪、改、查是一樣的,我們修改的都是她里面的東西,其實是因為block最后拿到的還是person地址里面的name值 例如:
 // 對象類型
void test6() {
    Person *p = [Person new];
    p.name = @"hello block!";
    void(^block)(void) = ^{
        p.name = @"fuck block!";
        NSLog(@"--- %@",p.name);
    };
    block();
}
打印結(jié)果:
2020-04-13 21:18:06.198269+0800 blockTest[3486:167284] --- fuck block!

void test8() {
    NSMutableArray * arr = [NSMutableArray new];
    void(^block)(void) = ^{
        [arr addObject:@2];
        NSLog(@"--- %@",arr);
    };
    block();
}
打印結(jié)果:
2020-04-13 21:25:44.568467+0800 blockTest[3515:170553] --- (
    2
)
  • 但是我們改變他們本身就會報錯,例如:


    圖片.png

    圖片.png
  • 一定要知會的一點就是有__block修飾的捕獲變量和沒有__block修飾的捕獲變量要分開,內(nèi)存管理上不要混淆,這樣思路才更清晰。這也是跟上一篇分開解析的原因

  • 其實我們要加__block就兩種情況,局部變量和局部類對象,其他的都可以直接訪問在block中修改,廢話有點多 開搞 開搞

// __block作用
void test7() {
    
    __block int age = 10;
    __block NSObject *objc = [[NSObject alloc] init];
    void(^block)(void) = ^ {
        objc = nil;
        age = 20;
    };
    block();
}
  • 老套路clang一波
把轉(zhuǎn)換的去掉簡化代碼,擺好步驟:
// 第一步
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        test7();
    }
    return 0;
}

// 第二步
void test7() {
    // __block int age = 10;
    __Block_byref_age_0 age = {
        0,
        &age,
        0,
        sizeof(__Block_byref_age_0),
        10
        
    };
    //    __block NSObject *objc = [[NSObject alloc] init];
    __Block_byref_objc_1 objc = {
        0,
        &objc,
        33554432,
        sizeof(__Block_byref_objc_1),
        __Block_byref_id_object_copy_131,
        __Block_byref_id_object_dispose_131,
        objc_msgSend((id)(objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))
     };
    
    // 調(diào)用
    void(*block)(void) = &__test7_block_impl_0(__test7_block_func_0, &__test7_block_desc_0_DATA, (__Block_byref_objc_1 *)&objc, (__Block_byref_age_0 *)&age, 570425344));
    block->FuncPtr(block);
}

// 第三步
struct __Block_byref_age_0 {
  void *__isa; // isa指針
__Block_byref_age_0 *__forwarding; // 指向自己的指針
 int __flags; // 不清楚是什么鬼 猜測是標識符什么的
 int __size; // 當前結(jié)構(gòu)體的大小
 int age; // 捕獲值
};

// 第三步
struct __Block_byref_objc_1 {
  void *__isa; // isa指針
__Block_byref_objc_1 *__forwarding; // 指向自己的指針
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);  // 執(zhí)行copy操作
 void (*__Block_byref_id_object_dispose)(void*); // 執(zhí)行dispose操作
 NSObject *objc;// 捕獲 NSObject類型指針
};

// 第四步
static void __test7_block_func_0(struct __test7_block_impl_0 *__cself) {

    // 拿到 __Block_byref_objc_1里面 *objc指針
  __Block_byref_objc_1 *objc = __cself->objc; // bound by ref
    // 拿到__Block_byref_age_0里面 *age指針
  __Block_byref_age_0 *age = __cself->age; // bound by ref
        //objc->__forwarding指針指向 __Block_byref_objc_1 拿到里面的的objc 完成賦值
        (objc->__forwarding->objc) = __null;
        //age->__forwarding指針指向 __Block_byref_age_0 拿到里面的的age 完成賦值
        (age->__forwarding->age) = 20;
}

// 第五步
struct __test7_block_impl_0 {
  struct __block_impl impl;
  struct __test7_block_desc_0* Desc;
  __Block_byref_objc_1 *objc; //__Block_byref_objc_1 類型結(jié)果體
  __Block_byref_age_0 *age; //__Block_byref_age_0類型結(jié)果體
  __test7_block_impl_0(void *fp, struct __test7_block_desc_0 *desc, __Block_byref_objc_1 *_objc, __Block_byref_age_0 *_age, int flags=0) : objc(_objc->__forwarding), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 看得眼花,那我們走一個,其實前面搞好了,這里就一目了然:

1.第一步一樣,第二步多了兩個東西,其實是一個鬼東西,差不多 __Block_byref_age_0和__Block_byref_objc_1兩個結(jié)構(gòu)體
2.第三就是對這兩個結(jié)構(gòu)體的解析,是不是就是一個類,包裝成類,然后再保存在block結(jié)構(gòu)體里面
3.第四步就是賦值個過程了__forwarding指針為啥要這樣搞,明明已經(jīng)拿到*objc的指針了,又(objc->__forwarding->objc) 這樣拿objc,這他么不是嚇搞嗎?我們想一下,前面說的內(nèi)存管理,很多時候block會copy到堆上的,然后蘋果是這樣設計的 如圖:


圖片.png

圖片.png
這樣搞的好處就是:
  • block在棧上的時候 block在棧上的 __forwarding指針指向自己
  • block在堆上的時候 block在棧上的 __forwarding指針指向堆內(nèi)存 block在堆上的_forwarding指針指向堆內(nèi)存
  • 所以你是指向棧還是堆 最后都能找到這個變量

二、__weak 修飾、 __strong修飾、block的循環(huán)引用問題

  • 其實到上面block已經(jīng)搞完了,這個步驟都是根據(jù)之前的解析的都可以推導出來,還是要自己去多推導 還是說說吧
  • __weak 修飾的時候可以解除block的循環(huán)引用問題,首先你要明白怎么樣才會造成循環(huán)引用問題:
  1. 兩個是否相互強引用 2.兩個以上是否形成強引用閉環(huán)
  • 那么我們可以得出結(jié)論 block只有在堆上才會形成強引用,就是執(zhí)行copy操作的時候 對就是結(jié)合上一篇文章說道的內(nèi)存管理總結(jié):


    圖片.png

    圖片.png
  • 有些同學就問了 GCD里面的block調(diào)用self會嗎 UIview animation的block調(diào)用self會嗎 self要用__weak 修飾嗎?嗯 是不會的 因為 GCD 跟你的UIView 沒有強引用block 沒有形成相互強引用,還有很多第三方庫的block里面用self 也是一樣的,首先你要判斷的是 self有沒有強引用第三方的block 比如: Masonry AFN 第三方的block跟你的類不是強引用關(guān)系,所以不會形成循環(huán)引用

  • 有同學會問 NSTimer的block里面會是強引用呢? 因為timer是self的屬性strong的啊,然后你在人家的block里面調(diào)用肯定就形成相互強引用

  • 最后關(guān)于循環(huán)引用的問題就是前面說的兩個

  1. 兩個是否相互強引用 2.兩個以上是否形成強引用閉環(huán)
  • 還有種特殊的場景比較少見,用__weak 修飾的對象,什么情況下會在block里面再用__strong修飾,就是在block里面執(zhí)行多線程耗時操作的時候,為啥這么說,因為__weak 修飾的對象block結(jié)束的時候block里面的對象就釋放了,可是你后面的線程還要使用block里面的對象所以就會有空指針的問題(weak/__weak 修飾的對象釋放后為nil)

  • 關(guān)于block的底層解析就到這里了,也是對自己學習block的一個總結(jié)吧,以后遇到block的難題基本都是這樣一套推倒,還有一些深入的東西和細節(jié)估計說得不到位,希望各位大老爺指出

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

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

  • 引導語: Block原理是怎樣的?本質(zhì)是什么? _block的作用是什么?使用需要注意什么? block的屬性修飾...
    三月木頭閱讀 275評論 0 2
  • Block的本質(zhì)<一> 1.對象類型的auto變量 在第一篇文章中我們講了在block中使用基本類型的自動變量的情...
    雪山飛狐_91ae閱讀 1,386評論 0 10
  • 1. Block是啥? 答:Block是將函數(shù)及其執(zhí)行上下文封裝起來的對象。 使用終端編譯.m內(nèi)容: 編譯之后會生...
    DoBetter1閱讀 354評論 0 1
  • Block在開發(fā)中常用的,要想解決Block在開發(fā)中遇到的問題,我們需要了解Block的本質(zhì)、截獲變量的特性、__...
    字節(jié)碼閱讀 623評論 0 2
  • 朋友在畢業(yè)之際選擇申請留學。開始忙碌,偏偏我這人,又屬于較真的人。 可能是比較介意他之前說的話沒有做到,于是就說話...
    小核桃吖閱讀 203評論 0 0

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