-
Notifications
You must be signed in to change notification settings - Fork 4
block
block
主要有三种类型:全局block
、堆区block
和 栈区block
OC对象的底层是由 C++
结构体支撑的,block
也不例外
-
探索代码
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); void (^block)(void) = ^(){ NSLog(@"___func____"); }; block(); } return 0; }
-
编译成
C++
代码xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o global-block-main-arm64.cpp
-
在
C++
代码中提取出关键部分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; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_b2997c_mi_1); } 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; NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_b2997c_mi_0); void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
通过 main
函数中对 block
的定义和执行可以发现
-
初始化的时候,传递了两个参数
- 函数地址 -> 对应我们
block
代码块部分,被包装成一个static void __main_block_func_0(struct __main_block_impl_0 *__cself)
函数- 并且都会将
block
这个结构体作为第一个参数传递
- 并且都会将
- 也是一个结构体,类型是
static struct __main_block_desc_0
- 这个结构体中包含两个成员,第一个是保留字段,第二个记录了当前我们定义的
block
的大小
- 这个结构体中包含两个成员,第一个是保留字段,第二个记录了当前我们定义的
__main_block_impl_0
的构造函数实则 3个 参数,最后一个默认值 0 - 函数地址 -> 对应我们
-
当前我声明的这个全局
block
由一个__main_block_impl_0
的struct
来呈现, 包含两个成员-
impl
,也是个结构体类型__block_impl
-
FuncPtr
-> 对应的就是block
代码块的函数地址,构造函数中也是这么来接收的 - 通过
isa
这个成员定义来看,结构有点和 OC 对象结构相仿
-
-
Desc
,也是个结构体类型__main_block_desc_0
-
-
调用执行
block
我是没有系统性的学习过 C++ 的,只有一些 C 基础,看资料讲解,在 C 结构体中不允许定义函数,但是 C++ 允许
转换成
C++
这份代码中,执行调用block
的语句是:((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
-
这里之所以能够将这个全局
block
强转成__block_impl
类型而直接去通过指针调用FuncPtr
( 而不是block->impl->FuncPtr
一层层正常思路调用 )-
结构体内存分配
-
结构体传值
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; };
impl
这个成员刚好排在首位,首元素地址和是结构体地址相同 -
-
-
将
block
改为有参,同样没有访问外部变量int number1 = 20; int number2 = 30; void (^block)(int, int) = ^(int a, int b){ NSLog(@"___func____: %d + %d = %d", a, b, a + b); }; block(number1, number2);
-
C++
代码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) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_66a426_mi_0, a, b, a + b); } 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 number1 = 20; int number2 = 30; void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, number1, number2); } return 0; }
通过代码可见,
block
和上面的结构基本一样,只不过是代码块对应的函数多了两个参数而已,并不会转加给block
自己的结构体上
刚刚的源码中定义的是一个全局 block
,不包含任何外部变量的访问,当我们使用外部变量时,block
又将如何处理
-
Object-C
代码#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... int number1 = 20; int number2 = 30; void (^block)(void) = ^(){ NSLog(@"___func____: %d + %d = %d", number1, number2, number1 + number2); }; block(); } return 0; }
-
C++
代码struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int number1; int number2; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number1, int _number2, int flags=0) : number1(_number1), number2(_number2) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int number1 = __cself->number1; // bound by copy int number2 = __cself->number2; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_ec65bb_mi_0, number1, number2, number1 + number2); } 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 number1 = 20; int number2 = 30; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, number1, number2)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
通过源码可见,两个外部变量被直接存放在
__main_block_impl_0
结构体内
auto
: C语言的关键字,用来修饰局部变量,其实我们声明的局部变量默认都会包含,只不过省略了,代表含义就是离开作用域就自动销毁static
: 用static
修饰的局部变量存放在全局数据区,会延长局部变量的寿命,超出函数的生存期
-
为什么同样是局部变量,捕获方式不同
虽然访问局部外部变量时, 默认(auto) 和static
修饰后都会被捕获到block
的结构体内,但是static
的生命周期被延长了-
auto
随着作用域释放,在将来时刻肯定不能去通过内存来访问了- 当我们用
__block
修饰时,其实也是针对的auto
变量,__block
不能用来修饰全局或者static
变量
- 当我们用
-
static
局部变量一段时期内仍在内存中,在将来仍可访问,所以就使用了指针捕获
-
-
为何局部变量需要捕获,而全局变量不需要捕获
从
C++
对block
实现的代码层可以了解到,block
定义部分被定义成一个单独的函数,访问的外部变量的真正使用也是在这个函数内,通过block
自己(函数的第一个参数)访问,由于局部变量作用域的限制,所以需要被捕获,而全局可直接访问,所以不需要捕获
顺着这个思路推理,为什么
self
会被block
捕获?
OC 方法底层会被转成 C 函数, C 函数默认包含两个参数: self
和 _cmd
, 所以 self
是作为形参提供的, 本来也是个指针,肯定是局部变量,所以会被捕获, 并且是指针捕获
当我们捕获的外界变量比较复杂时,比如是一个对象,就会涉及到内存管理,此时
__main_block_desc_0
的结构也会变得复杂
会多两个函数,一个copy
一个dispose
,copy
函数会在block
被copy
操作的时候自动调用,另外一个用来释放block
捕获对象的计数
-
Object-C
代码__block int number = 20; void (^block)(void) = ^(){ number = 30; NSLog(@"___func____: %d ", number); }; block();
-
C++
代码struct __Block_byref_number_0 { void *__isa; __Block_byref_number_0 *__forwarding; int __flags; int __size; int number; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_number_0 *number; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_number_0 *_number, int flags=0) : number(_number->__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_number_0 *number = __cself->number; // bound by ref (number->__forwarding->number) = 30; NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_rdy2pt0x3yn9d2qf1qng0wk40000gn_T_main_0ef772_mi_0, (number->__forwarding->number)); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->number, (void*)src->number, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->number, 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_number_0 number = {(void*)0,(__Block_byref_number_0 *)&number, 0, sizeof(__Block_byref_number_0), 20}; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_number_0 *)&number, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
与最开始探索 __NSGlobalBlock__
转成的 C++
代码,又多了一个结构体 __Block_byref_number_0
,也就是将我们需要捕获的目标又包装成了一层结构体(也就是包装成了一个对象), 同时与我们捕获对象时一样, __main_block_desc_0
的结构也会多出两个函数 copy
和 dispose
-
__Block_byref_number_0
struct __Block_byref_number_0 { void *__isa; __Block_byref_number_0 *__forwarding; int __flags; int __size; int number; };
-
__block
修饰变量后,在block
内部结构中默认对齐保持一份强引用 (仅适用于 ARC 下, 早期 MRC 下情况有所不同 ) -
对象类型
的auto
类型局部变量会根据__strong
和__weak
决定其内部是强引用还是弱引用
-
-
被
__block
修饰的对象__attribute__((__blocks__(byref))) __Block_byref_number_0 number = { (void*)0, (__Block_byref_number_0 *)&number, 0, sizeof(__Block_byref_number_0), 20 };
-
__forwarding
指向自己
-
最终: 在
block
这个对象中存在一个__Block_byref_number_0
类型结构体指针number
,在这个结构体中number
这个成员变量记录了访问的对象
-
修改外部变量值
__Block_byref_number_0 *number = __cself->number; // bound by ref (number->__forwarding->number) = 30;
这里在取出
__Block_byref_number_0 *number;
后,是通过->__forwarding->number
指针来修改值的(修改的是 block 对象内存空间内的成员变量值) -
定义外部变量时
__attribute__((__blocks__(byref))) __Block_byref_number_0 number = { (void*)0, (__Block_byref_number_0 *)&number, 0, sizeof(__Block_byref_number_0), 20 };
ShenYj.github.io - 简书地址 - 返回首页
-
Apple
Common
蓝牙
LBS
音视频
- AVAudioSessionchange_route
- 切换线路
- StreamingKit
- Audio Unit 基础
OC 与 Swift 混编
Object-C
- 代码混淆
- autoreleasepool
- 忽略编译器(clang)警告
- 定时器
- 锁
- RunLoop
- block
- NS_REFINED_FOR_SWIFT
- NS_CLOSED_ENUM
- NS_TYPED_ENUM、NS_STRING_ENUM
- NS_TYPED_EXTENSIBLE_ENUM、NS_EXTENSIBLE_STRING_ENUM
- 关键字nonnull和nullable
- class、objc_getClass和object_getclass方法区别
- isKindOfClass和isMemberOfClass
- 应用程序的加载
- non-lazy classes & lazy classes
- load方法
- initialize方法
- 方法的本质
- 类型编码
- self和super
- 类的内存分析
Swift
- precondition
- 权限控制
- Array常用Api
- String初始化、定义
- String常用Api
- String截取演练
- Set定义、创建
- Set访问和修改
- Dictionary操作
- Dictionary和KeyValuePairs
- Dictionary与String转换
- 常用高阶函数
- enum原始值
- enum关联值
- enum遍历
- 递归enum
- enum内存分配
- 指针
- for循环
- break跳出循环
- 变量名与关键字冲突
- 类的定义
- 类的继承和初始化
- 关键字: final
- 关键字: mutating
- 关键字: lazy
- 修饰类方法的关键字: static
- 关键字: final、dynamic、objc和_dynamicReplacement
- 关键字:@dynamicMemberLookup和@dynamicCallable
- 关键字: propertyWrapper
- 自定义运算符
- 下标: subscript
- 扩展: extension
- 协议: protocol
- 协议和扩展
- 为什么需要泛型
- 泛型函数定义
- 泛型类型
- 泛型的类型约束
- 关联类型
- 为泛型定义要求
- 泛型下标
- 多线程
- Attributes
- 错误处理
- Codable
- DispatchSourceTimer
- Swift 5.x 演练: 更多功能编辑页
- Swift 5.x 类库收集
- 单元测试笔记
- 实例对象内存结构
- 元类型、Type、Self
- frozen
- convention
- Swift(5.3.2)源码编译
- SQLite.Swift类库演练
- Swift 5.5 关键字: async/await
- Swift 5.5 新特性: Continuations
- Swift 5.5 新特性: Actor
- Swift 方法调度
- Swift Mirror
- Swift 关键字: @_silgen_name
- Swift 关键字: @_disfavoredOverload
- swiftmodule
- Swift 5.6 新特性: Type placeholders
- Swift 5.6 新特性: #unavailable
- Swift 5.6 新特性: CodingKeyRepresentable
- Swift 5.6 新特性: existential any
- Swift 5.7 新特性: if-let/guard 语法简化
- Swift 5.7 新特性: Multi-statement closure type inference
- Swift 5.8 新特性: @backDeployed
- Swift 5.9 新特性: if switch expressions
- Swift 6.0 新特性:@preconcurrency
RxSwift
macOS - AppKit
-
iOS Assembly(ARM64)
-
C++
C++ 基础
- cout、cin
- 函数重载
- 默认参数
- extern "C"
- pragma once
- inline function
- const
- Reference
- 汇编
- 类和对象
- 堆空间内存管理
- Constructor
- Destructor
- 成员变量初始化
- 声明与实现分离
- namespace
- 继承
- 访问权限
- 初始化列表
- 多态:虚函数
- 多态:虚函数实现原理
- 多态:虚析构函数
- 多态:纯虚函数
- 多态:抽象类
- 多继承
- static
- static: 单例模式
- const 成员
- 引用类型成员
- 拷贝构造函数
- 调用父类的拷贝构造函数
- 浅拷贝、深拷贝
- 对象型参数和返回值
- 匿名对象
- 隐式构造
- 编译器自动生成的构造函数
- 友元
- 内部类
- 局部类
- 运算符重载
- 模板
- 类型转换
- C++标准
- Lambda
- 异常
- 智能指针
-
Flutter
Dart
Flutter
-
Go
Go 基础
-
Ruby
Ruby 基础
-
React-Native
React-Native
-
工具篇
-
Swift Package Manager
-
自动化
-
TroubleShooting
-
扩展