首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >cancelPreviousPerformRequestsWithTarget后的自释放

cancelPreviousPerformRequestsWithTarget后的自释放
EN

Stack Overflow用户
提问于 2013-02-28 21:45:16
回答 2查看 1.1K关注 0票数 4

使用ARC和iOS 6.1,这里有一个简单的类来演示我的问题:

代码语言:javascript
复制
#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被实例化,我启动了doSomething,它执行一些工作(即日志记录),如果_cancel为false,则在0.25s之后调用自己。同时,我在1.0之后安排了一个触发通知(最终将_cancel设置为true)。然后我就没有blah了。

因此,我的期望是,由performSelector:withObject:withDelay创建的计时器拥有对MyClass的引用。

但是,当我在启用僵尸的情况下运行这个测试时,我得到了以下输出:

2013-02-28 15:30:55.518测试11946:C07 ATest/testBlah 2013-02-28 15:30:56.789 Tests11946: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 Tests11946:c07 logme 2013-02-28 15:30:56.792 Tests11946:c07 ATest/testBlah✔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 Tests11946: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 Tests11946: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 Tests11946: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 Tests11946: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 Tests11946:c07 * -MyClass doSomething:发送给已释放的实例0xb584880的消息

在调用self方法中的cancelPreviousPerformRequestsWithTarget:之后,为什么要释放reset

这个问题是ARC问题还是编码错误?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-02-28 22:50:23

很好的问题。我将此称为NSNotificationCenter中的一个bug。下面是具有相同行为的代码的简化版本。我们所做的就是设置自己来聆听一个通知,并保持自己在一个强大(静态)引用中的活力。当通知发出时,我们将清除该引用。(在您的示例中,对对象的最后一次强引用是在performSelector:机器中;保留了performSelector:的目标,当您取消它时,它释放了对您的引用。)

代码语言:javascript
复制
@interface MyClass : NSObject
@end

static MyClass *instance;

@implementation MyClass

-(id)init {
    self = [super init];
    if(self) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clearReference) name:@"dude" object:nil];
        NSLog(@"I'm alive");
        instance = self;
    }
    return self;
}

- (void)clearReference {
    instance = nil;
    [self logMe];
}

-(void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    NSLog(@"I'm dead");
}

-(void)logMe {
    NSLog(@"logme");
}

@end

// Test case
[[MyClass alloc] init];
[[NSNotificationCenter defaultCenter] postNotificationName:@"dude" object:nil];

这导致了[self logMe]上的僵尸消息。原因是在clearReference中,当我们执行instance = nil;时,这是对我们的最后一个强有力的引用,所以在调用[self logMe];之前,我们是被释放的。但是,你可能会问,为什么ARC不抓住我们?

嗯,ARC从不保留self,因为通常可以安全地假定方法的调用方对self有很强的引用,而且如果每个方法都必须保留/释放self,则会增加大量开销。(对于在ARC下编译的代码,这种假设实际上总是正确的,因为要调用对象上的方法,首先需要对其进行引用。)不幸的是,在调用方法之前,NSNotificationCenter没有保留您的对象。我将此称为bug:在非ARC代码中,在调用对象上的某个未知回调之前,确保至少有一个对对象的临时强引用是礼貌的:

代码语言:javascript
复制
id objectToCall = ...;
[objectToCall retain];
[objectToCall performSelector:...]; // the actual callback
[objectToCall release];

这样的代码将确保您所看到的崩溃不会发生。显然,NSNotificationCenter并不是这样做的。您可以通过查看僵尸仪器中对象的保留历史来验证这一点。

由于您无法更改NSNotificationCenter,我以前使用过的一个非常糟糕的解决方法是这样的:

代码语言:javascript
复制
- (void)clearReference {
    CFRetain((__bridge CFTypeRef)(self));
    instance = nil;
    [self logMe];
    CFRelease((__bridge CFTypeRef)(self));
}

这样,至少您可以确定,直到您的方法结束时,您才会被释放。

票数 1
EN

Stack Overflow用户

发布于 2016-01-07 02:36:13

__weak the (self) (weakSelf) =self;而不是保留-释放舞蹈:

代码语言:javascript
复制
CFRetain((__bridge CFTypeRef)(self));
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(foo) object:nil];
[self bar];
CFRelease((__bridge CFTypeRef)(self));

我更喜欢这样的ARC方式:

代码语言:javascript
复制
__weak typeof (self) (weakSelf) = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(foo) object:nil];
[weakSelf bar];

如果第2行使自释放,那么第3行的weakSelf将为零而不是僵尸指针,而零条是安全的。

weakSelf解决方案有两个优点:

  1. 在保留-释放舞蹈解决方案,自棒节省CPU时间,并没有任何意义。
  2. 这个弱点看起来比将一个对象转换为CFType和手动调用、保留和释放更漂亮。

附注:

和其他任何xxxxx舞蹈一样,我想将weakSelf解决方案命名为“哲学舞蹈”:)

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15146235

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档