-
Notifications
You must be signed in to change notification settings - Fork 105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
block #70
Comments
Block 总结(《Objective-C 高级编程》学习总结)
目录一、Block 简介
二、Block 的实现
三、Block 循环引用 延伸阅读 |
一、Block 简介1. 什么是 block?block 是可以在函数内定义的匿名函数。 2. block 值的语法block 的语法跟函数相比,多了 BOOL isOdd(int num) {
return num % 2 != 0;
}
// block 的语法跟函数相比,多了 ^,少了函数名
BOOL (^isOdd)(int) = ^BOOL (int num) {
return num % 2 != 0;
}; 返回值类型可以省略: BOOL (^isOdd)(int) = ^(int num) {
return (BOOL)(num % 2 != 0);
}; 如果不需要传参数,那么参数列表也可以省略: void (^foo)(void) = ^void (void) {
printf("This block has no arguments.");
};
// 如果不需要传参数,那么参数列表也可以省略
void (^foo)(void) = ^void {
printf("This block has no arguments.");
};
// 进一步简化
void (^foo)(void) = ^{
printf("This block has no arguments.");
}; 3. block 类型名的语法函数指针: BOOL isOdd(int num) {
return num % 2 != 0;
}
BOOL (*funcPtr)(int) = &isOdd; block 类型的声明跟函数指针相比,只是把 BOOL (^isOdd)(int) = ^BOOL (int num) {
return num % 2 != 0;
}; block 作为方法和函数的参数和返回值: // 当 block 作为方法的参数时,参数类型括号里只写类型 BOOL(^)(int),参数名放到了括号后面
- (void)doSomething:(BOOL(^)(int))isOdd;
// 当 block 作为方法的返回值,不写参数名,只有类型 BOOL(^)(int)
- (BOOL(^)(int))doSomething;
// 当 block 作为函数的返回值,跟 block 变量类型名定义的类似,将函数名加 `()`放到中间
BOOL(^doSomething())(int) {
return ^BOOL(int num) {
return num % 2 != 0;
};
}
使用 typedef 可以让 block 类型名的可读性更好: // 写法跟定义一个 block 类型的自动变量很相似,变量名变成了新的类型名
typedef BOOL(^NumberValidator)(void);
- (void)doSomething:(NumberValidator)isOdd;
- (NumberValidator)doSomething; 4. block 捕获自动变量的值int count = 20;
void (^foo)(void) = ^void() {
printf("%d\n", count); // 输出:20
};
foo();
5. block 中如何修改捕获到的自动变量值(
|
二、Block 的实现(一)首先,关于 block 的数据结构和 runtime 是开源的,可以在LLVM 开源项目中的 Blocks Runtime 中看到,或者下载苹果的 libclosure 库的源码来看(也可以在线查看),其中包含了很多示例和文档说明。 另外,在公开的 除此之外,我们还可以使用 1. block 的本质
int main() {
void (^blk)(void) = ^void(void) {
printf("Block\n");
};
blk();
return 0;
} 使用 /// block 的执行内容转成的静态函数
/// 参数 __cself 为指向 __main_block_impl_0 结构实例本身的变量
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
int main() {
// 这里实际上是初始化一个 __main_block_impl_0 结构体实例,然后再将这个实例的指针赋值给变量 blk
// 第一个参数是一个函数指针,指向由 block 语法转换的 C 函数 __main_block_func_0
// 第二个参数是一个结构体指针,指向这个 block 结构存储相关的结构体 __main_block_desc_0_DATA
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 执行 blk 时,是从 __main_block_impl_0 实例中取出函数指针,调用该函数
// 函数参数是这个 __main_block_impl_0 实例本身
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
} 下面是 /// 存储 block 关键信息的结构体
struct __block_impl {
void *isa; // 像 Objective-C 对象一样,识别身份的 isa 指针
int Flags; // 记录某些标记,比如引用计数
int Reserved; // 今后版本升级所需的区域
void *FuncPtr; // 存储 block 执行函数的指针
};
/// 存储 block 描述信息的结构体
/// 这里直接创建了一个静态全局变量
static struct __main_block_desc_0 {
size_t reserved; // 今后版本升级所需的区域
size_t Block_size; // block 的大小
} __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
/// block 转成的结构体,这个就是 block 的真实面貌
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) {
// 该 block 的类型为 _NSConcreteStackBlock
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
结论:
|
二、Block 的实现(二)2. block 是如何捕获自动变量的
int main () {
int val1 = 256;
int val2 = 10;
const char *fmt = "val2 = %d\n";
void (^myBlock)(void) = ^void(void) {
printf(fmt, val2);
};
val2 = 2;
fmt = "These values were changed. val2 = %d\n";
myBlock();
return 0;
} 使用 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy,获取捕获到的自动变量(保存在成员变量中)
int val2 = __cself->val2; // bound by copy,获取捕获到的自动变量(保存在成员变量中)
printf(fmt, val2);
}
int main () {
int val1 = 256; // block 中没用到,也就不会截获
int val2 = 10;
const char *fmt = "val2 = %d\n";
// 构造函数多了两个参数,用于保存捕获到的自动变量
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val2));
val2 = 2;
fmt = "These values were changed. val2 = %d\n";
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
} 下面是 struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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 对应的结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// block 中捕获的自动变量被声明成了成员变量
const char *fmt;
int val2;
// 构造函数中也多了两个参数,用来传入捕获的自动变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val2, int flags=0) : fmt(_fmt), val2(_val2) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
结论:
|
二、Block 的实现(三)3. block 中是如何捕获静态变量、静态全局变量和全局变量的?block 中不能修改捕获到自动变量值。但是可以修改捕获到的非全局的静态变量、静态全局变量和全局变量的值。这是怎么做到的呢?
int global_val = 60;
static int static_global_val = 10;
int main() {
int val = 10;
static int static_val = 4;
void (^myBlock)(void) = ^void(void) {
static_val = 30;
global_val = 6;
static_global_val = 50;
printf("myBlock:val(10) = %d,\n static_val(4) = %d,\n global_val(60) = %d,\n static_global_val(10) = %d\n", val, static_val, global_val, static_global_val);
};
myBlock();
} 使用 int global_val = 60;
static int static_global_val = 10;
// block 的实现对应的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy,
int val = __cself->val; // bound by copy
// 通过访问静态局部变量的指针来改变变量值
(*static_val) = 30;
// 全局变量和静态变量可以直接访问
global_val = 6;
static_global_val = 50;
// 自动变量无法访问,只能读取变量值
printf("myBlock:val(10) = %d,\n static_val(4) = %d,\n global_val(60) = %d,\n static_global_val(10) = %d\n", val, (*static_val), global_val, static_global_val);
}
int main() {
int val = 10;
static int static_val = 4;
// 这里把静态变量的指针传入了 __main_block_impl_0 结构体的构造函数并保存
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA,
&static_val,
val));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
} 下面是 struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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 的数据结构
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val; // 捕获进来的 static 变量的地址
int val; // 捕获进来的自动变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int _val, int flags=0) : static_val(_static_val), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
结论:
|
二、Block 的实现(四)4.
|
二、Block 的实现(五)5. block 本身是怎么存储的(”block 对象“的三种类型)block 的存储域主要有三种:
5.1
|
二、Block 的实现(六)6.
|
初始时 __block 变量的存储域 |
Block 从栈复制到堆时的影响 |
---|---|
栈 | 从栈复制到堆上,并被 Block 持有 |
堆 | 被 Block 持有(引用计数加1?) |
(1)一个 block 中使用一个 __block
变量
如果在一个 block 中使用了 __block
变量,那么当这个 block 被复制到堆上时,它使用的所有的 __block
变量也都会被复制到堆上,并且被这个 block 所持有(如下图所示)。
如果 block 中使用的这个__block
变量已经在堆上了,当这个 block 被复制到堆上时,那么这个 __block
变量就会被持有,引用计数增加。
如果对一个已经被复制到堆上的 block 进行再次 copy,该操作对 block 中使用的__block
变量没有影响。
(2)在多个 block 中使用同一个 __block
变量
一开始所有的 block 和 __block
变量都存储在栈上,当其中一个 block 被复制到堆上时,__block
变量也会一并从栈中复制到堆上,并被这个 block 所持有。当后面其他的 block 被复制到堆上时,会持有这个 __block
变量,并增加它的引用计数(如下图所示)。
如果堆上的 block 被销毁,那么它所持有的 __block
变量也会被释放。
6. 2 __block
变量转成的结构体__Block_byref_block_val_0
中的成员变量 __forwarding
的目的是什么?
在第 4 节中的 C++ 代码中,我们可以看到被捕获的 __block
变量最终被转成了 __Block_byref_block_val_0
结构体类型的变量,不论是在 block 中,还是在 block 后面的代码中,所有的 int
类型都被替换成了这个 __Block_byref_block_val_0
类型。
int main() {
// int 类型被转成了 __Block_byref_block_val_0 结构体类型
// 第二个参数是把 block_val 的地址传给了成员变量 __forwarding
__attribute__((__blocks__(byref))) __Block_byref_block_val_0 block_val = {(void*)0,
(__Block_byref_block_val_0 *)&block_val,
0,
sizeof(__Block_byref_block_val_0),
8};
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_block_val_0 *)&block_val, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
// 后面继续使用 __block 变量时,是用 block_val 来访问的
printf("block_val = %d\n", (block_val.__forwarding->block_val));
}
当 __block
变量被复制到堆上之后,block 内使用的 block_val
是复制到堆上的 __Block_byref_block_val_0
实例,而 block 后面使用的 block_val
仍然是栈上的 __Block_byref_block_val_0
实例。
只是栈上的 __Block_byref_block_val_0
实例在 __block
变量被复制到堆上时,会将 __forwarding
指针修改成被复制到堆上的 __Block_byref_block_val_0
实例的地址(如下图所示)。
在上一节中我们通过苹果开源的 libclosure 源码,看到了_Block_copy
最终会调用 _Block_call_copy_helper
函数,而这个函数做的就是取出 block 结构体中的 descriptor
,调用它的 copy
函数:
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->copy)(result, aBlock); // do fixup
}
在第4节 block 捕获 __block
变量的代码转成 C++ 代码后,我们可以看到这个 copy
函数调用的是 _Block_object_assign
。该函数在 libclosure 中的实现是判断它的第三个参数 flag,比如,如果捕获的是栈上的 __block
变量,那就是 BLOCK_FIELD_IS_BYREF
,这种 case 下调用的就是 _Block_byref_assign_copy
函数:
// Runtime entry points for maintaining the sharing knowledge of byref data blocks.
// A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
// Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
// We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment it.
// Otherwise we need to copy it and update the stack forwarding pointer
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;
...
// src points to stack
bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
// if its weak ask for an object (only matters under GC)
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
...
_Block_memmove(copy+1, src+1,
src->size - sizeof(struct Block_byref));
...
// assign byref data block pointer into new Block
_Block_assign(src->forwarding, (void **)destp);
}
二、Block 的实现(七)7. block 是如何捕获 Objective-C 对象的(1)block 捕获 Objective-C 对象的实现
int main() {
typedef void(^blk_t) (id obj);
blk_t blk;
{
// 捕获 Objective-C 对象
id array = [[NSMutableArray alloc] init];
blk = ^void(id obj) {
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
}; // 在 ARC 中这里并不需要手动 copy,在 MRR 中就需要手动 copy(p125)
}
blk([[NSObject alloc] init]);
return 0;
} 使用 static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
id array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nt_bkkycgbs2hv63tthr5dbd2g40000gn_T_main8_c8bb70_mi_0, ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
int main() {
typedef void(*blk_t) (id obj);
blk_t blk;
{
// Objective-C 对象没有变化
id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
// Objective-C 对象直接当成参数传了
blk = ((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344));
}
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
return 0;
} 下面是 struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
// 其中有两个管理内存的函数指针 copy 和 dispose
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
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array; // 捕获到的 Objective-C 对象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}; 结论:
(2)
|
二、Block 的实现(八)8. 在对象类型的变量上使用
|
三、Block 循环引用1. Block 循环引用(书 p128~p134)
苹果的官方文档 "Transitioning to ARC Release Notes" 中的建议如下:
2. Strong-Weak Dance"Transitioning to ARC Release Notes" 中有一条这样的建议:
官方文档也没有讲清楚为什么要在 block 里面用 不过在 bs 的这篇文章中讲了一个场景: __weak MyViewController *wself = self;
self.completionHandler = ^(NSInteger result) {
__strong __typeof(wself) sself = wself; // 强引用一次
[sself.property removeObserver: sself forKeyPath:@"pathName"];
}; 我们可不可以换种方式,不用 顺便讲一下另外一个
所以,有可能方法执行到一般,当前的对象已经被释放了,但是传参进来的 3. RAC 中的
|
四、非 ARC 下的 block 与
|
五、Q&A1. block 的嵌套Clang 文档中有如下描述:
2. block 的递归使用问题 int (^completion)(int);
__block __weak int (^weakCompletion)(int);
weakCompletion = completion = ^int(int a) {
if (a == 0) {
return 1;
} else {
return a * weakCompletion(a-1);
}
}; 参考: |
目录
The text was updated successfully, but these errors were encountered: