Block

Block

一蚪缀、什么是block

1誓竿、block是什么

下面是一個(gè)簡(jiǎn)單的block:

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

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

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

// main函數(shù)中第0個(gè)block,即上面代碼中的block:^{printf("這是一個(gè)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ù)會(huì)默認(rèn)增加一個(gè)隱藏的__cself參數(shù),用來(lái)指向block對(duì)象本身郊酒。
//block代碼塊中的代碼被封裝成 __main_block_func_0 函數(shù)遇绞,F(xiàn)uncPtr則存儲(chǔ)著 __main_block_func_0 函數(shù)的地址
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("這是一個(gè)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時(shí)就創(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的實(shí)際結(jié)構(gòu)

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

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

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

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


block-struct.jpg

二银亲、block的類(lèi)型

block有三種類(lèi)型:_NSConcreteGlobalBlock(全局)、_NSConcreteStackBlock(棧)和_NSConcreteMallocBlock(堆)纽匙。其中_NSConcreteGlobalBlock和_NSConcreteStackBlock可以在代碼中創(chuàng)建务蝠。下面代碼中創(chuàng)建了一個(gè)global block和一個(gè)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;
}

運(yùn)行結(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是哪種類(lèi)型烛缔?
(1)沒(méi)有訪問(wèn)auto變量的block是NSGlobalBlock 馏段,放在數(shù)據(jù)段;
(2)訪問(wèn)了auto變量的block是NSStackBlock践瓷;
(3)[NSStackBlock copy]操作就變成了NSMallocBlock院喜。

NSConcreteMallocBlock類(lèi)型的block通常不會(huì)在源碼中直接出現(xiàn),因?yàn)槟J(rèn)它是當(dāng)一個(gè)block被copy的時(shí)候晕翠,才會(huì)將這個(gè)block復(fù)制到堆中喷舀。由于block的拷貝最終都會(huì)調(diào)用_Block_copy_internal函數(shù)(runtime.c中的_Block_copy_internal函數(shù)),所以觀察這個(gè)函數(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) {
        // 申請(qǐng)block的堆內(nèi)存
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return NULL;
      // 拷貝棧中block到剛申請(qǐng)的堆內(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類(lèi)型
        result->isa = _NSConcreteMallocBlock;
        _Block_call_copy_helper(result, aBlock);
        return result;
    }
    else {
        //省略...
    }
}

三硫麻、block與變量

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

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

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

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

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

//全局變量c和全局靜態(tài)變量d存儲(chǔ)在靜態(tài)數(shù)據(jù)存儲(chǔ)區(qū)浇辜,在程序結(jié)束前不會(huì)被銷(xiāo)毀,所以block直接訪問(wèn)了對(duì)應(yīng)的變量唾戚,因此在結(jié)構(gòu)體__testBlockVar_block_impl_0中沒(méi)有拷貝
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代碼塊引用的局部變量都會(huì)成為結(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存儲(chǔ)在靜態(tài)數(shù)據(jù)存儲(chǔ)區(qū),在程序結(jié)束前不會(huì)被銷(xiāo)毀弛房,所以block直接可以訪問(wèn)對(duì)應(yīng)的變量道盏,因此在結(jié)構(gòu)體__testBlockVar_block_impl_0中沒(méi)有也不需要拷貝變量作為結(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");
}

運(yùn)行結(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

大括號(hào)執(zhí)?完畢之后荷逞,p依然不不會(huì)被釋放媒咳。p為auto變量,即block有一個(gè)強(qiáng)引?指向p种远,所以block不被銷(xiāo)毀的話涩澡,p也不會(huì)銷(xiāo)毀。所以Person類(lèi)的dealloc沒(méi)有執(zhí)行坠敷。查看編譯后代碼:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong p; //強(qiáng)引用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;
}

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

上面代碼的運(yùn)行結(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類(lèi)兼容
struct __Block_byref_b_0 {
    void *__isa;
    __Block_byref_b_0 *__forwarding;//__forwarding是指向自己在堆中的地址,訪問(wèn)時(shí)通過(guò)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ù)都會(huì)成為結(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用來(lái)保證操作的始終是堆中的拷貝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對(duì)象
    __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頁(yè)變成了__Block_byref_blockStr_1對(duì)象
    __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訪問(wèn)的都是堆中的變量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ù)類(lèi)型
        NSString *c = @"c";
        someBlock = ^{
            NSLog(@"a=%d, b=%d, c=%@", a, b, c);
        };
        someBlock();
    }
    return 0;
}

在捕獲變量為_(kāi)_block修飾的基本類(lèi)型懊亡,或者為對(duì)象時(shí)依啰,block才會(huì)有這兩個(gè)輔助函數(shù)。編譯之后:

//Step 3(copy操作):_Block_object_assign函數(shù)內(nèi)部會(huì)根據(jù)變量在__main_block_impl_0中的修飾符進(jìn)行引用計(jì)數(shù)器的操作店枣。如果為strong計(jì)數(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)對(duì)block進(jìn)行copy操作時(shí)鸯两,主要執(zhí)行如下操作:
(1)調(diào)用__main_block_desc_0中的__main_block_copy_0函數(shù)闷旧;
(2)__main_block_copy_0函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù);
(3)_Block_object_assign函數(shù)內(nèi)部根據(jù)變量在__main_block_impl_0中的修飾符進(jìn)行引用計(jì)數(shù)器的操作钧唐,如果為strong計(jì)數(shù)器+1忙灼,為weak則不變。

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

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

1、場(chǎng)景一:

//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沒(méi)有被釋放忽舟,產(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淮阐、場(chǎng)景二:

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

//運(yùn)行結(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)建了一個(gè)Person *__strong self的強(qiáng)指針指向init方法中self 指針?biāo)赶虻膒erson對(duì)象,使person引?計(jì)數(shù)+1刁品,而person對(duì)block也有?個(gè)強(qiáng)引用泣特。這?就造成了循環(huán)引?用。

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

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

運(yùn)行結(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竞阐、場(chǎng)景三:

在block中調(diào)?用super也會(huì)造成循環(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]時(shí)暑劝,會(huì)調(diào)用objc_msgSend函數(shù)骆莹,第一個(gè)參數(shù)receiver就是self,?第二個(gè)參數(shù)担猛,要先找到self所在的這個(gè)class的?法列表
//當(dāng)使用[super class]時(shí)幕垦,會(huì)調(diào)用objcmsgSendSuper函數(shù),此時(shí)會(huì)先構(gòu)造一個(gè) __rw_objc_super 的結(jié)構(gòu)體作為objcmsgSendSuper的第?個(gè)參數(shù)傅联。該結(jié)構(gòu)體第一個(gè)成員變量receiver仍然是self先改,而第二個(gè)成員變量super_class即是所在類(lèi)的?類(lèi)
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對(duì)外暴暴露露的類(lèi)型為:
//結(jié)構(gòu)體第一個(gè)成員receiver代表方法的接收者,第二個(gè)成員super_class代表方法接收者的父類(lèi)
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強(qiáng)引用了self仇奶,而self也強(qiáng)引用了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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末该溯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子别惦,更是在濱河造成了極大的恐慌狈茉,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掸掸,死亡現(xiàn)場(chǎng)離奇詭異氯庆,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)扰付,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)堤撵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人悯周,你說(shuō)我怎么就攤上這事粒督∨愀停” “怎么了禽翼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵屠橄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我闰挡,道長(zhǎng)锐墙,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任长酗,我火速辦了婚禮溪北,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘夺脾。我一直安慰自己之拨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布咧叭。 她就那樣靜靜地躺著蚀乔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪菲茬。 梳的紋絲不亂的頭發(fā)上吉挣,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音婉弹,去河邊找鬼睬魂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛镀赌,可吹牛的內(nèi)容都是我干的氯哮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼商佛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蛙粘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起威彰,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤出牧,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后歇盼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體舔痕,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年豹缀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伯复。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邢笙,死狀恐怖啸如,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情氮惯,我是刑警寧澤叮雳,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布想暗,位于F島的核電站,受9級(jí)特大地震影響帘不,放射性物質(zhì)發(fā)生泄漏说莫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一寞焙、第九天 我趴在偏房一處隱蔽的房頂上張望储狭。 院中可真熱鬧,春花似錦捣郊、人聲如沸辽狈。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)稻艰。三九已至,卻和暖如春侈净,著一層夾襖步出監(jiān)牢的瞬間尊勿,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工畜侦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留元扔,地道東北人情妖。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓滚秩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親萍膛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子验懊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355