Block探究

block

A block is an anonymous inline collection of code, and sometimes also called a “closure”。通俗的讲,block就是带有自动变量值的匿名函数。

Features

Block的实质

Block语法看起来很特别,但它实际上是作为普通的C语言源代码来处理的。但clang(LLVM编译器)居具有转换为我们可阅读的源代码的功能。通过clang -rewrite-objc + file's name选项就能够将含有Block语法的源代码转换为C的源代码。

一段简单的源代码:

int main() {
	void (^blk)(void) = ^{
   		printf("Hello World!");
	};
	blk();
	return 0;
}

通过clang变换为以下形式:

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() {
    void (*blk)(void) = 
    (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结构

上面的源代码中并没有copy、dispose等,稍后解释

可以看出,一个block实际有6部分组成;

  1. isa指针,所有对象都有该指针,用于实现对象相关的功能。
  2. flags,用于按bit位表示一些block的附加信息,本文后面介绍block copy的实现代码可以看到对该变量的使用。
  3. reserved,保留变量。
  4. invoke,函数指针,指向具体的block实现的函数调用地址。
  5. descriptor, 表示该block的附加描述信息,主要是size大小,以及copy和 dispose函数的指针。
  6. variables,capture过来的变量,block能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。

对于该block的构造函数,如下代码,

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

去掉转化部分,具体为:

struct __main_block_impl_0 tmp = 
	__main_block_impl_0(__main_block_func_0,
 	&__main_block_desc_0_DATA);
 
struct __main_block_impl_0 *blk = &blk;

该源码将__main_block_impl_0结构体类型的自动变量赋值给__main_block_impl_0结构体指针类型的变量blk。

Block访问符,__block说明符

Block能够截获自动变量的值,但在实现上不能改写被截获的自动变量的值,因此当编译器在编译过程中检出给被截获的自动变量赋值的操作时,会产生编译错误。

解决这个问题有俩种方法。第一种:C语言中有一个变量,允许Block改写值。具体如下:

  • 静态变量
  • 静态全局变量
  • 全局变量

虽然Block语法的匿名函数部分简单的变换为了C语言函数,但从这个变换的函数中访问静态全局变量/全局变量并没有任何改变,可直接使用。

但是静态变量的情况下,转换后的函数本来就设置在含有Block语法的函数外,所以无法从变量作用域访问。

查看下列源代码。

int global_val = 1;
static int static_global_val = 3;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int static_val = 5;
        
        void (^block)() = ^{
            global_val *= 1;
            static_global_val *= 3;
            static_val *= 5;
        };
        
        block();
        printf("%d,%d,%d",
         global_val, static_global_val, static_val);
    }
    return 0;
}

该源代码使用了Block改写全局变量global_val、静态全局变量static_global_val和静态变量static_val。通过clang转换之后如下:

int global_val = 1;
static int static_global_val = 3;

struct __main_block_impl_0 {
	struct __block_impl impl;
	struct __main_block_desc_0* Desc;
	int *static_val;
	__main_block_impl_0(void *fp, 
	struct __main_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 __main_block_func_0(
struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
        global_val *= 1;
        static_global_val *= 3;
        (*static_val) *= 5;
    }

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, const char * argv[]) {
/* @autoreleasepool */ 
	{ __AtAutoreleasePool __autoreleasepool; 
	    static int static_val = 5;
	
	    void (*block)() = 
	    (void (*)())
	    &__main_block_impl_0((void *)__main_block_func_0,
	             &__main_block_desc_0_DATA, &static_val);
	
	    ((void (*)(__block_impl *))
	    ((__block_impl *)block)->FuncPtr)((__block_impl *)block);
	    printf("%d,%d,%d", global_val, static_global_val, static_val);
	}
	return 0;
}

对于静态变量static_val的访问,查看方法__main_block_func_0,可以看出使用静态变量static_val的指针对其进行访问。将静态变量static_val的指针传递给__main_block_func_0结构体的构造函数并保存。这是超出作用域使用变量的最简单的方法。

解决Block不能保存值的第二种方法是使用”__block”说明符。更准确的表述方式为”__block存储域类说明符”。

在编译错误的变量前加上__block说明符

__block int val = 3;
void (^block)() = ^{
    val = 5;
};

对该源代码进行编译,结果如下。

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) = 5;
    }
    
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(int argc, const char * argv[]) {
    /* @autoreleasepool */ 
    { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) 
        __Block_byref_val_0 val = {
        	(void*)0,
        	(__Block_byref_val_0 *)&val, 
        	0, 
        	sizeof(__Block_byref_val_0), 
        	3
        };
        void (*block)() = 
        (void (*)())&__main_block_impl_0(
        (void *)__main_block_func_0, 
        &__main_block_desc_0_DATA, 
        (__Block_byref_val_0 *)&val, 
        570425344);
        ((void (*)(__block_impl *))
        ((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

__block变量var变成了结构体实例。__block变量也同Block一样变成__Block_byref_val_0结构体类型的自动变量,即栈上生成的__Block_byref_val_0结构体实例。这里可以看出上面留下的问题,cope和dispose是因为__block在clang之后出现的。

初始化block的时候,对__Block_byref_val_0结构体赋值,即为初始化的值。刚刚在Block中向静态变量赋值时,使用了该静态变量的指针。而向__block变量赋值要比这个复杂的多。Block的__main_block_impl_0结构体实例持有指向__block变量的__Block_byref_val_0结构体实例的指针。

__Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量var。(成员变量var是该实例自身持有的变量,它相当于原自动变量。)如图所示。

block __forwarding指向

另外,__block变量的__Block_byref_val_0结构体并不在Block的__main_block_impl_0结构体中,这样做是为了在多个Block中使用__block变量。我们看一下下面的源代码。

__block int var = 1;
    
void (^blk0)() = ^{
	var = 0;
};
   
void (^blk1)() = ^{
	var = 1;
};

Block类型变量blk0和blk1访问__block变量var。将转换的之后的结果摘录出来。

struct __Block_byref_var_0 {
	void *__isa;
	__Block_byref_var_0 *__forwarding;
	int __flags;
	int __size;
 	int var;
};
	
void (*blk0)() = (void (*)())&__main_block_impl_0(
(void *)__main_block_func_0, 
&__main_block_desc_0_DATA, 
(__Block_byref_var_0 *)&var, 
570425344
);

void (*blk1)() = (void (*)())&__main_block_impl_1(
(void *)__main_block_func_1,
 &__main_block_desc_1_DATA, 
 (__Block_byref_var_0 *)&var, 
 570425344
 );

俩个Block都是使用了__Block_byref_var_0结构体实例var的指针。这样一来就可以从多个Block中使用同一个__block变量。当然,反过来从一个Block中使用多个__block变量也是可以的。只要增加Block的结构体成员变量与构造函数的参数,便可对应多个使用多个__block变量。

Block存储域

通过前面的说明可知,Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。如下表所示。

名称 实质
Block 栈上Block的结构体实例
__block变量 栈上__block变量的结构体实例

之前的clang之后的代码中,出现了 impl.isa = &_NSConcreteStackBlock;,说明该Block的类型为_NSConcreteStackBlock。同时与之相对应的类有:

  • _NSConcreteStackBlock
  • _NSConcreteGlobalBlock
  • _NSConcreteMallocBlock

对应的存储区域如下表。

对应的存储域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 程序中的数据区域(.data区)
_NSConcreteMallocBlock

应用程序的内存分配如下图。

block存储区

到现为止出现的Block例子使用的都是_NSConcreteStackBlock类,且都设置在栈上。在记述全局变脸的地方使用Block语法时,生成的Block为_NSConcreteGlobalBlock类对象。例如:

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

此源代码通过使用全局变量blk来使用Block语法。该Block的类在ARC和非ARC下都为_NSConcreteGlobalBlock。此Block即该Block的结构体实例设置在程序的数据区域中。由此Block的结构体实例的内容不依赖于执行时的状态,或者说为该Block不会capture变量,所以整个程序中只需要一个实例。因此将Block的结构体实例设置在与全局变量相同的数据区域中。

只在截获自动变量时,Block的结构体实例截获的值才会根据执行时的状态变化。例如一下源代码中,虽然多次使用同一个Block语法blk,但每个for循环中截获的自动变量的值都不同。

typedef int (^blk_t)(int);
int main(int argc, const char * argv[]) {
	@autoreleasepool {        
       for (int rate = 0; rate < 10; rate++) {
       		blk_t blk = ^(int count) {
         	  		return rate * count;
        	  	};            
       		blk_t blk2 = ^(int count) {
            		return count;
        		};
    	}
	}
	return 0;
}

在非ARC下,blk为_NSConcreteStackBlock,blk2为_NSConcreteGlobalBlock

在ARC下,blk为_NSConcreteMallocBlock,blk2为_NSConcreteGlobalBlock

这里先给出ARC下的状态,稍后解释ARC下的不同,先重点关注非ARC。

配置在全局变量上的Block,从变量作用域外也可以通过指针安全的访问。但设置在栈上的Block,如果其所属的变量作用域结束,则该__block变量也会被废弃,如下图所示。

block作用域

Block提供了将Block和__block变量从栈上复制到堆上的方法来解决这个问题。将配置在栈上的Block赋值到堆上,这样即使Block语法记述的变量作用域结束,堆上的Block还可以继续存在。如下图所示。

block作用域

复制到堆上的Block将_NSConcreteMallocBlock类对象写入Block的结构体实例的成员变量isa。

impl.isa = &_NSConcreteMallocBlock;

而__block变量的结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上都能正确访问__block变量。

有时在__block变量配置在堆上的状态下,也可以访问栈上的__block变量。在此情形下,只要栈上的结构体实例成员变量__forwarding指向堆上的结构体实例,那么不管是从栈上的__block变量还是从堆上的__block变量都能够正确访问。

那么Blocks提供的复制方法究竟是什么呢?实际上当ARC有效时,大多数情形下编译器会恰当的进行判断,自动生成将Block从栈上复制到堆上的代码。分析下面源代码。

typedef int (^blk_t)(int);
blk_t func ( int rate )
{
	return ^(int count){
    	return rate * count;
	};
}

该源代码为返回配置在栈上的Block函数。即程序执行中从该函数返回函数调用方时变量作用域结束,因此栈上的Block也被废弃。

上面源代码是在ARC环境下(clang命令需要声明为ARC环境,clang -fobjc-arc -rewrite-objc file’s name),非ARC下会编译错误,提示Returning block that lives on the local stack。但是可以通过增加临时变量的方式使得编译通过。

typedef int (^blk_t)(int);
blk_t func (int rate) {
    blk_t tmp = ^(int count) {
        return count * rate;
    };
    return tmp;
}
int main(int argc, const char * argv[]) {
    return 0;
}

把原来的返回值赋给一个变量,然后再返回这个变量。不过虽然编译通过了,这个返回的Block作用域仍是在函数栈中的,因此一旦函数运行完毕后再使用这个Block很可能会引发BAD_ACCESS错误。

下面我们来看下面的源代码。

id getBlockArray() {
 return @[^{NSLog(@"111");}, ^{NSLog(@"222");}];
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *arr = getBlockArray();
        NSLog(@"%@", arr);
    }
    return 0;
}

该源代码为NSGlobalBlock,所以在ARC和非ARC下都能正常运行。

将该源代码稍微修改一下:

id getBlockArray() {
	int var = 1;
	return @[^{NSLog(@"blk0=%d", var);}, 
	         ^{NSLog(@"blk1=%d", var);}];
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id obj = getBlockArray();
        typedef void (^blk_t)(void);
        blk_t blk = (blk_t)[obj objectAtIndex:0];
        blk();
    }
    return 0;
}

上面源代码在ARC下正常,为NSMallocBlock

在非ARC下会在栈上生成2个Block,其中的blk(),在执行时发生异常,应用程序强制结束。这是由于在getBlockArray函数执行结束时,栈上的Block被废弃的缘故。此时编译器不能判断是否需要复制。也可以不让编译器进行判断,而使其在所有情况下都能复制。但将Block从栈上复制到堆上是相当消耗CPU的。当Block设置在栈上也能够使用时,将Block从栈上复制到堆上只是浪费资CPU资源。因此只能自己进行复制。

该源代码像下面这样修改一下即可正常运行。

id getBlockArray() {
	int var = 1;
	return @[[^{NSLog(@"blk0=%d", var);} copy], 
	         [^{NSLog(@"blk1=%d", var);} copy]];
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id obj = getBlockArray();
        typedef void (^blk_t)(void);
        blk_t blk = (blk_t)[obj objectAtIndex:0];
        blk();
    }
    return 0;
}

此时Block为__NSMallocBlock__。不仅对于Block可以copy,对于Block变量同样可以。这个稍后详细说明。

根据Block存储域,将copy方法进行赋值的动作总结一下为:

Block的类 副本源的配置存储域 复制效果
_NSConcreteStackBlock 从栈复制到堆
_NSConcreteGlobalBlock 程序的数据区域 什么也不做
_NSConcreteMallocBlock 引用记数增加

不管Block配置在何处,用copy方法复制都不会引起任何问题。在不确定时调用copy方法即可。但在ARC中不可显示的release,copy没影响。

__block变量存储域

对于Block从栈复制到堆时,__block也会收到相应影响。如下表。

__block变量的配置存储域 Block从栈复制到堆时的影响
从栈复制到堆并被Block持有
被Block持有

若在一个Block中使用__block变量,则当该Block从栈复制到堆上时,使用的所有__block变量也必定配置在栈上,将会全部从栈复制到堆。此时,Block持有__block变量。即使该Block已复制到堆的情形下,复制Block也对所使用的__block变量没有任何影响。如下图。

block变量存储域

在多个Block中使用__block变量时,因为最先会将所有的Block配置在栈上,所以__block变量也会配置在栈上。在任何一个Block从栈复制到堆时,__block变量也会一并从栈复制到堆并被该Block所持有。当剩下的Block从栈复制到堆时,被复制的Block持有__block变量,并增加__block变量的引用记数。

如果配置在栈上的Block被废弃,那么它所使用的__block变量也就被释放。

到这里可以看出,此思考方式与Object-C的引用计数式内存管理方式完全相同。使用__block变量的Block持有__block变量。如果Block被废弃,它所持有的__block变量也就被释放。

这里,理解下使用__block变量用结构体成员变量__forwarding的原因。 “不管__block变量配置在栈上还是堆上,都能够正确的访问该变量“。通过Block的复制,__block变量也从栈复制到堆,此时可同时访问栈上的__block变量和堆上的__block变量。源代码如下。

__block int var = 0;
	
void (^blk)() = ^{
	++var;
};
	
blk();
	
NSLog(@"%d", var);

利用copy方法复制使用了__block变量的Block语法。Block和__block变量两者均是从栈复制到堆。此代码中在Block语法的表达式中使用初始化后的__block变量。

^{++var;};

然后在Block语法之后使用与Block无关的变量。

++var;

以上俩种源代码均可转换为如下形式:

++(var.__forwarding->var);

查看下面源代码。

int main(int argc, const char * argv[]) {
	@autoreleasepool {
        int a = 123;
        __block int b = 123;
        NSLog(@"&a = %p, &b = %p", &a, &b);
  
        void(^block)() = ^{
            NSLog(@"&a = %p, &b = %p", &a, &b);
        };
        block = [block copy];
        block();
        
        NSLog(@"&a = %p, &b = %p", &a, &b);
        [block release];
    }
    return 0;
}

上述源代码输出为

&a = 0x7fff5fbff7ac, &b = 0x7fff5fbff7a0
&a = 0x100300028, &b = 0x100300088
&a = 0x7fff5fbff7ac, &b = 0x100300088

可以看到,在block执行中,他所引用的变量a和b都被复制到了堆上。而被标记__block的变量事实上应该说是被移动到了堆上,因此,当block执行后,函数栈内访问b的地址会变成堆中的地址。而变量a,仍会指向函数栈内原有的变量a的空间。

在变换Block语法的函数中,该变量var为复制到堆上的__block变量用结构体实例,而使用的与Block无关的变量var,为复制前栈上的__block变量用结构体实例。

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

block复制

通过该功能,无论是在Block语法中、Block语法外使用__block变量,还是__block变量配置在栈上或堆上,都可以顺利的访问同一个__block变量。

截获对象

以下源代码生成并持有 NSMutableArray 类的对象,由于附有 __strong 修饰符的赋值目标变量作用域立即结束,因此对象被立即释放并废弃。

{
	id array = [[NSMutableArray alloc] init];
}

再看Block语法中使用该变量array的代码:

typedef void (^blk_t)(id);
blk_t blk;
    
{
    id array = [[NSMutableArray alloc] init];
    blk = [^ (id obj){
        [array addObject:obj];
        NSLog(@"%p", &array);
        NSLog(@"array count = %ld", [array count]);
    } copy];
}
    
blk(@1);
blk(@2);
blk(@3);

变量作用域结束的同时,变量array被废弃,其强引用失效,因此赋值给变量array的 NSMutableArray 类的对象必定被释放并废弃。但该源代码运行正常,其执行结果如下:

0x100206990
array count = 1
0x100206990
array count = 2
0x100206990
array count = 3

这一结果意味着赋值给array的 NSMutableArray 类的对象在该源代码最后Block的执行部分超出其变量作用域而存在。通过编译器转换后的源代码发现:

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

__main_block_copy_0 函数使用 _Block_object_assign 函数将对象类型赋值给Block结构体的成员变量array中并持有该对象。

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

另外,__main_block_dispose_0 函数使用 _Block_object_dispose 函数,释放赋值在Block结构体成员变量中的对象

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

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

在调用Block的copy函数实例方法时,如果Block配置在栈上,那么该Block会从栈复制到堆。Block作为函数返回值返回时、将Block赋值给 __strong 修饰符id类型的类或Block类型成员变量时,编译器自动将对象的Block作为参数并调用 _Block_copy 函数,这与直接调用Block的copy实例方法的效果相同。

copy函数和dispose函数能够截获对象和__block变量,通过俩种类型来区分。

对象 BLOCK_FIELD_IS_OBJECT
__block变量 BLOCK_FIELD_IS_BYREF

循环引用

循环引用是指本类中含有block,同时block还有对self的引用。一般在自己写的API中需要处理循环引用,在系统API中,要视情况而定。类似下面这种就是不需要的。

[UIView animateWithDuration:duration animations:^{ 
	[self.superview layoutIfNeeded];
}];
 
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ 
	self.someProperty = xyz; 
}]; 

[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" 
				object:nil 
				queue:[NSOperationQueue mainQueue] 
				usingBlock:^(NSNotification * notification) {
	self.someProperty = xyz; 
}];

但如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:

__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
});

类似的:

 __weak __typeof__(self) weakSelf = self;
  _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
                                                                object:nil
                                                                 queue:nil
                                                            usingBlock:^(NSNotification *note) {
      __typeof__(self) strongSelf = weakSelf;
      [strongSelf dismissModalViewControllerAnimated:YES];
  }];

self –> _observer –> block –> self 显然这也是一个循环引用。

测试

经过上面的分析,对于Block的了解基本差不多了,最后在附上一盘Block的小测试。

Objective-C Blocks Quiz

参考

在学习的过程中,查阅了以下文章,一起分享给大家。