iOS底层原理 - Block
关于block的问题
1.block的原理是怎样的?本质是什么?
2.__block的作用是什么?有什么使用注意点?
3.block的属性修饰词为什么是copy?使用block有哪些使用注意?
4.block在修改NSMutableArray,需不需要添加__block?
我们可以通过得到 xcrun -sdk iphonos clang -arch arm64 -rewrite-objc main.m 得到源码 block函数调用的本质
int age = 20;
void (^block)(int, int) = ^(int a , int b){
NSLog(@"this is a block! -- %d", age);
};
block(10, 10);
//block的底层实现
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 _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
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;
//定义block变量
void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
//通过block调用FuncPtr
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
}
return 0;
}
block的本质
1.block本质上也是一个OC对象,它内部也有个isa指针
2.block是封装了函数调用以及函数调用环境的OC对象
bl右图所示
block的变量捕获(capture)
image.pngblock的类型
image.pngblock有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
void test() {
// Global:没有访问auto变量
void (^block1)(void) = ^{
NSLog(@"block1---------");
};
// Stack:访问了auto变量
int age = 10;
void (^block2)(void) = ^{
NSLog(@"block2---------%d", age);
};
// Malloc:__NSStackBlock__调用了copy
NSLog(@"%p", [block2 copy]);
}
image.png
block的copy
在_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
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函数中会自动释放引用的auto变量
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*);
}
当block内部访问了对象类型的auto变量时
如果block是在栈上,将不会对auto变量产生强引用
如果block被拷贝到堆上
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
如果block从堆上移除
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
block作为函数返回值时
将block赋值给__strong指针时
block作为Cocoa API中方法名含有usingBlock的方法参数时
block作为GCD API的方法参数时
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
__block修饰符
我们都知道只要在block里面修改临时变量就会直接报错,稍微有点开发经验的就直接直接加__block来修饰变量就直接达到目的,那到底为什么呢???
typedef void (^CCBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
CCBlock block = ^{
age = 20;//Variable is not assignable (missing __block type specifier)
NSLog(@"age is %d", age);
};
block();
}
return 0;
}
struct __Block_byref_weakPerson_0 {
void *__isa; // 8
__Block_byref_weakPerson_0 *__forwarding; // 8
int __flags; // 4
int __size; // 4
void (*__Block_byref_id_object_copy)(void*, void*); // 8
void (*__Block_byref_id_object_dispose)(void*); // 8
MJPerson *__weak weakPerson;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_weakPerson_0 *weakPerson; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__block可以用于解决block内部无法修改auto变量值的问
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象
从源码里面我们进行具体的分析
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // 我们的age变量会变成结构体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;
}
};
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
NSObject *p = __cself->p; // bound by copy
///1.block内部存在一个指针指向age结构体
///2.通过指针找到结构体的内存地址,把age结构体的age的值修改完成
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0, p);
}
__block的内存管理
当block在栈上时,并不会对__block变量产生强引用
当block被copy到堆时
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会对__block变量形成强引用(retain)
image.png
当block从堆中移除时
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的__block变量(release)