Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/objective-c/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Objective c 取消以前的PerformRequestsWithTarget后的自解除分配_Objective C_Automatic Ref Counting_Selector - Fatal编程技术网

Objective c 取消以前的PerformRequestsWithTarget后的自解除分配

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

使用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 = [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/testBlah
2013-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行的弱点将为零,而不是僵尸点