本节内容针对block的一些实例进行分析,block的分类,循环引用等。先有一个大致的印象,然后再去分析block的底层结构。
block的分类
- NSGlobalBlock
-
- 位于全局区
-
- 在Block内部不使用外部变量,或者只使用静态变量和全局变量
-
static int a = 12;
void (^globalBlock)(void) = ^{
NSLog(@"---%d", a);
};
// <__NSGlobalBlock__: 0x1082af100>
NSLog(@"%@",globalBlock);
- NSMallocBlock
- 位于堆区
- 在block内部使用变量或者OC属性,并且赋值给强引用或者Copy修饰的变量
int a = 10;
void (^block)(void) = ^{
NSLog(@"haha - %d",a);
};
NSLog(@"%@",block);
- NSStackBlock
- 位于栈区
- 与MallocBlock一样,可以在内部使用局部变量或者OC属性。但是不能赋值给强引用或Copy修饰的变量
int a = 10;
void (^__weak block)(void) = ^{
NSLog(@"haha - %d",a);
};
NSLog(@"%@",block);
栈Block拷贝到堆Block的四种情况(注block里面有变量或者oc属性的情况下)
- 手动copy
- block作为返回值或参数
- 被强引用或者被Copy修饰
- 系统API包含usingBlock
捕获外部变量-对外部变量的引用计数处理
NSObject *objc = [NSObject new];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
// block 底层源码
// 捕获 + 1
// 堆区block
// 栈 - 内存 -> 堆 + 1
void(^strongBlock)(void) = ^{
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));//3
};
strongBlock();
void(^__weak weakBlock)(void) = ^{ // + 1
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));//4
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];//5
mallocBlock();
对于
void(^strongBlock)(void) = ^{
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));//3
};
等于
void(^__weak weakBlock)(void) = ^{ // + 1
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));//4
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];//5
这两个的效果,一个是捕获变量+1,然后copy到堆上面+1,所以是3。这个在下一节源码分析中会讲到
对block copy的初探
我们知道block用copy修饰,当然arc下copy和strong效果相同,但是为什么要用copy修饰呢,为什么要将block拷贝到堆上面呢 我们先看一个demo
- (void)blockDemo1{
int a = 0;
void(^ __weak weakBlock)(void) = ^{
NSLog(@"-----%d", a);
};
//_DWBlock就是一个模拟源码对block的封装
struct _DWBlock *blc = (__bridge struct _DWBlock *)weakBlock;
// 深浅拷贝
id __strong strongBlock = weakBlock;
blc->invoke = nil;
void(^strongBlock1)(void) = strongBlock;
strongBlock1();
}
这个demo运行会崩溃,因为我们把blc的invoke置为了nil,所以下面调用的时候就找不到了,所以报错
在上面的weakBlock和StrongBlock都是栈Block
如果改为
id __strong strongBlock = [weakBlock copy];就可以正常运行,对block进行了copy,strongBlock是堆上的block,所以之后修改了blc但是和strongBlock没有任何关系。
demo2
- (void)blockDemo3{
// 压栈 内存平移
NSObject *a = [NSObject alloc];
void(^__weak weakBlock)(void) = nil;
{//一个代码块
void(^__weak strongBlock)(void) = ^{
NSLog(@"---%@", a);
};
weakBlock = strongBlock;
NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
}
//打印结果
//**1 - <__NSStackBlock__: 0x7ffee5f47450> - <__NSStackBlock__: 0x7ffee5f47450>**
//**---(null)**
我们知道在栈上面,内存由系统回收,weakBlock()的作用域在整个函数内,所以weakBlock()的时候会执行
我们把void(^__weak strongBlock)(void) = ^{改为void(^ strongBlock)(void) = ^{
此时会崩溃,输出1 - <__NSMallocBlock__: 0x600003334840> - <__NSMallocBlock__: 0x600003334840>崩溃在weakBlock()-Thread 1: EXC_BAD_ACCESS (code=1, address=0x10);
这个时候是堆block我们看到出了代码块之后,strongBlock就没有了,此时weakBlock也就为空了,所以后面在调用会崩溃。
Block的循环引用
我们知道,大多数情况下,A中持有了B,当A释放到时候,B也释放,在有些情况下,A中持有了B,B中也持有了A,然后就造成一个相互的引用循环。 eg:
@property (nonatomic, strong) DWBlock block;
@property (nonatomic, copy) NSString *name;
self.block = ^{
NSLog(@"%@", self.name);
};
self.block();
在dealloc里面打印NSLog(@"dealloc 来了");发现当页面退出的时候,并没有走dealloc。
这种情景比较熟悉,self持有了block,block里面又持有了self,造成一个循环引用。 我们都知道加上__weak来破除循环引用
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@", weakSelf.name);
};
如果block里面有一个耗时操作,这个时候会怎么样呢
__weak typeof(self) weakSelf = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", weakSelf.name);
});
};
self.block();
//**dealloc 来了**
//**(null)**
这个时候就会出现问题,先走了dealloc,weakSelf被释放,打印的值不准确。
当然我们可以对weakSelf进行一个强持有,从而保证打印的准确性。
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongSelf.name);
});
};
self.block();
这也是一些block里面用到__strong的情况之一,__strong typeof(self) strongSelf = weakSelf;虽然是个强引用,但是它是一个局部变量,出了作用域之后就会被回收掉,所以这个里面不存在循环引用的情况。对于循环引用的情况也要具体情况具体分析,有时候会用到__strong保持对象不会回收。
我们也可以在合适的时候将self置为nil来破除循环引用
__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();
前面产生循环引用的原因是因为block对里面的变量进行了持有,那么我们将vc作为参数来处理,就可以巧妙的避免了这个问题的产生
self.block = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.block(self);
上面我们介绍了三种破除循环引用的方式,下面我们来分析下下面的示例是否会产生循环引用。 eg1:
static ViewController *staticSelf_;
- (void)blockWeak_static {
// 是同一片内存空间
__weak typeof(self) weakSelf = self;
staticSelf_ = weakSelf;
// staticSelf_ -> weakSelf -> self
}
由于self间接被静态变量所持有,不随页面退出而回收,所以会产生循环引用
- (void)block_weak_strong {
__weak typeof(self) weakSelf = self;
self.doWork = ^{
__strong typeof(self) strongSelf = weakSelf;
weakSelf.doStudent = ^{
NSLog(@"%@", strongSelf);
};
weakSelf.doStudent();
};
self.doWork();
}
虽然__strong typeof(self) strongSelf = weakSelf;strongSelf是一个临时变量,但是 weakSelf.doStudent对strongSelf有一个持有,导致它引用计数+1,它持有了weakSelf,weakSelf持有了self,当页面退出的时候,它里面的引用计数还不能被清空回收,所以也不会走dealloc。