iOS block

block的数据结构

先来一个最简单的block,看看这个block到底执行了什么

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void(^block)(void) = ^{
            NSLog(@"this is block");
        };
        block();
    }
    return 0;
}

cdmain.m的目录下,执行:

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

我们可以看到上述的代码,转换为c++代码为:

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

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

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

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_6ab938_mi_0);
        }

//  main函数
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

block声明简化后为:

*block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));

全局搜索__main_block_impl_0,我们在结构体__main_block_impl_0里面找到了同名构造函数。

结论:block是包含isa指针的对象。

block的类型

通过打印classsuperclass可以看出继承关系如下所示,class__NSGlobalBlock__

image.png

当我们传递一个局部变量进block的时候,发现class发生改变,变成__NSMallocBlock__
image.png

ARC环境转成MRC之后,
image.png

执行相同的代码,发现blockclass变成__NSStackBlock__
image.png

结论:block是对象类型,有完整的继承链,当声明block时,会转化成__NSGlobalBlock____NSStackBlock____NSMallocBlock__三种类型。

1、创建一个不包含外部变量的block的时候,则该block在全局区,是__NSGlobalBlock__类型。
2、block包含局部变量的时候,MRC环境下,则该block在栈区,是__NSStackBlock__类型。
3、block包含局部变量的时候,ARC环境下,则该block被拷贝到堆区,是__NSMallocBlock__类型。
ARC环境下成为__NSMallocBlock__条件:有需要自己处理对象生命周期的时候,会将栈区的__NSStackBlock__类型copy到堆区,生成__NSMallocBlock__类型。

经典问题:如下代码打印结果是什么,以及为什么(涉及到__block

#import 

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        int a = 10;
        void(^block)(void) = ^{
            NSLog(@"a = %d" , a);
        };
        a = 20;
        block();
    }
    return 0;
}

打印结果为10,现在开始解析原因,将上述代码重新编译成C++代码之后,代码为:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_cf236d_mi_0 , a);
        }

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; 

        int a = 10;
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

在创建block的时候,__main_block_impl_0结构中,会创建一个int a;,在block执行构建函数的时候,将外面的a作为参数传进去,再对__main_block_impl_0结构中,创建的int a;参数进行赋值操作,此时,block内部的a,其初始值即为外面a的值,为10,后面执行a = 20;的操作实际上是对外面的a的操作,并没有影响到内部的结果,所以打印结果为:a = 10,打印的实际上是捕获到的值。

那么我们如何处理a值不变的问题呢?

方法一:使用局部静态变量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int a = 10;
        void(^block)(void) = ^{
            NSLog(@"a = %d" , a);
        };
        a = 20;
        block();
    }
    return 0;
}

底层代码实现原理如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_8dcf0c_mi_0 , (*a));
        }

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; 

        static int a = 10;
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

构建函数的时候,传递了static int a = 10;的地址,block内部用一个int *a接收地址,所以调用的时候,实际上是捕获静态局部变量的地址,所以可以值会同步打印。【局部静态变量,block获取指针地址】

方法二:使用全局变量

static int a = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"a = %d" , a);
        };
        a = 20;
        block();
    }
    return 0;
}

底层代码实现原理如下:

static int a = 10;

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_7445b2_mi_0 , a);
        }

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; 
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

因为是全局变量,所以直接调用全局变量。

方法三(重要):使用__block
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int a = 10;
        void(^block)(void) = ^{
            NSLog(@"a = %d" , a);
        };
        a = 20;
        block();
    }
    return 0;
}

底层代码实现原理如下:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

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

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        (a.__forwarding->a) = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

当我们执行__block int a = 10;的时候,实际底层代码转换为:

        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};

__main_block_impl_0中会出现一个__Block_byref_a_0 *a;,是包含isa指针的对象,__Block_byref_a_0中包含int a;,我们在__main_block_desc_0结构中,多出了__main_block_copy_0__main_block_dispose_0函数,

  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

copy操作实际上是调用_Block_object_assign函数,进行copy(拷贝)操作,dispose操作实际上是调用_Block_object_dispose函数,进行dispose(销毁)操作。

结论:当__block作用于局部变量时,会将局部变量封装成一个__Block_byref_XXX_0类型的对象,局部变量的值复制给了__Block_byref_XXX_0类型对象中的同名成员变量中,此时暴露在外面的__block int a = 10;实际上已经封装成了一个对象,当我们对a进行赋值操作的时候,实际上执行的是(a.__forwarding->a) = 20;操作,__forwarding存储的地址就是__Block_byref_a_0 *a的地址,目的是为了保证栈区的block copy到堆区的时候依然可以找到相对应的地址

block循环引用

申明一个BlockObject对象

@interface BlockObject : NSObject
@property (nonatomic, assign) int num;
@property (nonatomic, copy) void(^block)(void);
@end

@implementation BlockObject
- (void)dealloc {
    NSLog(@"%s", __func__);
}
@end

//  main.m
#import 
#import "BlockObject.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BlockObject *objc = [[BlockObject alloc] init];
        objc.num = 10;
        objc.block = ^{
            NSLog(@"%d", objc.num);
        };
    }
    NSLog(@"-----------");
    return 0;
}

运行结果显示,没有执行dealloc里面的方法,造成了循环引用。

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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_a023f6_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)objc, sel_registerName("num")));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        BlockObject *objc = ((BlockObject *(*)(id, SEL))(void *)objc_msgSend)((id)((BlockObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("BlockObject"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)objc, sel_registerName("setNum:"), 10);
        ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)objc, sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, objc, 570425344)));
    }
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_a023f6_mi_1);
    return 0;
}

block里面包含对象类型时,__main_block_impl_0包含了BlockObject *objc;对象,__main_block_copy_0objc对象执行了copy操作,objcretain+1。当执行__main_block_dispose_0的时候,则出现:objc对象中引用了block属性,在block对象中,有了一个被强引用的objc属性,当执行dispose的时候,则会发生互相等待被销毁的循环引用问题。

解决方法1:使用__weak
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BlockObject *objc = [[BlockObject alloc] init];
        objc.num = 10;
        __weak typeof(objc) weakObjc = objc;
        objc.block = ^{
            NSLog(@"%d", weakObjc.num);
        };
    }
    NSLog(@"-----------");
    return 0;
}

由于__weak会用到runtime相关的东西,OC转为C++可以使用如下方式:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

可以看到如下数据结构:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  BlockObject *__weak weakObjc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, BlockObject *__weak _weakObjc, int flags=0) : weakObjc(_weakObjc) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__weak实际上是对block里面的对象执行了weak操作,结构为:BlockObject *__weak weakObjc;__main_block_copy_0objc对象执行了copy操作,实际上也只是进行了一次weak引用,所以不会造成循环引用。

解决方法2:使用__block
//   BlockObject.h
@interface BlockObject : NSObject

@property (nonatomic, assign) int num;
@property (nonatomic, copy) void(^block)(void);

- (void)test;

@end

//   BlockObject.m
@implementation BlockObject

- (void)dealloc {
    NSLog(@"%s", __func__);
}

- (void)test {
    __block id blockSelf = self;
    self.block = ^{
        NSLog(@"%p", blockSelf);
        blockSelf = nil;
    };
    self.block();
}
@end

//  main.m
#import 
#import "BlockObject.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BlockObject *objc = [[BlockObject alloc] init];
        objc.num = 10;
        [objc test];
    }
    NSLog(@"-----------");
    return 0;
}

这个方法一般用的比较少,实际上实现原理是形成三角关系,由于__block创建了一个__Block_byref_XXX_0类型的对象,block内部执行了blockSelf = nil;的操作实际上就是切断三角关系,达到去除循环引用的效果。

你可能感兴趣的:(iOS block)