Block 的应用、循环引用、底层

一、block的语法
 返回类型(^block名称)(参数类型) = ^返回类型(变量类型 变量名称){实现}

直接定义block时,可以省略定义时的返回类型,即

返回类型(^block名称)(参数类型) = ^(变量类型 变量名称){实现}

若参数类型为void,可省略写成

返回类型(^block名称)(void) = ^{实现}

匿名block:block定义时,等号右边的即为匿名block

1.2 typedef简化block的声明

typedef 返回类型(^block名称)(参数类型);

1.3 block是个对象

NSLog(@"%@",block);

输出这个block,通过这个%@ 我们可以看出block其实是个对象,通过输出结果__NSGlobalBlock__可以看出,该block存储在全局区

二、block的循环应用

解决block循环引用的三种方法
1、__weak来解决

    self.name = @"hello";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });
    };
    
    block();

ps : 其实很多时候,__strong会被省略掉,这样在一定情形下,会发生数据丢失。
比如,我们进入一个页面,代码执行block,接着在不过三秒的情况下,退出该页面。如果不用__strong来修饰,二级页面销毁后,其成员变量name也跟着销毁释放了,这样在三秒后执行的这个线程里,name这个成员变量,通过getter方法访问,返回结果会是null

2、__block

    self.name = @"hello";
    __block ViewController *vc = self;

    self.block = ^{
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
            vc = nil; // self -> block -> vc(nil) ->(断开循环引用) block
        });
    };
    
    block();

3、通过block参数解决

    self.name = @"hello";
    self.block = ^(ViewController *vc){
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
        });
    };
    
    self.block(self);
三、分类
  • 栈block
    _NSConcreteStackBlock :保存在栈中的 block,当函数返回时会被销毁。

  • 堆block
    _NSConcreteMallocBlock
    保存在堆中的 block,当引用计数为 0 时会被销毁。

  • 全局的block
    _NSConcreteGlobalBlock:全局的静态 block,在block中不访问外部局部变量,可以访问外部全局变量和静态变量。此时为NSGlobalBlock。

三、代码+底层分析
  • 通过clang命令查看编译器是如何实现Block的,在终端输入clang -rewrite-objc main.m,然后会在当前目录生成main.cpp的C++文件,但是,这一步可能会报错,如下:
In file included from /Users/apple/Desktop/Block_test/Block_test/ViewController.m:8:
/Users/apple/Desktop/Block_test/Block_test/ViewController.h:8:9: fatal error: 
      'UIKit/UIKit.h' file not found
#import 
        ^~~~~~~~~~~~~~~
1 error generated.
  • 没关系,可以试试这个方法感觉还是挺管用的,cd到当前的文件目录下,然后将之前执行的命令替换成为:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk  XXX.m文件

成功:


截屏2021-04-26 下午5.25.49.png

1、Demo1

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];    

    void(^block)(void) = ^{        

        NSLog(@"hello world!");        

    };
    block();    

    // Do any additional setup after loading the view.
}
@end
  • 底层代码的实现:
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  // 构造函数
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_db_80twdv6s18s_zk2z35wwh1cw0000gn_T_ViewController_8cc697_mi_0);
}

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

    void(*block)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
   
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);


}
  • 为了方便我们阅读代码,我们先熟悉几个规律:
    ①、C++中 结构体的名称、方法体的名称,一般可以看成是一层一层拼接的
    ②、xxx_0的意思,这个0 代表的是 第一个,也就是首个
    ③、C++的强转换比较多,可以对代码进行一定量的删除操作。

  • 代码分析
    通过底层我们可以看出,block的C++实现,是一个结构体,有两个成员变量(impl* Desc)和一个构造函数(对结构体进行初始化的函数)。

接着我们看看他的两个成员变量:

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

__block_impl也是一个结构体

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

__ViewController__viewDidLoad_block_desc_0
static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

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

reserved:Block版本升级所需的预留区空间,在这里为0。
Block_size:Block大小(sizeof(struct __blockTest_block_impl_0))。
__ViewController__viewDidLoad_block_desc_0_DATA是一个__ViewController__viewDidLoad_block_desc_0的一个实例。

__ViewController__viewDidLoad_block_func_0

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

static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_db_80twdv6s18s_zk2z35wwh1cw0000gn_T_ViewController_8cc697_mi_0);
}
ViewDidLoad方法
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {

    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

    void(*block)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
   
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

代码简化,并进行一一对应

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    
    // [super viewDidLoad];
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    // void(^block)(void) = ^{NSLog(@"hello world");};
    void(*block)(void) = &__ViewController__viewDidLoad_block_impl_0
                         (
                           __ViewController__viewDidLoad_block_func_0,
                           &__ViewController__viewDidLoad_block_desc_0_DATA
                         );
    // block();
    block->FuncPtr(block);
}
  • 第二行代码:定义了Block。

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

__ViewController__viewDidLoad_block_func_0:Block块的函数指针。
__ViewController__viewDidLoad_block_desc_0_DATA:作为静态全局变量初始化__ViewController__viewDidLoad_block_desc_0的结构体实例指针

  • 第三行代码:调用了Block
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)通过block->FuncPtr指针找到__blockTest_block_func_0函数并且转成(void (*)(__block_impl *))类型。
    ((__block_impl *)block)然后将block作为参数传给这个函数调用。
    简单点说就是,block调用xxxx_func_0方法,并将自己传了过去。
补充:Flags,__block_impl的参数的用途

在这里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 时动态调用。
Demo1总结

以上是一个最基本的block的底层实现。到这里其实我们才走了第一步。你想当我们使用__block 、__weak、block调取外部变量又是什么情况呢?底层又是怎样实现的?变量是如何捕获的?

Demo2 探究block截获变量
截获auto变量值
image.png

我们看到直接在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));
    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 *)&num, 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 *)&num, 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 中查看。

// 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也跟着变化:

image.png

Block在栈上时,__block的存储域是栈,__block变量被栈上的Block持有。
Block被复制到堆上时,会通过调用Block内部的copy函数,copy函数内部会调用_Block_object_assign函数。此时__block变量的存储域是堆,__block变量被堆上的Block持有。
当堆上的Block被释放,会调用Block内部的disposedispose函数内部会调用_Block_object_dispose,堆上的__block被释放。

image.png
  • 当多个栈上的Block使用栈上的__block变量,__block变量被栈上的多个Block持有。
  • Block0被复制到堆上时,__block也会被复制到堆上,被堆上Block0持有。Block1仍然持有栈上的__block,原栈上__block变量的__forwarding指- 向拷贝到堆上之后的__block变量。
  • Block1也被复制到堆上时,堆上的__block被堆上的Block0Block1只有,并且__block的引用计数+1。
  • 当堆上的Block都被释放,__block变量结构体实例引用计数为0,调用_Block_object_dispose,堆上的__block被释放。

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

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

image.png
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截获对象(对象类型的auto变量)

1、定义一个简单的block:

  MJBlock block;
  {
    MJPerson *person = [[MJPerson alloc] init];
    person.age = 10;
    block = ^{
      NSLog(@"---------%d", person.age);
    };
  }
  NSLog(@"------");
  • 第二个nslog打印完成之后,person不会销毁,因为block有个指针指向了外面的person对象,block在堆上,是malloc类型的。block不销毁,person也不会销毁。但是如果改成mrc环境,栈上的block不会强引用auto对象。但是如果person用__weak修饰的话,person就会先销毁。
    上面的block改成下面这样:
        MJBlock block;
        {
            MJPerson *person = [[MJPerson alloc] init];
            person.age = 10;
            __weak MJPerson *weakPerson = person;
            block = ^{
                NSLog(@"---------%d", weakPerson.age);
            };
        }
        NSLog(@"------");
  • 这种情况下用之前的clang xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m转变成底层c++代码会报错 cannot create __weak reference in file using manual reference 因为weak是弱引用是在runtime下进行的,所以用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m转换之后:
typedef void (*MJBlock)(void);
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  MJPerson *__weak person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  MJPerson *__strong person = __cself->person; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c41e64_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
            }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->person, 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};

之前的MJPerson * person;也转换成了MJPerson *__weak person;

总结如下:

  • 当block内部访问了对象类型的auto变量时
  • 1、如果block是在栈上,不论是强指针还是弱指针都不会去对auto对象进行强引用。
  • 2、如果block被拷贝到堆上。在arc环境下,当一个block被强引用引用着,就会进行copy操作,如果block进行copy操作的时候会调用内部的_Block_object_assign函数 ,调用这个函数会根据外部的auto对象的修饰关键字MJPerson *__weak person或者MJPerson *__strong person对auto进行强引用或者弱引用
  • 3、如果block从堆上移除。会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量,类似于release操作。


    image.png

面试题:

    MJPerson *p = [[MJPerson alloc] init];
    __weak MJPerson *weakP = p;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"1-------%@", p);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"2-------%@", weakP);
        });
    });
    NSLog(@"-----------");

经过nslog1跟nslog2位置的变换,得出一个结论,person的释放时间是看person强引用什么时候释放,不用管弱引用释放

block在ARC和MRC中的声明引用有些区别.
  • block可以存储在栈中,也可以在堆中
  • 默认存储在栈中,不需要管理内存
  • 存储在堆中的block会对block进行retain操作
  • (MRC)block在堆中时,不想对block进行retain操作,前面加__block
  • (ARC)前面加__weak或__unsafe_unretained
  • __weak和__unsafe_unretained的区别:__weak则在释放时会对对象赋值nil,后者不会
  • Block_copy使栈中的block转移到堆中,并对block会引用的对象进行retain操作
  • 避免block引用的对象进行retain操作,在引用对象声明时前面加__block
ARC下获取引用计数(retain count)

注意:以下方法只可用于debug,而且在多线程等情况下返回值不是100%可信。

1.使用KVC

[obj valueForKey:@"retainCount"]

2.使用私有API

OBJC_EXTERN int _objc_rootRetainCount(id);
_objc_rootRetainCount(obj)

3.使用CFGetRetainCount

CFGetRetainCount((__bridge CFTypeRef)(obj))

出处
链接:https://www.jianshu.com/p/221d0778dcaa
链接:https://www.jianshu.com/p/60c0bc161201
链接:https://blog.csdn.net/weixin_37547351/article/details/105106559
链接:https://blog.csdn.net/zhangwenhai001/article/details/46702271
链接:https://blog.csdn.net/iuyo89007/article/details/51761720/

你可能感兴趣的:(Block 的应用、循环引用、底层)