OC底层-Block本质(六、block内修改变量的值)
2020/7/2 23:26:55
本文主要是介绍OC底层-Block本质(六、block内修改变量的值),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
如何修改
分析从代码入手:
int main(int argc, const char * argv[]) { @autoreleasepool { int age = 10; Block block = ^ { age = 20; // 无法修改,会报错 NSLog(@"%d",age); }; block(); } return 0; } 复制代码
默认情况下block不能修改外部的局部变量。通过之前对源码的分析可以知道,因为变量的作用于都不在同一个函数中,当然不能去想当然的做修改了,那到底怎么去修改呢,下面会详细说道。
age是在main函数内部声明的,说明age的内存存在于main函数的栈空间内部,但是block内部的代码在__main_block_func_0函数内部。__main_block_func_0函数内部无法访问age变量的内存空间,两个函数的栈空间不一样,__main_block_func_0内部拿到的age是block结构体内部的age,因此无法在__main_block_func_0函数内部去修改main函数内部的变量。
使用static修饰修改变量的值
前面文章有提到过static修饰的age变量传递到block内部的是指针传递,在__main_block_func_0函数内部就可以拿到age变量的内存地址,因此就可以在block内部修改age的值。
__block修改
__block用于解决block内部不能修改auto变量值的问题.
注意:__block不能修饰静态变量(static) 和全局变量
我们将上面的代码做个修改,然后运行起来。
int main(int argc, const char * argv[]) { @autoreleasepool { __block int age = 10; Block block = ^ { age = 20; NSLog(@"%d",age); /// 打印 20 }; block(); } return 0; } 复制代码
通过__block可以修改auto age的值。这是什么原因造成的呢?我们可以深入源码中去找到答案
源码
同样通过命令行
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
来生成我们的main.cpp文件
/// 通过关键字__block生成的age结构体 struct __Block_byref_age_0 { void *__isa; // isa指针 __Block_byref_age_0 *__forwarding; // __forwarding指针指向自己 int __flags; //默认值,暂时不考虑 int __size; // 变量占用的空间大小 int age; // age 变量 }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_age_0 *age; // by ref 变量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; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_age_0 *age = __cself->age; // bound by ref __main_block_impl_0 *__cself 去找到__main_block_impl_0 内部age结构体 (age->__forwarding->age) = 20; // 通过age结构体找到__forwarding指针,然后通过__forwarding指向自己在找到age变量去赋值20,做修改 NSLog((NSString *)&__NSConstantStringImpl__var_folders_n2_0nslhwnj04qg5hyxlg2d8ych0000gn_T_main_6af310_mi_0,(age->__forwarding->age)); } /// 内部函数, block copy会调用 static void __main_block_copy_0(struct __main_block_impl_0*dst,struct __main_block_impl_0*src){ _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/ );} /// block 销毁会调用 static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 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, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344)); /// block内部的函数调用 ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; } 复制代码
上述源码中可以发现
首先被__block修饰的age变量声明变为名为age的__Block_byref_age_0结构体,也就是说加上__block修饰的话捕获到的block内的变量为__Block_byref_age_0类型的结构体。
__Block_byref_age_0结构体
__isa指针 :__Block_byref_age_0中也有isa指针也就是说__Block_byref_age_0本质也一个对象。
__forwarding :__forwarding是__Block_byref_age_0结构体类型的,并且__forwarding存储的值为(__Block_byref_age_0 *)&age,即结构体自己的内存地址。
__flags :0
__size :sizeof(__Block_byref_age_0)即__Block_byref_age_0所占用的内存空间。
age :真正存储变量的地方,这里存储局部变量10。
接着将__Block_byref_age_0结构体age存入__main_block_impl_0结构体中,并赋值给__Block_byref_age_0 *age;
之后调用block,首先取出__main_block_impl_0中的age,通过age结构体拿到__forwarding指针,__forwarding中保存的就是__Block_byref_age_0结构体本身,这里也就是age(__Block_byref_age_0),在通过__forwarding拿到结构体中的age(10)变量并修改其值。
__forwarding
__forwarding是指向自己的指针。这样的做法是为了方便内存管理,后面会有详细说明。
到此为止,__block为什么能修改变量的值已经很清晰了。__block将变量包装成对象,然后在把age封装在结构体里面,block内部存储的变量为结构体指针,也就可以通过指针找到内存地址进而修改变量的值。
__block修饰对象类型
int main(int argc, const char * argv[]) { @autoreleasepool { __block Person *person = [[Person alloc] init]; NSLog(@"%@",person); Block block = ^{ NSLog(@"%@",person); }; block(); } return 0; } 复制代码
源码
struct __Block_byref_person_0 { void *__isa; __Block_byref_person_0 *__forwarding; int __flags; int __size; //相比于int age 这对象person多了两个函数,是用来管理内存管理的 void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); Person *__strong person; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_person_0 *person; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__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_person_0 *person = __cself->person; // bound by ref NSLog((NSString *)&__NSConstantStringImpl__var_folders_n2_0nslhwnj04qg5hyxlg2d8ych0000gn_T_main_7476f0_mi_1,(person->__forwarding->person)); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 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, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"))}; NSLog((NSString *)&__NSConstantStringImpl__var_folders_n2_0nslhwnj04qg5hyxlg2d8ych0000gn_T_main_7476f0_mi_0,(person.__forwarding->person)); Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; } 复制代码
通过源码查看,将对象包装在一个新的结构体中。结构体内部会有一个person对象,不一样的地方是结构体内部添加了内存管理的两个函数__Block_byref_id_object_copy和__Block_byref_id_object_dispose,这两个函数会在下一章节内存管理中心详细说明。
疑问
以下代码是否可以正确执行
int main(int argc, const char * argv[]) { @autoreleasepool { NSMutableArray *array = [NSMutableArray array]; Block block = ^{ [array addObject: @"20"]; [array addObject: @"30"]; NSLog(@"%@",array); }; block(); } return 0; } 复制代码
答:可以正确执行,因为在block块中仅仅是使用了array的内存地址,往内存地址中添加内容,并没有修改arry的内存地址,因此array不需要使用__block修饰也可以正确编译。
因此当仅仅是使用局部变量的内存地址,而不是修改的时候,尽量不要添加__block,通过上述分析我们知道一旦添加了__block修饰符,系统会自动创建相应的结构体,占用不必要的内存空间。
这篇关于OC底层-Block本质(六、block内修改变量的值)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值