摘要
Blocks是C语言的扩充功能, iOS 4中引入了这个新功能“Blocks”,那么block到底是什么东西呢。其实它就是一个闭包,一个带有自动变量(局部变量)的匿名函数。很多语言也实现自己的闭包,比如C#的lamda表达式。这篇文章将从分析源码的角度来分析下block到底是什么鬼。
研究工具:clang
为了研究编译器的实现原理,我们使用clang(LLVM编译器,和GCC类似),通过命令clang -rewrite-objc main.m
,解析main.m,这样我们就会得到对应的cpp文件main.cpp,就能看到block内部实现代码(后面有源码),借此可以研究 block 中各个特性的源码实现方式。
一、 block捕获外部变量
说到block怎么捕获外部变量,我们要知道c语言中的5种变量:
- 自动变量
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
今天主要对除函数参数变量之外的四种变量的捕获情况进行研究
根据这四种变量写出测试代码如下:
测试代码出错,原因是变量d没有加
__block
修饰,由于__block
稍微复杂,我们后边再讲解,现在我们先对静态变量、静态全局变量、全局变量进行分析,代码:
#import <Foundation/Foundation.h>
int global_a = 1;
static int static_global_b = 2;
int main(int argc, const char * argv[]) {
static int static_c = 3;
int d = 4;
void(^TestBlock)() = ^{
NSLog(@"block内部:global_a = %d, static_global_b = %d, static_c = %d, d = %d", global_a, static_global_b, static_c, d);
};
global_a ++;
static_global_b ++;
static_c ++;
d ++;
NSLog(@"block外部:global_a = %d, static_global_b = %d, static_c = %d, d = %d", global_a, static_global_b, static_c, d);
TestBlock();
return 0;
}
运行结果:
block外部:global_a = 2, static_global_b = 3, static_c = 4, d = 5
block内部:global_a = 2, static_global_b = 3, static_c = 4, d = 4```
在这里有两个问题:
1、为何不加__block就不能修改自动变量的值?
2、为何自动变量的值没有增加,其他变量的值增加?自动变量什么情况下才能在block中增加修改?
为了弄清楚以上两个疑问,我们用clang转换一下源码分析:
int global_a = 1;
static int static_global_b = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_c;
int d;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_c, int _d, int flags=0) : static_c(_static_c), d(_d) {
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_c = __cself->static_c; // bound by copy
int d = __cself->d; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_4acb3a_i_0, global_a, static_global_b, (*static_c), d);
}
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[]) {
static int static_c = 3;
int d = 4;
void(*TestBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_c, d));
global_a ++;
static_global_b ++;
static_c ++;
d ++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_4acb3a_i_1, global_a, static_global_b, static_c, d);
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
return 0;
}
我们先简单解释下三个概念:`__main_block_impl_0`、`__main_block_func_0`、 `__main_block_desc_0`
- ######__main_block_func_0
block内部的实现函数,其中__block_impl中的FuncPtr指向这个函数
- ######__main_block_desc_0
1、`reserved`:保留字段默认为0
2、`Block_size`:为`sizeof(struct __main_block_impl_0)`,用来表示block所占内存大小。因为没有持有变量,block大小为impl的大小加上Desc指针大小
3、`__main_block_desc_0_DATA`:`__main_block_desc_0`的一个结构体实例
这个结构体,用来描述block的大小等信息。如果持有可修改的捕获变量时(即加__block),会增加两个函数(copy和dispose),我们后面会分析
- __main_block_impl_0
我们可以看到__main_block_impl_0结构体中包含`__block_impl` 、`__main_block_desc_0`两个类型的结构体变量,其中`__block_impl`的内部实现源码如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};```
1、isa指针
,如果我们对runtime了解的话,就明白isa指向Class的指针。
2、Flags
,当block被copy时,应该执行的操作
3、Reserved
为保留字段
4、FuncPtr指针
,指向block内的函数实现
__block_impl
保存block的类型isa(如&_NSConcreteStackBlock),标识(当block发生copy时,会用到),block的方法。
接下来我们看下在main函数中block实现代码:
void(*TestBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_c, d));```
去掉一些类型转换代码:
void(*TestBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &static_c, d));```
调用block时的代码:
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);```
去掉类型转换之后代码:
TestBlock->FuncPtr(TestBlock);```
可以看到以上代码是调用__main_block_impl_0
结构体中的构造函数,将变量传入结构体内部保存,之后将这个结构体作为参数传给FuncPtr指向的函数即__main_block_func_0
, 其中静态变量static_c传入block内部的是地址,自动变量传入的是值,而且在block外部执行d++
之前已经将d的值捕获进入block内部, 这也就能说明为何block内部不能改变静态变量的值的原因
最终在block内部实现结果:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
static_c = 3;
d = 4;```
到此,__main_block_impl_0结构体就是这样把自动变量捕获进来的。也就是说,在执行Block语法的时候,Block语法表达式所使用的自动变量的值是被保存进了Block的结构体实例中,也就是Block自身中。
这里值得说明的一点是,如果Block外面还有很多自动变量,静态变量,等等,这些变量在Block里面并不会被使用到。那么这些变量并不会被Block捕获进来,也就是说并不会在构造函数里面传入它们的值。
Block捕获外部变量仅仅只捕获Block闭包里面会用到的值,其他用不到的值,它并不会去捕获。
我们再来看一下__main_block_func_0函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_c = __cself->static_c; // bound by copy
int d = __cself->d; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_4acb3a_i_0, global_a, static_global_b, (*static_c), d);
}```
可以看到,在函数内部通过__cself->static_c
、__cself->d
来获取static_c
和d
的值,由于结构体中捕获了变量的值,因此__main_block_impl_0
类型__cself
能够获取到内部保存的变量值,但是在函数内部只能修改捕获地址值的static_c
变量,不能修改传入值变量的d
的值。
到此为止,上面提出的二个问题就解开答案了。首先全局变量global_a和静态全局变量static_global_b的值增加,以及它们被Block捕获进去,这一点很好理解,因为是全局的,作用域很广,所以Block捕获了它们进去之后,在Block里面进行++操作,就像局部函数一样,可以成功修改全局变量的值,Block结束之后,它们的值依旧可以得以保存下来。自动变量是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值。Block捕获的外部变量可以改变值的是静态变量,静态全局变量,全局变量。
总结一下:在Block中改变变量值有2种方式,一是传递内存地址指针到Block中,二是改变存储区方式(__block)。
我们先试一下第一种方式:传递内存地址,代码:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
NSMutableString *str = [NSMutableString stringWithString:@"123"];
void(^TestBlock)() = ^{
[str appendString:@" 456"];
NSLog(@"block中 %@", str);
};
NSLog(@"block前 %@", str);
TestBlock();
return 0;
}```
控制台输出:
block前 123
block中 123 456
内部实现源码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSMutableString *str;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *_str, int flags=0) : str(_str) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSMutableString *str = __cself->str; // bound by copy
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_1);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_2, str);
}
static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {_Block_object_assign((void)&dst->str, (void)src->str, 3/BLOCK_FIELD_IS_OBJECT/);}
static void __main_block_dispose_0(struct __main_block_impl_0src) {_Block_object_dispose((void)src->str, 3/BLOCK_FIELD_IS_OBJECT/);}
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[]) {
NSMutableString *str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("stringWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_0);
void(*TestBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_3, str);
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
return 0;
}
__main_block_impl_0构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *str, int flags=0) : str```
可以看出传递的是NSMutableString *类型,即传递的地址,进而可以改变str的值。
上边代码中我们可以看到__main_block_copy_0
和_main_block_dispose_0
概念,接下来对这两个概念进行初步讲解.
二、Block的copy和dispose
OC中,一般Block就分为以下3种,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock
先来说明一下3者的区别。
1.从捕获外部变量的角度上来看
-
_NSConcreteStackBlock:
只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。
StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。
-
_NSConcreteMallocBlock:
有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制
-
_NSConcreteGlobalBlock:
没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束。
没有用到外部变量肯定是_NSConcreteGlobalBlock,这点很好理解。不过只用到全局变量、静态变量的block也是_NSConcreteGlobalBlock。举例如下:
#import <Foundation/Foundation.h>
int global_a = 1;
static int static_global_b = 2;
int main(int argc, const char * argv[]) {
static int static_c = 3;
void (^myBlock)(void) = ^{
NSLog(@"Block中 变量 = %d %d %d",static_global_b ,static_c, global_a);
};
NSLog(@"%@",myBlock);
myBlock();
return 0;
}
控制台结果:
<__NSMallocBlock__: 0x100203980>
Block中 变量 = 2 3 1```
可见,只用到全局变量、静态变量的block也可以是_NSConcreteGlobalBlock。
所以在ARC环境下,3种类型都可以捕获外部变量。
##### 2. 从持有对象的角度上来看:
- ######_NSConcreteStackBlock是不持有对象的
import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
NSObject *obj = [[NSObject alloc]init];
void (^myBlock)(void) = ^{
NSLog(@"block中 %ld",obj.retainCount);
};
NSLog(@"block外 %ld",obj.retainCount);
myBlock();
return 0;
}```
输出结果:
block外 1
block中 1```
- ######_NSConcreteMallocBlock是持有对象的
//以下是在MRC下执行的
import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
NSObject *obj = [[NSObject alloc]init];
NSLog(@"block-1 %ld",obj.retainCount);
void (^myBlock)(void) = [^{
NSLog(@"block-3 %ld",obj.retainCount);
} copy];
NSLog(@"block-2 %ld",obj.retainCount);
myBlock();
return 0;
}```
输出结果:
block-1 1
block-1 2
block-1 2
-
_NSConcreteGlobalBlock也不持有对象
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
void (^myBlock)(void) = ^{
NSObject *obj = [[NSObject alloc]init];
NSLog(@"block %ld",obj.retainCount);
};
myBlock();
return 0;
}```
输出结果:
block 1
由于_NSConcreteStackBlock所属的变量域一旦结束,那么该Block就会被销毁。在ARC环境下,编译器会自动的判断,把Block自动的从栈copy到堆。比如当Block作为函数返回值的时候,肯定会copy到堆上。
- 手动调用copy
- Block是函数的返回值
- Block被强引用,Block被赋值给__strong或者id类型
- 调用系统API入参中含有usingBlcok的方法
以上4种情况,系统都会默认调用copy方法把Block赋复制
但是当Block为函数参数的时候,就需要我们手动的copy一份到堆上了。这里除去系统的API我们不需要管,比如GCD等方法中本身带usingBlock的方法,其他我们自定义的方法传递Block为参数的时候都需要手动copy一份到堆上。
copy函数把Block从栈上拷贝到堆上,dispose函数是把堆上的函数在废弃的时候销毁掉。
define Block_copy(...) ((__typeof(VA_ARGS))_Block_copy((const void *)(VA_ARGS)))
define Block_release(...) _Block_release((const void *)(VA_ARGS))
// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// Lose the reference, and if heap based and last reference, recover the memory
BLOCK_EXPORT void _Block_release(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);```
上面是源码中2个常用的宏定义和4个常用的方法,一会我们就会看到这4个方法。
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
if (!arg) return NULL;
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
latching_incr_int(&aBlock->flags);
return aBlock;
} else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size);
result->flags &= ~(BLOCK_REFCOUNT_MASK);
result->flags |= BLOCK_NEEDS_FREE | 1;
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
(*aBlock->descriptor->copy)(result, aBlock);
}
return result;
}```
上面这一段是Block_copy的一个实现,实现了从_NSConcreteStackBlock复制到_NSConcreteMallocBlock的过程.
void _Block_release(void *arg) {
struct Block_layout *aBlock = (struct Block_layout )arg;
if (!aBlock) return;
int32_t newCount;
newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
if (newCount > 0) return;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(aBlock->descriptor->dispose)(aBlock);
_Block_deallocator(aBlock);
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
;
}
else {
printf("Block_release called upon a stack Block: %p, ignored\\n", (void *)aBlock);
}
}
上面这一段是Block_release的一个实现,实现了怎么释放一个Block。
因为在C语言的结构体中,编译器没法很好的进行初始化和销毁操作。这样对内存管理来说是很不方便的。所以就在 `__main_block_desc_0`结构体中间增加成员变量` void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*)`和`void (*dispose)(struct __main_block_impl_0*)`,利用OC的Runtime进行内存管理。
相应的增加了2个方法。
static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {_Block_object_assign((void)&dst->str, (void)src->str, 3/BLOCK_FIELD_IS_OBJECT/);}
static void __main_block_dispose_0(struct __main_block_impl_0src) {_Block_object_dispose((void)src->str, 3/BLOCK_FIELD_IS_OBJECT/);}```
这里的_Block_object_assign
和_Block_object_dispose
就对应着retain和release方法。
三.Block中__block实现原理
1.普通非对象的变量
先来看看普通变量的情况
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
__block int a = 0;
void (^myBlock)(void) = ^{
a ++;
};
myBlock();
return 0;
}```
转化后的代码:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) ++;
}
static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {_Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);}
static void __main_block_dispose_0(struct __main_block_impl_0src) {_Block_object_dispose((void)src->a, 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[]) {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
}```
从以上代码看出
- 被__block修饰的变量,内部实现多了一个
__Block_byref_a_0
类型的结构体,这个结构体有5个成员变量。第一个是isa指针
,第二个是指向自身类型的__forwarding
指针,第三个是一个标记flag
,第四个是它的大小,第五个是变量值。 - 被__block修饰的变量a转化成
__Block_byref_a_0
类型的变量a - 将
__Block_byref_a_0
类型的变量a的地址传入__main_block_impl_0
内部的__Block_byref_a_0
修饰的变量a - 在
__main_block_func_0
函数中通过__cself->a
取到__Block_byref_a_0
结构体变量,在通过(a->__forwarding->a) ++
实现被__block修饰的变量a的值加一
ARC环境下,一旦Block赋值就会触发copy,__block就会copy到堆上,Block也是__NSMallocBlock。ARC环境下也是存在__NSStackBlock的时候,这种情况下,__block就在栈上。
MRC环境下,只有copy,__block才会被复制到堆上,否则,__block一直都在栈上,block也只是__NSStackBlock,这个时候__forwarding指针就只指向自己了。
2.对象的变量
//ARC环境下
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
__block NSObject *block_obj = [[NSObject alloc] init];
NSObject *obj = [[NSObject alloc] init];
NSLog(@"block外 block_obj:%p; obj:%p", &block_obj, &obj);
void (^myBlock)(void) = ^{
NSLog(@"block中 block_obj:%p; obj:%p", &block_obj, &obj);
};
myBlock();
NSLog(@"%@", myBlock);
return 0;
}```
结果输出:
block外 block_obj:0x7fff5fbff758; obj:0x7fff5fbff728
block中 block_obj:0x100300578; obj:0x1003001e0
<NSMallocBlock: 0x1002004d0>```
以上代码转换后:
struct __Block_byref_block_obj_0 {
void *__isa;
__Block_byref_block_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *block_obj;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *obj;
__Block_byref_block_obj_0 *block_obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__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_block_obj_0 *block_obj = __cself->block_obj; // bound by ref
NSObject *obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_0486e2_mi_1, &(block_obj->__forwarding->block_obj), &obj);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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[]) {
__attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)&block_obj, 33554432, sizeof(__Block_byref_block_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_0486e2_mi_0, &(block_obj.__forwarding->block_obj), &obj);
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)&block_obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
}```
首先需要说明的一点是对象在OC中,默认声明自带__strong所有权修饰符的,所以main开头我们声明的
__block NSObject *block_obj = [[NSObject alloc] init];
NSObject *obj = [[NSObject alloc] init];```
等价于:
__block __strong NSObject *block_obj = [[NSObject alloc] init];
__strong NSObject *obj = [[NSObject alloc] init];```
从以上转化代码看出:
- 被__block 修饰的变量block_obj转化成`__Block_byref_block_obj_0`的变量,名称相同
- `__main_block_impl_0`构造方法中将`__Block_byref_block_obj_0`类型的`block_obj`变量的地址传入给结构体的`__Block_byref_block_obj_0 类型`block_obj`变量,将变量obj的地址传入给结构体的变量obj
- 在`__main_block_func_0`函数中通过`__cself->block_obj`获取`__Block_byref_block_obj_0`类型变量`block_obj`,再通过`block_obj->__forwarding->block_obj`获取外部捕获的变量;通过`__cself->obj`获取obj变量
- Block捕获了__block,并且强引用了,因为在__Block_byref_block_obj_0结构体中,有一个变量是id block_obj,这个默认也是带__strong所有权修饰符的
- ARC环境下,Block捕获外部对象变量,是都会copy一份的,地址都不同。只不过带有__block修饰符的变量会被捕获到Block内部持有
我们再来看看MRC环境下的情况,还是将上述代码的例子运行在MRC中:
block外 block_obj:0x7fff5fbff758; obj:0x7fff5fbff728
block中 block_obj:0x7fff5fbff758; obj:0x7fff5fbff700
<NSStackBlock: 0x7fff5fbff6e0>```
这个时候block在栈上,NSStackBlock,可以打印出来retainCount值都是1。当把这个block copy一下,就变成NSMallocBlock,对象的retainCount值就会变成2了。
总结:
在MRC环境下,__block根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制。
而在ARC环境下,对于声明为__block的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象,所以才会产生循环引用的问题!
- 对于非对象的变量
自动变量的值,被copy进了Block,不带__block的自动变量只能在里面被访问,并不能改变值;带__block的自动变量 和 静态变量 就是直接地址访问。所以在Block里面可以直接改变变量的值 - 静态全局变量,全局变量,函数参数
可以在直接在Block中改变变量值的,但是他们并没有变成Block结构体__main_block_impl_0
的成员变量,因为他们的作用域大,所以可以直接更改他们的值
值得注意的是,静态全局变量,全局变量,函数参数他们并不会被Block持有,也就是说不会增加retainCount值 - 对于对象
在MRC环境下,__block根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制。
而在ARC环境下,对于声明为__block的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象。
最后
在ARC环境下,Block也是存在__NSStackBlock
的时候的,平时见到最多的是_NSConcreteMallocBlock
,是因为我们会对Block有赋值操作,所以ARC下,block 类型通过=进行传递时,会导致调用objc_retainBlock->_Block_copy->_Block_copy_internal
方法链。并导致 __NSStackBlock__
类型的 block 转换为__NSMallocBlock__
类型。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
__block int temp = 10;
NSLog(@"%@",^{NSLog(@"*******%d %p",temp ++,&temp);});
return 0;
}
<__NSStackBlock__: 0x7fff5fbff768>```
参考:
http://www.jianshu.com/p/ca6ac0ae93ad
http://www.jianshu.com/p/ee9756f3d5f6
网友评论