1、block的内部实现,结构体是什么样的?
Block其实就是Objective-C对象,因为它的结构体中含有isa指针。
创建block时,实际就是在方法中声明一个struct,并且初始化该struct的成员。
而执行block时,就是调用那个单独的C函数,并把该struct指针传递过去。
//等号右侧的代码是这个Block的值:它是等号左侧定义的block类型的一种实现
int (^blk)(int) = ^(int count){
return count + 1;
};
//在项目中经常使用某种相同类型的block,我们可以用typedef来抽象出这种类型的Block
typedef int(^AddOneBlock)(int count);
AddOneBlock block = ^(int count){
return count + 1;//具体实现代码
};
使用 clang -rewrite-objc block.m,Objective-C 转 C++的实现。
OC代码:
int main()
{
void (^blk)(void) = ^{
printf("Block\n");
};
return 0;
}
C++代码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//block结构体
struct __main_block_impl_0 {
struct __block_impl impl;//第一个是成员变量impl,它是实际的函数指针,它指向__main_block_func_0
struct __main_block_desc_0* Desc;
//Block构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;//isa指针,isa指针保持这所属类的结构体的实例的指针
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//将来被调用的block内部的代码:block值被转换为C的函数代码,*__cself 是指向Block的值的指针,也就相当于是Block的值它自己(相当于C++里的this,OC里的self)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
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)};
//main 函数
int main()
{
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
return 0;
}
2、block是类吗,有哪些类型?
__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;
}
在这个结构体的构造函数里,isa指针保持着所属类的结构体的实例的指针。
__main_block_imlp_0结构体就相当于Objective-C类对象的结构体,这里的_NSConcreteStackBlock相当于Block的结构体的类对象,最终都是继承自NSBlock类型,所以Block是类。
一共有三种类型的Block:
_NSConcreteStackBlock: 在程序内存的栈区
_NSConcreteGlobalBlock:在程序内存的数据区
_NSConcreteMallocBlock: 在程序内存的堆区
每一种类型的block调用copy后的结果如下所示:
3、一个int变量被 __block 修饰与否的区别?block的变量截获?
3.1、一个int变量被 __block 修饰与否的区别?
添加 __block 之前
3.1.1.1
如果不进行额外操作,局部变量一旦被Block捕获,在Block内部就不能被修改了。
一旦捕获外部的布局变量,Block结构体内部会生成一个新变量进行保存,和外界的变量无关了。
添加 __block 之后
3.1.2.1、可以再block内部修改 __block 变量的值了。
__block int a = 10;
int b = 20;
PrintTwoIntBlock block = ^(){
a -= 10;
printf("%d, %d\n",a,b);
};
block();//0 20
a += 20;
b += 30;
printf("%d, %d\n",a,b);//20 50
block();/10 20
3.1.2.2、被__block修饰的变量生成了一个结构体,这个结构体其中一个成员变量持有原来的val变量。结构体中增加了__forwarding变量,通过__forwarding,可以实现无论__block变量配置在栈上还是堆上都能正确地访问__block变量,也就是说__forwarding是指向自身的。
int main()
{
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
};
return 0;
}
//在main函数中可以看到`__main_block_impl_0`结构体中增加了 `__Block_byref_val_0 *val; // by ref`变量,而`__Block_byref_val_0`就是加了`__block`生成的结构体。
int main()
{
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
3.2、为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
4、block在修改NSMutableArray,需不需要添加__block?
如果往可变数组中添加对象、或者从可变数组中删除对象是不需要添加__block的。
如果直接对可变数组进行赋值操作,修改可变数组地址的指向,不添加__block是不可以修改的。
为什么不添加__block的局部变量不能修改?
直接在block内部修改局部变量会报以下的错。
Variable is not assignable (missing __block type specifier)
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int a = 10;
NSObject *obj = [NSObject new];
void (^blk)(void) = ^() {
NSLog(@"in - bock 执行了 a:%p - obj:%p", &a,obj);
};
NSLog(@"out - bock 执行了 a:%p - obj:%p", &a,obj);
blk();
}
return 0;
}
打印如下:
2022-01-28 14:01:59.866176+0800 TestBlock[89883:3565569] out - bock 执行了 a:0x7ffeefbff3dc - obj:0x10067b480
2022-01-28 14:01:59.866682+0800 TestBlock[89883:3565569] in - bock 执行了 a:0x1006926c8 - obj:0x10067b480
可以看到被block捕获的基本数据类型的int 变量的地址改变了,而对象类型的obj,地址没有发生变化,不能直接赋值的原因我猜测就是语法上不允许,基本数据类型的int 变量不允许修改block内部的变量、对象类型不允许block内部修改外部变量的值。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
NSObject *obj;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSObject *_obj, int flags=0) : a(_a), obj(_obj) {
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 = __cself->a; // bound by copy
NSObject *obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dd_2vrrl1d50md6yz3qycn55dym0000gn_T_main_a3de10_mi_0, &a,obj);
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, obj, 570425344));
}
5、怎么进行内存管理的?
OC的运行时库能够准确把握Block从栈复制到堆,以及堆上的block被废弃的时机,在实现上是通过__main_block_copy_0函数和__main_block_dispose_0函数进行的:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
其中,_Block_object_assign会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain),将对象赋值在对象类型的结构体成员变量中。 _Block_object_dispose相当于release操作。
__main_block_copy_0函数的调用时机是:Block从栈复制到堆上时
__main_block_dispose_0函数的调用时机是:堆上的Block被废弃时
6、block可以用strong修饰吗?
在ARC的情况,strong底层也是使用的Copy操作。
You should specify
copyas the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior.
References:
developer.apple.com/library/arc…
在MRC的
@interface Person : NSObject
@property (nonatomic,strong) void (^block)(void);
@end
在ARC下可以使用Strong修饰block。
MRC下strong修饰block,且不引用外部变量,block类型为__NSGlobalBlock,不存在问题
MRC下strong修饰block,引入外部变量,block类型为__NSMallocBlock,不存在问题
7、解决循环引用时为什么要用__strong、__weak修饰?
使用__weak修饰block捕获的外部变量的时候,在Block的结构体中捕获的变量就是 weak类型的,也就是block不对外部的变量产生强引用。
@interface Person : NSObject
@property(nonatomic,copy) void (^block)(void);
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *person = [[Person alloc] init];
__weak typeof(person) weakPerson = person;
person.block = ^{
NSLog(@"person - %@",weakPerson);
};
}
return 0;
}
对存在有使用__weak修饰变量的block时, 使用命令 clang -rewrite-objc main.m 会报错
cannot create __weak reference because the current deployment target does not support weak references
需要使用下面的命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在上面的代码中person强引用着block,block结构体中,block中 Person *__weak weakPerson;block不持有weakperson,这样才能避免循环引用。
为什么要使用__strong在Block执行内部?
__strong能够使weakVC的引用计数+1,在执行Block的过程中,控制器被释放的话,也能保证执行完Block代码块再释放,weakVC的引用计数+1并不是Block对他强引用,就是单纯引用计数+1,在执行完Block代码块,weakVC的引用计数就会自动-1。这应该是编译器的特点帮助做的处理,目的是为了保证执行Block块的过程中控制器不释放。
场景1:点击控制器,执行Block回调,在Block代码块执行耗时操作的时候,退出控制器,此时控制器没有被释放,而是等着Block块执行完了才被释放。
@interface BlockViewController : UIViewController
@property (nonatomic,copy) void (^block)(void);
@end
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakVC = self;
self.block = ^{
__strong typeof(weakVC) strongVC = weakVC;
NSLog(@"-----enter block");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(5);
NSLog(@"vc - %@",strongVC);
});
};
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
NSLog(@"%s",__func__);
}
- (void)dealloc {
NSLog(@"%s",__func__);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.block();
}
2022-01-28 18:40:56.051666+0800 OffcnApp[93611:3800193] -----enter block
2022-01-28 18:40:57.717987+0800 OffcnApp[93611:3800193] -[BlockViewController viewDidDisappear:]
2022-01-28 18:41:01.055916+0800 OffcnApp[93611:3800369] vc - <BlockViewController: 0x7fc585c1c0b0>
2022-01-28 18:41:01.056265+0800 OffcnApp[93611:3800193] -[BlockViewController dealloc]
场景2:执行场景1的操作,和场景1 不同的是,不在Block回调的代码块中添加__strong,比如viewDidLoad修改为以下代码,其他部分代码不变
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakVC = self;
self.block = ^{
NSLog(@"-----enter block");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(5);
NSLog(@"vc - %@",weakVC);
});
};
}
打印如下:
2022-01-28 18:47:27.447492+0800 OffcnApp[93712:3804939] -----enter block
2022-01-28 18:47:29.181081+0800 OffcnApp[93712:3804939] -[BlockViewController viewDidDisappear:]
2022-01-28 18:47:29.182817+0800 OffcnApp[93712:3804939] -[BlockViewController dealloc]
2022-01-28 18:47:32.452758+0800 OffcnApp[93712:3806128] vc - (null)
8、block发生copy时机?
- 调用block的copy函数时
- Block作为函数返回值返回时
- 将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量时
- 方法中含有usingBlock的Cocoa框架方法或者GCD的API中传递Block时
9、Block访问对象类型的auto变量时,在ARC和MRC下有什么区别?
1、MRC不支持__weak的,在ARC中可以使用__weak、__unsafe_unretained解决循环引用问题,MRC只能使用__unsafe_unretained解决。
2、调用_Block_object_assign这个函数时,这里仅限于ARC时会retain,MRC时不会retain。