本文旨在对block底层进行详细的探究,源码参考libclosure-79。
2.Block的类型 2.1.类型归纳block类型 | 环境 |
---|---|
__ NSGlobalBlock__ (_NSConcreteGlobalBlock) | 没有访问auto变量 |
__ NSStackBlock__ (_NSConcreteStackBlock ) | 访问了auto变量 |
__ NSMallocBlock__ ( _NSConcreteMallocBlock) | __NSStackBlock__调用了copy |
三种Block在内存的中位置如下:
dispatch_block_t globalBlock = ^{
NSLog(@"this is global block");
};
NSLog(@"%@", [globalBlock class]);
globalBlock();
Console输出如下:
2022-03-04 17:40:34.791202+0800 Test[36063:420956] __ NSGlobalBlock__
2022-03-04 17:40:34.792149+0800 Test[36063:420956] this is global block
对globalBlock所在的文件执行 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m [-o xxx.cpp ],上面的代码会编译成下面的代码
//基本数据结构
struct __block_impl {
void *isa; //说明block是oc对象
int Flags;
int Reserved;
void *FuncPtr; //函数地址
};
//block的描述
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; //block的size
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
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; //clang生成的是中间文件,以运行时为准
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__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_e49a1b_mi_0);
}
/*执行代码 start*/
//block初始化
dispatch_block_t globalBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//获取globalBlock的class
NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_e49a1b_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)globalBlock, sel_registerName("class")));
//调用__main_block_func_0函数, 并block作为参数传递
((void (*)(__block_impl *))((__block_impl *)globalBlock)->FuncPtr)((__block_impl *)globalBlock);
/*执行代码 end*/
2.3 __ NSStackBlock__ 类型
2.3.1.论证访问auto变量的Block是 __ NSStackBlock__类型
//开启MRC, ARC下被强引用的block, 会自动拷贝到堆上,成为__ NSMallocBlock__类型
int age = 20;
dispatch_block_t stackBlock = ^{
NSLog(@"this is stack block, age:%d", );
};
NSLog(@"%@", [stackBlock class]);
stackBlock();
Console输出如下:
2022-03-04 17:40:34.791202+0800 Test[36063:420956] __ NSStackBlock__
2022-03-04 17:40:34.792149+0800 Test[36063:420956] this is stack block, age:20
对stackBlock所在的文件执行 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m [-o xxx.cpp ],上面的代码会编译成下面的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_9f69a9_mi_0, age);
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age; //capture 了age auto变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
/*执行代码 start*/
int age = 20;
//block初始化, age以值的形式传递
dispatch_block_t stackBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
//获取stackBlock的class
NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_9f69a9_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)stackBlock, sel_registerName("class")));
//调用__main_block_func_0函数, 并block作为参数传递
((void (*)(__block_impl *))((__block_impl *)stackBlock)->FuncPtr)((__block_impl *)stackBlock);
/*执行代码 end*/
哪为啥访问了auto变量就成了__ NSStackBlock__ 类型?原因是auto变量是存在栈当中,函数执行完就会销毁,如果block还是__ NSGobalBlock__,auto变量销毁了再执行block,就会出现野指针。
2.4__ NSMallocBlock__ 类型 2.3.1.论证__NSStackBlock__调用了copy就变成__ NSMallocBlock__ 类型//开启MRC, ARC下被强引用的__NSStackBlock__类型block, 会自动拷贝到堆上,成为__ NSMallocBlock__类型
int age = 20;
dispatch_block_t mallocBlock = [^{
NSLog(@"this is malloc block, age:%d", );
} copy];
NSLog(@"%@", [mallocBlock class]);
mallocBlock();
Console输出如下:
2022-03-07 16:45:51.057649+0800 Test[78667:918108] NSMallocBlock
2022-03-07 16:45:51.058730+0800 Test[78667:918108] this is malloc block, age:20
基本和__ NSStackBlock__ 一致,就不做研究了。
3.Block底层数据结构 3.1 整体如下图 3.2 源码探究和分析Block的定义在Block_private.h中
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
BLOCK_SMALL_DESCRIPTOR = (1 << 22), // compiler
#endif
BLOCK_IS_NOESCAPE = (1 << 23), // compiler
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler, 是否有copy和dispose方法
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler,是否全局Block
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler,是否有签名
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
void(*copy)(void *, const void *);
void(*dispose)(const void *);
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
struct Block_layout {
void * isa; //删除了__ptrauth_objc_isa_pointer, 表示Block的类型
volatile int32_t flags; // 第一个枚举中定义的值
int32_t reserved; //保留位
void (*invoke)(void *, ...); //函数指针,指向block具体的执行函数
/*
descriptor 描述Block size、copy、dispose、signature、layout等信息,但copy、dispose只有引用了对象才会出现,所以Block_layout只出现了Block_descriptor_1
*/
struct Block_descriptor_1 *descriptor;
// imported variables
};
在ARC下 Block中访问了__block修饰的变量还有以下数据结构
// Values for Block_byref->flags to describe __block variables
enum {
// Byref refcount must use the same bits as Block_layout's refcount.
// BLOCK_DEALLOCATING = (0x0001), // runtime
// BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler
BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler
BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler
BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler
BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler
BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime
};
struct Block_byref {
void * isa;
struct Block_byref *forwarding; //保证栈和堆的变量访问的数据都是正确的,具体可以见下面的forwarding指针
volatile int32_t flags; // contains ref count
uint32_t size;
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
void(*copy)(struct Block_byref*, struct Block_byref*);
void(*dispose)(struct Block_byref *);
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
举例说明
__block int age = 20;
dispatch_block_t block = ^{
NSLog(@"age:%d", age);
};
block();
clang之后代码如下:
//__Block_byref 类型,对age变量进行包装
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_350481_mi_0, (age->__forwarding->age));
}
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*/);}
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};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__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;
}
};
/*执行代码 start*/
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 20};
dispatch_block_t 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);
/*执行代码 end*/
4.Block的变量捕获和访问方式
4.1 局部变量的捕获
值传递
4.1.1.auto对象局部变量代码块
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
@end
int main(int argc, char * argv[]) {
Person *person = [[Person alloc] init];
person.name = @"abc";
void (^block)() = ^{
NSLog(@"name:%@", person.name);
};
block();
}
clang之后
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *person; //auto对象局部变量是强引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Person *person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_f5ba61_mii_1, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("name")));
}
//通过_Block_object_assign做内存管理
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, 3/*BLOCK_FIELD_IS_OBJECT*/);}
//通过_Block_object_dispose做内存管理
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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, char * argv[]) {
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_f5ba61_mii_0);
void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}、
捕获auto对象局部变量,是需要处理内存管理, 主要由_Block_object_assign和_Block_object_dispose来完成,下面对这两个函数的源码
_Block_object_assign源码// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
enum {
BLOCK_ALL_COPY_DISPOSE_FLAGS =
BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_BYREF |
BLOCK_FIELD_IS_WEAK | BLOCK_BYREF_CALLER
};
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT: //捕获的是对象
/*******
id object = ...;
[^{ object; } copy];
********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK: //捕获的是Block
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
default:
break;
}
}
_Block_object_dispose源码
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
4.1.3.static局部变量
指针传递
static int age = 20;
dispatch_block_t block = ^{
NSLog(@"age:%d", age);
};
block();
clang 之后
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *age; //是个指针
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_938f88_mi_0, (*age));
}
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)};
/*执行代码 start*/
static int age = 20;
dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age)); //指针传递
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
/*执行代码 end*/
看clang之后的代码,确实把static局部变量捕获到Block内部,且是指针访问。那是为啥呢?
把static局部变量捕获到Block内部,原因是Block 可能在其他的场景执行指针访问,原因是首先static的作用域是定义它的函数或语句块结束时,其作用域随之结束,所以Block也是它的作用域,Block是可以访问和修改的,另外static变量是存在数据区,函数执行完不会销毁,不用担心野指针 4.2全局变量直接访问
int age = 10;
int main(int argc, char * argv[]) {
dispatch_block_t block = ^{
age++;
};
}
clang之后
int age = 10;
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) {
age++;
}
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, char * argv[]) {
dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
}
可以看出__main_block_impl_0 没有增加 age变量,中间没有age的传递,在__main_block_func_0中是直接访问的
5.Block的copy对三种Block类型执行copy后的结果
注意事项:
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
__block int age = 20;
dispatch_block_t block = ^{
NSLog(@"age:%d", age);
};
//age被包装成__Block_byref_age_0, 自己成为__Block_byref_age_0的属性
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
...
};
6.2.内存管理
当block被copy到堆时a.会调用block内部的copy函数
b.copy函数内部会调用_Block_object_assign函数
c._Block_object_assign函数会对__block变量形成强引用(retain) 6.3.__forwarding指针
__forwarding存在的意义:保证当栈上的__block变量中的__forwarding copy到堆之后,栈上的__block变量通过__forwarding还能正确访问数据。
7.常见问题 循环引用self.block = ^{
NSLog(@"%@", self);
};
解决方式
//ARC
__weak typeof(self) weakSelf = self; //__unsafe_unretained
self.block = ^{
NSLog(@"%@", weakSelf);
};
//MRC
__block id weakSelf = self; //__unsafe_unretained
self.block = ^{
NSLog(@"%@", weakSelf);
};
__weak clang 失败,换成 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-12.0.0 xxx.m [-o xxx.cpp ] 即可 //12.0.0 只是一个版本,可以换成其它的Thread 1: EXC_BAD_ACCESS (code=1, address=0x10) //64bits address=0x10, 32bits address=0x0c
//出现的场景如下
dispatch_block_t block = nil;
block();
//原因,看clang之后的数据结构,就一目了然
struct __block_impl {
void *isa; //64bits 8, 32bits 4
int Flags;//64bits 4, 32bits 4
int Reserved;//64bits 4, 32bits 4
void *FuncPtr; //执行block调用的是这个函数地址
};
MRC下数组添加Block, 访问可能Crash为什么第二个会Crash呢,原因是第一个Block是__ NSGlobalBlock__ 一直存在数据区域, 第二个是 __ NSStackBlock__ 在栈上,添加到数组不会拷贝到堆上,函数执行完就会销毁。 8.总结 block本质上也是一个OC对象,它内部也有个isa指针。block是封装了函数调用以及函数调用环境的OC对象。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)