Blocks

一. Blocks的认识

什么是Blocks?

Blocks是C语言的拓展功能(带有局部变量的匿名函数)。局部变量跟block的关系就像成员变量和类。
所谓匿名函数,就是不带名称的函数。C语言的标准是不存在这样的函数的。

//声明一个函数
int  func( int  count);

//直接调用函数时
int result = func(10);
//用函数指针调用函数时
int result =(* func)(10);

在C语言中,不使用想赋值的函数的名称,就无法取得该函数的地址。而通过Blocks,就能够使用不带名称的函数。

Block的语法

完整的Block语法与C语言函数相比有两点不同:没有函数名和带有“^”。

^ 返回值类型 参数列表 表达式

^ int (int count){return  count + 1;}

但是Block语法可以省略返回值类型和参数列表。省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,如果没有return语句就使用void类型。


Block语法省略返回值类型和参数列表.png
//省略后的Block
^{printf("Blocks\n");}
Blocks的使用

我们先来看看C语言函数的使用:

int func(int count){
  return count + 1;
}
//将函数地址赋值给函数指针类型变量
int (*funcptr)(int) = &func;

声明Block类型变量以及变量之间的赋值:

int (^blk)(int)  = ^(int count){return count + 1};
int (^blk1)(int);
blk1 = blk;

Block类型变量作为函数参数和函数返回值:

int (^func())(int){
  return ^(int count){return count + 1};
}

使用typedef声明Block类型变量

typedef int (^blk_t)(int);
int func(blk_t blk){
  return blk(10);
}

也可以像C语言变量一样使用,使用Block的指针类型变量。

typedef int (^blk_t)(int);
blk_t blk = ^(int count){return count + 1};
blk_t *blkptr = &blk;
(*blkptr)(10);
Block截获的自动变量值

Block表达式截获所使用的自动变量的值是该自动变量的瞬间值,不能在Block中改变(编译器会报错),只能输出该瞬间值。如果在Block中修改该值,要加上__block修饰符。

int let = 10;
__block int var = 20;
void (^blk)(void) = ^{
  printf("%d,%d",let,var);  //输出结果 10,21
  var = 22;                //这边也可以改变var的值,而let不可以
};
let = 11;      
var = 21;
blk();

二. Block的底层实现

通过clang(LLVM编译器)将Block语法的源代码转换为C++源代码来了解Block的底层实现。

  • 在Xcode创建Block.m文件
  • 打开终端,cd到存放Block.m的文件夹
  • 输入clang -rewrite-objc Block.m,生成Block.cpp
int mian(){
    void (^blk)(void) = ^{
        printf("Block");
    };
    blk();
    return 0;
}

打开.cpp拉到最后面,这些才是我们转换后的源码:

struct __block_impl {
  void *isa;      //对象都有的isa指针,指向元类
  int Flags;      //标志
  int Reserved;   //今后版本升级所需的区域
  void *FuncPtr;  //函数指针
};

/* block结构体 */
struct __mian_block_impl_0 {
   struct __block_impl impl;           
   struct __mian_block_desc_0* Desc;  
 
   // block构造函数 
   __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int flags=0) {
     impl.isa = &_NSConcreteStackBlock;   
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
  }
};

/* block中的代码 */
//__cself相当于Objective-C中的self(用OC类的说法就是self 执行__mian_block_func_0)
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
        printf("Block");
 }

static struct __mian_block_desc_0 {
  size_t reserved;     //今后版本升级所需的区域
  size_t Block_size;   //Block的大小
} __mian_block_desc_0_DATA = {     //这部分为赋值
  0, 
  sizeof(struct __mian_block_impl_0)
};

/*  mian函数  */
int mian(){
    void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA));

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

    return 0;
}

我们先看看main函数里面的代码,我们可简化成以下代码:

//创建Block
struct __mian_block_impl_0  temp = __mian_block_impl_0(__mian_block_func_0, &__mian_block_desc_0_DATA));
struct __mian_block_impl_0  *blk = &tmp;

//执行Block
(*blk->impl.FuncPtr)(blk);

该源代码将__mian_block_impl_0结构体类型的自动变量,即栈上生成的__mian_block_impl_0结构体实例的指针,赋值给__mian_block_impl_0结构体指针类型的变量blk。通俗地讲,就是将__mian_block_impl_0结构体实例的指针赋给变量blk。
__mian_block_impl_0构造函数(即源代码中的Block构造函数)的第一个参数是Block语法转换的C语言指针,第二个参数是作为静态全局变量初始化的__mian_block_desc_0结构体实例指针。
我们再来看执行Block的部分,这是简单地使用函数指针调用函数。在构造函数中,由Block语法转换的__mian_block_func_0函数的指针被赋值给成员变量FuncPtr,并以Block自身为参数。
补充重要的一点,前面提到的:

 impl.isa = &_NSConcreteStackBlock;    

其实,block就是Objective-C对象。这边isa指针指向的_NSConcreteStackBlock,该Block的信息放置于_NSConcreteStackBlock中(相当于子类和父类的关系)。

截获自动变量值

我们先来看看截获自动变量值的源代码,理解自动变量值为什么只是一个瞬间值。
Objective-C代码:

int mian(){
    int val = 10;
    void (^blk)(void) = ^{
        printf("val = %d",val); //输出结果 val= 10
    };
    val = 11;
    blk();
    return 0;
}

clang后的源代码:

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

/ * Block结构体 */
struct __mian_block_impl_0 {
  struct __block_impl impl;
  struct __mian_block_desc_0* Desc;
  int val;            //比之前的结构多出该成员变量val
  /* 构造函数 */
  __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

/ * Block中执行的函数 */
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
    int val = __cself->val;     // bound by copy
    printf("val = %d",val);     //打印的是Block自己的成员变量的值
}

static struct __mian_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __mian_block_desc_0_DATA = { 0, sizeof(struct __mian_block_impl_0)};

int mian(){
    int val = 10;

    //构造函数中传入val的值
    void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, val));

    val = 11;

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

    return 0;
}

通过源码,我们看出,Block语法表达式中使用的自动变量被作为成员变量追加到__mian_block_impl_0结构体中,并且会从构造函数中传入val的值作为参数。改写该值报编译错误的原因我们也由想而知。

struct __mian_block_impl_0 {
  struct __block_impl impl;
  struct __mian_block_desc_0* Desc;
  int val;            //比之前的结构多出该成员变量val
  /* 构造函数 */
  __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

在后面的输出函数中,也是输出Block的成员变量val的值。由此可见,在__mian_block_impl_0结构体实例中,自动变量值被截获。

static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
    int val = __cself->val;     // bound by copy
    printf("val = %d",val);     //打印的是Block自己的成员变量的值
}
__blcok说明符的值

那如何才能改变Block中截获的自动变量?有两种方法:
① C语言的静态变量、静态全局变量、全局变量允许Block改写值

int global_val = 1; //全局变量
static int static_global_val = 2; //静态全局变量

int mian(){
    static int static_val = 10;  //静态变量
    void (^blk)(void) = ^{
        global_val *= 1;
        static_global_val *= 2;
        static_val *=3;
    };
    blk();
    return 0;
}

转换后的源码为:

int global_val = 1;
static int static_global_val = 2;

struct __mian_block_impl_0 {
  struct __block_impl impl;
  struct __mian_block_desc_0* Desc;
  int *static_val;    //保存的是静态变量的指针
  __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy
        global_val *= 1;
        static_global_val *= 2;
        (*static_val) *=3;
    }

static struct __mian_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __mian_block_desc_0_DATA = { 0, sizeof(struct __mian_block_impl_0)};

int mian(){
    static int static_val = 10;
    void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, &static_val));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

对静态全局变量static_global_val和全局变量global_val的访问和转换前完全相同。
而静态变量static_val,是使用其指针进行访问的。将静态变量static_val的指针传递给__mian_block_impl_0结构体的构造函数并保存。那为什么自动变量不用指针访问的方法呢?因为自动变量存放在栈帧中,变量作用域结束的同时,自动变量被废弃,Block将不能用指针访问到原来的自动变量。

② 使用__block说明符
__block说明符类似于static、auto和register说明符,它们用于指定将变量值设置到哪个存储域中。例如,auto表示作为自动变量存储在栈中,static表示作为静态变量存储在数据区中。

int mian(){
    __block int val = 10;
    void (^blk)(void) = ^{
        val = 1;
    };
    blk();
    return 0;
}

转换后的源代码:

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

/* __block变量生成的结构体 */
struct __Block_byref_val_0 {
 void *__isa;
 __Block_byref_val_0 *__forwarding;  
 int __flags;
 int __size;
 int val;     //保存的val的值
};

struct __mian_block_impl_0 {
  struct __block_impl impl;
  struct __mian_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref

  __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

        (val->__forwarding->val) = 1;
}

static void __mian_block_copy_0(struct __mian_block_impl_0*dst, struct __mian_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __mian_block_dispose_0(struct __mian_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __mian_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __mian_block_impl_0*, struct __mian_block_impl_0*);
  void (*dispose)(struct __mian_block_impl_0*);
} __mian_block_desc_0_DATA = {
   0, 
  sizeof(struct __mian_block_impl_0), 
  __mian_block_copy_0, 
  __mian_block_dispose_0
};

int mian(){
    //创建__Block_byref_val_0结构体实例
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
 
    //将__Block_byref_val_0结构体指针作为参数
    void (*blk)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));

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

    return 0;
}

加上__block说明符,我们发现__block变量同Block一样变成__Block_byref_val_0结构体类型的自动变量。__Block_byref_val_0结构体的地址作为参数传入Block的构造函数中。因此,Block的__mian_block_impl_0结构体实例持有指向__block变量的__Block_byref_val_0结构体实例的指针。
至于__Block_byref_val_0结构体为什么在Block外创建,是为了方便多个Block使用。

struct __Block_byref_val_0 {
 void *__isa;
 __Block_byref_val_0 *__forwarding;  //指向自身的指针
 int __flags;
 int __size;
 int val;         //保存的val的值
};

在给__bloc变量赋值的源码中:

static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {

    __Block_byref_val_0 *val = __cself->val; // bound by ref
     (val->__forwarding->val) = 1;
}

__Block_byref_val_0结构体实例的成员变量__forwarding指向该实例自身的指针。通过成员变量__forwarding访问成员变量val。(这边有点绕,要弄懂先要弄清楚Block的存储域)

访问__block变量.png

Block的存储域

Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。那Block超出变量作用域是如何继续存在的呢?
要了解这点,我们要先知道Block的三种类型以及它们的存储域:

  • _NSConcreteStackBlock
  • _NSConcreteGlobalBlock
  • _NSConcreteMallocBlock
不同类型Block的存储域.png

通过声明全局变量blk来使用Block语法,那该Block类为_NSConcreteGlobalBlock。因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量的截获。因此将Block用结构体实例设置在与全局变量相同的数据区域中即可。另外,在函数内而不在记述广域变量的地方使用Block语法时,只要Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区域。
综上,以下情况Block为_NSConcreteGlobalBlock类对象,除此之外的Block语法生成的Block为_NSConcreteStackBlock类对象,且设置在栈上。

  • 记述全局变量的地方有Block语法时
  • Block语法的表达式中不使用应截获的自动变量时

    配置在全局变量的Block,从变量作用域外也可以通过指针安全地使用。但设置在栈上的Block,如果所属的变量作用域结束,该Block和__block变量都会被废弃。针对这个问题,Blocks提供了将Block和__block变量从栈上复制到堆上的方法。
    从栈复制到堆上的Block与__block变量.png
typedef int (^blk_t)(int)

blk_t func(int rate){
  return ^(int count){return rate * count};
}
blk_t func(int rate){
  blk_t tmp = &__func_block_impl_0(__func_block_func_0,&__func_block_desc_0_DATA,rate);
/*
 * 将配置在栈上的Block的结构体实例赋值给tmp
 */

  tmp = objc_retainBlock(tmp);  
/*
 * 等价于tmp = _Block_copy(tmp)
 * 将栈上的Block赋值到堆上,并将堆上的地址作为指针赋值给tmp
 */

  return objc_autoreleaseReturnValue(tmp);
 /*
  * 将堆上的Block对象注册到autoreleasepool并返回
  */
}

将Block作为函数返回值时,编译器会自动生成复制到堆上的代码。当向方法或函数的参数中传递Block时,需要我们手动生成代码,调用Block的copy方法。

Block的Copy.png

不过以下方法或函数不用手动调用:

  • Cocoa框架的方法且方法名中含有usingBlock等时(比如NSArray类的enumerateObjectsUsingBlock实例方法)
  • GCD的API

当使用__block变量的Block从栈上复制到堆上时,这些__block变量也全部被从栈复制到堆,Block持有__block变量。

Block持有__blcok变量.png
Block的废弃和__block变量的释放.png

使用__block变量的Block持有__block变量(__block变量作为Block的一个成员变量)。如果Block被废弃,它所持有的__block变量也就被释放。栈上的__block用结构体实例在从栈复制到堆时,会将成员变量__forwarding的值替换为复制目标堆上的__block用结构体实例的地址。这样不管__block变量配置在栈上还是堆上,都可以正确访问该变量。

复制__block变量.png
__block int val = 0;
void (^blk)(void) = [^{++val;}  copy];
++val;
blk();

两个++val均可转换成以下源码:

++(val.__forwarding->val);

第一个val为复制到堆上的__block变量用结构体实例,会通过指针访问到自己。第二个val为复制前栈上__block变量用结构体实例,通过指针访问到堆上的__block变量用结构体实例。通过该功能,都可以顺利访问同一个__block变量。

截获对象

前面我们截获的是普通值或带__block说明符的值,那要是在Block语法中使用对象呢?

typedef void(^blk_t)(id obj);
blk_t blk;
void setupBlk(){
    id array = [[NSMutableArray alloc] init];
    blk = [^(id obj){
        [array addObject:obj];
        NSLog(@"array count = %ld",[array count]);
    } copy];
}
int main(){
    setupBlk();
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    return 0;
}

输出结果为:

array count = 1
array count = 2
array count = 3

本来array的变量域结束的同时,array被废弃,其强引用失效,因此赋值给变量array的NSMutableArray类的对象必定被释放并废弃。但是代码运行正常,由此可见赋值给array的NSMutableArray类的对象在最后Block的执行部分超出其变量作用域而存在。通过编译器转换后的源代码如下:

typedef void(*blk_t)(id obj);
blk_t blk;

struct __setupBlk_block_impl_0 {
  struct __block_impl impl;
  struct __setupBlk_block_desc_0* Desc;
  id array;
  /* 构造方法 */
  __setupBlk_block_impl_0(void *fp, struct __setupBlk_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __setupBlk_block_func_0(struct __setupBlk_block_impl_0 *__cself, id obj) {
        id array = __cself->array; // bound by copy
        ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_9v_3rmg01ds0zj0gpwn04shlxwc0000gn_T_Block_097da3_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
    }

static void __setupBlk_block_copy_0(struct __setupBlk_block_impl_0*dst, struct __setupBlk_block_impl_0*src) {
  //相当于retain方法
  _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __setupBlk_block_dispose_0(struct __setupBlk_block_impl_0*src) {
  //相当于release方法
  _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __setupBlk_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __setupBlk_block_impl_0*, struct __setupBlk_block_impl_0*);
  void (*dispose)(struct __setupBlk_block_impl_0*);
} __setupBlk_block_desc_0_DATA = {
   0,
   sizeof(struct __setupBlk_block_impl_0),
   __setupBlk_block_copy_0, 
  __setupBlk_block_dispose_0
};

void setupBlk(){
    id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
    blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__setupBlk_block_impl_0((void *)__setupBlk_block_func_0, &__setupBlk_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
}

int main(){
    setupBlk();

    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));

    return 0;
}

我们可以发现,Block结构体中截获__strong修饰符的成员变量array:

struct __setupBlk_block_impl_0 {
  struct __block_impl impl;
  struct __setupBlk_block_desc_0* Desc;
  id array;  //等同于id  __strong array
}

在ARC需要遵守的规则中,对象型变量不能作为C语言结构体的成员变量。因为编译器不知道何时进行C语言结构体的初始化和废弃,不能很好地管理内存。但是Objective-C的运行时库能够准确把握Block从栈复制到堆以及堆上的Block被废弃的时机,因此Block用结构体中即使含有附有__strong或__weak修饰符的变量,也能恰当地进行初始化和废弃。
在该Block源码中,__setupBlk_block_desc_0中增加了copy和dispose函数指针的成员变量,以及作为指针赋值给该成员变量的__setupBlk_block_copy_0和__setupBlk_block_dipose_0方法。
__setupBlk_block_copy_0函数调用相当于retain实例方法的函数,将对象赋值在对象类型的结构体成员变量中,__setupBlk_block_dipose_0函数调用相当于release实例方法的函数,释放赋值在对象类型的结构体成员变量中的对象。简单说,就是对Block结构体中成员变量array赋值和废弃。当然,在Block从栈复制到堆时以及堆上的Block被废弃时才会调用这些函数。

如果在在array前面加上__block说明符呢?
转换的源码如下:

struct __Block_byref_array_0 {
  void *__isa;
__Block_byref_array_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id array;
};

struct __setupBlk_block_impl_0 {
  struct __block_impl impl;
  struct __setupBlk_block_desc_0* Desc;
  __Block_byref_array_0 *array; 

  __setupBlk_block_impl_0(void *fp, struct __setupBlk_block_desc_0 *desc, __Block_byref_array_0 *_array, int flags=0) : array(_array->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __setupBlk_block_copy_0(struct __setupBlk_block_impl_0*dst, struct __setupBlk_block_impl_0*src{
  _Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __setupBlk_block_dispose_0(struct __setupBlk_block_impl_0*src) {
  _Block_object_dispose((void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
}

同Block截获对象一样,当__block变量从栈复制到堆时,使用_Block_object_assign函数,持有赋值给__block变量的对象。当堆上的__block变量被废弃时,使用_Block_object_dispose函数,释放赋值给__blcok变量的对象。
由此可见,只要堆上的Block的成员变量(__block变量或者对象)存在,那么该对象就会继续处于被持有状态。

如果是__weak和__block的组合呢?
当超出作用域时,__weak的对象也会置于nil。所以会得出结果:

array count = 0
array count = 0
array count = 0

如果不调用copy方法呢?
执行该代码后,程序会强制结束。因为只有调用_Block_copy才能持有截获的附有__strong修饰符的对象类型的自动变量值,否则即使截获了对象,没有retain,它也会随着变量作用域的结束而被废弃。
所以,Block中使用对象类型自动变量时,除以下情形外,需要调用Block的copy方法。

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

三. Block的循环引用

如果在Block中使用__strong 修饰符的对象类型自动变量,那么当Block从栈复制到堆时,该对象为Block所持有。这样容易引起循环引用。

typedef void(^blk_t)(void);
@interface MyObject()
{
    blk_t _blk;
}
@end
@implementation MyObject
- (instancetype)init{
    self = [super init];
    _blk = ^{NSLog(@"self = %@",self);};
    return self;
}
- (void)dealloc{
    NSLog(@"dealloc");
}

//在main函数创建MyObject
int main(){
    id obj = [[MyObject alloc] init];
    return 0;
}

MyObject类对象的成员变量_blk持有Block的强引用,即MyObject对象持有Block。由于Block是赋值给成员变量_blk,Block会由栈复制到堆,并持有所使用的self。所以造成循环引用。

使用Block成员变量循环引用.png

那避免循环引用有哪些方法呢?
① 声明附有__weak修饰符的变量,并将self赋值使用。

- (instancetype)init{
    self = [super init];
    id __weak tmp = self;
    _blk = ^{NSLog(@"self = %@",tmp);};
    return self;
}
__weak避免循环引用.png

②使用__block变量(可用于ARC无效时,不过缺点是必须执行Block,将临时变量tmp置为nil)

- (instancetype)init{
    self = [super init];
    id __block tmp = self;
    _blk = ^{
        NSLog(@"self = %@",tmp);
        tmp = nil;
    };
    return self;
}
__block避免循环引用.png

还有一点需要注意的时,__block说明符解决循环引用时的方式在ARC和MRC是不一样的。MRC中,Block从栈复制到堆时,Block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain,若没有附有__block,会被retain。所以以下可以解决循环引用。

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

推荐阅读更多精彩内容