本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !
在block中可以直接修改外面的auto变量吗 ?如果想修改怎么可以做到 ?
typedef void (^XYHBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
XYHBlock block = ^{
age = 20;
NSLog(@"age is %d", age);
};
block();
}
return 0;
}
答:无法修改外部的auto变量,在编译阶段会直接报错;因为age是值铺获,在block内部只存储了age 的值,不是age的内存地址;这个值在编译时就已经固定无法修改。
一定要修改的操作方法:
- 使用 static 来修饰age使其变为静态变量后就可以修改,这时是指针捕获;指针指向age的内存空间可以修改里面存储的值。(static修饰的变量是放在全局区的,也就是说被修饰后的变量就不再是临时变量了,会一直被保存在内存中);
- 把age声明为全局变量;
- 使用 __block 关键字来修饰局部变量age;
关于__block 修饰符本质
- __block 可以用于解决block内部无法修改auto变量值的问题;
- __block 不能修饰全局变量、静态变量(static);
- 编译器会将__block 修饰的变量包装成一个对象;
- 注意这里的auto变量包括基本数据类型和OC对象,如果是OC对象的话会多内存管理的代码copy 和 dispose 函数
为什么__block修饰后就可以改变auto变量的值 ?
block内部会有一个指针(__Block_byref_age_0 *age; // by ref)指向结构体(struct __Block_byref_age_0),通过结构体找到结构体的内存把结构体中的值给改掉。
结论 :编译器会将__block 修饰的变量包装成一个对象,auto变量被包装在对象内部,由此可以通过对象来访问修改变量的值 !
struct __Block_byref_age_0 {
void *__isa; 该对象的isa指针
__Block_byref_age_0 *__forwarding; 指向结构体本身的指针
int __flags; 标记
int __size; 当前结构体大小
int age; 变量值
};
分析以下代码:
typedef void (^XYHBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
XYHBlock block = ^{
age = 20;
NSLog(@"age is %d", age);
};
block();
}
return 0;
}
编译后的代码
将来block要执行的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
获取age对象
__Block_byref_age_0 *age = __cself->age; // bound by ref
拿到__forwarding 进而拿到 age 变量
(age->__forwarding->age) = 20;
NSLog(
(NSString*)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0,
(age->__forwarding->age
);
}
block的构造函数
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref // 如果不使用__block 修饰,这里的代码会是 int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,__Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
1. 获取编译器编译完成后__block 对 age 包装后的age对象
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
2. 定义block
XYHBlock block = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_age_0 *)&age,
570425344
));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
第一段代码分析
//int age = 10; 编译后的代码
//简化后 __Block_byref_age_0 就是 __block 对变量age包装后的对象
__Block_byref_age_0 age =
{
0, //把(void *)0 赋值给 __isa
&age, //把&age 赋值给 __forwarding
0, //把0 赋值给 __flag
sizeof(__Block_byref_age_0), // __size
10 //把 10 赋值给 age
};
第二段代码分析
XYHBlock block = &__main_block_impl_0
(
__main_block_func_0,
&__main_block_desc_0_DATA,
&age,
570425344
));
__main_block_impl_0 :block的构造函数
__main_block_func_0 : 将来block要执行的函数 --> fp
&__main_block_desc_0_DATA :描述信息 --> desc
&age :age 对象的内存地址 --> __Block_byref_age_0
570425344 : 标记 --> __flags
下面代码会不会报错,为什么 ?
NSMutableArray *array = [NSMutableArray array];
XYHBlock block = ^{
[array addObject:@"战云"];
[array addObject:@"滤布"];
};
block();
答 :不会;因为block 中的操作准确的来说是在使用array这个数组,给数组中添加成员;并没有做修改;修改应该是如 array = nil ,直接对array赋值 !
__block细节
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
XYHBlock block = ^{
age = 20;
NSLog(@"age is %d", age);
};
struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
NSLog(@"%p", &age);
}
return 0;
}
以上代码中输出的地址值是 __Block_byref_age_0 *age 的地址还是 __Block_byref_age_0 结构体中 int age 的地址值 ?为什么 ?
答:是__Block_byref_age_0结构体内部的 int age 的地址;
原因:
- 从设计逻辑上来说__Block_byref_age_0是运行时对__block修饰的变量包装后生成的结构体,从外部看是不知道这个结构体存在的 ,所以是 int age 在代码的可读性理解方面就降低难度 ;之所以包装成结构体是因为oc的语言特性。
- 拿到__Block_byref_age_0地址值通过运算得到 int age 的地址值对比log输出地址
- 通过lldb命令打印出 age 的地址 对比 log输出地址