Block

Block

一、什么是block

1、block是什么

下面是一個簡單的block:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ^{printf("這是一個block");}();
    }
    return 0;
}

對其執(zhí)行clang -rewrite-objc編譯轉(zhuǎn)換成C++實現(xiàn),得到以下代碼:

struct __block_impl {
    void *isa; //指向所屬類(即block類型)的指針,isa指針說明block也是對象
    int Flags;
    int Reserved;
    void *FuncPtr; //block執(zhí)行時調(diào)用的函數(shù)指針,block函數(shù)的地址。存儲著 __main_block_func_0 函數(shù)的地址
};

// main函數(shù)中第0個block,即上面代碼中的block:^{printf("這是一個block");}();
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    //結(jié)構(gòu)體的構(gòu)造函數(shù)
    __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;
    }
};

//block塊函數(shù)體的定義部分,可以看出block的代碼塊都轉(zhuǎn)化為了普通的函數(shù),并且函數(shù)會默認(rèn)增加一個隱藏的__cself參數(shù),用來指向block對象本身。
//block代碼塊中的代碼被封裝成 __main_block_func_0 函數(shù),F(xiàn)uncPtr則存儲著 __main_block_func_0 函數(shù)的地址
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("這是一個block");}

static struct __main_block_desc_0 {
    size_t reserved; //保留字段
    size_t Block_size; //block大小(sizeof(struct __main_block_impl_0))
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        //__main_block_impl_0的FuncPtr指向了函數(shù)__main_block_func_0
        //__main_block_impl_0的Desc也指向了定義__main_block_desc_0時就創(chuàng)建的__main_block_desc_0_DATA,其中紀(jì)錄了block結(jié)構(gòu)體大小等信息
        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

2、block的實際結(jié)構(gòu)

Block_private.h文件中對block的相關(guān)結(jié)構(gòu)體的真實定義:

/* Revised new layout. */
struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src); //輔助拷貝函數(shù),處理block范圍外的變量時使用
    void (*dispose)(void *); //輔助銷毀函數(shù),處理block范圍外的變量時使用
};

struct Block_layout {
    void *isa; //isa指針,所有對象都有該指針,用于實現(xiàn)對象相關(guān)的功能。
    int flags;
    int reserved; //reserved,保留變量
    void (*invoke)(void *, ...); //函數(shù)指針,指向具體的block實現(xiàn)的函數(shù)調(diào)用地址。block定義時內(nèi)部的執(zhí)行代碼都在這個函數(shù)中
    struct Block_descriptor *descriptor; //block的詳細(xì)描述
    /* Imported variables. */
};

block的結(jié)構(gòu)如下圖:


block-struct.jpg

二、block的類型

block有三種類型:_NSConcreteGlobalBlock(全局)、_NSConcreteStackBlock(棧)和_NSConcreteMallocBlock(堆)。其中_NSConcreteGlobalBlock和_NSConcreteStackBlock可以在代碼中創(chuàng)建。下面代碼中創(chuàng)建了一個global block和一個stack block。

typedef void(^SomeBlock)();
//global block
void (^globalBlock)(void) = ^{ printf("全局block"); };

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int a = 10;
        void (^aBlock)(void) = ^{
            printf("a = %d", a);
        };
        //
        void (^bBlock)() = ^{};
        //
        SomeBlock cBlock = ^{};
        
        NSLog(@"globalBlock=%@\n,aBlock=%@\n,bBlock=%@\n,cBlock=%@\n", globalBlock,aBlock, bBlock, cBlock);
    }
    return 0;
}

運行結(jié)果

2019-03-19 11:46:26.388349+0800 BlockTest[90500:9974201] 
globalBlock=<__NSGlobalBlock__: 0x1000020b8>,
aBlock=<__NSStackBlock__: 0x7ffeefbff4d8>,
bBlock=<__NSGlobalBlock__: 0x100002118>,
cBlock=<__NSGlobalBlock__: 0x100002158>
Program ended with exit code: 0

如何判斷block是哪種類型?
(1)沒有訪問auto變量的block是NSGlobalBlock ,放在數(shù)據(jù)段;
(2)訪問了auto變量的block是NSStackBlock;
(3)[NSStackBlock copy]操作就變成了NSMallocBlock

NSConcreteMallocBlock類型的block通常不會在源碼中直接出現(xiàn),因為默認(rèn)它是當(dāng)一個block被copy的時候,才會將這個block復(fù)制到堆中。由于block的拷貝最終都會調(diào)用_Block_copy_internal函數(shù)(runtime.c中的_Block_copy_internal函數(shù)),所以觀察這個函數(shù)就可以知道堆中block是如何被創(chuàng)建的:

static void *_Block_copy_internal(const void *arg, const bool wantsOne) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GC) {
        // GC refcounting is expensive so do most refcounting here.
        if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 2)) {
            // Tell collector to hang on this - it will bump the GC refcount version
            _Block_setHasRefcount(aBlock, true);
        }
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }

    // Its a stack block.  Make a copy.
    if (!isGC) {
        // 申請block的堆內(nèi)存
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return NULL;
      // 拷貝棧中block到剛申請的堆內(nèi)存中
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
      // 改變isa指向_NSConcreteMallocBlock,即堆block類型
        result->isa = _NSConcreteMallocBlock;
        _Block_call_copy_helper(result, aBlock);
        return result;
    }
    else {
        //省略...
    }
}

三、block與變量

1、block與基本數(shù)據(jù)類型變量

void testBlockVar(void);

int c = 30; //全局變量
static int d = 40; //全局靜態(tài)變量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        testBlockVar();
    }
    return 0;
}

void testBlockVar() {
    int a = 10; //局部變量
    static int b = 20; //靜態(tài)局部變量
    void (^someBlock)(void) = ^(void) {
        b = 2222;
        c = 33;
        d = 44;
        NSLog(@"a = %d, b = %d, c = %d, d = %d\n", a, b, c, d);
    };
    a = 11;
    b = 22;
    someBlock();
}

上面代碼運行結(jié)果a = 10, b = 2222, c = 33, d = 44。編譯之后:

void testBlockVar(void);
//全局變量c和全局靜態(tài)變量d存儲在靜態(tài)數(shù)據(jù)存儲區(qū),在程序結(jié)束前不會被銷毀,所以block直接訪問了對應(yīng)的變量,因此在結(jié)構(gòu)體__testBlockVar_block_impl_0中沒有拷貝
int c = 30;
static int d = 40;

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        testBlockVar();
    }
    return 0;
}

//全局變量c和全局靜態(tài)變量d存儲在靜態(tài)數(shù)據(jù)存儲區(qū),在程序結(jié)束前不會被銷毀,所以block直接訪問了對應(yīng)的變量,因此在結(jié)構(gòu)體__testBlockVar_block_impl_0中沒有拷貝
struct __testBlockVar_block_impl_0 {
    struct __block_impl impl;
    struct __testBlockVar_block_desc_0* Desc;
    int *b; //指針傳遞。static修飾的靜態(tài)局部變量b傳遞到block內(nèi)部的是指針,在 __testBlockVar_block_func_0 函數(shù)內(nèi)部就可以拿到b的內(nèi)存地址,因此就可以在block內(nèi)部修改b的值。
    int a; //值傳遞
    __testBlockVar_block_impl_0(void *fp, struct __testBlockVar_block_desc_0 *desc, int *_b, int _a, int flags=0) : b(_b), a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __testBlockVar_block_func_0(struct __testBlockVar_block_impl_0 *__cself) {
    int *b = __cself->b; // bound by copy
    int a = __cself->a; // bound by copy
    
    (*b) = 2222;
    c = 33;
    d = 44;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_fb_61h7kdld7qz8hz9ph2rt22k00000gn_T_main_ccbce4_mi_0, a, (*b), c, d);
}

void testBlockVar() {
    int a = 10;
    static int b = 20;
    void (*someBlock)(void) = ((void (*)())&__testBlockVar_block_impl_0((void *)__testBlockVar_block_func_0, &__testBlockVar_block_desc_0_DATA, &b, a));
    a = 11;
    b = 22;
    ((void (*)(__block_impl *))((__block_impl *)someBlock)->FuncPtr)((__block_impl *)someBlock);
}

查看編譯后的代碼可以得出結(jié)論:
(1)局部變量:所有在block代碼塊引用的局部變量都會成為結(jié)構(gòu)體的同名數(shù)據(jù)成員,因此struct __testBlockVar_block_impl_0結(jié)構(gòu)體增加了名為int a的成員變量。
(2)靜態(tài)局部變量:static修飾的靜態(tài)局部變量b傳遞到block內(nèi)部的是指針,在 __testBlockVar_block_func_0 函數(shù)內(nèi)部就可以拿到b的內(nèi)存地址,因此就可以在block內(nèi)部修改b的值。
(3)全局變量和全局靜態(tài)全局變量:全局變量c和全局靜態(tài)變量d存儲在靜態(tài)數(shù)據(jù)存儲區(qū),在程序結(jié)束前不會被銷毀,所以block直接可以訪問對應(yīng)的變量,因此在結(jié)構(gòu)體__testBlockVar_block_impl_0中沒有也不需要拷貝變量作為結(jié)構(gòu)體成員變量。如下圖:


block_basic_var.png

2、block與__strong和__weak變量

//main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [Person new];
        p.name = @"胖虎の朋友";
    
        someBlock = ^{
            NSLog(@"---block內(nèi)部:%@", p.name);
        };
        someBlock();
        NSLog(@"======");
    }
    return 0;
}

//Person.m
- (void)dealloc
{
    NSLog(@"Person dealloc");
}

運行結(jié)果:

2019-03-15 16:39:18.747356+0800 BlockTest[14917:6258900] ---block內(nèi)部:胖虎の朋友
2019-03-15 16:39:18.747505+0800 BlockTest[14917:6258900] ======
Program ended with exit code: 0

大括號執(zhí)?完畢之后,p依然不不會被釋放。p為auto變量,即block有一個強引?指向p,所以block不被銷毀的話,p也不會銷毀。所以Person類的dealloc沒有執(zhí)行。查看編譯后代碼:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong p; //強引用p
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _p, int flags=0) : p(_p) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

如果為在block內(nèi)部調(diào)用的p變量添加__weak修飾符:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [Person new];
        p.name = @"胖虎の朋友";
    
        __weak Person *weakP = p;
        someBlock = ^{
            NSLog(@"---block內(nèi)部:%@", weakP.name);
        };
        someBlock();
        NSLog(@"======");
    }
    return 0;
}

運行結(jié)果:

2019-03-15 16:41:20.836909+0800 BlockTest[14952:6266465] ---block內(nèi)部:胖虎の朋友
2019-03-15 16:41:20.837082+0800 BlockTest[14952:6266465] ======
2019-03-15 16:41:20.837098+0800 BlockTest[14952:6266465] Person dealloc
Program ended with exit code: 0

編譯之后:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__weak weakP; //weak p
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakP, int flags=0) : weakP(_weakP) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

3、block與__block修飾的變量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        testBlock();
    }
    return 0;
}

void testBlock()
{
    //下面分別定義各種類型的變量
    __block int b = 20;                //帶__block修飾符的block普通變量
    NSString *str = @"123";
    __block NSString *blockStr = str;  //帶__block修飾符的block OC變量
    
    //定義一個block塊并帶一個參數(shù)
    void (^someBlock)(void) = ^{
        NSLog(@"b=%d, str=%@, blockStr=%@", b, str, blockStr);
    };
    b = 40;  //修改值會影響someBlock內(nèi)的計算結(jié)果
    str = @"str"; //修改值不會影響someBlock內(nèi)的計算結(jié)果
    blockStr = @"blockStr"; //修改值會影響someBlock內(nèi)的計算結(jié)果
    someBlock();  //執(zhí)行block代碼
}

上面代碼的運行結(jié)果:b=40, str=123, blockStr=blockStr。由此可以看出只有帶有__block修飾符的變量b和blockStr被修改后影響了block中的值。將代碼編譯后:

//__block修飾的變量b變成了結(jié)構(gòu)體__Block_byref_b_0,內(nèi)存結(jié)構(gòu)和OC類兼容
struct __Block_byref_b_0 {
    void *__isa;
    __Block_byref_b_0 *__forwarding;//__forwarding是指向自己在堆中的地址,訪問時通過b->__forwarding->b保證操作的是堆中的變量b
    int __flags;
    int __size;
    int b;//保存定義的變量b
};

//__block修飾的變量blockStr變成了結(jié)構(gòu)體__Block_byref_blockStr_1
struct __Block_byref_blockStr_1 {
    void *__isa;
    __Block_byref_blockStr_1 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    NSString *__strong blockStr;
};

struct __testBlock_block_impl_0 {
    struct __block_impl impl;
    struct __testBlock_block_desc_0* Desc;
    //所有在block代碼塊引用的外部數(shù)據(jù)都會成為結(jié)構(gòu)體的同名數(shù)據(jù)成員
    NSString *__strong str;
    __Block_byref_b_0 *b; // by ref
    __Block_byref_blockStr_1 *blockStr; // by ref
    //結(jié)構(gòu)體的構(gòu)造函數(shù)
    __testBlock_block_impl_0(void *fp, struct __testBlock_block_desc_0 *desc, NSString *__strong _str, __Block_byref_b_0 *_b, __Block_byref_blockStr_1 *_blockStr, int flags=0) : str(_str), b(_b->__forwarding), blockStr(_blockStr->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __testBlock_block_func_0(struct __testBlock_block_impl_0 *__cself) {
    __Block_byref_b_0 *b = __cself->b; // bound by ref
    __Block_byref_blockStr_1 *blockStr = __cself->blockStr; // bound by ref
    NSString *__strong str = __cself->str; // bound by copy
    ////b->__forwarding->b用來保證操作的始終是堆中的拷貝b,而不是棧中的b
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_fb_61h7kdld7qz8hz9ph2rt22k00000gn_T_main_c2a8d8_mi_1, (b->__forwarding->b), str, (blockStr->__forwarding->blockStr));
}

void testBlock()
{
    //由__block修飾的變量b變成了__Block_byref_b_0對象
    __attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
    NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_fb_61h7kdld7qz8hz9ph2rt22k00000gn_T_main_c2a8d8_mi_0;
    //由__block修飾的blockStr頁變成了__Block_byref_blockStr_1對象
    __attribute__((__blocks__(byref))) __Block_byref_blockStr_1 blockStr = {(void*)0,(__Block_byref_blockStr_1 *)&blockStr, 33554432, sizeof(__Block_byref_blockStr_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, str};
    
    void (*someBlock)(void) = ((void (*)())&__testBlock_block_impl_0((void *)__testBlock_block_func_0, &__testBlock_block_desc_0_DATA, str, (__Block_byref_b_0 *)&b, (__Block_byref_blockStr_1 *)&blockStr, 570425344));
}

結(jié)構(gòu)體__Block_byref_b_0中__Block_byref_b_0 *__forwarding:指向自己在堆中的地址。由此可以保證只要是使用b->_forwarding->b訪問的都是堆中的變量b。如下圖所示:


forwarding.png

四、block輔助函數(shù)

主要指的是copy和dispose輔助函數(shù),負(fù)責(zé)block的拷貝和釋放。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        __block int b = 20; //block修飾的基本數(shù)據(jù)類型
        NSString *c = @"c";
        someBlock = ^{
            NSLog(@"a=%d, b=%d, c=%@", a, b, c);
        };
        someBlock();
    }
    return 0;
}

在捕獲變量為__block修飾的基本類型,或者為對象時,block才會有這兩個輔助函數(shù)。編譯之后:

//Step 3(copy操作):_Block_object_assign函數(shù)內(nèi)部會根據(jù)變量在__main_block_impl_0中的修飾符進行引用計數(shù)器的操作。如果為strong計數(shù)器+1,為weak則不變
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  NSString *__strong c;
  __Block_byref_b_0 *b; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSString *__strong _c, __Block_byref_b_0 *_b, int flags=0) : a(_a), c(_c), b(_b->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_b_0 *b = __cself->b; // bound by ref
    int a = __cself->a; // bound by copy
    NSString *__strong c = __cself->c; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_fb_61h7kdld7qz8hz9ph2rt22k00000gn_T_main_4bb492_mi_1, a, (b->__forwarding->b), c);
}

//Step 2(copy操作):調(diào)用_Block_object_assign函數(shù)
//copy輔助函數(shù)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->c, (void*)src->c, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

//dispose輔助函數(shù)
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->c, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

//Step 1(copy操作):調(diào)用__main_block_desc_0中的__main_block_copy_0函數(shù)
//Step 1(dispose操作):調(diào)用__main_block_desc_0中的__main_block_dispose_0函數(shù);
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};

當(dāng)對block進行copy操作時,主要執(zhí)行如下操作:
(1)調(diào)用__main_block_desc_0中的__main_block_copy_0函數(shù);
(2)__main_block_copy_0函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù);
(3)_Block_object_assign函數(shù)內(nèi)部根據(jù)變量在__main_block_impl_0中的修飾符進行引用計數(shù)器的操作,如果為strong計數(shù)器+1,為weak則不變。

當(dāng)block從堆中移除時:_Block_object_dispose會斷開對對象的引用,而對象是否被釋放取決于對象自己的引用計數(shù)。
(1)自動調(diào)用__main_block_desc_0中的__main_block_dispose_0函數(shù);
(2)__main_block_dispose_0函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)。

五、block與循環(huán)引用

1、場景一:

//main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person new];
        person.name = @"啊哈哈";
        person.block = ^{
            NSLog(@"---%@", person.name);
        };
        person.block();
    }
    return 0;
}

//Person.h
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) void(^block)(void);
@end

//Person.m
@implementation Person
- (void)dealloc
{
    NSLog(@"Person dealloc");
}
@end
執(zhí)行結(jié)果:2019-03-15 18:27:15.358609+0800 BlockTest[16449:6652153] ---啊哈哈

person沒有被釋放,產(chǎn)生了循環(huán)引用。其原因如下圖所示:


circular_ref.png

稍作修改之后:

//main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person new];
        person.name = @"啊哈哈";
        __weak Person *wp = person;
        person.block = ^{
            NSLog(@"---%@", wp.name);
        };
        person.block();
    }
    return 0;
}

//打印結(jié)果:
2019-03-15 18:32:37.898324+0800 BlockTest[16538:6672390] ---啊哈哈
2019-03-15 18:32:37.898488+0800 BlockTest[16538:6672390] Person dealloc
Program ended with exit code: 0

2、場景二:

//main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.block();
    }
    return 0;
}

//Person.m
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.block = ^{
            NSLog(@"===%@",[self class]);
        };
    }
    return self;
}
- (void)dealloc
{
    NSLog(@"Person dealloc");
}

//運行結(jié)果:
2019-03-15 18:38:39.059249+0800 BlockTest[16718:6698586] ===Person
Program ended with exit code: 0

編譯Person.m文件:
struct __Person__init_block_impl_0 {
  struct __block_impl impl;
  struct __Person__init_block_desc_0* Desc;
  Person *__strong self;
  __Person__init_block_impl_0(void *fp, struct __Person__init_block_desc_0 *desc, Person *__strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

這?可以看到 __Person__init_block_impl_0 結(jié)構(gòu)體中創(chuàng)建了一個Person *__strong self的強指針指向init方法中self 指針?biāo)赶虻膒erson對象,使person引?計數(shù)+1,而person對block也有?個強引用。這?就造成了循環(huán)引?用。

原因:之前說過block會捕獲局部變量,上?的OC函數(shù)調(diào)用轉(zhuǎn)化為runtime代碼為 objc_msgSend(self, @selector(init)) 在OC的方法中有2個隱藏參數(shù)self和cmd,這2個參數(shù)作為函數(shù)的形參在?法作?域中屬于局部變量, 所以在block中使用self就滿足之前提到的block會捕獲局部變量。解決方案如下:

//Person.m
- (instancetype)init
{
    self = [super init];
    if (self) {
        __weak typeof(self) weakself = self;
        self.block = ^{
            NSLog(@"===%@\n",[weakself class]);
        };
    }
    return self;
}

運行結(jié)果:
2019-03-15 18:55:48.421348+0800 BlockTest[17081:6778502] ===Person
2019-03-15 18:55:48.421693+0800 BlockTest[17081:6778502] Person dealloc
Program ended with exit code: 0

編譯Person.m:
struct __Person__init_block_impl_0 {
  struct __block_impl impl;
  struct __Person__init_block_desc_0* Desc;
  Person *__weak weakself;
  __Person__init_block_impl_0(void *fp, struct __Person__init_block_desc_0 *desc, Person *__weak _weakself, int flags=0) : weakself(_weakself) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

3、場景三:

在block中調(diào)?用super也會造成循環(huán)引用,如下:
//main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.block();
    }
    return 0;
}

//Person.m
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.block = ^{
            [super init];
        };
    }
    return self;
}

- (void)dealloc
{
    NSLog(@"Person dealloc");
}

編譯后:

//當(dāng)使?[self class]時,會調(diào)用objc_msgSend函數(shù),第一個參數(shù)receiver就是self,?第二個參數(shù),要先找到self所在的這個class的?法列表
//當(dāng)使用[super class]時,會調(diào)用objcmsgSendSuper函數(shù),此時會先構(gòu)造一個 __rw_objc_super 的結(jié)構(gòu)體作為objcmsgSendSuper的第?個參數(shù)。該結(jié)構(gòu)體第一個成員變量receiver仍然是self,而第二個成員變量super_class即是所在類的?類
static void __Person__init_block_func_0(struct __Person__init_block_impl_0 *__cself)
{
    ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
        (id)self,
        (id)class_getSuperclass(objc_getClass("Person"))
    },
    sel_registerName("init"));
}

//
struct __rw_objc_super { 
    struct objc_object *object; 
    struct objc_object *superClass; 
    __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 
};

//runtime對外暴暴露露的類型為:
//結(jié)構(gòu)體第一個成員receiver代表方法的接收者,第二個成員super_class代表方法接收者的父類
struct objc_super {
    __attribute__((objc_ownership(none))) _Nonnull id receiver;
    __attribute__((objc_ownership(none))) _Nonnull Class super_class;
};

因此:

self.block = ^{
            [super init];
        };

轉(zhuǎn)換為源碼是:

self.block = ^{
    struct objc_super superInfo = {
        .receiver = self,
        .super_class = class_getSuperclass(objc_getClass("Person")), 
    };
    ((Class(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superInfo,@selector(init)); 
};

可以明顯看到,block強引用了self,而self也強引用了block。

解決方案:

__weak __typeof(self) weakSelf = self; 
self.block = ^{
    struct objc_super superInfo = {
        .receiver = weakSelf,
        .super_class = class_getSuperclass(NSClassFromString(@"Person")), 
    };
    ((Class(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superInfo,@selector(class)); 
};

上面代碼編譯后,self已經(jīng)變成了weakSelf:
static void __Person__init_block_func_0(struct __Person__init_block_impl_0 *__cself) {
    Person *__weak weakSelf = __cself->weakSelf; // bound by cop
    struct objc_super superInfo = {
        .receiver = weakSelf,
        .super_class = class_getSuperclass(NSClassFromString((NSString *)&__NSConstantStringImpl__var_folders_fb_61h7kdld7qz8hz9ph2rt22k00000gn_T_Person_d2ee36_mi_0))
    };
    ((Class(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superInfo,sel_registerName("class"));
}
參考資料:
https://opensource.apple.com/source/libclosure/libclosure-65/Block_private.h.auto.html
https://opensource.apple.com/source/libclosure/libclosure-65/runtime.c.auto.html
http://blog.devtang.com/2013/07/28/a-look-inside-blocks/
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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