ios笔记: block 详细解析
Block 就是 Objective-C 语言对于闭包的实现。
在编程语言中,闭包是函数或引用环境的函数的一个函数或引用 - 一个存储对该函数的每个非局部变量(也称为自由变量或更高值)的引用的表。by wikipedia translate
这篇文章主要解析一下几点
- block 从源码分析其原理
- block 的内存管理
- block 通过什么方式来修改外部变量
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*dispose)(void *);
void (*copy)(void *dst, void *src);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
};
一个block由几部分构成:
- isa指针,所有对象都存在该指针,用于实现对象的相关功能
- flags, 用于表示一些block的附加信息
- reserved,保留变量值
- invoke,指向实现函数调用地址
- descriptor,描述信息,copy函数指针,
size大小{(sizeof(struct __main_block_impl_0))}等
block类型
首先我们看一下block的几个类型:
NSConcreteGlobalBlock:全局静态block,不会访问外部变量
NSConcreteStackBlock: 存在栈中的block,当函数作用域结束或者返回时被销毁
NSConcreteMallocBlock:存在堆中的block,内部由引用计数器管理是否销毁
block 从源码分析其原理
使用 clang 命令可以查看源码
clang -rewrite-objc main.m
NSConcreteGlobalBlock 类型
int main(int argc, char * argv[]) {
@autoreleasepool {
^{ };
return 0;
}
}
将上面的代码通过命令得到main.cpp文件, 剔除无关代码
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) {
}
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, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
return 0;
}
}
__main_block_impl_0 其实就是block的实现,从它我们可以看出:
- block由isa、Flags、Reserved 组成,它其实就是一个对象
- 源码中 isa 指向了** _NSConcreteStackBlock**,但是在开启ARC 的 LLVM中 block 应该是指向 _NSConcreteGlobalBlock类型。
- impl为函数指针,指向 __main_block_func_0
NSConcreteStackBlock类型
int main(int argc, char * argv[]) {
@autoreleasepool {
int stackValue;
^{ stackValue; };
return 0;
}
}
源码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int stackValue;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _stackValue, int flags=0) : stackValue(_stackValue) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int stackValue = __cself->stackValue; // bound by copy
stackValue; }
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, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int stackValue;
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, stackValue));
return 0;
}
}
- isa 指向了_NSConcreteStackBlock类型
- __main_block_impl_0 中增加了变量 ** int stackValue; **,在blokc实际修改变量时,修改的是这个内部的变量,外部的变量不会改变
如果使用关键字 ** __block ** 来修饰stackValue变量
struct __Block_byref_stackValue_0 {
void *__isa;
__Block_byref_stackValue_0 *__forwarding;
int __flags;
int __size;
int stackValue;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_stackValue_0 *stackValue; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_stackValue_0 *_stackValue, int flags=0) : stackValue(_stackValue->__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_stackValue_0 *stackValue = __cself->stackValue; // bound by ref
(stackValue->__forwarding->stackValue); }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->stackValue, (void*)src->stackValue, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->stackValue, 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, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_stackValue_0 stackValue = {(void*)0,(__Block_byref_stackValue_0 *)&stackValue, 0, sizeof(__Block_byref_stackValue_0)};
;
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_stackValue_0 *)&stackValue, 570425344));
return 0;
}
}
__Block_byref_id_object_copy、__Block_byref_id_object_dispose,以实现对对象内存的管理。其中两者的最后一个参数131表示BLOCK_BYREF_CALLER|BLOCK_FIELD_IS_OBJECT,BLOCK_BYREF_CALLER表示在内部实现中不对a对象进行retain或copy
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
...
else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}
- 源码中加入了 __Block_byref_stackValue_0 结构体,保存临时变量 stackValue
- 在__main_block_func_0中使用 ** __forwarding** 来保证变量始终在堆中
- __Block_byref_stackValue_0 也是一个对象
- 我们需要管理相应的内存,在__main_block_desc_0 中加入了copy 和 dispose
NSConcreteMallocBlock类型
NSConcreteMallocBlock 无法查看具体源码,我们可以根据一个实力代码来查看 NSConcreteMallocBlock类型
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的基本实现,需要注意:
- 如果在block中修改或者使用了外部变量,记得使用_weak typeof(self) weakSelf = self;
- 在多线程环境下(block中的weakSelf有可能被析构的情况下),需要先将self转为strong指针,避免在运行到某个关键步骤时self对象被析构。
参考文献
A look inside blocks
Block技巧与底层解析
谈Objective-C block的实现