【iOS小结】Blocks

2017-11-07  本文已影响0人  WellsCai

一. 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的底层实现。

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的三种类型以及它们的存储域:

不同类型Block的存储域.png

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

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

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

当使用__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 修饰符的对象类型自动变量,那么当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;
}
上一篇下一篇

猜你喜欢

热点阅读