使用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.0s之后触发通知(最终将_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测试[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测试[11946: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测试[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
为什么在self
方法中调用cancelPreviousPerformRequestsWithTarget:
后,reset
被解除分配?
此问题是ARC问题还是编码错误?
答案 0 :(得分:1)
整洁的问题。我会称之为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,那么它将加起来很多开销。 (对于在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));
}
这样,至少,您确信在方法结束之前不会取消分配。
答案 1 :(得分:1)
__弱类型(自我)(weakSelf)=自我;而不是保留释放舞蹈:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.sectionIndexBackgroundColor = [UIColor clearColor]; //Replace it with your desired color
}
我更喜欢这样的ARC方式:
CFRetain((__bridge CFTypeRef)(self));
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(foo) object:nil];
[self bar];
CFRelease((__bridge CFTypeRef)(self));
如果第2行自行解除分配,那么第3行的weakSelf将为nil而不是僵尸指针,[nil bar]是安全的。
weakSelf解决方案有两个优点:
1。 在保留释放舞蹈解决方案中,[自我吧]花费了CPU时间,并没有任何意义 2。 弱点看起来比将对象转换为CFType并手动调用retain和release。
P.S。
像任何其他xxxxx舞蹈一样,我想将weakSelf解决方案命名为“哲学舞蹈”。 :)