gpt4 book ai didi

objective-c - cancelPreviousPerformRequestsWithTarget 之后的 self 释放

转载 作者:搜寻专家 更新时间:2023-10-30 19:46:16 24 4
gpt4 key购买 nike

使用 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 被实例化,我启动 doSomething,它执行一些工作(即日志记录),然后在 0.25 秒后调用自身,如果 _cancel 为假。同时,我安排了一个通知在 1.0 秒后触发(最终将 _cancel 设置为 true)。然后我将 blah 归零。

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

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

2013-02-28 15:30:55.518 Tests[11946:c07] ATest/testBlah
2013-02-28 15:30:56.789 Tests[11946:c07] Re-running: ATest/testBlah
2013-02-28 15:30:56.790 Tests[11946:c07] I'm alive
2013-02-28 15:30:56.790 Tests[11946:c07] dude:0
2013-02-28 15:30:56.791 Tests[11946:c07] scheduled
2013-02-28 15:30:56.791 Tests[11946:c07] logme
2013-02-28 15:30:56.792 Tests[11946:c07] ATest/testBlah ✔ 0.00s
2013-02-28 15:30:56.991 Tests[11946:c07] dude:0
2013-02-28 15:30:56.992 Tests[11946:c07] scheduled
2013-02-28 15:30:56.992 Tests[11946:c07] logme
2013-02-28 15:30:57.193 Tests[11946:c07] dude:0
2013-02-28 15:30:57.194 Tests[11946:c07] scheduled
2013-02-28 15:30:57.194 Tests[11946:c07] logme
2013-02-28 15:30:57.395 Tests[11946:c07] dude:0
2013-02-28 15:30:57.395 Tests[11946:c07] scheduled
2013-02-28 15:30:57.396 Tests[11946:c07] logme
2013-02-28 15:30:57.596 Tests[11946:c07] dude:0
2013-02-28 15:30:57.597 Tests[11946:c07] scheduled
2013-02-28 15:30:57.597 Tests[11946:c07] logme
2013-02-28 15:30:57.792 Tests[11946:c07] reset
2013-02-28 15:30:57.793 Tests[11946:c07] I'm dead
2013-02-28 15:30:57.793 Tests[11946:c07] * -[MyClass doSomething]: message sent to deallocated instance 0xb584880

为什么在 reset 方法中调用 cancelPreviousPerformRequestsWithTarget:self 被释放?

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

最佳答案

很好的问题。我将此称为 NSNotificationCenter 中的错误。这是具有相同行为的代码的简化版本。我们所做的就是让自己设置为收听通知​​,并通过一个强(静态)引用让自己保持活力。当通知关闭时,我们清除该引用。 (在你的情况下,对你的对象的最后一个强引用是在 performSelector: 机制中; performSelector: 的目标被保留,当你取消它时,它被释放它指的是你。)

@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 在调用你的方法之前没有保留你的对象。我会称这是一个错误:在非 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));
}

这样,至少,您可以确定在方法结束之前您不会被释放。

关于objective-c - cancelPreviousPerformRequestsWithTarget 之后的 self 释放,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15146235/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com