一个简单示例:ios
int main(int argc, const char * argv[]) {
void (^block)(void) = ^{
NSLog(@"hey");
};
block();
return 0;
}
复制代码
将以上 Objective-C 源码转换成 c++ 相关源码,使用命令行 : xcrun -sdk iphoneos xclang -arch arm64 -rewrite-objc 文件名
c++
c++ 的结构体与通常的类类似。api
int main(int argc, const char * argv[]) {
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;
}
复制代码
其中 Block 的数据结构为:bash
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
复制代码
impl 变量数据结构:数据结构
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
复制代码
FuncPtr:函数实际调用的地址,由于 Block 可看做是捕获自动变量的匿名函数。iphone
Desc 变量数据结构:函数
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
复制代码
Objective-C 中 Block 有三种类型,其最终类型都是 NSBlock 。ui
Block 类型的不一样,主要根据捕获变量的不一样行为产生:this
Block 类型 | 行为 |
---|---|
NSGlobalBlock | 没有访问 auto 变量 |
NSStackBlock | 访问 auto 变量 |
NSMallocBlock | NSStackBlock 调用 copy |
内存五大区:栈、堆、静态区(BSS 段)、常量区(数据段)、代码段spa
不一样类型的 Block 调用 copy 操做,也会产生不一样的复制效果:
Block 类型 | 副本源的配置存储域 | 复制效果 |
---|---|---|
__NSConcreteStackBlock | 栈 | 从栈复制到堆 |
__NSConcreteGlobalBlock | 数据段(常量区) | 什么也不作 |
__NSConcreteMallocBlock | 堆 | 引用计数增长 |
在 ARC 环境下,声明的 block 属性用 copy 或 strong 修饰的效果是同样的,但在 MRC 环境下,则用 copy 修饰。
为了保证在 Block 内部可以正常访问外部变量,Block 有一套变量捕获机制:
变量类型 | 是否捕获到 Block 内部 | 访问方式 |
---|---|---|
局部 auto 变量 | 是 | 值传递 |
局部 static 变量 | 是 | 指针传递 |
全局变量 | 否 | 直接访问 |
若局部 static 变量是基础类型
int val
,则访问方式为int *val
若局部 static 变量是对象类型JAObject *obj
,则访问方式为JAObject **obj
一个简单示例:
int age = 10;
// static int age = 10;
void (^block)(void) = ^{
NSLog(@"age is %d", age);
};
block();
复制代码
struct __main_block_impl_0 {
···
int age; // 传递值
}
复制代码
struct __main_block_impl_0 {
···
int *age; // 传递指针
}
复制代码
一个简单示例:
JAPerson *person = [[JAPerson alloc] init];
person.age = 10;
void (^block)(void) = ^{
NSLog(@"age is %d", person.age);
};
block();
复制代码
struct __main_block_impl_0 {
···
JAPerson *person;
}
复制代码
struct __main_block_impl_0 {
···
JAPerson **person;
}
复制代码
当捕获的变量是对象类型或者使用 __Block 将变量包装成一个 __Block_byref_变量名_0 类型的 Objective-C 对象时,会产生 copy
和 dispose
函数。
一个简单示例:
JAPerson *person = [[JAPerson alloc] init];
person.age = 10;
void (^block)(void) = ^{
NSLog(@"age is %d", person.age);
};
block();
复制代码
其中生成的 Block 的数据结构中多了 JAPerson 类型指针变量 person :
struct __main_block_impl_0 {
···
JAPerson *person;
}
复制代码
Desc 变量数据结构多了内存管理相关的函数:
static struct __main_block_desc_0 {
···
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
}
复制代码
这两个函数的调用时机:
函数 | 调用时机 |
---|---|
copy | 栈上的 Block 复制到堆时 |
dispose | 堆上的 Block 被废弃时 |
copy 和 dispose 底层相关源码
// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// Lose the reference, and if heap based and last reference, recover the memory
BLOCK_EXPORT void _Block_release(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
复制代码
当 Block 内部访问了对象类型的 auto 变量时:
copy
函数,copy
函数内部会调用 _Block_object_assign
函数,_Block_object_assign
函数会根据 auto 变量的修饰符(__strong、__weak、__unsafe_unretain)做出相应的内存管理操做。注意:若此时变量类型为对象类型,这里仅限于 ARC 时会 retain ,MRC 时不会 retain 。
dispose
函数,dispose
函数内部会调用 _Block_object_dispose
函数,_Block_object_dispose
函数会自动 release 引用的 auto 变量。使用 __weak 修饰的 OC 代码转换对应的 c++ 代码会报错:
error: cannot create __weak reference because the current deployment target does not support weak references
此时终端命令需支持 ARC 并指定 Runtime 版本:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
局部 static 变量(指针访问)、全局变量(直接访问)均可以在 Block 内部直接修改捕获的变量,而局部 auto 变量则主要经过使用 __block 存储域修饰符来修改捕获的变量。
编译器会将 __block 修饰的变量包装成一个 Objective-C 对象。
一个简单示例:
__block int age = 10;
void (^block)(void) = ^{
NSLog(@"age is %d", age);
};
block();
复制代码
其中 Block 的数据结构多了一个 __Block_byref_age_0 类型的指针:
struct __main_block_impl_0 {
···
__Block_byref_age_0 *age; // by ref
}
复制代码
__Block_byref_age_0 结构体:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age; // age 真正存储的地方
};
复制代码
两个注意点:
age->__forwarding->age
的方式去取值,是由于这两个 age 均可能仍在栈上,此时直接 age->age
访问会有问题,而 copy 操做时 __forwarding 会指向堆上的 __Block_byref_age_0 ,此时就算第一个 age 仍在栈上,经过 age->__forwarding
会从新指向堆上的 __Block_byref_age_0 ,此时再访问 age 便不会有问题 age->__forwarding->age
。使用 __block 修饰符时的内存管理状况:
copy
函数,copy
函数会调用 __main_block_copy_0
函数对 __block 变量产生一个强引用。以下图dispose
函数,dispose
函数会调用 _Block_object_dispose
函数自动 release
__block 变量。以下图一个简单的示例:
JAPerson *person = [[JAPerson alloc] init];
person.age = 10;
__weak typeof(person) weakPerson = person;
void (^block)(void) = ^{
NSLog(@"person‘s age is %d", weakPerson.age);
};
复制代码
一个简单的示例:
JAPerson *person = [[JAPerson alloc] init];
person.age = 10;
__block __weak typeof(person) weakPerson = person;
void (^block)(void) = ^{
NSLog(@"person‘s age is %d", weakPerson.age);
};
block();
return 0;
复制代码
常见的循环引用问题:
_Block_object_assign
函数在 MRC 环境下对 block 内部的对象不会进行 retain 操做。