Objective c 取消以前的PerformRequestsWithTarget后的自解除分配
使用ARC和iOS 6.1,我在这里有一个简单的类来演示我的问题:Objective c 取消以前的PerformRequestsWithTarget后的自解除分配,objective-c,automatic-ref-counting,selector,Objective C,Automatic Ref Counting,Selector,使用ARC和iOS 6.1,我在这里有一个简单的类来演示我的问题: #import <GHUnitIOS/GHUnit.h> @interface MyClass : NSObject @property BOOL cancel; @property BOOL dead; -(void)doSomething; -(void)reset; -(void)logMe; @end @implementation MyClass -(id)init { self = [sup
#import <GHUnitIOS/GHUnit.h>
@interface MyClass : NSObject
@property BOOL cancel;
@property BOOL dead;
-(void)doSomething;
-(void)reset;
-(void)logMe;
@end
@implementation MyClass
-(id)init {
self = [super init];
if(self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reset) name:@"dude" object:nil];
NSLog(@"I'm alive");
}
return self;
}
-(void)dealloc {
_dead = YES;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[MyClass cancelPreviousPerformRequestsWithTarget:self];
NSLog(@"I'm dead");
}
-(void)doSomething {
NSLog(@"dude:%d", _dead);
if(!_cancel) {
[self performSelector:@selector(doSomething) withObject:nil afterDelay:0.2];
NSLog(@"scheduled");
}
[self logMe];
}
-(void)reset {
NSLog(@"reset");
[MyClass cancelPreviousPerformRequestsWithTarget:self];
_cancel = YES;
[self doSomething];
}
-(void)logMe {
NSLog(@"logme");
}
@end
@interface ATest : GHTestCase
@end
@implementation ATest
-(BOOL)shouldRunOnMainThread {return YES;}
-(void)setUpClass {}
-(void)tearDownClass {}
-(void)setUp {}
-(void)tearDown {}
-(void)testBlah {
MyClass* blah = [[MyClass alloc] init];
[blah doSomething];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){
[[NSNotificationCenter defaultCenter] postNotificationName:@"dude" object:nil];
});
blah = nil;
}
@end
#导入
@接口MyClass:NSObject
@财产申报取消;
@财产损失;
-(无效)剂量测定;
-(无效)重置;
-(无效)logMe;
@结束
@MyClass的实现
-(id)init{
self=[super init];
如果(自我){
[[NSNotificationCenter defaultCenter]添加观察者:自选择器:@selector(reset)name:@“dude”对象:nil];
NSLog(“我还活着”);
}
回归自我;
}
-(无效)解除锁定{
_死亡=是;
[[NSNotificationCenter defaultCenter]移除观察者:self];
[MyClass cancelPreviousPerformRequestsWithTarget:self];
NSLog(“我死了”);
}
-(无效)剂量{
NSLog(@“dude:%d”,_dead);
如果(!\u取消){
[自执行选择器:@selector(doSomething)with object:nil afterDelay:0.2];
NSLog(“预定”);
}
[自我记录];
}
-(无效)重置{
NSLog(“重置”);
[MyClass cancelPreviousPerformRequestsWithTarget:self];
_取消=是;
[自我剂量测定];
}
-(无效)logMe{
NSLog(@“logme”);
}
@结束
@接口ATest:htestcase
@结束
@实施测试
-(BOOL)应该runnonmainthread{返回YES;}
-(void)setUpClass{}
-(void)tearDownClass{}
-(无效)设置{}
-(无效)拆卸{}
-(无效)testBlah{
MyClass*blah=[[MyClass alloc]init];
[胡说八道];
调度时间(现在调度时间(int64 t)(1.0*NSEC\u/秒)),调度获取主队列()之后调度{
[[NSNotificationCenter defaultCenter]postNotificationName:@“dude”对象:nil];
});
布拉赫=零;
}
@结束
在测试中,MyClass
被实例化,我启动了doSomething
,它执行一些工作(即记录),如果\u cancel
为false,则在0.25秒后调用自己。同时,我计划在1.0s后发出通知(最终将\u cancel
设置为true)。然后我就一无所获了
因此,我的期望是由performSelector:withObject:withDelay
拥有对MyClass
的引用而创建的计时器
但是,当我在启用僵尸的情况下运行此测试时,会得到以下输出:
2013-02-28 15:30:55.518测试[11946:c07]ATest/testBlah2013-02-28 15:30:56.789测试[11946:c07]重新运行:ATest/testBlah
2013-02-28 15:30:56.790测试[11946:c07]我还活着 2013-02-28 15:30:56.790测试[11946:c07]都德:0
2013-02-28 15:30:56.791计划的测试[11946:c07] 2013-02-28 15:30:56.791测试[11946:c07]logme
2013-02-28 15:30:56.792测试✔ 0.00s
2013-02-28 15:30:56.991测试[11946:c07]都德:0
2013-02-28 15:30:56.992测试[11946:c07]计划进行
2013-02-28 15:30:56.992测试[11946:c07]logme
2013-02-28 15:30:57.193测试[11946:c07]都德:0
2013-02-28 15:30:57.194计划的测试[11946:c07] 2013-02-28 15:30:57.194测试[11946:c07]logme
2013-02-28 15:30:57.395测试[11946:c07]都德:0
2013-02-28 15:30:57.395计划的测试[11946:c07] 2013-02-28 15:30:57.396测试[11946:c07]logme
2013-02-28 15:30:57.596测试[11946:c07]都德:0
2013-02-28 15:30:57.597计划的测试[11946:c07] 2013-02-28 15:30:57.597测试[11946:c07]logme
2013-02-28 15:30:57.792测试[11946:c07]重置
2013-02-28 15:30:57.793测试[11946:c07]我死了
2013-02-28 15:30:57.793测试[11946:c07]*-[MyClass doSomething]:发送到解除分配实例0xb584880的消息 为什么在调用
reset
方法中的cancelPreviousPerformRequestsWithTarget:
后,self
被解除分配
此问题是ARC问题还是编码错误?问题很简单。我称之为NSNotificationCenter中的bug。下面是具有相同行为的代码的简化版本。我们所做的一切就是让自己听一个通知,并通过一个强(静态)引用保持自己的活力。当通知发出时,我们清除该引用。(在您的情况下,对对象的最后一个强引用是在
performSelector:
机械中;保留performSelector:
的目标,当您取消它时,它释放了对您的引用。)
这会在[self logMe]
处产生一条僵尸消息。原因是在clearference
中,当我们执行instance=nil时代码>这是对我们的最后一个强引用,因此我们在调用[self logMe]之前被解除分配代码>。但是,你可能会问,为什么ARC没有抓住我们
嗯,ARC从不保留self,因为假设方法的调用方对self有一个强引用通常是安全的,如果每个方法都必须保留/释放self,这将增加很多开销。(对于在ARC下编译的代码,这种假设实际上总是正确的,因为要对对象调用方法,首先需要对其进行引用。)不幸的是,NSNotificationCenter在调用方法之前没有保留对象。我称之为bug:在非ARC代码中,在调用某个未知回调之前,通常礼貌地确保至少有一个对对象的临时强引用:
id objectToCall = ...;
[objectToCall retain];
[objectToCall performSelector:...]; // the actual callback
[objectToCall release];
这样的代码可以确保您看到的崩溃不会发生。NSNotificationCenter显然没有这样做。您可以通过查看Zombies工具中对象的保留历史来验证这一点
由于您无法更改NSNotificationCenter,我以前使用过一种公认的丑陋的解决方法,当您可能被解除分配,并且您的呼叫方可能没有对您进行强引用时,它是这样的:
- (void)clearReference {
CFRetain((__bridge CFTypeRef)(self));
instance = nil;
[self logMe];
CFRelease((__bridge CFTypeRef)(self));
}
__weak typeof (self) (weakSelf) = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(foo) object:nil];
[weakSelf bar];
这样,你至少可以确定,在你的方法结束之前,你不会被释放;而不是保留释放舞蹈:
CFRetain((__bridge CFTypeRef)(self));
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(foo) object:nil];
[self bar];
CFRelease((__bridge CFTypeRef)(self));
我更喜欢这样的弧形方式:
- (void)clearReference {
CFRetain((__bridge CFTypeRef)(self));
instance = nil;
[self logMe];
CFRelease((__bridge CFTypeRef)(self));
}
__weak typeof (self) (weakSelf) = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(foo) object:nil];
[weakSelf bar];
如果第2行进行自我解除分配,那么第3行的弱点将为零,而不是僵尸点