iOS中Block实现原理的全面分析

栏目: IOS · 发布时间: 5年前

内容简介:通过clang命令查看编译器是如何实现Block的,在终端输入下面我们一个一个来看
void blockTest()
{
    void (^block)(void) = ^{
        NSLog(@"Hello World!");
    };
    block();
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        blockTest();
    }
}
复制代码

通过clang命令查看编译器是如何实现Block的,在终端输入 clang -rewrite-objc main.m ,然后会在当前目录生成 main.cpp 的C++文件,代码如下:

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_0048d2_mi_0);
    }
    

static struct __blockTest_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};

void blockTest()
{
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        blockTest();
    }
}

static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

复制代码

下面我们一个一个来看

__blockTest_block_impl_0

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
复制代码

__blockTest_block_impl_0Block 的C++实现,是一个结构体,从命名可以看出表示 blockTest 中的第一个( 0Block 。通常包含两个成员变量 __block_impl impl__blockTest_block_desc_0* Desc 和一个构造函数。

__block_impl

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

复制代码

__block_impl也是一个结构体

  • *isa:isa指针,指向一个类对象,有三种类型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock,本例中是_NSConcreteStackBlock类型。
  • Flags:block 的负载信息(引用计数和类型信息),按位存储。
  • Reserved:保留变量。
  • *FuncPtr:一个指针,指向 Block 执行时调用的函数,也就是 Block 需要执行的代码块。在本例中是 __blockTest_block_func_0 函数。

__blockTest_block_desc_0

static struct __blockTest_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};

复制代码

__blockTest_block_desc_0 是一个结构体,包含两个成员变量:

  • reserved: Block 版本升级所需的预留区空间,在这里为0。
  • Block_size: Block 大小( sizeof(struct __blockTest_block_impl_0))

__blockTest_block_desc_0_DATA 是一个 __blockTest_block_desc_0 的一个实例。

__blockTest_block_func_0

__blockTest_block_func_0 就是 Block 的执行时调用的函数,参数是一个 __blockTest_block_impl_0 类型的指针。

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_0048d2_mi_0);
    }

复制代码

blockTest

void blockTest()
{
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

复制代码

第一部分,定义Block

void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA));

复制代码

我们看到 block 变成了一个指针,指向一个通过 __blockTest_block_impl_0 构造函数实例化的结构体 __blockTest_block_impl_0 实例, __blockTest_block_impl_0 在初始化的时候需要两个个参数:

  • __blockTest_block_func_0Block 块的函数指针。
  • __blockTest_block_desc_0_DATA :作为静态全局变量初始化 __main_block_desc_0 的结构体实例指针。

第二部分,调用Block

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

复制代码

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr) 通过 block->FuncPtr 指针找到 __blockTest_block_func_0 函数并且转成 (void (*)(__block_impl *)) 类型。

((__block_impl *)block) 然后将 block 作为参数传给这个函数调用。

Flags

__block_impl 中我们看到 Flags ,现在来详细讲一讲。

在这里Block_private.h可以看到 Flags 的具体信息:

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

复制代码

引用 浅谈 block(1) - clang 改写后的 block 结构 的解释:

也就是说,一般情况下,一个 block 的 flags 成员默认设置为 0。如果当 block 需要 Block_copy()Block_release 这类拷贝辅助函数,则会设置成 1 << 25 ,也就是 BLOCK_HAS_COPY_DISPOSE 类型。可以搜索到大量讲述 Block_copy 方法的博文,其中涉及到了 BLOCK_HAS_COPY_DISPOSE

总结一下枚举类的用法,前 16 位即起到标记作用,又可记录引用计数:

  • BLOCK_DEALLOCATING :释放标记。一般常用 BLOCK_NEEDS_FREE 做 位与 操作,一同传入 Flags ,告知该 block 可释放。
  • BLOCK_REFCOUNT_MASK :一般参与判断引用计数,是一个可选用参数。
  • BLOCK_NEEDS_FREE :通过设置该枚举位,来告知该 block 可释放。意在说明 block 是 heap block ,即我们常说的 _NSConcreteMallocBlock 。
  • BLOCK_HAS_COPY_DISPOSE :是否拥有拷贝辅助函数(a copy helper function)。
  • BLOCK_HAS_CTOR :是否拥有 block 析构函数(dispose function)。
  • BLOCK_IS_GC :是否启用 GC 机制(Garbage Collection)。
  • BLOCK_HAS_SIGNATURE :与 BLOCK_USE_STRET 相对,判断是否当前 block 拥有一个签名。用于 runtime 时动态调用。

block截获变量

截获auto变量值

iOS中Block实现原理的全面分析

我们看到直接在 block 修改变量会提示错误,为什么呢?

void blockTest()
{
    int num = 10;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
    };
    num = 20;
    block();
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        blockTest();
    }
}

复制代码

打印结果是10,clang改写后的代码如下:

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  int num;
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
  int num = __cself->num; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_3c2714_mi_0,num);
    }
    
    void blockTest()
{
    int num = 10;
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, num));
    num = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

复制代码

__blockTest_block_impl_0 多了一个成员变量 int num; ,再看看构造函数 __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0) ,可以看到第三个参数只是变量的值,这也就解释了为什么打印的是10,因为** block 截获的是值**。

使用static修饰变量

void blockTest()
{
    static int num = 10;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

复制代码

可以在 block 内部修改变量了,同时打印结果是20,30。clang改写后的代码如下:

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  int *num;
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
  int *num = __cself->num; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_0,(*num));
        (*num) = 30;
    }
    
    void blockTest()
{
    static int num = 10;
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, #));
    num = 20;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_1,num);
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

复制代码

__blockTest_block_impl_0 多了一个成员变量 int *num; ,和上面不同的是,这次** block 截获的是指针**,所以可以在内部通过指针修改变量的值,同时在外部修改变量的值, block 也能"感知到"。那么为什么之前传递指针呢?因为变量是栈上,作用域是函数 blockTest 内,那么有可能变量比 block 先销毁,这时候 block 再通过指针去访问变量就会有问题。而 static 修饰的变量不会被销毁,也就不用担心。

全局变量

int num = 10;

void blockTest()
{
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

复制代码

打印结果是20,30。clang改写后的代码如下:

int num = 10;


struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_1875c6_mi_0,num);
        num = 30;
    }

复制代码

非常简单,在初始化 __blockTest_block_impl_0 并没有把 num 作为参数, __blockTest_block_func_0 中也是直接访问全局变量。

总结:

变量类型 是否捕获到block内部 访问方式
局部auto变量 值传递
局部static变量 指针传递
全局变量 直接访问

使用__block修饰变量

void blockTest()
{
    __block int num = 10;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

复制代码

效果和使用static修饰变量一样,clang改写后的代码如下:

struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding;
 int __flags;
 int __size;
 int num;
};

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  __Block_byref_num_0 *num; // by ref
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
  __Block_byref_num_0 *num = __cself->num; // bound by ref

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_0,(num->__forwarding->num));
        (num->__forwarding->num) = 30;
    }
    
static void __blockTest_block_copy_0(struct __blockTest_block_impl_0*dst, struct __blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __blockTest_block_dispose_0(struct __blockTest_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __blockTest_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __blockTest_block_impl_0*, struct __blockTest_block_impl_0*);
  void (*dispose)(struct __blockTest_block_impl_0*);
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0), __blockTest_block_copy_0, __blockTest_block_dispose_0};

void blockTest()
{
    __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)#, 0, sizeof(__Block_byref_num_0), 10};
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, (__Block_byref_num_0 *)#, 570425344));
    (num.__forwarding->num) = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_1,(num.__forwarding->num));
}

复制代码

哇,难受啊兄dei,怎么多出来这么多东西,没关系,慢慢分析。

__blockTest_block_impl_0 多出来一个成员变量 __Block_byref_num_0 *num; ,我们看到经过 __block 修饰的变量类型变成了结构体 __Block_byref_num_0__blockTest_block_impl_0 多出来一个成员变量 __Block_byref_num_0 *num; block 捕获的是 __Block_byref_num_0 类型指针

__Block_byref_num_0

我们看到 __Block_byref_num_0 是一个结构体,并且有一个 isa ,因此我们可以知道它其实就是一个对象。同时还有一个 __Block_byref_a_0 * 类型的 __forwardingnumnum 我们能猜到就是用来保存变量的值, __forwarding 就有一点复杂了,后面慢慢讲。

__blockTest_block_copy_0和**__blockTest_block_dispose_0**

__blockTest_block_copy_0 中调用的是 _Block_object_assign__blockTest_block_dispose_0 中调用的是 _Block_object_dispose

函数 调用时机
__blockTest_block_copy_0 __block 变量结构体实例从栈拷贝到堆时
__blockTest_block_dispose_0 __block 变量结构体实例引用计数为0时

关于 _Block_object_assign_Block_object_dispose 更详细代码可以在runtime.c中查看。

BLOCK_FIELD_IS_BYREF

我们看到 _Block_object_assign_Block_object_dispose 中都有个参数值为8, BLOCK_FIELD_IS_BYREF 类型,什么意思呢?在Block_private.h中可以查看到:

// Runtime support functions used by compiler when generating copy/dispose helpers

// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};
复制代码
  • BLOCK_FIELD_IS_OBJECT :OC对象类型
  • BLOCK_FIELD_IS_BLOCK :是一个block
  • BLOCK_FIELD_IS_BYREF :在栈上被 __block 修饰的变量
  • BLOCK_FIELD_IS_WEAK :被 __weak 修饰的变量,只在 Block_byref 管理内部对象内存时使用
  • BLOCK_BYREF_CALLER :处理 Block_byref 内部对象内存的时候会加的一个额外标记(告诉内部实现不要进行retain或者copy)

__blockTest_block_desc_0我们可以看到它多了两个回调函数指针 *copy*dispose ,这两个指针会被赋值为 __main_block_copy_0__main_block_dispose_0

最后我们看到访问 num 是这样的:

__Block_byref_num_0 *num = __cself->num; // bound by ref   

(num->__forwarding->num) = 30;
复制代码

下面就讲一讲为什么要这样。

Block的内存管理

在前面我们讲到 __block_impl 指向的_NSConcreteStackBlock类型的类对象,其实总共有三种类型:

类型 存储区域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 数据区
_NSConcreteMallocBlock

前面也讲到 copydispose ,在 ARC 环境下,有哪些情况编译器会自动将栈上的把 Block 从栈上复制到堆上呢?

Block从栈中复制到堆
调用Block的copy实例方法时
Block作为函数返回值返回时
在带有usingBlock的Cocoa方法或者GCD的API中传递Block时候
将block赋给带有__strong修饰符的id类型或者Block类型时

Bock 从栈中复制到堆, __block 也跟着变化:

iOS中Block实现原理的全面分析
  1. Block 在栈上时, __block 的存储域是栈, __block 变量被栈上的 Block 持有。
  2. Block 被复制到堆上时,会通过调用 Block 内部的 copy 函数,copy函数内部会调用 _Block_object_assign 函数。此时 __block 变量的存储域是堆, __block 变量被堆上的 Block 持有。
  3. 当堆上的 Block 被释放,会调用 Block 内部的 disposedispose 函数内部会调用 _Block_object_dispose ,堆上的 __block 被释放。
iOS中Block实现原理的全面分析
  1. 当多个栈上的 Block 使用栈上的 __block 变量, __block 变量被栈上的多个 Block 持有。
  2. Block0 被复制到堆上时, __block 也会被复制到堆上,被堆上 Block0 持有。 Block1 仍然持有栈上的 __block ,原栈上 __block 变量的 __forwarding 指向拷贝到堆上之后的 __block 变量。
  3. Block1 也被复制到堆上时,堆上的 __block 被堆上的 Block0Block1 只有,并且 __block 的引用计数+1。
  4. 当堆上的 Block 都被释放, __block 变量结构体实例引用计数为0,调用 _Block_object_dispose ,堆上的 __block 被释放。

下图是描述 __forwarding 变化。这也就能解释 __forwarding 存在的意义:

__forwarding 保证在栈上或者堆上都能正确访问对应变量

iOS中Block实现原理的全面分析
int main(int argc, char * argv[]) {

    int num = 10;

    NSLog(@"%@",[^{
        NSLog(@"%d",num);
    } class]);

    void (^block)(void) = ^{
        NSLog(@"%d",num);
    };

    NSLog(@"%@",[block class]);
}

复制代码

打印结果:

2019-05-04 18:40:48.470228+0800 BlockTest[35824:16939613] __NSStackBlock__
2019-05-04 18:40:48.470912+0800 BlockTest[35824:16939613] __NSMallocBlock__

复制代码

我们可以看到第一个 Block 没有赋值给 __strong 指针,而第二个 Block 没有赋值给 __strong 指针,所以第一个在栈上,而第二个在堆上。

Block截获对象

int main(int argc, char * argv[]) {
    {
        Person *person = [[Person alloc] init];
        person.name = @"roy";

        NSLog(@"%@",[^{
            NSLog(@"%@",person.name);
        } class]);
        NSLog(@"%@",@"+++++++++++++");
    }
    NSLog(@"%@",@"------------");
}

复制代码

打印结果:

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

@implementation Person

- (void)dealloc {
    NSLog(@"-------dealloc-------");
}

@end

typedef void(^Block)(void);

int main(int argc, char * argv[]) {
    {
        Person *person = [[Person alloc] init];
        person.name = @"roy";

        NSLog(@"%@",[^{
            NSLog(@"%@",person.name);
        } class]);
        NSLog(@"%@",@"+++++++++++++");
    }
    NSLog(@"%@",@"------------");
}
复制代码

我们看到当 Block 内部访问了对象类型的auto对象时,如果 Block 是在栈上,将不会对auto对象产生强引用。

auto Strong 对象

typedef void(^Block)(void);

int main(int argc, char * argv[]) {
    Block block;
    {
        Person *person = [[Person alloc] init];
        person.name = @"roy";

        block = ^{
            NSLog(@"%@",person.name);
        };
        person.name = @"david";
        NSLog(@"%@",@"+++++++++++++");
    }
    NSLog(@"%@",@"------------");
    block ();
}

复制代码

打印结果是

2019-05-04 17:46:27.083280+0800 BlockTest[33745:16864251] +++++++++++++
2019-05-04 17:46:27.083934+0800 BlockTest[33745:16864251] ------------
2019-05-04 17:46:27.084018+0800 BlockTest[33745:16864251] david
2019-05-04 17:46:27.084158+0800 BlockTest[33745:16864251] -------dealloc-------

复制代码

我们看到是先打印的 david 再调用 Person 的析构方法 dealloc ,在终端输入 clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.13 main.m -fobjc-arc ,clang在ARC环境下改写后的代码如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

复制代码

我们看到 __main_block_impl_0 中的 Person *__strong person; 成员变量。

Block 截获了auto对象,当 Block 被拷贝到堆上, Block 强引用auto对象,这就能解释了为什么超出了 person 的作用域, person 没有立即释放,当 Block 释放之后,会自动去掉对该对象的强引用,该对象就会被释放了。

auto Weak 对象

typedef void(^Block)(void);

int main(int argc, char * argv[]) {
    Block block;
    {
        Person *person = [[Person alloc] init];
        person.name = @"roy";
        __weak Person *weakPerson = person;

        block = ^{
            NSLog(@"%@",weakPerson.name);
        };
        weakPerson.name = @"david";
        NSLog(@"%@",@"+++++++++++++");
    }
    NSLog(@"%@",@"------------");
    block ();
}

复制代码

打印结果是

2019-05-04 17:49:38.858554+0800 BlockTest[33856:16869229] +++++++++++++
2019-05-04 17:49:38.859218+0800 BlockTest[33856:16869229] -------dealloc-------
2019-05-04 17:49:38.859321+0800 BlockTest[33856:16869229] ------------
2019-05-04 17:49:38.859403+0800 BlockTest[33856:16869229] (null)

复制代码

直接在终端输入 clang -rewrite-objc main.m 会报 cannot create __weak reference because the current deployment target does not support weak ref 错误。需要用 clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.13 main.m-fobjc-arc 代表当前是ARC环境 -fobjc-runtime=macosx-10.13 :代表当前运行时环境,缺一不可,clang之后的代码:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__weak weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
复制代码

我们看到 __main_block_impl_0 中的 Person *__weak weakPerson; 成员变量。

总结:

  1. Block 内部访问了对象类型的auto对象时,如果 Block 是在栈上,将不会对auto对象产生强引用。
  2. 如果block被拷贝到堆上,会调用 Block 内部的copy函数,copy函数内部会调用 _Block_object_assign 函数, _Block_object_assign 会根据auto对象的修饰符( __strong__weak__unsafe_unretained )做出相应的操作,当使用的是 __strong 时,将会对 person 对象的引用计数加1,当为 __weak 时,引用计数不变。
  3. 如果 Block 从对上移除,会调用block内部的 dispose 函数,内部会调用 _Block_object_dispose 函数,这个函数会自动释放引用的 auto 对象。

Block循环引用

@interface Person : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void (^block)(void);

- (void)testReferenceSelf;

@end

@implementation Person

- (void)testReferenceSelf {
    self.block = ^ {
        NSLog(@"self.name = %s", self.name.UTF8String);
    };
    self.block();
}

- (void)dealloc {
    NSLog(@"-------dealloc-------");
}

@end


int main(int argc, char * argv[]) {
    Person *person = [[Person alloc] init];
    person.name = @"roy";
    [person testReferenceSelf];
}

复制代码

打印结果是 self.name = royPerson 的析构方法 dealloc 并没有执行,这是典型的循环引用,下面我们研究研究为啥会循环引用。clang改写后的代码如下:

struct __Person__testReferenceSelf_block_impl_0 {
  struct __block_impl impl;
  struct __Person__testReferenceSelf_block_desc_0* Desc;
  Person *const __strong self;
  __Person__testReferenceSelf_block_impl_0(void *fp, struct __Person__testReferenceSelf_block_desc_0 *desc, Person *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void _I_Person_testReferenceSelf(Person * self, SEL _cmd) {
    ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__Person__testReferenceSelf_block_impl_0((void *)__Person__testReferenceSelf_block_func_0, &__Person__testReferenceSelf_block_desc_0_DATA, self, 570425344)));
    ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("block"))();
}

复制代码

我们看到本来Person中 testReferenceSelf 方法是没有参数的,但是转成C++之后多出来两个参数: * self_cmd ,再看看 __Person__testReferenceSelf_block_impl_0 中多出来一个成员变量 Person *const __strong self; ,因此我们知道Person中 block 捕获了 selfblock 强引用 self ,同时 self 也强引用 block ,因此形成循环引用。

Weak解除循环引用

@implementation Person

- (void)testReferenceSelf {
    __weak typeof(self) weakself = self;
    self.block = ^ {
        __strong typeof(self) strongself = weakself;
        NSLog(@"self.name = %s", strongself.name.UTF8String);
    };
    self.block();
}

- (void)dealloc {
    NSLog(@"-------dealloc-------");
}

@end

复制代码

打印结果:

2019-05-04 19:27:48.274358+0800 BlockTest[37426:17007507] self.name = roy
2019-05-04 19:27:48.275016+0800 BlockTest[37426:17007507] -------dealloc-------

复制代码

我们看到Person对象被正常释放了,说明不存在循环引用,为什么呢?clang改写后的代码如下:

struct __Person__testReferenceSelf_block_impl_0 {
  struct __block_impl impl;
  struct __Person__testReferenceSelf_block_desc_0* Desc;
  Person *const __weak weakself;
  __Person__testReferenceSelf_block_impl_0(void *fp, struct __Person__testReferenceSelf_block_desc_0 *desc, Person *const __weak _weakself, int flags=0) : weakself(_weakself) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


static void _I_Person_testReferenceSelf(Person * self, SEL _cmd) {
    __attribute__((objc_ownership(weak))) typeof(self) weakself = self;
    ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__Person__testReferenceSelf_block_impl_0((void *)__Person__testReferenceSelf_block_func_0, &__Person__testReferenceSelf_block_desc_0_DATA, weakself, 570425344)));
    ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("block"))();
}
复制代码

可以看到 __Person__testReferenceSelf_block_impl_0 结构体中weakself成员是一个 __weak 修饰的Person类型对象,也就是说 __Person__testReferenceSelf_block_impl_0 对Person的依赖是弱依赖。weak修饰变量是在runtime中进行处理的,在Person对象的Dealloc方法中会调用weak引用的处理方法,从weak_table中寻找弱引用的依赖对象,进行清除处理。

最后

好了,关于Block就写到这里了,花了五一的三天时间解决了一个基础知识点,如释重负,写的真心累。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

智能革命

智能革命

李彦宏 等 / 中信出版集团 / 2017-5-1 / 68.00元

人类历史上的历次技术革命,都带来了人类感知和认知能力的不断提升,从而使人类知道更多,做到更多,体验更多。以此为标准,李彦宏在本书中将人工智能定义为堪比任何一次技术革命的伟大变革,并且明确提出,在技术与人的关系上,智能革命不同于前几次技术革命,不是人去适应机器,而是机器主动来学习和适应人类,并同人类一起学习和创新这个世界。“人工智能”正式写入2017年政府工作报告,折射出未来人工智能产业在我国经济发......一起来看看 《智能革命》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码