本文用于记录近期学习block底层后的理解。
本文的参考博文:
Block技巧与底层解析
谈Objective-C block的实现
一、Block编译转换 OC->C++
通过使用命令clang -rewrite-objc
实现。
1.首先,新建一个main.m文件。
2.打开终端,cd到main.m文件所在目录。
3.输入 clang -rewrite-objc main.m
命令进行转换。
4.最后main.m文件所在的目录下,新生成一个main.cpp文件。
二、Block类型
1. NSConcreteGlobalBlock
以下两种情况下,block为NSConcreteGlobalBlock
a.记述全局变量的地方有block语法时。
b.block语法的表达式中不使用任何外部变量时。
更新(11.03 9:00):
这里有个快速判断的方法。如果Block的body里使用到了外部的非全局变量和非static静态变量,那么这个Block就会在栈上创建即_NSConcreteStackBlock。反之如果没有引用变量或者仅引用了全局变量或者static静态变量则是全局Block_NSConcreteGlobalBlock
来自:漫谈Block
举例1:
#include
void (^globalBlock)() = ^{
};
int main()
{
globalBlock();
return 0;
}
转换后的C++代码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __globalBlock_block_impl_0 {
struct __block_impl impl;
struct __globalBlock_block_desc_0* Desc;
__globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
结构体__block_impl
的isa
指针指向NSConcreteGlobalBlock
。
举例2:
我尝试了在main函数中创建一个block,并且block不去截获变量,但是通过clang转换,发现isa指针指向的却是NSConcreteStackBlock。这一点很奇怪。
我在《oc高级编程》中看到这样一句话:
即使在函数内而不在记述广域变量的地方使用Block语法时,只要Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区域。
2. NSConcreteStackBlock
保存在栈中的 block,当函数返回时会被销毁。
#include
int main() {
int a = 100;
void (^block2)(void) = ^{
a;
};
return 0;
}
转换后的C++代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
结构体__block_impl
的isa
指针指向NSConcreteStackBlock
。
3. NSConcreteMallocBlock
保存在堆中的 block,当引用计数为 0 时会被销毁。
但是NSConcreteMallocBlock 类型的 block 通常不会在源码中直接出现,当[block copy]
的时候,会被复制到堆中。
(以下代码来自:谈Objective-C block的实现)
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
// 1
if (!arg) return NULL;
// 2
aBlock = (struct Block_layout *)arg;
// 3
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 4
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 5
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 6
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// 7
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 8
result->isa = _NSConcreteMallocBlock;
// 9
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
当block被copy的时候,会调用_Block_copy_internal
方法,在内部result->isa = _NSConcreteMallocBlock;
。
更新(11.03 9:45):
感谢 啊哈呵 ,在他给的源码中找到以下代码:
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy.
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
可以很显然的看到是通过结构体中的flags来判断是否copy。
下面举一个copy的例子,在ARC下,当Block作为函数返回值时也会拷贝成为NSConcreteMallocBlock 类型,本质是调用copy方法。
如下代码:
typedef void (^Block)();
Block getBlock() {
char c = 'YQ';
void (^block)() = ^{
printf("%c", e);
};
return block;
}
void main {
Block block = getBlock();
block();
}
当在ARC环境下,能正常运行不会奔溃,是因为系统会在创建的时候调用objc_retainBlock
方法,而objc_retainBlock
方法实际上就是Block_copy
方法。(来自runtime/objc-arr.mm)
因此本质上,以上代码的系统实现流程就变成了:在栈上创建block结构体对象,然后再通过Block_copy
复制到堆上,然后把堆上的对象注册到自动释放池中,同时返回这个堆上的对象。
但是在MRC环境下,就要奔溃了,因为系统不会自动拷贝。所以需要手动拷贝:[block copy]
三、Block的拷贝
�三种类型的Block拷贝:
NSConcreteGlobalBlock拷贝后,什么也不做。
NSConcreteStackBlock拷贝后,从栈复制到堆中。
NSConcreteMallocBlock拷贝后,引用计数加一。
四、__block变量的拷贝
int main()
{
__block int i = 0;
void (^block)(void) = ^{
i = 1;
};
return 0;
}
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_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; // bound by ref
(i->__forwarding->i) = 1;
}
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*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
return 0;
}
当使用__block修饰符时,�基本数据类型 i 被转换成了__Block_byref_i_0
结构体。__Block_byref_i_0
结构体中带有 isa指针,说明它也是一个对象。
当block修改变量时,会调用下面代码:
__Block_byref_i_0 *i = __cself->i; // bound by ref
(i->__forwarding->i) = 1;
发现绕来绕去,下面理一下。
最让人无法理解的是__forwarding
指针。__forwarding
指针始终指向自己。
当__block int i
在栈中的时候,__forwarding
指向的是栈中的自己。
当 i 拷贝到堆中时候,__forwarding
指向的是堆中的自己。
正如《oc高级编程》所说:
__block修饰变量用结构体成员变量
__forwarding
可以实现无论 __block变量配置在栈上还是堆上都能够正确的访问 __block变量。
五、Block拷贝对__block变量的影响
影响
如果Block使用了__block变量,当Block从栈拷贝到堆中,
a.栈中的__block变量会拷贝到堆中并被Block持有。
b.堆中的__block变量被Block持有。
这个和OC的引用计数内存管理相同。
下面有BlockA、BlockB 、 __block a、 __block b。
如果BlockA使用了栈上的a和b,当[BlockA copy]拷贝到堆上,a,b也会同时拷贝到堆上,并且堆上的BlockA持有堆上的a,b。
同理,当BlockA,BlockB都使用了栈上的a,当[BlockA copy],[BlockB copy]拷贝到堆上,BlockA和BlockB会同时持有堆上的a。
现在再回过头去看第四点的__block int i
,就能理解为什么要有__forwarding
指针了。
现有以下代码:
int main()
{
__block int i = 0;
void (^block)(void) = [^{
++i;
} copy];
++i;
block();
printf("%d", i);
return 0;
}
把上面代码转换为C++后:
// block中的++i实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_i_0 *i = __cself->i; // bound by ref
++(i->__forwarding->i);
}
// main函数中的++i
++(i.__forwarding->i);
可以发现都是++(i.__forwarding->i);
,也就是说指向的都是堆中的i。
因此输出为2。
六、一些题目,判断ARC和MRC环境下能否运行
1 都能运行
void exampleA() {
char a = 'A';
^{
printf("%cn", a);
}();
}
2 ARC能运行
void exampleB_addBlockToArray(NSMutableArray *array) {
char b = 'B';
[array addObject:^{
printf("%cn", b);
}];
}
void exampleB() {
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
3 都能运行
void exampleC_addBlockToArray(NSMutableArray *array) {
[array addObject:^{
printf("Cn");
}];
}
void exampleC() {
NSMutableArray *array = [NSMutableArray array];
exampleC_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
4 ARC能运行
typedef void (^dBlock)();
dBlock exampleD_getBlock() {
char d = 'D';
return ^{
printf("%cn", d);
};
}
void exampleD() {
exampleD_getBlock()();
}
5 ARC能运行
typedef void (^eBlock)();
eBlock exampleE_getBlock() {
char e = 'E';
void (^block)() = ^{
printf("%cn", e);
};
return block;
}
void exampleE() {
eBlock block = exampleE_getBlock();
block();
}
七、更新(11.03 12:00)block对各种变量的处理
在看了漫谈Block后半部分后,很想把这一部分整理一下。
首先!如果捕获的变量为id, NSObject, __attribute__((NSObject)), block
类型变量和__block修饰的变量都会调用_Block_object_assign
方法。我就把_Block_object_assign
方法写在最上面!
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
*dest = _Block_copy(object);
break;
...
case BLOCK_FIELD_IS_BYREF:
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
*dest = object;
break;
...
default:
break;
}
}
1.变量为基本数据类型 无__block
#include
int main() {
int a = 100;
void (^block2)(void) = ^{
a;
};
return 0;
}
只会在block的结构体内部中添加一个int a变量。不能做修改。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
2.变量为基本数据类型 有__block
int main()
{
__block int i = 0;
void (^block)(void) = ^{
i = 1;
};
return 0;
}
有__block修饰的基本数据类型会转换成__Block_byref_i_0
结构体。
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
然后当他被拷贝后,会进入_Block_object_assign
方法的BLOCK_FIELD_IS_BYREF
case
case BLOCK_FIELD_IS_BYREF:
*dest = _Block_byref_copy(object);
break;
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
...
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
...
return src->forwarding;
}
通过代码发现,在堆上创建了一个copy
对象。然后通过
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
让其始终指向堆上的自己。
3.变量为对象 无__block
因此会进入到BLOCK_FIELD_IS_OBJECT
中
case BLOCK_FIELD_IS_OBJECT:
_Block_retain_object(object);
*dest = object;
break;
static void _Block_retain_object_default(const void *ptr __unused) { }
// 默认_Block_retain_object被赋值为_Block_retain_object_default,即什么都不做
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
// Called from CF to indicate MRR. Newer version uses a versioned structure, so we can add more functions
// without defining a new entry point.
void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
_Block_retain_object = callbacks->retain;
_Block_release_object = callbacks->release;
_Block_destructInstance = callbacks->destructInstance;
}
默认_Block_retain_object
被赋值为_Block_retain_object_default
,即什么都不做。也就是说,在ARC环境下,Block不会在这里持有对象。(ARC环境有了更完善的内存管理,如果外部变量由__strong、copy、strong
修饰时,Block会把捕获的变量用__strong来修饰进而达到持有的目的。)在MRR环境下,Block会通过_Block_retain_object
方法持有id, NSObject, __attribute__((NSObject))
类型变量。
4.变量为对象 有__block
因此会进入到BLOCK_FIELD_IS_OBJECT
中
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
*dest = object;
break;
直接赋值。
所以通过__block修饰可以避免调用到_Block_retain_object
方法,也就是在MRR环境下我们可以通过__block来避免Block强持有变量,进而避免循环引用。