概述
block
本质上也是一个 OC
对象,它内部也有个 isa
指针。
block
是封装了函数调用以及函数调用环境的 OC
对象。
block
的底层结构:
证明 block
是 OC
对象:
void (^block)(void) = ^{};
Class blockClass = [block class];
NSLog(@"%@ -> %@ -> %@", blockClass, [blockClass superclass], [[blockClass superclass] superclass]); // __NSGlobalBlock__ -> NSBlock -> NSObject
block基本使用
定义 block
语法:
// 定义block
返回值类型 (^block变量名)(参数列表) = ^代码段返回值类型(参数列表) {
代码段;
};
// 执行block
block变量名();
利用类型别名简化 block
:
// 定义block类型别名
typedef 返回值类型 (^Block类型名)(参数列表);
// 定义block
Block类型名 block变量名 = ^代码段返回值类型(参数列表) {
代码段;
};
// 执行block
block变量名();
block底层结构初探
定义一个简单的 block
:
int age = 10;
void (^block)(void) = ^{
NSLog(@"This is a block! %d", age);
};
然后转成 C++
代码,可以拿到下面结构(暂时忽略了函数定义,只保留结构)。可以发现捕获的 age
变量,也定义到了这个结构体中:
struct __block_impl {
void *isa; // isa指针,可以看出是OC对象
int Flags;
int Reserved;
void *FuncPtr; // block代码块里的代码封装的函数地址
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; // block大小
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age; // 捕获的age变量
};
在block代码块里打个断点,通过反汇编工具可以看到这个函数地址确实就是 FuncPtr
指针指向的地址:
block变量捕获
为了保证 block
内部能够正常访问外部的变量,block
有个变量捕获机制:
原始代码:
int no = 10;
static int size = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
static int height = 10;
void (^block)(void) = ^{
NSLog(@"%d, %d, %d, %d", age, height, no, size); // 10, 20, 20, 20
};
age = 20;
height = 20;
no = 20;
size = 20;
block();
}
return 0;
}
转为 C++
代码:
int no = 10;
static int size = 10;
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; // 捕获了age
int *height; // 捕获了height
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
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
int *height = __cself->height; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_25_pb1crnzn29gcfxnjh75vhb900000gn_T_main_b67dee_mi_0, age, (*height), no, size);
}
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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
static int height = 10;
// 声明block
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
age = 20;
height = 20;
no = 20;
size = 20;
// 调用block
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
从上面代码可以看出来,变量捕获是通过 __main_block_impl_0
结构体的构造函数传递的,其中自动局部变量
是值传递
,静态局部变量
是指针传递
。
而全局变量,则不会捕获,因为全局变量是可以直接访问的,没有必要捕获。
对象和成员变量捕获
原始代码:
@interface Person : NSObject
@property (nonatomic, assign) int age;
- (void)test;
@end
@implementation Person
- (void)test {
void (^block)(void) = ^{
NSLog(@"%@, %d", self, self.age); // 20
};
self.age = 20;
block();
}
@end
转为 C++
代码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self; // 捕获的对象
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
Person *self = __cself->self; // bound by copy
// 只捕获了对象,而对象的成员变量是通过对象直接访问的
NSLog((NSString *)&__NSConstantStringImpl__var_folders_25_pb1crnzn29gcfxnjh75vhb900000gn_T_Person_06fde7_mi_0, self, ((int (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("age")));
}
static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __Person__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
void (*dispose)(struct __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};
// test方法第一个参数是对象本身引用,第二个参数是方法SEL。都是局部变量
static void _I_Person_test(Person * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)self, sel_registerName("setAge:"), 20);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
static int _I_Person_age(Person * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_Person$_age)); }
static void _I_Person_setAge_(Person * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_Person$_age)) = age; }
从上面代码可以看出在 block
中调用了对象和对象中的成员变量,block
只捕获了对象,因为对象可以直接访问到本身的成员变量。
如果是在类方法中,self
则表示类对象,也是作为方法的第一个隐式参数,也会被 block
捕获。