Block的分类
Block有三种类型:全局Block,堆区Block,栈区Block
全局Block
当Block没有引用到局部变量时或者Block里面使用的是全局变量,静态变量时为全局Block
int a = 10;
void (^block)(void) = ^{
NSLog(@"hello world");
};
NSLog(@"block:%@", block);
输出结果:block:<__NSGlobalBlock__: 0x104e580f8>
static int a1 = 20;
void (^block)(void) = ^{
NSLog(@"hello - %d",a1);
};
NSLog(@"block:%@", block);
输出结果:block:<__NSGlobalBlock__: 0x100cec0f8>
堆区Block
当Block里有引用到局部变量时为堆区block
int a = 10;
void (^block)(void) = ^{
NSLog(@"hello - %d",a);
};
NSLog(@"block:%@", block);
输出结果:block:<__NSMallocBlock__: 0x281065950>
__block int a = 10;
void (^block)(void) = ^{
NSLog(@"hello - %d",a);
};
NSLog(@"block:%@", block);
输出结果:block:<__NSMallocBlock__: 0x281aad5c0>
栈区Block
在Block名前面加个__weak就是栈区block
__block int a = 10;
static int a1 = 20;
void (^__weak block)(void) = ^{
NSLog(@"hello - %d",a);
NSLog(@"hello - %d",a1);
};
NSLog(@"block:%@", block);
输出结果:block:<__NSStackBlock__: 0x16f7ccfb0>
堆Block和栈Block的区别
接下来看看这两种block有什么区别呢?
先看个示例:
__block int a = 10;
__block int b = 20;
NSLog(@"a:%p---b:%p", &a, &b);
void (^__weak block)(void) = ^{
NSLog(@"hello - %d---%p",a, &a);
a++;
};
void (^block1)(void) = ^{
NSLog(@"hello - %d---%p",b, &b);
b++;
};
block();
block1();
NSLog(@"block:%@---block1:%@", block, block1);
NSLog(@"a:%d---b:%d", a, b);
NSLog(@"a:%p---b:%p", &a, &b);
输出结果:
a:0x16bb7cfe8---b:0x16bb7cfc8
hello - 10---0x16bb7cfe8
hello - 20---0x283e0b1b8
block:<__NSStackBlock__: 0x16bb7cf70>---block1:<__NSMallocBlock__: 0x28307d9b0>
a:11---b:21
a:0x16bb7cfe8---b:0x283e0b1b8
通过结果我们看到,首先block
的地址是在栈区,而block1
的地址是在堆区,而栈block
引用的变量a
的地址并没有变化,而堆block1
引用的变量b
的地址也相应变成了堆区0x283e0b1b8
,并且后面使用的b
的地址都是堆区上的。
总结:栈block存放在栈区,对局部变量引用只拷贝局部变量的地址,而堆block存放在堆区,并且直接将局部变量拷贝了一份到堆空间。
接下来我们再来看个示例:
NSObject *objc = [NSObject new];
NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));// 1
// block 底层源码
// 捕获 + 1
// 堆区block
// 栈 - 内存 -> 堆 + 1
void(^strongBlock)(void) = ^{ // 1 - block -> objc 捕获 + 1 = 2
NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
void(^__weak weakBlock)(void) = ^{ // + 1
NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();
输出结果:
---1
---3
---4
---5
奇怪为什么堆区block里面的对象引用计数加2呢?而后面的mallocBlock只加1呢?
首先objc
在strongBlock
里面必然会拷贝一份到堆区,所以会加1,但是他是从当前函数的栈区拷贝吗?并不是,对于堆区Block
一开始编译时是栈block
这时候objc
对象地址拷贝了一份引用计数加1,后面从栈block
变成堆block
,又拷贝了一份引用计数又加1,所以这时候是3
weakBlock
是栈block
仅拷贝了一份,所以引用计数加1,这时候是4
mallocBlock
从weakblock
拷贝了一份,所以引用计数再加1,这时候是5
相当于strongBlock = weakblock + void(^mallocBlock)(void) = [weakBlock copy];
再来看个示例:
NSObject *a = [NSObject alloc];
NSLog(@"1---%@--%p", a, &a);
void(^__weak weakBlock)(void) = nil;
{
// 栈区
void(^__weak strongBlock)(void) = ^{
NSLog(@"2---%@--%p", a, &a);
};
weakBlock = strongBlock;
strongBlock();
NSLog(@"3 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
NSLog(@"4---%@--%p", a, &a);
输出结果:
1-----0x16bcf4fd8
2-----0x16bcf4fc0
3 - <__NSStackBlock__: 0x16bcf4fa0> - <__NSStackBlock__: 0x16bcf4fa0>
2---(null)--0x16bcf4fc0
4-----0x16bcf4fd8
当前是栈区strongBlock
的赋值给外面的栈区weakBlock
,因为都是存放在栈空间的,只有当前函数结束才会被销毁,随意这边weakBlock
调用并不会有什么问题。如果换成堆区block
就不一样了
注意:这边的a对象
在weakBlock()
调用时是nil,通过上面打印可以看出a对象
在进入到strongblock里,&a
拷贝了一份,拷贝的这一份地址指向的跟外面一样,但是当strongblock
出了{},尽管strongblock
对象不再了,但是其指向的内存空间还在,销毁之前给了外面的weakBlock,同理a
也一样,对象(此时a指向的内容)不在了,但是内存空间却还在。
NSObject *a = [NSObject alloc];
// NSLog(@"1---%@--%p", a, &a);
void(^__weak weakBlock)(void) = nil;
{
// 堆区
void(^strongBlock)(void) = ^{
NSLog(@"2---%@--%p", a, &a);
};
weakBlock = strongBlock;
strongBlock();
NSLog(@"3 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
// NSLog(@"4---%@--%p", a, &a);
输出结果:
2-----0x281e7f6e0
3 - <__NSMallocBlock__: 0x281e7f6c0> - <__NSMallocBlock__: 0x281e7f6c0>
调用weakBlock时崩溃
为什么呢?因为在{}里面的堆区strongBlock
出了大括号就会被销毁,此时你去调用这个block就会崩溃
注意:这边weakBlock
为什么也是__NSMallocBlock__
,其实weakBlock
相当于是指针,此时指向的是一个堆上的内存所以是__NSMallocBlock__
Block的循环引用
内存泄漏一个主要原因就是block的循环引用。那么如何解决循环引用呢?
- (void)viewDidLoad {
[super viewDidLoad];
// 循环引用
self.name = @"hongfa";
self.block = ^{
NSLog(@"%@", self.name);
}
self.block();
}
这边vc 引用了block ,block引用了vc,最后面造成了循环引用,那么如何解决呢?
方案一:通过weak来解决
- (void)viewDidLoad {
[super viewDidLoad];
// 循环引用
self.name = @"hongfa";
__weak typeof(self) weakself = self;
self.block = ^{
NSLog(@"%@", weakself.name);
};
self.block();
}
直接使用weakself来代替self,weakself是弱引用,这样不会导致引用计数+1。
这边也有一个问题如下:
self.name = @"hongfa";
__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 = @"hongfa";
__weak typeof(self) weakself = self;
self.block = ^{
__strong typeof(weakself) strongself = weakself;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongself.name);
});
};
self.block();
__strong
可以让对象暂时在存活一段时间,用完就会销毁,这样也不会带来内存泄漏。
以上就是通过__weak
和__strong
来解决block
的循环引用。
方案二:通过临时变量来解决
__block ViewController *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传进去,传参的话,参数是在栈区,函数运行好栈区销毁参数也就跟着销毁,所以也可以解决循环引用问题
// 通讯 参数 block 通知
self.hfblock = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.hfblock(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
通过上面的例子我们就很清楚block的底层结构以及他是如何对引用局部变量