深入理解 Block

本文主要根据《Objective-C高级编程》这本书中的第二章来进行的一个总结,其中包含了查看其它文章后的总结和自己的一些理解,关于 block 的一些定义在这里就不说了,这里主要讲一下 block 中的截获自动变量和 __block 关键字的实现
文章主要内容如下:

  • 一个普通的 block
  • 截获自动变量值
  • __block 修饰符
  • 循环引用

一个普通的 block

int main(int argc, char * argv[]) {
    void(^blk)() = ^{
        printf("Hello World");
    };
    blk();
    return 0;
}

一个最普通的 block,内部没有使用到任何变量,让我们来看一下它的内部结构,通过 clang -rewrite-objc 命令将上面的代码解析为一份 C++ 代码:

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) {

        printf("Hello World");
    }

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, char * argv[]) {
    void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

main 函数

对 main 函数进行分解处理,这样可以更加直观的查看

void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

分解为

struct __main_block_impl_0 tempBlock = __main_block_impl_0(__main_block_func_0, &_main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tempBlock;

可以看出,在编译之后 blk 是一个指向一个 __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;
  }
};

从上面的 main 函数可以看到,创建的就是这样一个类型的结构体,我们来看一下这个结构体的内部构造:

  • impl:__block_impl 类型,成员变量之一,内部含有函数指针
  • Desc:__main_block_desc_0 类型指针,成员变量之一,用来描述当前 block 的一些附加信息,例如:结构体的大小
  • __main_block_impl_0 :结构体初始化函数,在结构体中进行成员变量的初始化

接下来我们看一下 impl 所属结构体的主要构造

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
  • isa:非常常见的指向类的指针
  • FuncPtr:函数指针,在 main 函数的初始化过程中,可以观察到是将一个静态函数 __main_block_func_0 对其赋值

接下来看一下 Desc 指针的结构

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)};
  • reserved:今后升级版本所需要区域
  • Block_size:block 的大小

调用结构体初始化函数

从源码可以看出,在 main 函数中通过调用结构体的初始化函数来创建一个结构体

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

我们可以看出,isa 指针保持所属类的结构体的实例的指针,这也说明了 block 其实也就是一个 OC 对象,因为其包含了 isa 指针

截获自动变量值

先来看一段代码:

int a = 10;
void(^blk)() = ^{
    printf("%d\n", a);
};
a = 20;
blk();//输出结果:10

我们可以发现,当我们定义一个变量之后,又定义一个 block,并且在 block 内部引用了这个变量,然后在外面修改变量,然后调用 block,得到的变量还是修改前的变量,这是为什么呢?好像是我们定义 block 的时候将使用的变量 copy 了一份然后自己存起来,当调用 block 的时候,使用的变量是我们 copy 的那一份,所以不管外面对变量如何进行修改,都不会影响我们保存的那一份,当然这只是猜想,带着问题走进源码看一看.

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

        printf("%d\n", 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, char * argv[]) {
    int a = 10;
    void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    a = 20;
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

对比前面那个普通的 block,我们发现几个不同点

  • __main_block_impl_0 结构体中多了一个成员变量 a, 并且在初始化函数中多了一个参数 a,这个 a 就是 block 截获的自动变量值
  • 在静态函数 __main_block_func_0 使用截获的自动变量的时候,用到了一个指针 _cself, 这是什么呢?在 main 可以发现这个参数是在进行 block 调用的时候传入的,传入的就是 block 本身,所以说在静态函数中操作的变量也就是在 block 创建的时候的那个成员变量,而不是操作的外部变量,也就是说,block 只是截获的自动变量的值,而并不是截获了其地址,所以外部变量在 block 创建之后进行修改在这里也没什么效果,前面的猜想得证.

这里需要注意,block 只会截获使用到的变量,而对于没有使用到的变量是不会进行截获的.

那是不是所有的变量都是如此呢?我们做测试的是局部变量,但是我们知道,在 C 语言中变量还有全局变量、全局静态变量和局部静态变量,我们使用这些来尝试一下,以下使用局部静态变量做测试,至于全局变量和全局静态变量留给读者自己探索,后面也会进行解释:

static int a = 10;
void(^blk)() = ^{
    printf("%d\n", a);
};
a = 20;
blk();//输出20

我们发现输出变了,怎么回事呢?还是看源码

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

        printf("%d\n", (*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, char * argv[]) {
    static int a = 10;
    void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
    a = 20;
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

和上面的局部变量作对比,可以发现,在 __main_block_impl_0 结构体中,变成了存储变量 a 的指针,那么我们访问的时候访问的是指针 a 指向的变量,那么就可以解释输出为 20 的原因.

__block 修饰符

当我们修改截获的变量的时候,示例如下:

int a = 10;
void(^blk)() = ^{
    a = 20;//报错:Variable is not assignable(missing,__block type specifier)
    printf("%d\n", a);
};
blk();

因为在底层实现中,即使在 block 内部对对象进行改变,外部的对象没有任何改变,原因也就是前面的解释的自动变量的截获,所以可能苹果就在编译阶段就告诉开发者这样是无效的,也就是直接编译报错。
那么,现在可以得出一个结论:被截获的自动变量值不能直接进行修改,但是,有两种方法可以解决这个问题:

  • 改变存储于特殊存储区域的变量
  • 使用 __block 修饰符

我们先来说一下第一种,在 C 语言中变量一般分为五种,分别是:

  • 自动变量(局部变量)
  • 函数参数
  • 静态变量(局部静态变量)
  • 静态全局变量
  • 全局变量

这里需要去除函数参数这一项,那么写一个测试代码,因为前面已经做过自动变量和静态变量的测试,所以这里只进行全局变量和静态全局变量的测试,如下:

int globalA = 10;
static int intGlobalA = 20;
int main(int argc, char * argv[]) {
    void(^blk)() = ^{
        globalA += 10;
        intGlobalA += 20;
        printf("%d %d\n", globalA, intGlobalA);//输出:20 40
    };
    blk();
    return 0;
}

查看输出,发现可以进行改变,我们来看一下源码:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
int globalA = 10;
static int intGlobalA = 20;

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) {

        globalA += 10;
        intGlobalA += 20;
        printf("%d %d\n", globalA, intGlobalA);
    }

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, char * argv[]) {
    void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

通过源码我们发现,block 并未对两个变量进行截获,而进行访问和修改的时候,并未经过 block,也就是并未使用到 __cself 指针,而是直接进行访问和修改,如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        globalA += 10;
        intGlobalA += 20;
        printf("%d %d\n", globalA, intGlobalA);
    }

至此,可以得出一个结论:

  • 全局变量:可以直接进行访问,修改
  • 静态全局变量:可以直接进行访问,修改
  • 静态变量:指针引用,可以进行访问,修改
  • 自动变量:值引用,不可进行修改

然后,我们再来看一下第二种,使用 __block 修饰符,使用 __block 修饰符修饰的变量称为 block 变量

测试代码如下:

int main(int argc, char * argv[]) {
    __block int a = 10;
    void(^blk)() = ^{
        a = 20;
        printf("%d\n", a);//输出:20
    };
    blk();
    printf("%d\n", a);//输出:20
    return 0;
}

可以发现,在使用 __block 修饰符之后 block 内可以修改自动变量了,同样的,我们来查看一下源码的实现:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
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) = 20;
        printf("%d\n", (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, char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    void(*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);
    printf("%d\n", (a.__forwarding->a));
    return 0;
}

可以发现,在使用 __block 修饰符之后,源码中多了很多东西,我们来一个个进行解析

首先,还是从 __main_block_impl_0 这个结构体开始,可以发现,此时的 block 并不是将截获的 a 直接作为成员变量,而是使用一个结构体 __Block_byref_a_0 类型的同名变量来代替,我们来看一下这个结构体:

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

可以看到,这个结构体中含有一个成员变量 a,即是截获的自动变量,也就是 __block 修饰的自动变量,这里可以看出,当一个自动变量使用 __block 修饰之后,原来的自动变量被生成一个结构体,而结构体中的一个成员变量持有原来的自动变量
在这个结构体中,可以看到一个 __forwarding 指针,这个指针是指向自己的,它的作用就是无论当前 block 是处于栈中,还是堆中,都能够准确的进行访问,所以后面就是通过 __forwarding 指针来访问成员变量 a 的,这里引用《Objective-C 高级编程》中的一张图片来解释一下:

深入理解 Block_第1张图片
取自《Objective-C 高级编程》
  • 最初 block 在栈上,那么 forwarding 指针指向自身的 __block 变量结构体
  • 在 block 被复制到堆上时,会将 forwarding 的值替换为堆上的目标 block 变量用结构体实例的地址,而在堆上的目标 block 变量自己的 forwarding 的值就指向它自己

而在使用变量的时候,如下:

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) = 20;
        printf("%d\n", (a->__forwarding->a));
    }

可以看出,在调用 block 之后,会生成一个 __Block_byref_a_0 类型的指针指向 block 结 构体中的封装截获变量的结构体成员变量,然后通过 __Block_byref_a_0 结构体中的 __forwarding 指向目标 block 变量进行修改

从源码中我们看到多了两个函数:

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

在 OC 中,C 语言结构体不能含有附有 __strong 修饰符的变量,因为编译器不知道应该什么时候进行 C 语言结构体的初始化和废弃操作,不能很好的管理内存,但是 OC 中的运行时库能够准确把握 block 从栈复制到堆以及堆上的 block 被废弃的时机,实际上就是使用了 __main_block_copy_0__main_block_dispose_0 这两个函数.
其中,_Block_object_assign 相当于 retain 操作,将对象赋值在对象类型的结构体成员变量中。
_Block_object_dispose相当于 release 操作。

调用 copy 函数和 dispose 函数的时机:

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

栈上的 block 复制到堆上的情况

  • 调用 block 的 copy 函数时
    在调用 block 的 copy 实例方法时,如果此时 block 在栈上,那么会被复制到堆上
  • Block 作为函数返回值返回时
    调用 _Block_copy 函数
  • 将 Block 赋值给附有 __strong 修饰符 id 类型的类或者 Block 类型成员变量时
    调用 _Block_copy 函数
  • 方法中含有 usingBlock 的 Cocoa 框架方法或者 GCD 的 API 中传递 Block 时
    调用 _Block_copy 函数或者 block 的 copy 实例方法

堆上的 block 被废弃的情况

  • 堆上的 Block 被释放后,谁都不再持有Block时调用 dispose 函数

block 的存储域

前面说到,block 可以看做是一个 OC 对象,从源码中,我们可以看到 block 中的 isa 指针指向的是 &_NSConcreteStackBlock, 那么就可以说,此时 block 的类为 _NSConcreteStackBlock,和这个类相似的还有:

  • _NSConcreteStackBlock(栈区)
  • _NSConcreteMallocBlock(堆区)
  • _NSConcreteGlobalBlock(数据区)

NSConcreteGlobalBlock

在两种情况下,block 会存在于数据区,即isa 指针指向_NSConcreteGlobalBlock:

  • 记述全局变量的地方有 block 语法时
    测试代码:
void(^globalBlk)() = ^{};
int main(int argc, char * argv[]) {
    return 0;
}
  • block 的语法表达式中不使用应该截获的自动变量时

NSConcreteStackBlock

除了以上两种情况外生成的 block 语法生成的 block 都是 NSConcreteStackBlock 类对象

NSConcreteMallocBlock

配置在全局变量上的 block,从变量作用域外也可以通过指针安全的使用,但是设置在栈上的 block,如果其所属的变量作用域结束,该 block 就会被废弃,由于 __block 变量也是在栈上的,同理,如果其所属的变量作用域结束,该变量就会被废弃.

为了解决这个问题,通过将 block 和 __block 变量复制到堆上来解决,这样即使其所属的变量作用域结束,堆上的 block 依然存在,复制到堆上的 block 的 isa 变为 _NSConcreteMallocBlock

三种类型 block 被复制之后:

类型 存储位置 复制之后
NSConcreteGlobalBlock 数据区 什么也不做
NSConcreteStackBlock 栈上 从栈上复制到堆上
NSConcreteMallocBlock 堆上 引用计数增加

在 block 中使用对象类型自动变量时,除以下几种情况外,推荐手动调用 block 的 copy 方法:

  • block 作为函数值返回的时候
  • 将 Block 赋值给附有 __strong 修饰符 id 类型的类或者 Block 类型成员变量时
  • 向方法中含有 usingBlock 的 Cocoa 框架方法或者 GCD 的 API 中传递 Block 时

当一个 block 被复制到堆上的时候,__block 变量也会被复制到堆上,并且被这个 block 所持有,如果又有 block 被复制到堆上,那么 __block 变量的引用计数会增加,然后如果 block 被废弃,那么就不再持有 __block 变量.

block 循环引用

如果在 block 中使用附有 __strong 修饰符的对象类型自动变量,那么当 block 从栈上复制到堆上的时候,该对象被 block 所持有,这样就容易发生循环引用

例如下面这份代码:

typedef void(^blk)(void);

interface BlockTest : NSObject
{
    blk blk_;
}

@implementation BlockTest

- (instancetype)init
{
    self = [super init];
    blk_ = ^{
        NSLog(@"self----------------%@",self);
    };
    return self;
}

@end

上面这份代码中就出现了循环引用,即BlockTest 类对象持有 block,而在 block 中使用持有 __strong 修饰符的 id 类型变量 self,二者互相持有,导致都无法释放,这就是循环引用
如下图所示:

深入理解 Block_第2张图片
block 循环引用.png

在编译器进行代码编译的时候,可以检测出这种循环引用,并且发出警告,但是并不是任何情况下都能有警告,这里需要注意

为了避免循环引用,这里有几种方法,我们一个一个看

__weak 修饰符

即如下代码:

- (instancetype)init
{
    self = [super init];
    id __weak weakSelf = self;
    blk_ = ^{
        NSLog(@"self---------------%@",weakSelf);
    };
    return self;
}

加上 __weak 修饰符之后,block 不再是持有 self,而是持有对对象的弱引用,也就是 self 强引用 block,而 block 弱引用 self,这样就不会发生循环引用

__block 修饰符

先看一下如下代码:

- (instancetype)init
{
    self = [super init];
    __block id tempSelf = self;
    blk_ = ^{
        NSLog(@"self = %@",tempSelf);
        tempSelf = nil;
    };
    return self;
}

- (void)dellocBlk
{
    blk_();
}

在上面的代码中,乍一看二者还是互相引用,但是在 block 内部执行完毕之后有了一个将 tempSelf 置为 nil 的操作,这样就是将 self 对 block 的强引用切断,那么循环引用被破坏,但是这样有一个缺点,那就是如果不执行 blk_ 这个 block,这个循环引用将会永远存在,只有执行了这个 block,tempSelf 才会被置为 nil,循环引用才会被破坏,但是这种方案并非没有优点,很明显的一个优点就是我们可以依靠这个 block 来控制对象的持有时间,这样可以控制对象的释放时间.

Strong-Weak Dance

在解决 block 的循环引用时,除了使用 __weak 修饰符外,还有一个被苹果称为:“Strong-Weak Dance” 的方法,那这个是什么呢?先看一下代码:

- (instancetype)init
{
    self = [super init];
    id __weak weakSelf = self;
    _blk = ^{
        id __strong strongSelf = weakSelf;
        if (strongSelf) {
            NSLog(@"self---------------%@", strongSelf);
        }
    };
    return self;
}

是不是很奇怪,我们好不容易使用 __weak 消除的强引用,为什么在里面又进行了强引用呢?也就是强引用->弱引用->强引用这个过程的作用是什么?我们先来看一下普通的 __weak 有什么问题

__weak BlockTest *weakSelf = self;
self.completionHandler = ^(NSInteger result) {
    [weakSelf.property removeObserver: weakSelf forKeyPath:@"pathName"];
};

在上面的代码中,__weak 确实解决了循环引用的问题,但是假设当前 block 在子线程中执行,但是在执行的时候 self 被释放了,这时候 weakSelf 也被置为 nil,但是我们使用 KVO 来进行移除操作,这时候就会发生崩溃
这时候就需要 Strong-Weak Dance 登场了,还是先来看代码:

- (instancetype)init
{
    self = [super init];
    id __weak weakSelf = self;
    _blk = ^{
        id __strong strongSelf = weakSelf;
        NSLog(@"self---------------%@", strongSelf);
    };
    return self;
}

在 block 内部对 self 又进行一次强引用之后,self 所指对象的引用计数变为 2 ,即使主线程中的 self 被释放,那么对象的引用计数变为1,此时对象并没有销毁

这时候,可能会有人有问题,在里面对 self 的对象进行了强引用,这样岂不是又变成了互相持有,循环引用?
这个时候需要注意,block 只有截获外部变量时,才会引用它。如果是内部新建一个,则没有任何问题。

但是这样真的能够解决多线程下,weakSelf 指向的对象在 block 执行之前被释放的问题吗?如果在执行 id __strong strongSelf = weakSelf 之前 weakSelf 指向的对象就已经被释放了,那么这个语句就没有任何意义了,在开始的那个 KVO 的例子中,崩溃还是依然会发生的,所以此时的 Strong-Weak Dance 没有任何作用,所以为了保证安全性,我们可以在 block 内部进行 strongSelf 的 nil 检测,即:

- (instancetype)init
{
    self = [super init];
    id __weak weakSelf = self;
    _blk = ^{
        id __strong strongSelf = weakSelf;
        if (strongSelf) {
            NSLog(@"self---------------%@", strongSelf);
        }
    };
    return self;
}

参考资料

  • 《Objective-C 高级编程》干货三部曲(二):Blocks篇
  • 深入研究Block捕获外部变量和__block实现原理

你可能感兴趣的:(深入理解 Block)