可以用xcrun -sdk iphoneos clang -arch arm64 -fobjc-arc -fobjc-runtime=ios-10.0.0 -rewrite-objc main.m -o main.cpp 来探究block底层
写法:
block的原理
本质是一个对象,成员变量包括函数的地址以及函数中用到的局部变量
block的底层结构
通过源码 源码地址
其descriptor是表面是一个Block_descriptor_1类型的指针, 但block的flag不同, descriptor其指向的真实内存可能包含Block_descriptor_1 \ Block_descriptor_2 \ Block_descriptor_3, 搭配flag, 可以获取Block_descriptor_1 \ Block_descriptor_2 \ Block_descriptor_3的值
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
// block
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor; // 如果
// imported variables
};
通过clang rewrite 来验证一下, 发现基本上是类似的
int main(int argc, const char * argv[]) {
int age = 0;
void(^block)(void) = ^{
age;
};
block();
return 0;
}
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;
int age; // 捕获的局部变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
age;
}
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)};
void(*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
age
);
(__block_impl *)block->FuncPtr(block);
变量捕获
遇到普通局部变量, 会进行值捕获, 因为这个局部变量, 可能在block调用的时候, 已经不存在了 遇到静态局部变量(static), 会进行地址捕获 遇到全局变量, 不会进行捕获
self本质
OC
@interface WYPerson : NSObject
@end
@implementation WYPerson
- (void)test {}
@end
C++
static void _I_WYPerson_test(WYPerson * self, SEL _cmd) {}
所以, self本质是个参数, objc_msgsend的时候,也会传递消息接收者和SEL, 所以self也是局部变量, 所以block也会对self进行值捕获
block类型
因为block是有isa指针的, 所以必然是有类型的
一共有三种类型
全局block: __NSGlobalBlock__
堆block: __NSMallocBlock__
栈block: __NSStackBlock__
如何区分
如果没有捕获局部变量, 则这个block就是__NSGlobalBlock__, 存在数据段(全局区)
如果有捕获局部变量, 则这个block就是__NSStackBlock__ , 存在栈
对__NSStackBlock__执行copy操作的返回值就是__NSMallocBlock__ , 存在堆
| 类型 | copy操作 |
|---|---|
__NSGlobalBlock__ | 不进行任何操作 |
__NSMallocBlock__ | 引用计数 + 1 |
__NSStackBlock__ | copy到堆上 |
arc环境下, 会自动对__NSStackBlock__ 进行copy操作, 比如:
- block作为返回值
- block被强指针引用
- gcd中使用的block
小tips, 可以通过在build settings中关闭arc
捕捉对象类型的变量时, block的内存结构变化
只有__main_block_desc_0会变化, 对应到源码上的话, 就是descriptor其指向的真实内存会包含Block_descriptor_2
捕捉对象类型的变量时
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->weakperson, (void*)src->weakperson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->weakperson, 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};
捕捉普通类型的变量时
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)};
block在copy时,会调用__main_block_copy_0函数, 其中会针对每个捕获的对象,调用_Block_object_assign函数, 会根据捕捉的对象类型的成员的修饰类型(__weak, __strong), 来决定是否执行类似retain的操作
block在dealloc时,会调用__main_block_dispose_0函数, 其中会调用_Block_object_dispose函数, 会根据捕捉的对象类型的成员的修饰类型(__weak, __strong), 来决定是否执行类似release的操作
所以, 栈block的捕获的对象变量, 在copy变成堆block之前, 是不会对捕获的对象变量进行内存管理的(不进行类似retain, release的操作)
__block
被__block修饰的变量, 会被转成一个特殊的结构体, 这个结构体中会有一个成员变量的值就是这个被修饰的变量, 看源码不太直观, 看clang rewrite的更加直观一点
源码
// Values for Block_byref->flags to describe __block variables
enum {
// Byref refcount must use the same bits as Block_layout's refcount.
// BLOCK_DEALLOCATING = (0x0001), // runtime
// BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler
BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler
BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler
BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler
BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler
BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime
};
struct Block_byref {
void * __ptrauth_objc_isa_pointer isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
用clang rewrite
修饰普通变量时
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
修饰对象类型的变量时 (当对象变量用__weak修饰时, 结构体里WYPerson *__strong person;的__strong会变成__weak)
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
// *******
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
// *******
WYPerson *__strong person; // __weak修饰时, __strong会变成__weak
};
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的成员变量__main_block_desc_0结构体中都会有copy和dispose的函数地址成员变量, 其中调用_Block_object_assign函数时,针对变量是否用__block修饰, 会传入不同的参数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person1, (void*)src->person1, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->person2, (void*)src->person2, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->person1, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->person2, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
还需注意的是, mrc时, 捕获的用__block修饰的成员变量, 在copy时, 不对该成员变量进行内存管理(retain,release)的操作
用__block修饰的变量为什么这么设计, (person1->__forwarding->person1);
// oc
MyBlock block = ^{
person1;
};
// c++
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_person1_0 *person1 = __cself->person1; // bound by ref
(person1->__forwarding->person1);
}
- 用
__block修饰的变量,在编译时,会转换成一个结构体, 函数运行时, 该结构体初始化后, 存在栈上, 我们称它为A block初始化时, 会将A的地址作为参数传入构造方法中,block有一个指针类型的成员变量指向A的地址block进行copy时,会将block结构体的内容,copy到堆上, 于此同时, 会将A的内容也copy到堆上, 并将block指向A的指针的成员变量的值改成堆上A的地址- 这时候有堆和栈上同时存储着A的内容, 这时候如果希望改动或访问时, 只访问堆上的内容,就需要改变栈上的A和堆上的A的
forwarding指针, 都指向堆上的地址, 并且在访问时, 访问__forwording
ps: 所以,block的
__Block_byref_person_0类型的成员变量的__forwarding指针,在block copy到堆上以前, 是指向栈上的地址, 在block copy到堆上以后, 会指到堆上的地址
因为希望任何地方的针对变量的改动都影响的是同一块内存,才这么设计
源码
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;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
(*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));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
为什么在block外加__weak, 在block中又加__strong
- 加
__weak的作用是解决循环引用 - 加
__strong的作用是一方面是为了避免在block执行过程中, 捕获的对象被释放, 另一方面是为了支持在block中使用指针的方法去访问捕获对象的成员变量
block也有方法实现, 如何获取对应的IMP和方法签名
源码中也有获取方法签名的函数, 跟下边的代码类似
// 获取block的方法实现
imp_implementationWithBlock
// 获取block方法签名
typedef NS_OPTIONS(int, BlockFlags) {
BlockFlagsHasCopyDisposeHelpers = (1 << 25),
BlockFlagsHasSignature = (1 << 30)
};
typedef struct _Block {
__unused Class isa;
BlockFlags flags;
__unused int reserved;
void (__unused *invoke)(struct _Block *block, ...);
struct {
unsigned long int reserved;
unsigned long int size;
// requires BlockFlagsHasCopyDisposeHelpers
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
// requires BlockFlagsHasSignature
const char *signature;
const char *layout;
} *descriptor;
// imported variables
} *BlockRef;
static NSMethodSignature* typeSignatureForBlock(id block) {
BlockRef layout = (__bridge void *)block;
if (layout->flags & BlockFlagsHasSignature) {
void *desc = layout->descriptor;
desc += 2 * sizeof(unsigned long int);
if (layout->flags & BlockFlagsHasCopyDisposeHelpers) {
desc += 2 * sizeof(void *);
}
if (desc) {
const char *signature = (*(const char **)desc);
return [NSMethodSignature signatureWithObjCTypes:signature];
}
}
return nil;
}
利用NSInvocation来调用block
- 设置参数要从第1位开始, 第0位系统会自动传block对象本身
- 利用invokeWithTarget: 传入block对象
- 如果不知道block的返回值和参数类型情况下, 可以利用block的NSMethodSignature来推断出
int(^block)(int,int) = ^(int a, int b) {
NSLog(@"%d - %d", a, b);
return a + b;
};
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:typeSignatureForBlock(block)];
int a = 5;
int b = 10;
[invocation setArgument:&a atIndex:1];
[invocation setArgument:&b atIndex:2];
[invocation invokeWithTarget:block];
int res = 0;
[invocation getReturnValue:&res];
NSLog(@"%d", res);