引言
Block是C级别的语法和运行时特性。Block比较类似C函数,但是Block比之C函数,其灵活性体现在栈内存、堆内存的引用,我们甚至可以将一个Block作为参数传给其他的函数或者Block。
目录
一、Block的基本介绍
1、什么是Block
2、Block和C语言函数指针和什么区别
3、如何调用Block
二、Block内存管理与其他特性
1、Block放在哪里
2、Block引用的变量在哪里
三、Block揭开神秘面纱
一、Block的基本介绍
1、什么是Block
Block是一个C Level的语法以及运行时的一个特性,和标准C中的函数(函数指针)类似,但是其运行需要编译器和运行时支持,从iOS4.0开始就很好的支持Block了。广泛用于两个对象之前的回调函数。
下面我们来看一下Block的声明:
int(^hbFunction) (BOOL a);
其中int为block的返回类型,hbFunction为block名称,a为参数。
2、Block和C语言函数指针和什么区别
首先我们来看看C函数指针:
int (* hbFunction) (int a); // 函数声明
int resut = hbFunction(10); // 函数调用
再看看Block的用法:
int (^ hbFunction) (int a); // 函数声明
int resut = hbFunction(10); // 函数调用
C语言函数指针typedef
typedef int (*SumFunction)(int a,int b);
Block中的typedef
typedef int (^SumBlock)(int a,int b);
3、如何调用Blocks
主动调用一下:
1
2
3
4
5
6
7
8
- ( void ) someMethod
{
BoolBlock ablock = ^ ( BOOL bValue ) {
NSLog ( @"Bool block!" );
};
ablock ();
}
作为参数返回:
1
2
3
4
5
6
7
8
typedef void ( ^ BoolBlock )( BOOL );
- ( BoolBlock ) foo ()
{
BoolBlock ablock = ^ ( BOOL bValue ) {
NSLog ( @"Bool block!" );
};
return [[ ablock copy ] autorelease ]; //一定要copy,将其复制到堆上,更详细的原理,将在后续章节讲解
}
类的一个成员:
1
2
3
@interface OBJ1 : NSObject
@property ( nonatomic , copy ) BoolBlock block ; //理由同上啊,同学们
@end
1
2
3
4
OBJ1 * obj1 = ...
obj1 . block = ^ ( BOOL bValue ) {
NSLog ( @"Bool block!" );
};
其他函数的参数:
1
2
3
4
5
6
- ( void ) foo ( BoolBlock block )
{
if ( block ) {
block ();
}
}
甚至其他block的参数:
1
2
3
4
BoolBlock bBlock = ^ ( BOOL bV ){ if ( Bv ){ /*do some thing*/ }};
HugeBlock hBlock = ^ ( BoolBlock bB ) { bB ();};
hBolck ( bBlock );
二、Block内存管理与其他特性
1、Block放在哪里
1.1栈和堆
以下情况中的block位于堆中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void foo ()
{
__block int i = 1024 ;
int j = 1 ;
void ( ^ blk )( void );
void ( ^ blkInHeap )( void );
blk = ^ { printf ( "%d, %d \n " , i , j );}; //blk在栈里
blkInHeap = Block_copy ( blk ); //blkInHeap在堆里
}
- ( void ) fooBar
{
_oi = 1 ;
OBJ1 * oj = self ;
void ( ^ oblk )( void ) = ^ { printf ( "%d \n " , oj . oi );};
void ( ^ oblkInHeap )( void ) = [ oblk copy ]; //oblkInHeap在堆中
}
1.2全局
以下情况中的block位于全局区:
1
2
3
4
5
6
7
8
9
10
static int ( ^ maxIntBlock )( int , int ) = ^ ( int a , int b ){ return a > b ? a : b ;};
- ( void ) fooBar
{
int ( ^ maxIntBlockCopied )( int , int ) = [ maxIntBlock copy ];
}
void foo ()
{
int ( ^ maxIntBlockCopied )( int , int ) = Block_copy ( maxIntBlock );
}
需要注意的是,这里复制过后的block依旧位于全局区,实际上,复制操作是直接返回了原block对象。
2、Block引用的变量在哪里
1.全局区
全局区的变量存储位置与block无关:
1
2
3
4
5
6
7
8
static int gVar = 0 ;
//__block static int gMVar = 1;
void foo ()
{
static int stackVar = 0 ;
// __block static int stackMVar = 0;
}
注意,static变量是不允许添加__block标记的
2.堆栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void foo ()
{
__block int i = 1024 ; // 此时i在栈上
int j = 1 ; // 此时j在栈上
void ( ^ blk )( void );
blk = ^ {
printf ( "%d, %d \n " , i , j );
}; //此时blk位于栈上,其使用的变量也在栈上
blk ();
j ++ ;
int ( ^ blkInHeap )( int , int ) = Block_copy ( blk );
// 复制block后,block位于堆上,有__block标记的i会被复制一份至堆,而没有__block标记的j并不会动依旧位于栈上。
}
三、Block揭开神秘面纱
1、Block到底是什么
我们使用clang的rewrite-objc命令来获取转码后的代码。
我们来看看最简单的一个block:
1
2
3
4
5
6
__block int i = 1024 ;
int j = 1 ;
void ( ^ blk )( void );
blk = ^ {
printf ( "i :%d,j:%d,&i:%p,&j:%p" , i , j , & i , & j );
};
这个block仅仅打印栈变量i和j的值,其被clang转码为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
struct __main_block_impl_0 {
struct __block_impl impl ;
struct __main_block_desc_0 * Desc ;
int j ;
__Block_byref_i_0 * i ;
__main_block_impl_0 ( void * fp , struct __main_block_desc_0 * desc , int _j , __Block_byref_i_0 * _i , int flags = 0 ) : j ( _j ), int ( _i -> __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_i_0 * i = __cself -> i ;
int j = __cself -> j ;
printf ( "i :%d,j:%d,&i:%p,&j:%p" , i -> __forwarding -> i , j , & ( i -> __forwarding -> i ), & j );
}
static void __main_block_copy_0 ( struct __main_block_impl_0 * dst , struct __main_block_impl_0 * src ){
_Block_object_assign (( void * ) & dst -> i , ( void * ) src -> i , 8 );
}
static void __main_block_dispose_0 ( struct __main_block_impl_0 * src ){
_Block_object_dispose (( void * ) src -> i , 8 );
}
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_i_0 = {( void * ) 0 ,( __Block_byref_i_0 * ) & i , 0 , sizeof ( __Block_byref_i_0 ), 1024 };
int j = 1 ;
void ( * blk )( void );
blk = ( void ( * )()) & __main_block_impl_0 (( void * ) __main_block_func_0 , & __main_block_desc_0_DATA , j ,( __Block_byref_i_0 * ) & i , 570425344 );
}
首先是一个结构体__main_block_impl_0
(从图二中的最后一行可以看到,block是一个指向__main_block_impl_0
的指针,初始化后被类型强转为函数指针),其中包含的__block_impl
是一个公共实现(学过c语言的同学都知道,__main_block_impl_0
的这种写法表示其可以被类型强转为__block_impl
类型):
1
2
3
4
5
6
struct __block_impl {
void * isa ;
int Flags ;
int Reserved ;
void * FuncPtr ;
};
isa指针说明block可以成为一个objc 对象。
__main_block_impl_0的意思是main函数中的第0个block的implementation,这就是这个block的主体了。
这个结构体的构造函数的参数:
1、block实际执行代码所在的函数的指针,当block真正被执行时,实际上是调用了这个函数,其命名也是类似的方式。
2、block的描述结构体,注意这个结构体声明结束时就创建了一个唯一的desc,这个desc包含了block的大小,以及复制和析构block时需要额外调用的函数。
3、接下来是block所引用到的变量们
4、最后是一个标记值,内部实现需要用到的。(我用计算器看了一下,570425344这个值等于1<<29,即BLOCK_HAS_DESCRIPTOR这个枚举值)
所以,我们可以看到:
1、为什么上一篇我们说j已经不是原来的j了,因为j是作为参数传入了block的构造函数,进行了值复制。
2、带有__block标记的变量会被取地址来传入构造函数,为修改其值奠定了基础
接下来是block执行函数__main_block_func_0
:
其唯一的参数是__main_block_impl_0
的指针,我们看到printf语句的数据来源都取自__cself
这个指针,比较有意思的是i的取值方式(带有__block
标记的变量i被转码为一个结构体),先取__forward
指针,再取i,这为将i复制到堆中奠定了基础。
再下来是预定义好的两个复制/释放辅助函数,其作用后面会讲到。
最后是block的描述信息结构体 __main_block_desc_0
,其包含block的内存占用长度,已经复制/释放辅助函数的指针,其声明结束时,就创建了一个名为__main_block_desc_0_DATA
的结构体,我们看它构造时传入的值,这个DATA结构体的作用就一目了然了:
长度用sizeof计算,辅助函数的指针分别为上面预定义的两个辅助函数。
注意,如果这个block没有使用到需要在block复制时进行copy/retian的变量,那么desc中不会有辅助函数
至此,一个block所有的部件我们都看齐全了,一个主体,一个真正的执行代码函数,一个描述信息(可能包含两个辅助函数)。
2、构造一个block
我们进入main函数:
图一中的第三行(block的声明),在图二中,转化为一个函数指针的声明,并且都没有被赋予初始值。
而图一中的最后一行(创建一个block),在图二中,成为了对__main_block_impl_0
的构造函数的调用,传入的参数的意义上面我们已经讲过了。
所以构造一个block就是创建了__main_block_impl_0
这个c++类的实例。
3、调用一个block
调用一个block的写法很简单,与调用c语言函数的语法一样:
blk();
其转码后的语句:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
将blk这个函数指针类型强转为block_impl类型,然后取其执行函数指针,然后将此指针类型强转为返回void*并接收一个 block_impl的函数指针,最后调用这个函数,传入强转为__block_impl 类型的blk,
即调用了前述的函数__main_block_func_0
4、objective-c类成员函数中的block
源码如下:
1
2
3
4
5
6
- ( void ) of1
{
OBJ1 * oj = self ;
void ( ^ oblk )( void ) = ^ { printf ( "%d \n " , oj . oi );};
Block_copy ( oblk );
}
这里我故意将self赋值给oj这个变量,是为了验证前一章提出的一个结论:无法通过简单的间接引用self来防止retain循环,要避免循环,我们需要__block标记(多谢楼下网友的提醒)
转码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct __OBJ1__of1_block_impl_0 {
struct __block_impl impl ;
struct __OBJ1__of1_block_desc_0 * Desc ;
OBJ1 * oj ;
__OBJ1__of1_block_impl_0 ( void * fp , struct __OBJ1__of1_block_desc_0 * desc , OBJ1 * _oj , int flags = 0 ) : oj ( _oj ) {
impl . isa = & _NSConcreteStackBlock ;
impl . Flags = flags ;
impl . FuncPtr = fp ;
Desc = desc ;
}
};
static void __OBJ1__of1_block_func_0 ( struct __OBJ1__of1_block_impl_0 * __cself ) {
OBJ1 * oj = __cself -> oj ; // bound by copy
printf ( "%d \n " , (( int ( * )( id , SEL ))( void * ) objc_msgSend )(( id ) oj , sel_registerName ( "oi" )));
}
objc方法中的block与c中的block并无太多差别,只是一些标记值可能不同,为了标记其是objc方法中的blcok。
注意其构造函数的参数:OBJ1 *_oj
这个oj在block复制到heap时,会被retain,而 oj与self根本就是相等的,所以,最终retain的就是self,所以如果当前实例持有了这个block,retain循环就形成了。
而一旦为其增加了__block标记:
1
2
3
4
5
- ( void ) of1
{
__block OBJ1 * bSelf = self ;
^ { printf ( "%d" , bSelf . oi ); };
}
其转码则变为:
//增加了如下行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct __Block_byref_bSelf_0 {
void * __isa ;
__Block_byref_bSelf_0 * __forwarding ;
int __flags ;
int __size ;
void ( * __Block_byref_id_object_copy )( void * , void * );
void ( * __Block_byref_id_object_dispose )( void * );
OBJ1 * bSelf ;
};
static void __Block_byref_id_object_copy_131 ( void * dst , void * src ) {
_Block_object_assign (( char * ) dst + 40 , * ( void * * ) (( char * ) src + 40 ), 131 );
}
static void __Block_byref_id_object_dispose_131 ( void * src ) {
_Block_object_dispose ( * ( void * * ) (( char * ) src + 40 ), 131 );
}
//声明处变为
__block __Block_byref_bSelf_0 bSelf = {(void*)0,(__Block_byref_bSelf_0 *)&bSelf, 33554432, sizeof(__Block_byref_bSelf_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self};
clang为我们的bSelf结构体创建了自己的copy/dispose辅助函数,33554432(即1<<25 BLOCK_HAS_COPY_DISPOSE)这个值告诉系统,我们的bSelf结构体具有copy/dispose辅助函数。
而131这个参数(二进制1000 0011,即BLOCK_FIELD_IS_OBJECT (3) |BLOCK_BYREF_CALLER(128))
中的BLOCK_BYREF_CALLER在内部实现中告诉系统不要进行retain或者copy,
也就是说,在 __block bSelf 被复制至heap上时,系统会发现有辅助函数,而辅助函数调用后,并不retain或者copy 其结构体内的bSelf。
这样就避免了循环retain。
2、内存管理的真面目
objc层面如何区分不同内存区的block
Block_private.h中有这样一组值:
/* the raw data space for runtime classes for blocks */
/* class+meta used for stack, malloc, and collectable based blocks */
BLOCK_EXPORT void * _NSConcreteStackBlock[32];
BLOCK_EXPORT void * _NSConcreteMallocBlock[32];
BLOCK_EXPORT void * _NSConcreteAutoBlock[32];
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];
BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
其用于对block的isa指针赋值
1.栈
1
2
3
4
5
6
7
8
9
10
11
struct __OBJ1__of2_block_impl_0 {
struct __block_impl impl ;
struct __OBJ1__of2_block_desc_0 * Desc ;
OBJ1 * self ;
__OBJ1__of2_block_impl_0 ( void * fp , struct __OBJ1__of2_block_desc_0 * desc , OBJ1 * _self , int flags = 0 ) : self ( _self ) {
impl . isa = & _NSConcreteStackBlock ;
impl . Flags = flags ;
impl . FuncPtr = fp ;
Desc = desc ;
}
};
在栈上创建的block,其isa指针是_NSConcreteStackBlock。
2.全局区
在全局区创建的block,其比较类似,其构造函数会将isa指针赋值为_NSConcreteGlobalBlock。
3.堆
我们无法直接创建堆上的block,堆上的block需要从stack block拷贝得来,在runtime.c中的_Block_copy_internal函数中,有这样几行:
1
2
3
4
5
6
7
8
// Its a stack block. Make a copy.
if ( ! isGC ) {
struct Block_layout * result = malloc ( aBlock -> descriptor -> size );
...
result -> isa = _NSConcreteMallocBlock ;
...
return result ;
}
可以看到,栈block复制得来的新block,其isa指针会被赋值为_NSConcreteMallocBlock
4.其余的isa类型
BLOCK_EXPORT void * _NSConcreteAutoBlock[32];
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
其他三种类型是用于gc和arc,我们暂不讨论
复制block
对block调用Block_copy方法,或者向其发送objc copy消息,最终都会调用runtime.c中的_Block_copy_internal
函数,其内部实现会检查block的flag,从而进行不同的操作:
1
2
3
4
5
static void * _Block_copy_internal ( const void * arg , const int flags ) {
...
aBlock = ( struct Block_layout * ) arg ;
...
}
1.栈block的复制
1
2
3
4
5
6
7
8
// reset refcount
result -> flags &= ~ ( BLOCK_REFCOUNT_MASK ); // XXX not needed
result -> flags |= BLOCK_NEEDS_FREE | 1 ;
result -> isa = _NSConcreteMallocBlock ;
if ( result -> flags & BLOCK_HAS_COPY_DISPOSE ) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
( * aBlock -> descriptor -> copy )( result , aBlock ); // do fixup
}
除了修改isa指针的值之外,拷贝过程中,还会将BLOCK_NEEDS_FREE置入,大家记住这个值,后面会用到。
最后,如果block有辅助copy/dispose函数,那么辅助的copy函数会被调用。
2.全局block的复制
1
2
3
else if ( aBlock -> flags & BLOCK_IS_GLOBAL ) {
return aBlock ;
}
全局block进行copy是直接返回了原block,没有任何的其他操作。
3.堆block的复制
1
2
3
4
5
if ( aBlock -> flags & BLOCK_NEEDS_FREE ) {
// latches on high
latching_incr_int ( & aBlock -> flags );
return aBlock ;
}
栈block复制时,置入的BLOCK_NEEDS_FREE标记此时起作用,_Block_copy_internal函数识别当前block是一个堆block,则仅仅增加引用计数,然后返回原block。
辅助copy/dispose函数
1.普通变量的复制
辅助copy函数用于拷贝block所引用的可修改变量,我们这里以 __block int i = 1024为例:
先看看Block_private.h中的定义:
1
2
3
4
5
6
7
8
9
struct Block_byref {
void * isa ;
struct Block_byref * forwarding ;
int flags ; /* refcount; */
int size ;
void ( * byref_keep )( struct Block_byref * dst , struct Block_byref * src );
void ( * byref_destroy )( struct Block_byref * );
/* long shared[0]; */
};
而我们的__block int i = 1024的转码:
1
2
3
4
5
6
7
8
9
10
struct __Block_byref_i_0 {
void * __isa ;
__Block_byref_i_0 * __forwarding ;
int __flags ;
int __size ;
int i ;
}; //所以我们知道,当此结构体被类型强转为Block_byref时,前四个成员是一致的,访问flags就相当于访问__flags,而内部实现就是这样使用的
...
__attribute__ (( __blocks__ ( byref ))) __Block_byref_i_0 i = {( void * ) 0 , ( __Block_byref_i_0 * ) & i , 0 , sizeof ( __Block_byref_i_0 ), 1024 }; //i初始化时__flags为0
static void __main_block_copy_0 ( struct __main_block_impl_0 * dst , struct __main_block_impl_0 * src ) { _Block_object_assign (( void * ) & dst -> i , ( void * ) src -> i , 8 /*BLOCK_FIELD_IS_BYREF*/ );}
此时,复制时调用的辅助函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void _Block_object_assign ( void * destAddr , const void * object , const int flags ) { //此处flags为8,即BLOCK_FIELD_IS_BYREF
...
if (( flags & BLOCK_FIELD_IS_BYREF ) == BLOCK_FIELD_IS_BYREF ) {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy ( destAddr , object , flags );
}
...
}
static void _Block_byref_assign_copy ( void * dest , const void * arg , const int flags ) { //此处flags为8,即BLOCK_FIELD_IS_BYREF
struct Block_byref ** destp = ( struct Block_byref ** ) dest ;
struct Block_byref * src = ( struct Block_byref * ) arg ;
...
else if (( src -> forwarding -> flags & BLOCK_REFCOUNT_MASK ) == 0 ) { //当初次拷贝i时,flags为0,进入此分支会进行复制操作并改变flags值,置入BLOCK_NEEDS_FREE和初始的引用计数
...
}
// already copied to heap
else if (( src -> forwarding -> flags & BLOCK_NEEDS_FREE ) == BLOCK_NEEDS_FREE ) { //当再次拷贝i时,则仅仅增加其引用计数
latching_incr_int ( & src -> forwarding -> flags );
}
// assign byref data block pointer into new Block
_Block_assign ( src -> forwarding , ( void ** ) destp ); //这句仅仅是直接赋值,其函数实现只有一行赋值语句,查阅runtime.c可知
}
所以,我们知道,当我们多次copy一个block时,其引用的__block变量只会被拷贝一次。
2.objc变量的复制
当objc变量没有__block修饰时:
1
2
3
4
5
6
7
8
9
10
11
12
static void __OBJ1__of2_block_copy_0 ( struct __OBJ1__of2_block_impl_0 * dst , struct __OBJ1__of2_block_impl_0 * src ) { _Block_object_assign (( void * ) & dst -> self , ( void * ) src -> self , 3 /*BLOCK_FIELD_IS_OBJECT*/ );}
void _Block_object_assign ( void * destAddr , const void * object , const int flags ) {
...
else if (( flags & BLOCK_FIELD_IS_OBJECT ) == BLOCK_FIELD_IS_OBJECT ) {
//printf("retaining object at %p\n", object);
_Block_retain_object ( object ); //当我们没有开启arc时,这个函数会retian此object
//printf("done retaining object at %p\n", object);
_Block_assign (( void * ) object , destAddr );
}
....
}
当objc变量有__block修饰时:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct __Block_byref_bSelf_0 {
void * __isa ;
__Block_byref_bSelf_0 * __forwarding ;
int __flags ;
int __size ;
void ( * __Block_byref_id_object_copy )( void * , void * );
void ( * __Block_byref_id_object_dispose )( void * );
OBJ1 * bSelf ;
};
static void __Block_byref_id_object_copy_131 ( void * dst , void * src ) {
_Block_object_assign (( char * ) dst + 40 , * ( void * * ) (( char * ) src + 40 ), 131 ); //131即为BLOCK_FIELD_IS_OBJECT|BLOCK_BYREF_CALLER
}
static void __Block_byref_id_object_dispose_131 ( void * src ) {
_Block_object_dispose ( * ( void * * ) (( char * ) src + 40 ), 131 );
}
... //33554432即为BLOCK_HAS_COPY_DISPOSE
__block __Block_byref_bSelf_0 bSelf = {( void * ) 0 ,( __Block_byref_bSelf_0 * ) & bSelf , 33554432 , sizeof ( __Block_byref_bSelf_0 ), __Block_byref_id_object_copy_131 , __Block_byref_id_object_dispose_131 , self };
BLOCK_HAS_COPY_DISPOSE告诉内部实现,这个变量结构体具有自己的copy/dispose辅助函数,而此时我们的内部实现不会进行默认的复制操作:
1
2
3
4
5
6
7
8
9
10
11
void _Block_object_assign ( void * destAddr , const void * object , const int flags ) {
//printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
if (( flags & BLOCK_BYREF_CALLER ) == BLOCK_BYREF_CALLER ) {
if (( flags & BLOCK_FIELD_IS_WEAK ) == BLOCK_FIELD_IS_WEAK ) {
_Block_assign_weak ( object , destAddr );
}
else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign (( void * ) object , destAddr );
}
}
当我们没有开启arc,且flags中具有BLOCK_BYREF_CALLER时,会进入_Block_assign函数,而此函数仅仅是赋值
所以,如果要避免objc实例中的block引起的循环引用,我们需要让block间接使用self:
__block bSelf = self;
其他
对于dipose辅助函数,其行为与copy是类似的,我们不再重复同样的东西,如果大家要了解,自行查阅runtime.c和Block_private.h即可。
我们已经理解了非arc非gc情况下的block的内存管理内部实现,对arc和gc的情况,其行为也是类似的,只是一些函数的指针指向的真正函数会改变,比如_Block_use_GC函数,会将一些函数指向其他的实现,使其适用于gc开启的情况。