Block的分类
Block有三种类型:
- 全局Block:__NSGlobalBlock,
- 堆区Block:__NSMallocBlock,
- 栈区Block:__NSStackBlock
block
的三种类型.并都继承于NSBlock
我们在讲block
的三种类型之前,先了解一下程序的内存分配情况,因为不同类型的block
分配的内存也不同.。
在iOS中内存主要分为五大区域:栈区、堆区、静态区、常量区、代码段
不同block类型的内存分配
一: __NSGlobalBlock
结论: 没有访问 auto变量 的block 就是 __NSGlobalBlock
-(void)gloabelBlock{
static int age = 10;
void(^block)(void) = ^{
NSLog(@"Hello, World! %d",age);
};
NSLog(@"%@",[block class]);
}
2022-06-03 11:58:58.803486+0800 DemoTest2022[47079:2167980] __NSGlobalBlock__
二: __NSStackBlock
结论:访问了auto变量 的block 就是 __NSStackBlock
-(void)stackBlock{
int age = 10;
void(^block)(void) = ^{
NSLog(@"Hello, World! %d",age);
};
NSLog(@"%@",[block class]);
}
2022-06-03 12:04:10.401128+0800 DemoTest2022[47193:2173770] __NSMallocBlock__
怎么打印的是NSMallocBlock,刚才不是说访问了auto变量就是__NSStackBlock吗?
因为这里我们使用的是ARC,在ARC环境下,Xcode编译器再某些情况会默认帮我们做调用copy 变成堆block ,我们在Build Settings中把ARC设置成MRC,再来打印一下:
2022-06-03 12:13:28.851386+0800 DemoTest2022[47687:2185431] __NSGlobalBlock__
我们思考一下,__NSStackBlock在访问外部变量时,会有什么问题?
会出现野指针crash 所以在ARC坏境Xcode帮我们处理成了堆block(NSMallocBlock)防止出现释放了还去访问导致野指针crash
三: __NSMallocBlock
结论: 当一个__NSStackBlock调用了copy操作,返回的就是一个__NSMallocBlock// ARC下不用copy会自动copy到堆区
-(void)stackBlock{
int age = 10;
void(^block)(void) = [^{
NSLog(@"Hello, World! %d",age);
} copy];
NSLog(@"%@",[block class]);
}
2022-06-03 12:29:42.356398+0800 DemoTest2022[48117:2204224] __NSMallocBlock__
四:Block注意事项
Block的循环引用,内存泄漏一个主要原因就是block的循环引用。那么如何解决循环引用呢?
#import "BlockViewController.h"
typedef void (^Block)(void);
@interface BlockViewController ()
@property(nonatomic,copy) Block block;
@end
@implementation BlockViewController
- (void)viewDidLoad {
[super viewDidLoad];
//循环引用
self.name = @"block";
self.block = ^{
NSLog(@"%@",self.name);
};
self.block();
// Do any additional setup after loading the view.
}
-(void)dealloc{
NSLog(@"%s",__func__);
}
这边vc 引用了block ,block引用了vc,最后面造成了循环引用,dealloc无法执行,那么如何解决呢?
方案一:通过weak来解决
使用weakself当vc退出是dealloc正常执行,但是也存在其他问题
-(void)test_dispatch_time{
//循环引用
self.name = @"block";
//直接使用weakself来代替self,weakself是弱引用,这样不会导致引用计数+1。
__weak typeof(self) weakself = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakself.name);
});
};
self.block();
}
如果当我们延迟使用weakself的话,这时候的weakself可能已经被销毁了,这时候就需要用到__strong typeof(weakself) strongself = weakself;
//循环引用
self.name = @"blockname";
//直接使用weakself来代替self,weakself是弱引用,这样不会导致引用计数+1。
__weak typeof(self) weakself = self;
self.block = ^{
__strong typeof (weakself) strongSelf = weakself;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
self.block();
__strong
可以让对象暂时在存活一段时间,用完就会销毁,这样也不会带来内存泄漏。
以上就是通过__weak
和__strong
来解决block
的循环引用。
方案二:通过临时变量来解决
self.name = @"block测试";
__block BlockViewController *vc = self;
self.block = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil;
});
};
self.block();
定义一个跟self
一样类型的vc = self
,然后block
引用vc
,用完之后再把vc=nil
,这样引用链:self -> block -> vc -> self
这样也可以解决循环引用问题
方案三:通过参数将self传进去,传参的话,参数是在栈区,函数运行好栈区销毁参数也就跟着销毁,所以也可以解决循环引用问题
self.name = @"block测试";
self.block2 = ^(BlockViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.block2(self);
以上就是目前掌握的三种解决循环引用的方案。
Block的本质
接下来看看block通过xcrun后究竟是什么?
int main(int argc, char * argv[]) {
int a = 9;
__block int b = 10;
NSObject *objc = [NSObject alloc];
void(^Block)(void) = ^{
NSLog(@"a:%d", a);
NSLog(@"b:%d", b);
NSLog(@"objc:%@", objc);
};
return 0;
}
xcrun -sdk iphonesimulator clang -rewrite-objc main.m
int main(int argc, char * argv[]) {
int a = 9;
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 10};
NSObject *objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));
void(*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, objc, (__Block_byref_b_0 *)&b, 570425344));
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
NSObject *objc;
__Block_byref_b_0 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSObject *_objc, __Block_byref_b_0 *_b, int flags=0) : a(_a), objc(_objc), b(_b->__forwarding) { // 构造函数
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
int b;
};
xcrun后看到的__block b
,底层是变成了结构体b
,里面保存了b的值10,整个block也变成了__main_block_impl_0
结构体对象,把a, b, objc
作为参数传进去。而在block结构体里定义了三个成员变量来保存a,b,objc
总结:
1:一共有三种类型的Block.分为__NSGlobalBlock,__NSStackBlock,__NSMallocBlock.
没有访问 auto变量 的block 就是 __NSGlobalBlock
访问了auto变量 的block 就是 __NSStackBlock
当一个__NSStackBlock调用了copy操作,返回的就是一个__NSMallocBlocksing
2:在ARC环境下,编译器会自动把栈上的block copy到堆上
3.block本质上也是一个oc对象,他内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。