Block

12 Nov 2019

block的本质:OC中的block其实是一个封装了函数调用及调用环境的OC对象

block 底层实现数据结构

int age = 20;
void (^block)(int, int) = ^(int a, int b){
    NSLog(@"block age -> %d", age);
    NSLog(@"block a -> %d", a);
    NSLog(@"block b -> %d", b);
};

定义一个block如上面的代码 使用如下指令在terminal中执行,把OC代码编译出cpp代码,窥探block cpp 实现的的数据结构

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

下面截取编译后和上面定义的block相关代码

// blcok的通用结构
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
// 描述block的结构体
struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
};
// 当前定义的`block`结构
struct __main_block_impl_0 {
    // block 通用结构
  struct __block_impl impl;
  // block 通用描述
  struct __main_block_desc_0* Desc;
  // block 捕获的变量
  int age;
};

// block 里边封装的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_jh_6_2dc41d2nn08vfc324ycsj00000gn_T_main_f1a625_mi_0, age);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_jh_6_2dc41d2nn08vfc324ycsj00000gn_T_main_f1a625_mi_1, a);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_jh_6_2dc41d2nn08vfc324ycsj00000gn_T_main_f1a625_mi_2, b);
        }
// 定义block的描述
static struct __main_block_desc_0  __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

// 声明block
 void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

// 简化之后的版本,去掉类型转换
 void (*block)(int, int) = &__main_block_impl_0(__main_block_func_0,
                                          &__main_block_desc_0_DATA,
                                                                age);

把上面定义的block转化成结构体

 __main_block_impl_0 *cBlock = (__bridge  __main_block_impl_0 *)block;

打断点,可以看到block转换成结构体之后的里边存储的数据 里边的函数指针 0x100000e80 指向 block 被调用时的函数0x100000e80 相关demo代码

Block 变量捕获

普通类型的捕获

分别定义不同类型的变量,在block中调用

// 全局变量
int c = 30;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        // 局部变量,默认是 auto,这个的autol可以去掉
        auto int a = 10;
        // 静态变量
        static int b = 20;
        
        void (^block)(void) = ^{
            NSLog(@"block--> %d, %d, %d", a, b, c);
        };

        a *= 2;
        b *= 2;
        c *= 2;

        block();
        //  block--> 10, 40, 60
    }
    return 0;
}

可以看出,只有局部变量 a 保留了传进去时的值,static和全局变量都会随着外部更改而更改。 接下来把上面的代码编译成cpp代码,编译后的关键代码如下

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

int c = 30;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a; //接收a的值
  int *b; // 接收b的地址
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  int *b = __cself->b; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_jh_6_2dc41d2nn08vfc324ycsj00000gn_T_main_a896b8_mi_0, a, (*b), c);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        auto int a = 10;
        static int b = 20;

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));

        a *= 2;
        b *= 2;
        c *= 2;

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    }
    return 0;
}

上面代码中

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a; //接收a的值
  int *b; // 接收b的地址
}

block的结构体存有 a 的值,b 的地址,使用的时候直接从block的结构体中取出, 而 c 时全局变量,没有存储。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy
    int *b = __cself->b; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_jh_6_2dc41d2nn08vfc324ycsj00000gn_T_main_a896b8_mi_0, a, (*b), c);
}

对象的捕获

@interface AYPerson : NSObject
@property(nonatomic, strong) NSString *name;
@end

@implementation AYPerson
- (void)test{
    void (^block)(void) = ^{
        NSLog(@"block -> %@", self);
    };
    block();
}
@end

编译成cpp代码之后的block结构体如下

struct __AYPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __AYPerson__test_block_desc_0* Desc;
  AYPerson *self;
};

同样的,这个block会有指向AYPerson实例的指针, 如果在block中调用对象的方法, 也会存有指向对象的指针

- (void)test{
    void (^block)(void) = ^{
        NSLog(@"block -> %@", [self name]);
    };
    block();
}
    
    
    
    
    V
struct __AYPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __AYPerson__test_block_desc_0* Desc;
  AYPerson *self;
};

Block捕获的是当前的对象 如下代码可以证明

AYPerson *p = [[AYPerson alloc] init];
void (^block)(void) = ^{
    NSLog(@"main block -> %p", p);
};

block();
NSLog(@"out side block->%p", p);
p = [[AYPerson alloc] init];
block();
NSLog(@"out side block->%p", p);

/**
 MyBlockCapture[44473:892748] main block -> 0x1006ade10
 MyBlockCapture[44473:892748] out side block->0x1006ade10
 MyBlockCapture[44473:892748] main block -> 0x1006ade10
 MyBlockCapture[44473:892748] out side block->0x1006b96e0
 */
 block的结构体
    
    
    
    
    V
 struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  AYPerson *p;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, AYPerson *_p, int flags=0) : p(_p) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以看出block会存有传入对象的地址,并持有它

Block的类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型 __NSGlobalBlock___NSConcreteGlobalBlock__NSStackBlock___NSConcreteStackBlock__NSMallocBlock___NSConcreteMallocBlock

在MRC模式下

int a = 20;

void (^block1)(void) = ^{
    NSLog(@"test1");
};

void (^block2)(void) = ^{
    NSLog(@"test1 - %d", a);
};

void (^block3)(void) = [^{
    NSLog(@"test1 - %d", a);
} copy];

NSLog(@"block1 -> %@, %@, %@, %@", [block1 class], [[block1 class]superclass], [[[block1 class] superclass] superclass], [[[[block1class] superclass] superclass] superclass]);
NSLog(@"block2 -> %@, %@, %@, %@", [block2 class], [[block3 class]superclass], [[[block2 class] superclass] superclass], [[[[block2class] superclass] superclass] superclass]);
NSLog(@"block3 -> %@, %@, %@, %@", [block3 class], [[block3 class]superclass], [[[block3 class] superclass] superclass], [[[[block3class] superclass] superclass] superclass]);
/**
 block1 -> __NSGlobalBlock__, __NSGlobalBlock, NSBlock, NSObject
 block2 -> __NSStackBlock__, __NSMallocBlock, NSBlock, NSObject
 block3 -> __NSMallocBlock__, __NSMallocBlock, NSBlock, NSObject
 */

如上可以看到不同类型的创建及继承类型,所有blcok都继承 NSBlockNSBlock继承NSObject

__NSStackBlock__, __NSMallocBlock__ 的比较

在MRC环境中

void (^myBlock1)(void);
void (^myBlock2)(void);

void test()
{
    int age = 10;
    myBlock1 = ^{
        NSLog(@"my block1 %d", age);
    };
    
    myBlock2 = [^{
        NSLog(@"my block2 %d", age);
    } copy];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        test();
        
        myBlock1();
        // my block1 -272632728
        // __NSStackBlock__ 的block 超出定义区间就会被释放
        
        myBlock2();
        // my block2 10
        // __NSMallocBlock__ 的block 存在堆区, 超出定义区间不会被释放
        }
    return 0;
}

Block 的 copy

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上

typedef void(^MYBlock)(void);

MYBlock test()
{
    int age = 10;
    
    return ^{
        NSLog(@"Hello %d", age);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int la = 30;
        MYBlock a = test();
        __weak MYBlock b = ^{
            NSLog(@"^^D%d", la);
        };
        MYBlock c = ^{
            NSLog(@"^^D%d", la);
        };
        
        // 函数返回值
        NSLog(@"TypeA: %@", [a class]);
        // 弱引用 __weak
        NSLog(@"TypeB: %@", [b class]);
        // 强引用 __strong
        NSLog(@"TypeC: %@", [c class]);
        // 无引用
        NSLog(@"Stack: %@", [^{
            NSLog(@"^^D%d", la);
        } class]);
        
        /**
         TypeA: __NSMallocBlock__
         TypeB: __NSStackBlock__
         TypeC: __NSMallocBlock__
         Stack: __NSStackBlock__
         */
    }
    return 0;
}

MRCblock属性的建议写法 @property (copy, nonatomic) void (^block)(void);

ARCblock属性的建议写法 @property (strong, nonatomic) void (^block)(void); @property (copy, nonatomic) void (^block)(void);

对象类型的引用机制

  1. block内部访问了对象类型的auto变量时

    如果block是在栈上,将不会对auto变量产生强引用

__weak void (^myblock)(void);

{
    AYPerson *p = [[AYPerson alloc] init];
    __weak typeof(p) weakP = p;
    myblock = ^{
        NSLog(@"inside-%@", p);
    };
}
myblock();
NSLog(@"---------------------");
/**
 <AYPerson: 0x1007185f0>-dealloc
 inside-(null)
 ---------------------
 */

上面代码中使用__weak修饰blockblock不持有AYPerson超出作用域就释放了

  1. 如果block被拷贝到堆上
void (^myblock)(void);
{
    AYPerson *p = [[AYPerson alloc] init];
    __weak typeof(p) weakP = p;
    myblock = ^{
        NSLog(@"inside-%@", p);
    };
}

myblock();

把上面的代码调用如下指令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m 编译成cpp代码,block定义的关键代码如下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  AYPerson *__strong p;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, AYPerson *__strong _p, int flags=0) : p(_p) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  AYPerson *__strong p = __cself->p; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_jh_6_2dc41d2nn08vfc324ycsj00000gn_T_main_be3335_mi_1, p);
            }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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函数
copy函数内部会调用_Block_object_assign函数 _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

如果block从堆上移除
会调用block内部的dispose函数, dispose函数内部会调用_Block_object_dispose函数, _Block_object_dispose函数会自动释放引用的auto变量(release)

函数 调用时机
copy函数 栈上的Block复制到堆时
dispose函数 堆上的Block被废弃时

__block修饰符

block内部想修改 auto类型的变量时需要使用__block进行修饰

__block int age = 10;
void (^block)(void) = ^{
    age = 20;
    NSLog(@"inside - %d", age);
};
block();
// inside - 20

使用__block修饰的变量编译时会被转换成一个对象__Block_byref_xxx_x 把上面的代码使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m 编译成cpp代码,截取编译后的关键代码如下

struct __Block_byref_age_0 {
  void *__isa;      //类型指针
__Block_byref_age_0 *__forwarding;   //指向当前结构体的指针
 int __flags;
 int __size;
 int age;   // 被包装的变量
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref

            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_jh_6_2dc41d2nn08vfc324ycsj00000gn_T_main_7a89f8_mi_0, (age->__forwarding->age));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

age被编译成 __Block_byref_age_0对象,里边有一个 int age 用来存age的值,block中存有__Block_byref_age_0 *age;, 修改的时候通过(age->__forwarding->age) = 20;来修改结构体中的age. 因为__main_block_impl_0中存有__Block_byref_age_0对象,所有会有__main_block_copy_0__main_block_dispose_0方法用来管理对象的引用及释放。

__block修饰的对象类型

 __block NSObject *obj = [[NSObject alloc] init];
void (^block)(void) = ^{
    obj = nil;
};
NSLog(@"ouside- %d, %@", age, obj);

block();

NSLog(@"ouside- %d, %@", age, obj);
/**
ouside- <NSObject: 0x100798d90>
ouside- 20, (null)
 */

把上面的代码编译成cpp代码,截取关键部分

struct __Block_byref_obj_1 {
  void *__isa;
__Block_byref_obj_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *__strong obj;
};

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_obj_1 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_1 *_obj, int flags=0) : obj(_obj->__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_obj_1 *obj = __cself->obj; // bound by ref
            (obj->__forwarding->obj) = __null;
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        __attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, (__Block_byref_obj_1 *)&obj, 570425344));

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

跟普通类型相比,被__block修饰的对象类型多了内存管理相关函数__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131.

循环引用及解决

typedef void(^MyBlock)(void);
@interface AYPerson : NSObject
@property(nonatomic, copy) MyBlock block;
@property(nonatomic, assign) int age;
@end

@implementation AYPerson
- (void)test
{
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"%@", self);
    };
}
@end

给对象添加一个block属性,并且让block捕获对象本身就会造成循环引用,导致内存泄漏。

解决循环引用的问题

  1. 使用__weak,__unsafe_unretained解决
    __weak typeof(self) weakSelf = self;
    self.block = ^{
     NSLog(@"%@", weakSelf);
    };
    

    __unsafe_unretained typeof(self) weakSelf = self;
    self.block = ^{
     NSLog(@"%@", weakSelf);
    };
    
  2. 使用 __block解决
    __block id weakSelf = self;
    self.block = ^{
     NSLog(@"%@", weakSelf);
     weakSelf = nil;
    };
    self.block();
    

    但必须调用block.