让我们看下代码:
//文件test.m
#import <Foundation/Foundation.h>
void test()
{
//下面分别定义各种类型的变量
int a = 10; //普通变量
__block int b = 20; //带__block修饰符的block普通变量
NSString *str = @"123";
__block NSString *blockStr = str; //带__block修饰符的block OC变量
NSString *strongStr = @"456"; //默认是__strong修饰的OC变量
__weak NSString *weakStr = @"789"; //带__weak修饰的OC变量
//定义一个block块并带一个参数
void (^testBlock)(int) = ^(int c){
int d = a + b + c;
NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr, weakStr);
};
a = 20; //修改值不会影响testBlock内的计算结果
b = 40; //修改值会影响testBlock内的计算结果。
testBlock(30); //执行block代码。
}
大家都知道打印的结果,block里a的值为10,b为40。a在外部的更改并没有影响到block内部的值。为什么呢?让我们看一下底层的实现机制是什么。
我们打开终端控制台,并到test.m文件所在的路径下执行如下的命令:
clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations test.m
得到cpp文件,这个文件是OC代码的c++实现,我们可以通过查看c++来分析block的实现结构和方法,下面是c++部分代码。
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
int b;
};
struct __Block_byref_blockStr_1 {
void *__isa;
__Block_byref_blockStr_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *blockStr;
};
// 结构体 __block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int a;
NSString *strongStr;
NSString *weakStr;
__Block_byref_b_0 *b; // by ref
__Block_byref_blockStr_1 *blockStr; // by ref
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _a, NSString *_strongStr, NSString *_weakStr, __Block_byref_b_0 *_b, __Block_byref_blockStr_1 *_blockStr, int flags=0) : a(_a), strongStr(_strongStr), weakStr(_weakStr), b(_b->__forwarding), blockStr(_blockStr->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself, int c) {
__Block_byref_b_0 *b = __cself->b; // bound by ref
__Block_byref_blockStr_1 *blockStr = __cself->blockStr; // bound by ref
int a = __cself->a; // bound by copy
NSString *strongStr = __cself->strongStr; // bound by copy
NSString *weakStr = __cself->weakStr; // bound by copy
int d = a + (b->__forwarding->b) + c;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_3, d, strongStr, (blockStr->__forwarding->blockStr), weakStr);
}
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->strongStr, (void*)src->strongStr, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->blockStr, (void*)src->blockStr, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->weakStr, (void*)src->weakStr, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __test_block_dispose_0(struct __test_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->strongStr, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->blockStr, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->weakStr, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0), __test_block_copy_0, __test_block_dispose_0};
void test()
{
int a = 10;
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_0;
__attribute__((__blocks__(byref))) __Block_byref_blockStr_1 blockStr = {(void*)0,(__Block_byref_blockStr_1 *)&blockStr, 33554432, sizeof(__Block_byref_blockStr_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, str};
NSString *strongStr = (NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_1;
NSString *weakStr = (NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_2;
void (*testBlock)(int) = ((void (*)(int))&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a, strongStr, weakStr, (__Block_byref_b_0 *)&b, (__Block_byref_blockStr_1 *)&blockStr, 570425344));
a = 20;
(b.__forwarding->b) = 40;
((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 30);
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
可以看到,__block int b 被转换为一个block字眼的结构体。它具备5个属性:
1.__isa指针
2.__forwarding我理解为传入变量的地址,这个最关键,也是为什么可以改变__block修饰的变量的原因
3.__flags我理解为标记位,编译器分配的
4.__size结构体大小
5. b,真正的值
这里插一句,__block NSString* blockString 会多出两个属性,这也完美解释了,MRC下为什么对象前面要加__block来防止内存循环引用,多出的两个属性为
1.__Block_byref_id_object_copy
2.__Block_byref_id_object_dispose
当Block从栈复制到堆时,使用_Block_object_copy函数,持有Block截获的对象。当堆上的Block被废弃时,使用_Block_object_dispose函数,释放Block截获的对象。
回到正题,看一下__block int b对应的赋值情况
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
__isa指针传值为空(ps:但对于block的初始化来讲,isa指针指向的是该block的三种类型),__forwarding指向了变量b本身的地址,__flags分配了0,__size为结构体的大小,b等于20。用图说明赋值的情况:
所以,没有__block修饰的变量 和有__block修饰的变量在调用时的实现为:
a = 20;
(b.__forwarding->b) = 40;
通过打印地址,你会发现,对于自动释放变量(指出了作用域会被释放的变量),在block里只是用到了变量指向的值,而不是把变量本身传入了block;对于__block修饰的变量,block会创建block结构体,并且改变原变量的地址。成员变量和全局变量前后地址相同,见下图。
1.黄色和绿色是__block 修饰的int变量和string变量,会发现,当block执行后,原变量的地址被改了。
2.蓝色为成员变量,它的地址不变。这也是为什么成员变量不经过__block修饰也可以改变值的原因。
ps:关于__block修饰的变量,在block执行完后,地址被改的情况,如果有知道原因的大大们,小生在此虚心请教,多谢大大们~
现在说一下block的存储域和相互引用
ps:部分摘自https://www.jianshu.com/p/12c324c9dcc4
顾名思义,相互引用就是,A持有B,B又持有A,导致两个对象都无法释放。
1.先说一个不会存在相互引用的例子,依然用上面的代码,在block实现里小做修改:
//定义一个block块并带一个参数
void (^testBlock)(int) = ^(int c){
int d = a + b + c;
NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr, weakStr);
NSLog(@"self=%@, strongStr=%@, blockStr=%@, weakStr=%@", self);
};
我们在block里打印了self,但并没有造成内存循环引用,原因就是self并没有持有block,这个block紧紧是在某个方法下定义的,存储栈上面,出了作用域后会自动销毁。
2.说一个会存在相互引用的例子,这里我们用__block,证明用__block修饰过也会造成相互引用。
typedeft void (^blk_t)(void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
- (id)init
{
self = [super init];
__block id blockSelf = self;
blk_ = ^{
NSLog(@"self = %@", blockSelf);
};
return self;
}
为什么加了__block仍然会造成相互引用呢?
因为blk_t的存储域现在是在堆上,堆上的block会对__block修饰变量做持有,栈上的block只会对__block修饰的变量做使用,因此这个代码的引用情况为MyObject->blk_t->MyObject。
如何打破相互以后用呢?
只需在使用完后,将__block变量置空,打破循环。
blk_ = ^{
NSLog(@"self = %@", blockSelf);
blockSelf = nil; //打破循环
};
刚才我们说到堆会对__block变量做持有,为什么?这就是接下来要说的“存储域”
block的存储域有三种:
● _NSConcretStackBlock 栈
● _NSConcretGlobalBlock 全局
● _NSConcretMallocBlock 堆
一.全局_NSConcretGlobalBlock
1.定义在方法外部的,且没有捕获任何变量。
typedef void (^blk_t)(void);
- (void)test{
blk_t blck = ^{
NSLog(@"_global");
};
blck();
}
2.定义在方法内部的,且没有捕获任何变量。
- (void)test{
void(^block)(int) = ^(int c){
} ;
block(1);
}
二.栈_NSConcretStackBlock
arc下会在block赋值时自动加copy,将_NSConcretStackBlock变成_NSConcretMallocBlock,只有mrc下,能看到。目前我知道能保存在栈上的block只有在声明时才会看到,代码如下
NSLog(@"block is %@", ^{
NSLog(@"test Arr :%@", self.str);
});
打印为:block is <__NSStackBlock__: 0x7ffee7376068>
三,堆_NSConcretMallocBlock
堆是我们最常见的block,对栈上的block进行copy后,就会被存储到堆上,什么时候会进行copy操作?ps:我们说的都是arc下的
1.当block作为函数返回值返回时
2.当 block 被赋值给 __strong id 类型的对象或 block 的成员变量时,ps:arc下默认都会加__strong
3.当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API时。
补充:当多个block引用同一个__block变量时,在堆上会使__block变量引用计数+1;