clang分析iOS的block实现

iOS开发中,我们经常使用block,尤其在一些异步需要回调的场景,通过向方法传递一个block参数,在异步操作执行完成之后,回调这个block。

block的基本使用

我们常常在文件头部声明block的类型,比如

typedef void(^blkTest)(void);

这里声明了一个没有返回值,没有参数的block,其后如果要使用block的话,可以声明一个block变量,并且调用它。

 blkTest blk = ^(){
    
    
     NSLog(@"block test");
 };
 blk();

block可以作为方法的返回值,也可以作为方法的参数。在方法的内部,可以生成block类型的变量然后执行它。

block的类型

为了查看block的类型,我们可以调用superclass方法获取它的实际类型。

NSLog(@"%@", [[[blk class] superclass] superclass]);

控制台打印的结果为NSObject,这也验证了block实际为NSObject类型,它是一个对象。

block的实现

为了探究block的底层实现,我们可以使用clang编译器把Objective-C代码转换为C++代码。先写好Objective-C代码,新建一个文件命名为blockTest.m,写入以下代码

#import <Foundation/Foundation.h>

typedef void(^blkTest)(void);

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

使用clang命令转换为C++代码

clang -rewrite-objc blockTest.m

当前目录下生成了blockTest.cpp文件,打开这个文件,可以发现block的实现代码。

typedef void(*blkTest)(void);

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

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

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; 
        blkTest blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)blk)->__main_block_func_0)((__block_impl *)blk);
    }
    return 0;
}

可以发现有三个结构体,分别为__block_impl,__main_block_impl_0,

__main_block_desc_0。__main_block_impl_0持有了

__block_impl和__main_block_desc_0,__main_block_impl_0实际上是block的实现方式。

__block_impl里面保存了isa指针和FuncPtr函数指针,block的具体实现被封装成了函数__main_block_func_0,这个__main_block_func_0会传入__main_block_impl_0结构体用于初始化。

在调用block的时候,实际执行的就是函数指针FuncPtr,也就是

__main_block_func_0

block的具体实现基本就清楚了,通过函数指针封装block的具体实现,并把函数指针传入到__main_block_impl_0结构体用于初始化。block的执行实际是找到这个函数指针并且调用它。

在block的实际使用中,我们常常会捕获变量用于实际处理。修改Objective-C代码,加入变量捕获,看看block的C++实现有什么变化。

#import <Foundation/Foundation.h>

typedef void(^blkTest)(void);

int main(int argc, const char * argv[]) {
    
    
    @autoreleasepool {
    
     
        int a = 1;       
        blkTest blk = ^(){
    
    
            NSLog(@"%d", a);
            NSLog(@"block test");
        };
        blk();
    }
    return 0;
}

代码中多了变量a,block中捕获并且打印了变量a的值。再次使用clang命令查看C++代码实现。

clang -rewrite-objc blockTest.m

在生成的C++代码中,可以发现有了一些变化。主要是__main_block_impl_0中多了int a ,__main_block_func_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_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_8cd99b_mi_0, a);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_8cd99b_mi_1);
        }

可以发现,存在变量捕获的情况下,捕获的变量会传递到block结构体中。注意这里传递的值,而不是引用。捕获变量的情况下,如果修改了变量值,Xcode会报错。如果想修改捕获的变量的值,需要在变量前面加上__block声明,block前面是两个下划线。加了__block声明后,block的C++实现有什么变化呢?

#import <Foundation/Foundation.h>

typedef void(^blkTest)(void);

int main(int argc, const char * argv[]) {
    
    
    @autoreleasepool {
    
     
        __block int a = 1;       
        blkTest blk = ^(){
    
    
            a = 2;
            NSLog(@"%d", a);
            NSLog(@"block test");
        };
        blk();
    }
    return 0;
}

接着使用clang命令查看C++实现。

clang -rewrite-objc blockTest.m

在生成的C++代码中,发生了一些变化。

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

            (a->__forwarding->a) = 2;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_f1cf0e_mi_0, (a->__forwarding->a));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_f1cf0e_mi_1);
        }

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), 1};
        blkTest blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    return 0;
}

可以发现多了__Block_byref_a_0结构体,__main_block_impl_0的初始化参数也多了这个结构体。变量a转换成结构体__Block_byref_a_0,并且传入__main_block_impl_0结构体。注意main函数的中代码,传递的是变量a的引用,而不是a的值,这样block就可以修改了变量a的值了。

 blkTest blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

block的内存区域

在iOS的内存分布中,有栈,堆,BSS,全局变量区,代码段。block的分类包括__NSMallocBlock__,NSGlobalBlock__和__NSStackBlock

ARC环境下,对于没有捕获变量的block而言,默认是NSGlobalBlock类型,并且方法到全局变量区。对于捕获了变量的block而言,实际执行时会自动把NSStackBlock从栈上拷贝到堆上,变为NSMallocBlock类型,存储位置在堆上。

block循环引用问题

Objective-C的内存使用了计数的方式管理内存,如果block和变量相互持有的话,会产生循环引用,这个时候可以使用weak解除循环引用。

 __weak typeof (self) weakSelf = self;

在block中使用weakSelf不会导致循环引用的发生。

猜你喜欢

转载自blog.csdn.net/u011608357/article/details/127718747