Block原理探索

Block定义

闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称为自由变量)
block实际上就是OC对于闭包的实现。
block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象

Block结构分析

void blockTest()
{
    void (^block)(void) = ^{
        NSLog(@"Hello World!");
    };
    block();
}

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

编译后得到👇:

struct __block_impl {
  void *isa;//isa指针,指向一个类对象,有三种类型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock,可以看出这里使用的是_NSConcreteStackBlock
  int Flags;//Block的负载信息(引用计数和类型信息),按位存储
  int Reserved;//保留变量
  void *FuncPtr;//一个指针,指向Block执行时的函数,也就是Block需要执行的代码块,在这里指向的是__blockTest_block_func_0函数
};

//通常包含两个成员变量:__block_impl、__blockTest_block_desc_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_func_0就是Block执行调用的函数,参数是一个__blockTest_block_impl_0类型的指针
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_42229c_mi_0);
}

static struct __blockTest_block_desc_0 {
  size_t reserved;//Block版本升级所需预留区空间,这里为0
  size_t Block_size;//Block的大小, sizeof(struct __blockTest_block_impl_0)
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};//是__blockTest_block_desc_0的一个实例

void blockTest()
{
    /**
     1.block的定义:通过__blockTest_block_impl_0结构体生成一个实例,并用一个指针指向了当前实例,
     __blockTest_block_impl_0q在初始化时需要两个参数:
     __blockTest_block_func_0:Block块的函数指针
     __blockTest_block_desc_0_DATA:作为静态全局变量初始化__blockTest_block_desc_0_DATA结构体的实例指针
     */
    void (*block)(void) = (&__blockTest_block_impl_0(
                                                     __blockTest_block_func_0,
                                                     &__blockTest_block_desc_0_DATA)
                           );
    /**
     2.调用block:通过block)->FuncPtr找到__blockTest_block_func_0函数指针
     然后将block作为参数传递给这个函数
     */
    (block)->FuncPtr)(block);
}

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        blockTest();
    }
}
  1. block的定义部分:
    block是一个结构体,该结构体需要两个参数
    __blockTest_block_func_0:Block块的函数指针;
    __blockTest_block_desc_0_DATA:作为静态全局变量初始化__blockTest_block_desc_0_DATA结构体的实例指针;
    通过__blockTest_block_impl_0结构体生成一个实例,并用一个指针指向了当前实例。
  2. block调用部分:
    通过block)->FuncPtr找到__blockTest_block_func_0函数指针,并将step1 block指针传递给该函数,

__blockTest_block_func_0就是block执行时调用的函数,接收的参数是__blockTest_block_impl_0类型的指针,step1生成的就是__blockTest_block_impl_0结构体的实例

Flags(Block_private.h)
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // 释放标记。一般常用 BLOCK_NEEDS_FREE 做 位与 操作,一同传入 Flags ,告知该 block 可释放。
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // 一般参与判断引用计数,是一个可选用参数。
    BLOCK_NEEDS_FREE =        (1 << 24), // 通过设置该枚举位,来告知该 block 可释放。意在说明 block 是 heap block ,即我们常说的 _NSConcreteMallocBlock 。
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // 是否拥有拷贝辅助函数(a copy helper function)。
    BLOCK_HAS_CTOR =          (1 << 26), // 是否拥有 block 析构函数(dispose function)。
    BLOCK_IS_GC =             (1 << 27), // 是否启用 GC 机制(Garbage Collection)。
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30)  // 与 BLOCK_USE_STRET 相对,判断是否当前 block 拥有一个签名。用于 runtime 时动态调用。
};

Block结构如图(网上借的):


Block

Block捕获变量

捕获auto变量(局部变量)

先看下面这段代码:

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

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

num应该输出什么?
答案:应该输出10
编译后的代码👇:

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

void blockTest()
{
    int num = 10;
    void (*block)(void) = (&__blockTest_block_impl_0(
                                                     __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结构体多了一个成员变量num
构造函数__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0)可以看到第三个参数num只是变量的值,这就解释了为什么num打印的是10,因为block捕获auto变量时,捕获的是其值。

捕获static变量

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

两次num分别输出什么?
答案:block块内的num输出20,第二个num输出30
编译后👇:

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_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a383ea_mi_0,(*num));
        (*num) = 30;
}

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

构造函数__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0)第三个参数*num传入的是num的指针,所以可以在内部和外部修改变量的值。

为什么auto变量就是传递的值,而static变量传递的是指针呢?

auto变量保存在栈中,并且会随着当前作用域(blockTest)消失而销毁,有可能销毁时机会比block更早,所以block内访问销毁的变量时会产生问题,而static变量保存在全局存储区(静态存储区),不会出现这样的问题。

全局变量

int num = 10;

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

编译后👇:

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_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_141607_mi_0,num);
        num = 30;
    }

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) = (&__blockTest_block_impl_0(
                                                     __blockTest_block_func_0,
                                                     &__blockTest_block_desc_0_DATA)
                           );
    num = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_141607_mi_1,num);
}



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

可以看到这里构造函数并没有传入变量的值或者指针,因为全局变量是直接可以访问的。
总结一下:

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

__block修饰的变量

1.在 block 内为什么不能修改 block 外部变量
答案:block 本质上是一个对象,block 的花括号区域是对象内部的一个函数,变量进入 花括号,实际就是已经进入了另一个函数区域---改变了作用域。在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。又比如我想在block内声明了一个与外部同名的变量,此时是允许呢还是不允许呢?只有加上了这样的限制,这样的情景才能实现。详解
2.除了使用static变量、全局变量外如何在block内改变变量的值?为什么?
答案:
使用__block;
static变量: block 内部对外部static修饰的变量进行指针捕获;
全局变量:block 内外可直接访问全局变量;
__block变量:要想在block内部修改auto变量,需要两个条件:
(1)从栈区拷贝到堆区(栈的内存是由系统管理,堆由我们管理,其实在ARC下所有进入block内的auto变量都会被拷贝到堆区见这里)
(2)把auto变量包装成结构体(对象),_block 作用是将 auto 变量封装为结构体(对象),在结构体内部新建一个同名 auto 变量,block 内截获该结构体的指针,在 block 中使用自动变量时,使用指针指向的结构体中的自动变量。于是就可以达到修改外部变量的作用。

总结一下就是如果想在block内修改变量:将 auto 从栈 copy 到堆;将 auto 变量封装为结构体(对象)

3.这三种修改变量值的方式哪个最好的?
这个问题请查看https://github.com/ChenYilong/iOSInterviewQuestions第38题,结论是__block是最优解。

从源码层面论证
void blockTest()
{
    __block int num = 10;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

编译后👇:

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_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a71778_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(
                                                                 __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_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a71778_mi_1,(num.__forwarding->num));
}



int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        blockTest();
    }
}
__block修饰的变量的拷贝
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*/);}

注意:__block变量的结构体__Block_byref_num_0内有一个__Block_byref_num_0类型的指针 __forwarding,而且获取__Block_byref_num_0结构体时候都会使用__forwarding获取,至于原因会在后面讲

该方法有一个dst(接收拷贝完成的对象)指针和一个src对象(被拷贝的对象),并调用了方法_Block_object_assign方法, _Block_object_assign需要三个参数,分别是:dst->numsrc->num、和一个flags 8,前两个参数就是__Block_byref_num_0的对象,这里先看一下这个flags枚举(Block_private.h):

// Runtime support functions used by compiler when generating copy/dispose helpers
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  //OC对象类型
    BLOCK_FIELD_IS_BLOCK    =  7,  //一个block变量
    BLOCK_FIELD_IS_BYREF    =  8,  // 在栈上被__block修饰的变量
    BLOCK_FIELD_IS_WEAK     = 16,  // 被__weak修饰的变量,只在Block_byref管理内部对象内存时使用
    BLOCK_BYREF_CALLER      = 128, // 处理Block_byref内部对象内存的时候会加的一个额外标记(告诉内部实现不要进行retain或者copy)
};

这里使用的是BLOCK_FIELD_IS_BYREF

_Block_object_assign(__block对象的copy)

接着通过runtime源码查看该方法的实现(只截取了关键部分):

void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******

         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/

        *dest = _Block_byref_copy(object);
        break;

调用_Block_byref_copy方法把object(src)传入到该函数,并返回到一个新的对象赋值给destdest就是新得到从栈上拷贝到堆上的新值。

_Block_object_dispose(__block对象的释放)
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents
void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}

当需要释放堆上的auto变量对象时,调用_Block_byref_release释放该对象

__block修饰的变量的包装

被__block修饰的auto变量会被包装成一个 __Block_byref_num_0的结构体,同样拥有isa,因此也是一个对象;

Block的内存管理

__block修饰的变量什么时候会被从栈拷贝到堆?

看这个问题之前我们先看一下我们先了解下Block的内存管理
Block一共有三种类型:

NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )

他们都继承自NSBlock,NSBlock继承自NSObject
三种类型对应的内存分配以及调用copy后的效果如下:

Block类型 副本源的配置存储域 copy效果
NSGlobalBlock 数据区 什么也不做
NSStackBlock 从栈复制到堆
NSMallocBlock 引用计数增加

有以下情况会把BLock从栈拷贝到堆:
1.调用Block的copy实例方法时
2.Block作为函数返回值返回时
3.在带有usingBlock的Cocoa方法或者GCD的API中传递Block时候
4.将block赋给带有__strong修饰符的id类型或者Block类型时
下面通过验证一下第4种情况看下是否准确(前三种可自行测试):
无__strong修饰符的id类型或者Block类型,代码⬇️:

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

打印结果:

__NSStackBlock__

有__strong修饰符的id类型或者Block类型,代码⬇️:

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

打印结果:

__NSMallocBlock__

Block的拷贝时机介绍完了,那么__block修饰的变量何时会从栈区拷贝到堆区呢?
答案:当Block从栈拷贝到堆区的时候,__block变量也会跟着Block被拷贝到堆区。
验证一下👇,
先看一下不拷贝到堆区的情况:

    __block int num = 10;
    NSLog(@"block前:%p",&num);
    ^{
        num = 20;
        NSLog(@"block内:%p",&num);
    }();
    NSLog(@"block后:%p",&num);

打印结果:

block前:0x7ffeea3f2c98
block内:0x7ffeea3f2c98
block后:0x7ffeea3f2c98

拷贝到堆区的情况:

    NSLog(@"block前:%p",&num);
    void (^block)(void) = ^{
        num = 20;
        NSLog(@"block内:%p",&num;
    };
    block();
    NSLog(@"block后:%p",&num);

打印:

block前:0x7ffee4af3c98
block内:0x600003650738
block后:0x600003650738

从内存地址不难看出不拷贝堆区时,__block变量也不会进行拷贝,当Block从栈区拷贝到堆区,__block变量也会进行拷贝

下面这段代码为什么MRC和ARC下打印不一样
    int num = 1;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
    };
    NSLog(@"%@",[block class]);

MRC打印:

__NSStackBlock__

ARC打印:

__NSMallocBlock__

原因:
由于在ARC环境下,使用strong修饰的变量指向block,会持有这个block。因此临时变量block会从栈复制到堆上

__forwarding指针存在的意义是什么?

__forwarding指针是为了在__block变量从栈复制到堆上后,在Block外对__block变量的修改也可以同步到堆上实际存储__block变量的结构体上。

__forwarding.png

__forwarding确保不管是堆栈访问__block变量结构体时都能访问到同一个对象

Block捕获对象

NSStackBlock

在栈上的Block不会对auto对象进行强引用;

NSMallocBlock

堆上的Block会对auto对象进行强引用,直到Block释放时,才解除对auto对象的强引用

typedef void(^Block)(void);

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

        block = ^{
            NSLog(@"%@",person.name);
        };
        person.name = @"david";
        NSLog(@"即将退出person作用域");
    }
    NSLog(@"已经退出person作用域");
    block ();
}

打印结果:

即将退出person作用域
已经退出person作用域
david
-[Person dealloc]

Block的循环引用

什么情况下会造成循环引用?

当一个对象person持有了了block对象,而block内又持有了person互相持有,这就造成了循环引用

如何打破循环引用?

1.使用__block修饰对象person
2.使用__unsafe_unretained修饰对象person
3.使用__weak修饰对象person

__block

    __block Person *person = [[Person alloc] init];
    person.blockTest = ^{
        person.name = @"toby";
        person = nil;
    };
    person.blockTest();

需要在block内指定person=nil,并且需要调用调用block函数。
__unsafe_unretained

    Person *person = [[Person alloc] init];
    __unsafe_unretained typeof(person) weakPerson = person;
    weakPerson.blockTest = ^{
        weakPerson.name = @"toby";
    };

使用__unsafe_unretained虽然能解除循环引用,但是不安全,当指向对象销毁时,指针存储地址不变,如果再次访问可能会造成悬垂指针⬇️:

访问悬垂指针.png

__weak

    Person *person = [[Person alloc] init];
    __weak typeof(person) weakPerson = person;
    weakPerson.blockTest = ^{
        weakPerson.name = @"toby";
    };

查看编译后的源码👇:

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

可以看到当前block内引用的是一个weak类型的person,对person的引用变成了弱引用,这就打破了双向持有的局面,至于该weak person的释放时机是由runtime维护的一个hash table决定的,当person对象dealloc时,会以person 地址当作键值在hash table中查找weak对象置为nil。runtime如何实现weak变量的自动置nil
但是实际应用会发现如果此时Block回调晚一些(异步线程执行耗时任务),此时的person已经出作用域,在block内访问weakPerson的时候就nil了。

    Person *person = [[Person alloc] init];
    __weak typeof(person) weakPerson = person;
    person.blockTest = ^{
        dispatch_async(dispatch_queue_create(0,DISPATCH_QUEUE_CONCURRENT), ^{
            [NSThread sleepForTimeInterval:3];
            NSLog(@"person = %@",weakPerson);
        });
    };
    person.blockTest();

此时的办法就是在block内使用__strong再修饰一下weakPerson,让person延迟释放,至于释放时机当然是block执行完成

    Person *person = [[Person alloc] init];
    __weak typeof(person) weakPerson = person;
    person.blockTest = ^{
        __strong typeof(person) strongPerson = weakPerson;
        dispatch_async(dispatch_queue_create(0,DISPATCH_QUEUE_CONCURRENT), ^{
            [NSThread sleepForTimeInterval:3];
            NSLog(@"strong person = %@",strongPerson);
        });
    };
    person.blockTest();
}

总结

1.Block是一个对象
2.Block捕获变量:
(1)auto变量:捕获的是值
(2)static变量:捕获指针
(3)global变量:无需捕获,直接访问
3.Block捕获__block修饰的auto变量时,会把该变量包装成一个对象,并会根据Block是否会被拷贝到堆区对auto变量进行拷贝,修改auto变量时需要满足两个条件:
(1)将 auto 从栈 copy 到堆;
(2)将 auto 变量封装为结构体(对象)
4.Block有三种类型,他们都继承自NSBlock->NSObject
NSGlobalBlock ( _NSConcreteGlobalBlock ) 数据区域
NSStackBlock ( _NSConcreteStackBlock ) 栈区
NSMallocBlock ( _NSConcreteMallocBlock ) 堆区
5.Block会被从栈拷贝到堆的情况:
(1)调用Block的copy实例方法时
(2)Block作为函数返回值返回时
(3)在带有usingBlock的Cocoa方法或者GCD的API中传递Block时候
(4)将block赋给带有__strong修饰符的id类型或者Block类型时
6. __forwarding指针是为了在__block变量从栈复制到堆上后,在Block外对__block变量的修改也可以同步到堆上实际存储__block变量的结构体上。
7.在栈上的Block不会对auto对象进行强引用;堆上的Block会对auto对象进行强引用,直到Block释放时,才解除对auto对象的强引用
8.解除Block的循环引用,最安全的方法是使用__weak修饰auto变量,并在block内部对auto变量进行__strong修饰

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343