block的深度探究(截取自动变量、__block、截获对象、存储域、copy函数 与 dispose函数、循环引用、ARC无效时Block的copy/release)

本章目录

  • Block截取自动变量
  • __block说明符
  • Block存储域
  • __block变量存储域
  • Block中截获对象
  • Block的copy函数 与 dispose函数调用时机
  • __block变量和对象
  • Block循环引用
  • ARC无效时Block的copy / release
  • 参考文献

Block截取自动变量

  • 如何截获自动变量?

Block语法转换成C函数后,Block语法表达式中用到的自动变量会被作为成员变量追加到了__main_block_impl_0结构体中。
而此结构体中的成员变量类型与自动变量类型完全相同,但仅限于Block语法中使用到的自动变量。
因此,所谓“截获自动变量值”意味着执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例中。

  • 为什么Block语法中不能使用数组(C语言创建的数组)?

因为结构体中的成员变量与自动变量类型完全相同。
所以结构体中使用数组截取数组值,而后调用时再赋值给另一个数组。
也就是数组赋值给数组,这在C语言中是不被允许的。

简单来说:Block转化后,是个C语言的Struct结构体。在C语言里,数组是不能直接赋值给数组的。所以,Block中无法截获C语言中的数组,因为对于C语言数组而言,直接赋值会报错。

而我们平常对NSMutableArray的增删和遍历操作是可以用的,但是遍历时不可进行删操作,否则数组越界报错。

__block说明符

  • 在Block中修改截获的自动变量值有两种方法。

  • 第一种:使用静态变量,静态全局变量,全局变量

从Block语法转换成的C语言函数中访问静态全局变量 / 全局变量并没有任何改变,可直接使用。
但是静态变量却是用静态变量的指针来对其进行访问,这是超出作用域使用变量的最简单方法。

  • 为何静态变量的这种方法不适用于自动变量?

因为静态变量会存储在堆上,而自动变量却存在栈上。
当超出其作用域的时候,静态变量还会存在,而自动变量所占内存则会被释放因而被废弃。所以不能通过指针访问原来的自动变量。

  • 第二种方法: 使用 “ __block ” 修饰符

__block存储域类说明符。存储域类说明符,指定将变量设置到哪个存储域中,如:auto 栈 , static 堆 , extern 全局 , register 寄存器 ,

若将__block修饰符修饰在一个自动变量前,如:

int main() {
    __block int val = 10;
    void (^blk)(void) = ^{val = 1;};
}

通过clang编译后提取的Block相关代码为:

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

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_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 __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;}

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 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() {
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};

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

}

在此源码中,__block(前面有两个“ _ ”,这个markDown编译器为何把这两个符号给吞了 )所修饰的变量也同Block一样变成了__Block_byref_val_0结构体类型的自动变量,即栈上生成的__Block_byref_val_0结构体实例。
关于此结构体的声明:

struct __Block_byref_val_0 {
  void *__isa; (isa指针,指向一个存储有自身基础信息的地址,类似OC中的元类)
__Block_byref_val_0 *__forwarding; (forwarding指针,指向自身)
 int __flags; (标记)
 int __size; (大小)
 int val; (存储的值,相当于原自动变量的成员变量)
};

给__block变量赋值的代码 ^{val = 1;}; 会被转换为:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}

刚刚在Block中向静态变量赋值时,使用了指向该静态变量的指针。
而向__block变量赋值时(又吞了),Block的__main_block_impl_0结构体实例会持有指向__block变量的_Block_byref_val_0 结构体实例的指针。

也就是Block的__main_block_impl_0结构体实例会持有一个指针,指向__block修饰的变量所转换的__Block_byref_val_0结构体实例。

而__Block_byref_val_0结构体通过成员变量__forwarding指针访问成员变量val

另外,为了能在多个Block中使用__block变量,__block变量的__Block_byref_val_0结构体并不在Block用__main_block_impl_0结构体中。
Block是通过__Block_byref_val_0结构体实例的指针来访问。

Block存储域

Block转换为Block的结构体类型的自动变量,__block(又吞了)变量转换为__block变量的结构体类型的自动变量。结构体类型的自动变量,即栈上生成的该结构体的实例。

所以,Block的实质是栈上Block的结构体实例,__block变量(又吞了)的实质是 栈上__block变量的结构体实例。

Block有三种类型:
_NSConcreteStackBlock
_NSConcreteGlobalBlock
_NSConcreteMallocBlock


Block的类型与Block的存储区域有关:
_NSConcreteStackBlock类的Block对象设置在栈上(Stack 栈)
_NSConcreteGlobalBlock类的Block对象与全局变量一样,设置在程序的数据区域(.data区)中(global 全局)
_NSConcreteMallocBlock类的Block对象则设置在堆中(Malloc 函数分配的内存块)

在声明全局变量的地方使用Block语法,生成的Block为_NSConcreteGlobalBlock
则该Block中的isa指针初始化时: impl.isa = &_NSConcreteGlobalBlock;
代表该Block的类为_NSConcreteGlobalBlock类。
则该Block结构体实例设置在程序的数据区域中。

还有一种情况,Block为_NSConcreteGlobalBlock类对象:
Block语法的表达式中不使用应截获的自动变量时。(注意是自动变量,包括函数形参和非static局部变量,说明这个变量是在栈上运行时自动创建自动撤销。)

因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量进行截获。
由此Block用结构体实例的内容不依赖于执行时的状态,所以整个程序中只需一个实例。
因此将Block用结构体实例设置在与全局变量相同的数据区域中即可。

也就是说只有当截获自动变量时,Block结构体实例截获的值才会根据执行时的状态变化。
而当Block语法的表达式中不使用应截获的自动变量时,Block结构体实例截获的值不会根据执行时的状态变化。

列如:

typedef int (^blk_t)(int);
for(int rate = 0; rate < 10; rate++){
    blk_t blk = ^(int count){return rate * count;};
}

此Block结构体实例每次for循环中截获的值都不同,但去掉return rate * count 中的自动变量rate,则Block的结构体实例每次截获的值都完全相同。

所以:

记述全局变量的地方有Block语法时
Block语法的表达式中不使用应截获的自动变量时(函数形参和非static的局部变量)
在以上两种情况下,Block为_NSConcreteGlobalBlock类对象。即Block配置在程序的数据区域中。

那么,若Block使用了一个全局变量Int a,而 a 在每次使用前都会修改值。
如:

typedef int (^blk_t)(int);
extern int a ;
for(int rate = 0; rate < 10; rate++){
    a = rate;
    blk_t blk = ^(int count){return count;};
    blk(a);
}

则该Block的类型仍旧为_NSConcreteGlobalBlock,因为并Block语法的表达式中没有使用应截获的自动变量,是直接使用的全局变量。

除此之外的Block语法生成的Block为_NSConcreteStackBlock类对象,且设置在栈上。
而栈上的Block通过Copy操作改变到堆上,就是_NSConcreteMallocBlock类对象。

设置在栈上的Block,如果其所属的变量作用域结束,该Block就会被废弃。

若__block变量也配置在栈上,如果其所属的变量作用域结束,则该__block(又吞)变量也会被废弃。
因此,Block通过Copy操作会复制到堆上,其所引用的__block(又吞)变量也会复制到堆上。

复制到堆上的Block其所属类型为:impl.isa = &_NSConcreteMallocBlock;

当__block变量复制到堆上时,之前所说的__block(又吞,下同,不再赘述)变量的结构体成员变量__forwading会指向复制到堆上的自身,由此实现无论__block变量配置在堆上还是栈上都能够正确的访问__block变量。

那么编译器何时会对Block自动判断进行Copy(复制)操作何时不能?

在ARC环境下自动判断进行Copy操作:

  • 将Block作为函数参数返回值返回时,编译器会自动进行Copy操作。
  • 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API传递Block时

如果不能自动Copy,调用一下Copy实例方法就可以复制到堆上。

ARC环境下,返回一个对象时会先将该对象赋值给一个临时实例指针,而后对其进行retain操作,最后将对象注册到注册表中返回。
而runtime/objc-arr.mm里面有说明,Block的retain操作objc_retainBlock函数实际上是Block_copy函数。
在执行对Block的retain操作,objc_retainBlock函数执行后,栈上的Block复制到堆上,同时返回堆上的地址作为指针赋值给临时实例变量。

  • 编译期不能自动判断进行Copy操作:

  • 向方法或函数的参数中传递Block时。(注意:Cocoa框架中的方法,其方法名中若含有usingBlock等时 与 GCD的API中会自动判断进行Copy操作)。

如NSArray的initWithObjects需要手动执行Copy操作,而enumerateObjectsUsingBlock则不用,因为编译器会自动进行复制操作。
复制操作,就是Block的实例执行Copy实例方法即可。

  • Block的复制效果

注意: 无论Block配置在何处,用copy方法复制都不会引起任何问题!包括内存泄露也不可能!不用担心无法进行releas操作!所以不确定的时候尽情调用Copy方法吧!

如:blk = [[[[blk copy] copy] copy] copy];
可转换为:

{
    //第一次copy操作会将配置在栈上的Block赋值给变量blk中

    //而后对blk进行Copy后,将配置在堆上的Block赋值给变量tmp
    //此时变量tmp持有强引用的Block

    blk_t tmp = [blk copy];

    //将变量tmp的Block赋值为变量blk,变量blk持有强引用的Block
    //因为之前赋值的Block配置在栈上,所以不受此赋值的影响。
    //此时Block的持有者为变量blk 和 变量 tmp

    blk = tmp;
}
    //变量作用域结束,所以变量tmp被废弃,其强引用失效并释放所持有的Block
    //由于Block被变量blk持有,所以没有被废弃

{
    //配置在堆上的Block被赋值变量blk,同时变量blk持有强引用的Block    

    //配置在堆上的Block被赋值到变量tmp中,变量tmp持有强引用的Block
    blk_t tmp = [blk copy];

    //由于向变量blk进行了赋值,所以现在赋值的Block的强引用失效,Block被释放
    //由于Block被变量tmp所持有,所以未被废弃
    //变量blk中赋值了变量tmp的Block,变量blk持有强引用的Block
    //此时Block的持有者为变量tmp与变量blk
    blk = tmp;
}
    //变量作用域结束,变量tmp被废弃
    //强引用失效并释放所持有的Block
    //由于变量blk还处于持有的状态,Block没有被废弃

    //以下重复,先用一个变量tmp持有堆上的Block再赋值给blk
    //于是blk释放旧值,保留新值
    //超出变量作用域后释放tmp,此时只有blk持有
{
    blk_t tmp = [blk copy];
    blk = tmp;
}
{
    blk_t tmp = [blk copy];
    blk = tmp;
}

__block变量存储域

block 1

如图所示,一个Block从栈复制到堆时,使用的所有__block变量也都会复制到堆上并被Block持有。若此时__block变量已经在堆上,则被该Block持有。

若配置在堆上的Block被废弃,那么它所有的__block变量也就被释放。

在代码:

__block int val = 0;
 void (^blk)(void) = [^{++val;} copy];
++val;
blk();

利用copy方法复制使用了__block变量的Block语法,于是二者都从栈复制到堆上。

而其中 ^{++val;} 与 ++val; 都可转换形式为: ++(val.__forwarding -> val);

在变换Block语法的函数中,该变量val为复制到堆上的__block变量的结构体实例。

而与Block无关的变量val,则为复制前栈上的__block变量用结构体实例。

但是栈上__block变量的结构体实例在__block变量从栈复制到堆上时,会将成员变量__forwarding的值替换为复制到目标堆上的__block变量用结构体实例的地址。如图:

屏幕快照 2016-11-15 上午9.48.49.png

由此实现无论Block语法中、语法外使用__block变量,还是__block变量配置在堆上或栈上,都可以顺利访问同一个__block变量。

Block中截获对象

在代码:

   id array = [NSMutableArray array];
        blk_t blk = ^(id obj){
            
            [array addObject:obj];
            NSLog(@"array count = %ld",[array count]);
        
        };
        
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);

输出结果为:
array count = 1 
array count = 2
array count = 3

意味着赋值给变量array的对象在超出其变量作用域后仍存在。
将源码用clang编译后,截取Block相关的部分:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
// @implementation block


struct __block__init_block_impl_0 {
  struct __block_impl impl;
  struct __block__init_block_desc_0* Desc;
  id array;
  __block__init_block_impl_0(void *fp, struct __block__init_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __block__init_block_func_0(struct __block__init_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_t9_2587xc0n2dv6f08yv3mdbf7c0000gn_T_block_679c7b_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));

        }
static void __block__init_block_copy_0(struct __block__init_block_impl_0*dst, struct __block__init_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __block__init_block_dispose_0(struct __block__init_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __block__init_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __block__init_block_impl_0*, struct __block__init_block_impl_0*);
  void (*dispose)(struct __block__init_block_impl_0*);
} __block__init_block_desc_0_DATA = { 0, sizeof(struct __block__init_block_impl_0), __block__init_block_copy_0, __block__init_block_dispose_0};

//使用部分

 id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
 
 blk_t blk = ((void (*)(id))&__block__init_block_impl_0((void *)__block__init_block_func_0, &__block__init_block_desc_0_DATA, array, 570425344));

      
  ((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")));

转换一下:
 id array = [NSMutableArray array];
blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,array,570425344);

(*blk -> impl.FuncPtr)(blk,[[NSObject alloc] init]);
(*blk -> imply.FuncPtr)(blk,[[NSObject alloc] init]);
(*blk -> imply.FuncPtr)(blk,[[NSObject alloc] init]);b

请注意此结构体:

struct __block__init_block_impl_0 {
  struct __block_impl impl;
  struct __block__init_block_desc_0* Desc;
  id array;
}

XCode8中对截获的变量array 不进行显示__strong(这里也有两个 “ _ ")修饰了,但依旧使用__block__init_block_copy_0 函数(在此之前是__main_block_copy_0) 和 __block__init_block_dispose_0函数(在此之前是__main_block_dispose_0)进行管理。

在此之前的OC中,C语言结构体不能含有附有__strong修饰符的变量,因为编译器不知道何时进行C语言结构体的初始化和废弃操作,因此不能很好地管理内存。
但是OC的运行库能准确把握Block从栈复制到堆以及堆上Block被废弃的时机。

因此Block的结构体中可以恰当地进行初始化和废弃,附有__strong修饰符或__weak修饰符的变量。
为此需使用在__main_block_desc_0结构体中增加的成员变量copy 和 dispose,以及作为指针赋值给该成员变量的__main_block_copy_0函数和__block__init_block_dispose_0函数。
但现在没有成员变量copy 和 dispose 而是直接使用函数指针__block__init_block_copy_0 函数 __block__init_block_dispose_0函数.

由于Block结构体中需要管理赋值给变量array的对象,因此__block(这里有四个 “ _ ")__init_block_copy_0函数通过_Block_object_assign函数将对象类型对象赋值给Block结构体的成员变量array中并持有该对象。

static void __block__init_block_copy_0(struct __block__init_block_impl_0*dst, struct __block__init_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

_Block_object_assign函数调用相当于retain实例方法的函数。

__block(又尼玛吞了四个“ _ ”!)__init_block_dispose_0 函数使用_Block_object_dispose函数,释放赋值在Block用结构体成员变量array中的对象。

static void __block__init_block_dispose_0(struct __block__init_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

_Block_object_dispose函数调用相当于release实例方法的函数,释放赋值在对象类型的结构体成员变量中的对象。

Block的copy函数 与 dispose函数调用时机

  • 什么时候栈上的Block会复制到堆上?

1.调用Block的copy实例方法
2.将Block作为函数参数返回值返回时,编译器会自动进行Copy操作。
3.将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量
4.在方法名中含有usingBlock的Cocoa框架方法或GCD的API传递Block时

Block从栈复制到堆时_Block_copy函数被调用。
释放复制到堆上的Block,谁都不持有Block而使其被废弃时调用dispose函数。相当于对象的dealloc实例方法。

由于此种构造,Block中截获的对象就能够超出其变量作用域而存在。

另外,在使用__block变量时也是使用了copy函数和dispose函数来管理。

通过这两个参数来区分copy函数和dispose函数的对象类型对象还是__block变量。

copy函数持有截获的对象 或 持有所使用的__block变量,dispose函数释放截获的对象 或 释放所使用的__block变量。

Block中使用的赋值给id类型的自动变量的对象 和 复制到堆上的__block变量由于被堆上的Block所持有,因而可超出其变量作用域而存在。

需要注意的是,若Block不复制到堆上,则其不会持有截获的对象。则对象会随着变量作用域结束而结束。

__block变量和对象

如前文所述,在Block中使用附有__strong修饰符的id类型或对象类型自动变量的情况下,当Block从栈复制到堆时,使用_Block_object_assign函数持有Block截获的对象。

当堆上的Block被废弃时,使用_Block_object_dispose函数释放Block截获的对象。

  • 在__block变量为附有__strong修饰符的id类型或对象类型自动变量的情形下会发生同样的过程

当__block变量从栈复制到堆时,使用_Block_object_assign函数,持有赋值给__block变量的对象。
当堆上的__block变量被废弃时,使用_Block_object_dispose函数,释放赋值给__block变量的对象。

如:  
 __block id array = [NSMutableArray array];
        blk_t blk = ^{
            NSLog(@"%@",array);
        };
        blk();
会转换为:
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;
};

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

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

__block变量声明部分:
 __attribute__((__blocks__(byref))) __Block_byref_array_0 array = {(void*)0,(__Block_byref_array_0 *)&array, 33554432, sizeof(__Block_byref_array_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"))};
简化为: __Block_byref_array_0 array = {
    0,
    &array,
    33554432,
    sizeof(__Block_byref_array_0),
    __Block_byref_id_object_copy_131, 
    __Block_byref_id_object_dispose_131,
    [[NSMutableArray alloc]init]
};

在Block中使用附有__weak修饰符的id类型变量会如何?如以下代码

   blk_t blk;
        
       {
            
            id array = [NSMutableArray array];
            id __weak array2 = array;
            blk = ^(id obj){
                
                [array2 addObject:obj];
                NSLog(@"array2 count = %ld" ,[array2 count]);
            };
        }
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
      
输出结果为:
array2 count = 0
array2 count = 0
array2 count = 0

因为__strong修饰的变量array在该变量作用域结束的同时被释放、废弃,nil被赋值给变量array2。

即使在array2前面加上__block说明符,结果也是不变。因为array依旧会被释放废弃,依旧是nil赋值给array2

另外,使用___unsafe_unretained修饰符的变量只不过与指针相同,不会像__strong或__weak修饰符那样进行处理,因而通过悬垂指针访问已被废弃的对象程序会崩溃。
比如上述代码的__weak替换为__unsafe_unretained就会崩溃。

若一个变量obj同时指定__autoreleasing修饰符和__block说明符会引起编译错误,因为并没有设定__autoreleasing修饰符与Block同时使用的方法。

Block循环引用

因为写的人实在太多,所以不想过多讲述,简单提下吧。

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

为避免此类事件,可用__weak修饰对象。

而在面向iOS4 与 OS Snow Leopard的应用程序中,必须使用__unsafe_unretained修饰符代替__weak。因为这个版本还没有__weak。

另外还可使用__block变量来避免循环引用,只要在Block语法中手动让__block修饰的对象置为nil即可,因此只要最少调用一次Block即可避免。

ARC无效时的copy / release

ARC无效时,需要手动将Block从栈复制到堆。此外,ARC无效时要手动释放复制的Block。使用copy实例方法来复制,用releas实例方法来释放。

只要Block有一次复制并配置在堆上,就可通过retain实例方法持有。若配置在栈上则retain实例方法不起任何作用。因此推荐用copy方法来持有。

由于Block是C语言的扩展,所以在C语言中也可以使用Block语法。此时用Block_copy函数 和 Block_release函数代替Copy / release实例方法,使用方法和引用计数的思考方式同OC中的copy / release实例方法:
Block_copy(blk)、Block_release(blk)
Block_copy函数就是之前提过的_Block_copy函数

ARC无效时,当Block从栈复制到堆时,若Block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain。因此,ARC无效时__block说明符被用来Block中的循环引用。而若Block使用的变量为没有__block说明符的id类型或对象类型的自动变量,则无论ARC有效与否都会被retain。

参考文献:

Objective - C 高级编程:iOS与OS X多线程和内存管理

推荐阅读更多精彩内容