Block

Block

一、什么是block

1、block是什么

下面是一个简单的block:

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

对其执行clang -rewrite-objc编译转换成C++实现,得到以下代码:

struct __block_impl {
    void *isa; //指向所属类(即block类型)的指针,isa指针说明block也是对象
    int Flags;
    int Reserved;
    void *FuncPtr; //block执行时调用的函数指针,block函数的地址。存储着 __main_block_func_0 函数的地址
};

// main函数中第0个block,即上面代码中的block:^{printf("这是一个block");}();
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    //结构体的构造函数
    __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块函数体的定义部分,可以看出block的代码块都转化为了普通的函数,并且函数会默认增加一个隐藏的__cself参数,用来指向block对象本身。
//block代码块中的代码被封装成 __main_block_func_0 函数,FuncPtr则存储着 __main_block_func_0 函数的地址
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指向了函数__main_block_func_0
        //__main_block_impl_0的Desc也指向了定义__main_block_desc_0时就创建的__main_block_desc_0_DATA,其中纪录了block结构体大小等信息
        ((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的实际结构

Block_private.h文件中对block的相关结构体的真实定义:

/* Revised new layout. */
struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src); //辅助拷贝函数,处理block范围外的变量时使用
    void (*dispose)(void *); //辅助销毁函数,处理block范围外的变量时使用
};

struct Block_layout {
    void *isa; //isa指针,所有对象都有该指针,用于实现对象相关的功能。
    int flags;
    int reserved; //reserved,保留变量
    void (*invoke)(void *, ...); //函数指针,指向具体的block实现的函数调用地址。block定义时内部的执行代码都在这个函数中
    struct Block_descriptor *descriptor; //block的详细描述
    /* Imported variables. */
};

block的结构如下图:


block-struct.jpg

二、block的类型

block有三种类型:_NSConcreteGlobalBlock(全局)、_NSConcreteStackBlock(栈)和_NSConcreteMallocBlock(堆)。其中_NSConcreteGlobalBlock和_NSConcreteStackBlock可以在代码中创建。下面代码中创建了一个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;
}

运行结果

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 ,放在数据段;
(2)访问了auto变量的block是NSStackBlock
(3)[NSStackBlock copy]操作就变成了NSMallocBlock

NSConcreteMallocBlock类型的block通常不会在源码中直接出现,因为默认它是当一个block被copy的时候,才会将这个block复制到堆中。由于block的拷贝最终都会调用_Block_copy_internal函数(runtime.c中的_Block_copy_internal函数),所以观察这个函数就可以知道堆中block是如何被创建的:

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的堆内存
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return NULL;
      // 拷贝栈中block到刚申请的堆内存中
        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与基本数据类型变量

void testBlockVar(void);

int c = 30; //全局变量
static int d = 40; //全局静态变量

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

void testBlockVar() {
    int a = 10; //局部变量
    static int b = 20; //静态局部变量
    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();
}

上面代码运行结果a = 10, b = 2222, c = 33, d = 44。编译之后:

void testBlockVar(void);
//全局变量c和全局静态变量d存储在静态数据存储区,在程序结束前不会被销毁,所以block直接访问了对应的变量,因此在结构体__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和全局静态变量d存储在静态数据存储区,在程序结束前不会被销毁,所以block直接访问了对应的变量,因此在结构体__testBlockVar_block_impl_0中没有拷贝
struct __testBlockVar_block_impl_0 {
    struct __block_impl impl;
    struct __testBlockVar_block_desc_0* Desc;
    int *b; //指针传递。static修饰的静态局部变量b传递到block内部的是指针,在 __testBlockVar_block_func_0 函数内部就可以拿到b的内存地址,因此就可以在block内部修改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);
}

查看编译后的代码可以得出结论:
(1)局部变量:所有在block代码块引用的局部变量都会成为结构体的同名数据成员,因此struct __testBlockVar_block_impl_0结构体增加了名为int a的成员变量。
(2)静态局部变量:static修饰的静态局部变量b传递到block内部的是指针,在 __testBlockVar_block_func_0 函数内部就可以拿到b的内存地址,因此就可以在block内部修改b的值。
(3)全局变量和全局静态全局变量:全局变量c和全局静态变量d存储在静态数据存储区,在程序结束前不会被销毁,所以block直接可以访问对应的变量,因此在结构体__testBlockVar_block_impl_0中没有也不需要拷贝变量作为结构体成员变量。如下图:


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内部:%@", p.name);
        };
        someBlock();
        NSLog(@"======");
    }
    return 0;
}

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

运行结果:

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

大括号执⾏完毕之后,p依然不不会被释放。p为auto变量,即block有一个强引⽤指向p,所以block不被销毁的话,p也不会销毁。所以Person类的dealloc没有执行。查看编译后代码:

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内部调用的p变量添加__weak修饰符:

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

运行结果:

2019-03-15 16:41:20.836909+0800 BlockTest[14952:6266465] ---block内部:胖虎の朋友
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块并带一个参数
    void (^someBlock)(void) = ^{
        NSLog(@"b=%d, str=%@, blockStr=%@", b, str, blockStr);
    };
    b = 40;  //修改值会影响someBlock内的计算结果
    str = @"str"; //修改值不会影响someBlock内的计算结果
    blockStr = @"blockStr"; //修改值会影响someBlock内的计算结果
    someBlock();  //执行block代码
}

上面代码的运行结果:b=40, str=123, blockStr=blockStr。由此可以看出只有带有__block修饰符的变量b和blockStr被修改后影响了block中的值。将代码编译后:

//__block修饰的变量b变成了结构体__Block_byref_b_0,内存结构和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变成了结构体__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代码块引用的外部数据都会成为结构体的同名数据成员
    NSString *__strong str;
    __Block_byref_b_0 *b; // by ref
    __Block_byref_blockStr_1 *blockStr; // by ref
    //结构体的构造函数
    __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));
}

结构体__Block_byref_b_0中__Block_byref_b_0 *__forwarding:指向自己在堆中的地址。由此可以保证只要是使用b->_forwarding->b访问的都是堆中的变量b。如下图所示:


forwarding.png

四、block辅助函数

主要指的是copy和dispose辅助函数,负责block的拷贝和释放。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        __block int b = 20; //block修饰的基本数据类型
        NSString *c = @"c";
        someBlock = ^{
            NSLog(@"a=%d, b=%d, c=%@", a, b, c);
        };
        someBlock();
    }
    return 0;
}

在捕获变量为__block修饰的基本类型,或者为对象时,block才会有这两个辅助函数。编译之后:

//Step 3(copy操作):_Block_object_assign函数内部会根据变量在__main_block_impl_0中的修饰符进行引用计数器的操作。如果为strong计数器+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操作):调用_Block_object_assign函数
//copy辅助函数
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辅助函数
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操作):调用__main_block_desc_0中的__main_block_copy_0函数
//Step 1(dispose操作):调用__main_block_desc_0中的__main_block_dispose_0函数;
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};

当对block进行copy操作时,主要执行如下操作:
(1)调用__main_block_desc_0中的__main_block_copy_0函数;
(2)__main_block_copy_0函数内部会调用_Block_object_assign函数;
(3)_Block_object_assign函数内部根据变量在__main_block_impl_0中的修饰符进行引用计数器的操作,如果为strong计数器+1,为weak则不变。

当block从堆中移除时:_Block_object_dispose会断开对对象的引用,而对象是否被释放取决于对象自己的引用计数。
(1)自动调用__main_block_desc_0中的__main_block_dispose_0函数;
(2)__main_block_dispose_0函数内部会调用_Block_object_dispose函数。

五、block与循环引用

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
执行结果:2019-03-15 18:27:15.358609+0800 BlockTest[16449:6652153] ---啊哈哈

person没有被释放,产生了循环引用。其原因如下图所示:


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

//打印结果:
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");
}

//运行结果:
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 结构体中创建了一个Person *__strong self的强指针指向init方法中self 指针所指向的person对象,使person引⽤计数+1,而person对block也有⼀个强引用。这⾥就造成了循环引⽤用。

原因:之前说过block会捕获局部变量,上⾯的OC函数调用转化为runtime代码为 objc_msgSend(self, @selector(init)) 在OC的方法中有2个隐藏参数self和cmd,这2个参数作为函数的形参在⽅法作⽤域中属于局部变量, 所以在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;
}

运行结果:
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中调⽤用super也会造成循环引用,如下:
//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");
}

编译后:

//当使⽤[self class]时,会调用objc_msgSend函数,第一个参数receiver就是self,⽽第二个参数,要先找到self所在的这个class的⽅法列表
//当使用[super class]时,会调用objcmsgSendSuper函数,此时会先构造一个 __rw_objc_super 的结构体作为objcmsgSendSuper的第⼀个参数。该结构体第一个成员变量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对外暴暴露露的类型为:
//结构体第一个成员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];
        };

转换为源码是:

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已经变成了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/
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343