- 本文扯些啥
- 一种取消GCD延时任务的解决方案
- 非常简单的介绍3种Block类型
- ARC模式打印引用计数retianCount
-
__NSGlobalBlock__、__NSStackBlock__、__NSMallocBlock__
测试 - ARC模式声明block属性不要用weak
- ARC模式声明block属性用copy、strong都可以
一种取消GCD延时任务的解决方案
在项目中经常用到延时操作,但是dispatch_after有个缺点,不能像perform~方法那样可以随时取消延时,有时会导致控制器被Block强引用而延时释放,可能导致崩溃,安全性不高。所以想实现一个可以取消的GCD延时任务的功能,但是折腾了很久没跳出这个坑,最后在Github上找到了一种解决方案。【Dispatch-Cancel】,代码虽少,但是效果极好,下面是源码。
typedef void(^SMDelayedBlockHandle)(BOOL cancel);
static SMDelayedBlockHandle perform_block_after_delay(CGFloat seconds, dispatch_block_t block) {
if (nil == block) {
return nil;
}
__block dispatch_block_t blockToExecute = [block copy];
__block SMDelayedBlockHandle delayHandleCopy = nil;
SMDelayedBlockHandle delayHandle = ^(BOOL cancel){
if (NO == cancel && nil != blockToExecute) {
dispatch_async(dispatch_get_main_queue(), blockToExecute);
}
#if !__has_feature(objc_arc)
[blockToExecute release];
[delayHandleCopy release];
#endif
blockToExecute = nil;
delayHandleCopy = nil;
};
delayHandleCopy = [delayHandle copy];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
if (nil != delayHandleCopy) {
delayHandleCopy(NO);
}
});
return delayHandleCopy;
};
static void cancel_delayed_block(SMDelayedBlockHandle delayedHandle) {
if (nil == delayedHandle) {
return;
}
delayedHandle(YES);
}
这是我们常规写法
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), dispatch_get_main_queue(), {
/// do something ,并且强引用外部对象,执行完才release
});
对比常规写法,源码的采用__block修饰的blockToExecute代替传进来的block,并且加了一个__block修饰的桥梁(中介) delayHandleCopy实现解耦,dispatch_after不会直接强引用外部对象,大致的引用关系如下,这样释放2个__block修饰的桥梁block,就可以解除了dispatch_after对外部对象的强引用。对NSTimer、KVO啥的都可以借助这种思想,利用桥梁对象实现解耦。
dispatch_after --> (__block) delayHandleCopy -> delayHandle --> (__block) blockToExecute -> block -> 外部对象
之前看源码的时候发现有2处用到了[block copy]
,比如下面的代码,以前没这么用过,就看了一些博客学习,然后自己捯饬。
__block dispatch_block_t blockToExecute = [block copy];
源码兼容了MRC,所以我把MRC相关的代码去掉,并且把2个[block copy]
也去掉,一捯饬,没毛病! 但还是搞不懂为啥MRC要加[block copy],而ARC可以不用copy,为啥声明Block属性要用copy?,所以继续瞎捯饬了两天,在这做个粗略总结。
三种Block类
这里只做简单介绍,不会介绍底层的那些结构体源码,看后面的代码也能大概了解block,如果想深入了解,简书也有很多相关的文章。
首先Block分3种Class,分别是__NSGlobalBlock__
、__NSStackBlock__
、__NSMallocBlock__
,主要在于内存区域的不同,其次是__NSGlobalBlock__
不会引用任务外部变量,__NSStackBlock__
会引用外部变量,__NSMallocBlock__
由[__NSStackBlock__ copy]
而来,所以也会引用外部变量,这里说的引用外部变量指的是在block里面直接引用Block外的变量对象,而不是引用传入block里面的参数对象。至于三种Block在内存中的储存位置,我找了一张图,来自【iOS Block源码分析系列】。如果对内存分区好奇,看看这篇文章【iOS内存分配和分区】
ARC模式打印引用计数retianCount
先介绍一种在ARC模式下打印引用计数的方法,但是使用CFGetRetainCount获取引用计数会比实际引用计数大1,所以下面做了-1操作(对一个对象只会+1一次,不会叠加),只是不适合打印字符串对象的引用计数(数值特别大),此文不会扯MRC基础原理,当然有疑问也可以讨论。
static inline void JKLogRetainCount(NSString * des ,id obj) {
if (nil != obj) {
/// 实际的RetainCount 比 CFGetRetainCount 小 1
NSLog(@"%@ RetainCount = %zd", des,CFGetRetainCount((__bridge CFTypeRef)obj) - 1);
} else {
NSLog(@"%@ RetainCount = 0, obj == nil",des);
}
}
NSGlobalBlock测试
UIView * testView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 50, 50)];
testView.backgroundColor = [UIColor orangeColor];
JKLogRetainCount(@"alloc testView",testView);
[self.view addSubview:testView];
JKLogRetainCount(@"add testView",testView);
dispatch_block_t globalBlock = ^(){};
NSLog(@"1:%@",globalBlock);
NSLog(@"2:%@",^(int a, int b){ a = a + b;});
void (^globalBlock_Temp) (UIView * , int ) = ^(UIView * a, int b) {
a.backgroundColor = [UIColor redColor];
JKLogRetainCount(@"作为GlobalBlock内部的参数 testView",a);
};
JKLogRetainCount(@"GlobalBlock外部的testView",testView);
globalBlock_Temp(testView, 3);
JKLogRetainCount(@"已执行完GlobalBlock,外部testView",testView);
NSLog(@"3:%@",globalBlock_Temp);
/// 作为方法的参数传入
[self globalBlockTest:^{
NSLog(@"没有强引用外部变量");
}];
- (void)globalBlockTest:(void(^)(void))globalBlock {
NSLog(@"globalBlock Func Log:%@",globalBlock);
}
打印结果:
[控制台打印] alloc testView RetainCount = 1
[控制台打印] add testView RetainCount = 2
[控制台打印] 1:<__NSGlobalBlock__: 0x10fa44100>
[控制台打印] 2:<__NSGlobalBlock__: 0x10fa44140>
[控制台打印] GlobalBlock外部的testView RetainCount = 2
[控制台打印] 作为GlobalBlock内部的参数 testView RetainCount = 3
[控制台打印] 已执行完GlobalBlock,外部testView RetainCount = 2
[控制台打印] 3:<__NSGlobalBlock__: 0x10fa44180>
[控制台打印] 作为GlobalBlock内部的参数 testView RetainCount = 3
[控制台打印] globalBlock Func Log:<__NSGlobalBlock__: 0x10fa441c0>
上面4种情况中的Block都没有引用外部变量,不管有没有赋值操作,都是__NSGlobalBlock__
类型。另外作为参数传入Block的外部对象,只有在执行GlobalBlock时,GlobalBlock会强引用参数变量(RetainCount+1),GlobalBlock执行结束就会解除强引用(RetainCount-1)。
在Block对对象参数的引用上,__NSGlobalBlock_、__NSStackBlock__、__NSMallocBlock__
都是一样的效果,即在执行Block的过程中,Block会强引用参数对象一次(RetainCount+1),执行完就会解除强引用(RetainCount-1)。如果要在Block里面调用Block外部的对象,传参是最安全的,但使用起来比较麻烦,代码不是很好理解。
NSStackBlock测试
testView = [[UIView alloc] initWithFrame:CGRectMake(100, 200, 50, 50)];
JKLogRetainCount(@"alloc testView",testView);
[self.view addSubview:testView];
JKLogRetainCount(@"add testView",testView);
NSLog(@"1:%@",^(){
testView.backgroundColor = [UIColor darkGrayColor];
JKLogRetainCount(@"stackBlock内部强引用的 testView",testView);
});
JKLogRetainCount(@"外部的TestView已被stackBlock引用,但是未调用stackBlock", testView);
__weak void (^weakStackBlock)(UIView *) = ^(UIView * view){
testView.backgroundColor = [UIColor darkGrayColor];
view.backgroundColor = [UIColor darkGrayColor];
JKLogRetainCount(@"stackBlock内部强引用的 testView",testView);
};
NSLog(@"2:%@",weakStackBlock);
weakStackBlock(testView);
JKLogRetainCount(@"weakStackBlock已执行完,外部的testView",testView);
NSLog(@"3:%@",weakStackBlock);
[self stackBlockTest:weakStackBlock];
void(^tempStackBlock)(UIView *) = weakStackBlock;
NSLog(@"tempStackBlock = weakStackBlock 2: %@",tempStackBlock);
- (void)stackBlockTest:(void(^)(UIView *))stackBlock {
NSLog(@"stackBlock Func Log:%@",stackBlock);
/// tempStackBlock会是__NSMallocBlock__,应该执行了copy
void(^tempMallocBlock)(UIView *) = stackBlock;
NSLog(@"tempMallocBlock = stackBlock 1: %@",tempMallocBlock);
}
/**<
[控制台打印] alloc testView RetainCount = 1
[控制台打印] add testView RetainCount = 2
[控制台打印] 1:<__NSStackBlock__: 0x7fff501be818>
[控制台打印] 外部的TestView已被stackBlock引用,但是未调用stackBlock RetainCount = 3
[控制台打印] 2:<__NSStackBlock__: 0x7fff501be7e8>
[控制台打印] stackBlock内部强引用的 testView RetainCount = 5
[控制台打印] weakStackBlock已执行完,外部的testView RetainCount = 4
[控制台打印] 3:<__NSStackBlock__: 0x7fff501be7e8>
[控制台打印] stackBlock Func Log:<__NSStackBlock__: 0x7fff501be7e8>
** 重点 **
[控制台打印] tempMallocBlock = stackBlock 1: <__NSMallocBlock__: 0x60000005b960>
[控制台打印] tempStackBlock = weakStackBlock 2: <__NSStackBlock__: 0x7fff501be7e8>
*/
__NSStackBlock__
的出栈入栈都由系统管理,在定义block时外部变量就会被stackBlock强引用一次,直到stackBlock销毁才会解除强引用。在上面的测试中,testView被2个stackBlock引用(一个未命名,一个是weakStackBlock),所以引用计数被+1了2次。另外testView作为参数传入atackBlock,在执行stackBlock过程中也被retain了一次,不过执行完又release了一次,这点跟GlobalBlock是一样的。值得注意的有3点:
- 将一个stackBlock赋值给一个__weak修饰的weakStackBlock,weakStackBlock还是
__NSStackBlock__
类型,后面的测试中是将普通的stackBlock赋值给一个__strong修饰或者普通的strongBlock,strongBlock会是__NSMallocBlock__
类型,原因ARC环境下在block赋值时会自动调用copy。- 将weakStackBlock赋值给一个普通的block,block还是
__NSStackBlock__
,并且block == weakStackBlock,ARC和MRC环境测试结果一样。- 调用方法时将weakStackBlock作为参数传入方法,在方法内部将weakStackBlock赋值给一个普通的block,将会是
__NSMallocBlock__
类型,而且block != weakStackBlock,或许__weak作用域/可识别域是有限的,这方面待研究。
NSMallocBlock
void (^mallocBlock_temp) (UIView *) = [stackBlock copy];
NSLog(@"1:%@",mallocBlock_temp);
testView = [[UIView alloc] initWithFrame:CGRectMake(100, 300, 50, 50)];
testView.backgroundColor = [UIColor orangeColor];
JKLogRetainCount(@"alloc testView",testView);
[self.view addSubview:testView];
JKLogRetainCount(@"add testView",testView);
** 如果把这行放到alloc View前面去,结果会怎么样? **
//void (^mallocBlock_temp) (UIView *) = [stackBlock copy];
//NSLog(@"1:%@",mallocBlock_temp);
void (^mallocBlock) (UIView *) = ^(UIView * view) {
testView.backgroundColor = [UIColor redColor];
view.backgroundColor = [UIColor blueColor];
JKLogRetainCount(@"mallocBlock内部的testView", testView);
};
NSLog(@"2:%@",mallocBlock);
mallocBlock(testView);
JKLogRetainCount(@"已调用mallocBlock,testView被强引用,也作为参数传入mallocBlock", testView);
[self mallocBlockTest:mallocBlock];
void (^mallocBlock_Copy)(UIView *) = [mallocBlock copy];
NSLog(@"mallocBlock_Copy %@",mallocBlock_Copy);
JKLogRetainCount(@"copy mallocBlock之后", testView);
__strong void (^mallocBlock_Copy_Copy)(UIView *) = [mallocBlock_Copy copy];
NSLog(@"mallocBlock_Copy_Copy %@",mallocBlock_Copy_Copy);
JKLogRetainCount(@"copy mallocBlock_Copy_Copy之后", testView);
[控制台打印] alloc testView RetainCount = 1
[控制台打印] add testView RetainCount = 2
[控制台打印] 1:<__NSMallocBlock__: 0x6000002409f0>
[控制台打印] 2:<__NSMallocBlock__: 0x600000240b70>
[控制台打印] mallocBlock内部的testView RetainCount = 5
[控制台打印] 已调用mallocBlock,testView被强引用,也作为参数传入mallocBlock RetainCount = 4
[控制台打印] mallocBlock Func Log:<__NSMallocBlock__: 0x600000240b70>
[控制台打印] mallocBlock内部的testView RetainCount = 4
[控制台打印] mallocBlock_Copy <__NSMallocBlock__: 0x600000240b70>
[控制台打印] copy mallocBlock之后 RetainCount = 4
[控制台打印] mallocBlock_Copy_Copy <__NSMallocBlock__: 0x600000240b70>
[控制台打印] copy mallocBlock_Copy_Copy之后 RetainCount = 4
在ARC模式下,将stackBlock赋值给一个不被__weak修饰的mallocBlock,系统会自动执行[stackBlockw copy],所以__NSMallocBlock__
是我们经常见的block类型。并且我在MRC环境下测试,[stackBlock retain]还是__NSStackBlock__
类型,只有[stackBlock copy]会产生一个__NSMallocBlock__
类型的block,所以系统是自动执行了copy。所以前面我删了部分源码在ARC模式下也能运行,是系统帮我们调用了copy,而copy globalBlock和mallocBlock还是原类型,像浅拷贝,所以我们再调用copy也无碍。
外部变量obj开始会被stackBlock强引用一次,在[stackBlock copy]过程又会被强引用一次,所以外部变量会被强引用2次,有点像深拷贝的效果。另外3种类型的block调用copy有不同的结果:
- stackBlock != [stackBlcok copy]
- mallocBlock == [mallocBlock copy]
- globalBlock == [globalBlock copy]
[stackBlcok copy]像深拷贝,而[mallocBlock copy]和[globalBlock copy]则像浅拷贝。PS:只能说像,并期待大神解答。
ARC模式声明block属性不要用weak
之所以不能用weak,是因为存在很大的安全隐患。比如在ViewDidLoad中将一个stackBlock赋值给self.weakBlock,在viewDidAppear中调用self.block(),会出现崩溃,原因是self.weakBlock在viewDidLoad方法结束后会被系统释放掉,self.weakBlock == nil。
@property (nonatomic, weak) void(^weakBlock)();
- (void)viewDidLoad {
[super viewDidLoad];
testView = [[UIView alloc] initWithFrame:CGRectMake(100, 400, 50, 50)];
[self.view addSubview:testView];
self.weakBlock = ^(){
testView.backgroundColor = [UIColor redColor];
JKLogRetainCount(@"self.weakBlock 内部testView", testView);
};
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
/// 会崩溃
self.weakBlock();
}
ARC环境下声明block属性,copy和strong都可以
@property (nonatomic, copy) void(^copyBlock)();
@property (nonatomic, strong) void(^strongBlock)();
testView = [[UIView alloc] initWithFrame:CGRectMake(100, 400, 50, 50)];
testView.backgroundColor = [UIColor orangeColor];
JKLogRetainCount(@"alloc testView",testView);
[self.view addSubview:testView];
JKLogRetainCount(@"add testView",testView);
self.copyBlock = ^(){
testView.backgroundColor = [UIColor blueColor];
JKLogRetainCount(@"self.copyBlock 内部testView", testView);
};
NSLog(@"2:%@",self.copyBlock);
self.copyBlock();
JKLogRetainCount(@"self.copyBlock 外部testView", testView);
[控制台打印] alloc testView RetainCount = 1
[控制台打印] add testView RetainCount = 2
[控制台打印] 2:<__NSMallocBlock__: 0x60800044d080>
[控制台打印] self.copyBlock 内部testView RetainCount = 4
[控制台打印] self.copyBlock 外部testView RetainCount = 4
testView = [[UIView alloc] initWithFrame:CGRectMake(100, 400, 50, 50)];
testView.backgroundColor = [UIColor orangeColor];
JKLogRetainCount(@"alloc testView",testView);
[self.view addSubview:testView];
JKLogRetainCount(@"add testView",testView);
self.strongBlock = ^(){
testView.backgroundColor = [UIColor blueColor];
JKLogRetainCount(@"self.strongBlock 内部 testView", testView);
};
NSLog(@"3:%@",self.strongBlock);
self.strongBlock();
JKLogRetainCount(@"self.strongBlock 外部 testView", testView);
[控制台打印] alloc testView RetainCount = 1
[控制台打印] add testView RetainCount = 2
[控制台打印] 3:<__NSMallocBlock__: 0x60800044d050>
[控制台打印] self.strongBlock 内部 testView RetainCount = 4
[控制台打印] self.strongBlock 外部 testView RetainCount = 4
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
self.copyBlock();
self.strongBlock();
NSLog(@"self.copyBlock :%@",self.copyBlock);
NSLog(@"self.strongBlock :%@",self.strongBlock);
/**<
[控制台打印] self.copyBlock 内部testView RetainCount = 3
[控制台打印] self.strongBlock 内部 testView RetainCount = 3
[控制台打印] self.copyBlock :<__NSMallocBlock__: 0x60800044d080>
[控制台打印] self.strongBlock :<__NSMallocBlock__: 0x60800044d050>
*/
}
从打印结果来看,外部变量testView的引用计数在Self.copyBlock和self.strongBlock强引用后都+2,也就是testView被强引用了2次效果一致。stackBlock赋值给self.copyBlock时,系统会执行[stackBlock copy],另外在setter方法里面也会执行copy。赋值给self.strongBlock的结果和赋值给self.copyBlock是一样的,上面提到过,在MRC模式下,[stackBlock retain]不会产生__NSMallocBlock__
类型的block对象,所以我想苹果是对Block的赋值做了特殊处理。总之在ARC模式下,声明copy和strong类型的block属性效果是一样的,不会影响使用。
Block的原始类型 | newBlock = [Block copy] newBlock的类型 |
---|---|
__NSGlobalBlock__ |
__NSGlobalBlock__ |
__NSStackBlock__ |
__NSMallocBlock__ |
__NSMallocBlock__ |
__NSMallocBlock__ |
- stackBlock != [stackBlcok copy]
- mallocBlock == [mallocBlock copy]
- globalBlock == [globalBlock copy]
另外发现一个现象,声明Block、NSString、NSDictionary、NSArray属性(self.property)都适合用copy,它们的共同点是 self.property == [self.property copy],而且[self.property copy]这个过程都像or是浅拷贝。
所以在此提出一个猜测:"除blcok对象以外,可以用 obj == [obj copy]来判断对象obj是否可变",欢迎讨论。
猜完了再看看这篇文章
下一篇文章设计copy和mutableCopy对(NSString、NSDictionary、NSArray对象)的影响,并涉及深拷贝、浅拷贝。
参考门