仅在iOS 4上的ARC中“修改正在最终确定的图层”

时间:2012-02-27 22:08:42

标签: objective-c ios xcode calayer automatic-ref-counting

在iOS5上,我的应用程序全天运行,没有问题。在iOS 4.3上,当我尝试切换视图时,应用程序开始喷出“修改正在最终确定的图层”,指向视图的CALayer,直到我杀死程序或它自身崩溃为止。 / p>

我正在构建的应用程序大约有8个迷你游戏,每个游戏都包含一个视图。我有一个主视图控制器,在那个类中我保留了对当前游戏视图的引用。

UIView* currentView;

主视图完全是空的。通过基本上调用以下内容将视图加载到其中:

SomeView*someView = [[SomeView alloc]initWithFrame:self.bounds];
someView.delegate = self;
currentView = someView;
[self.view addSubview:currentView];

我注意到的是,在iOS 4上,在Dealloc上调用了removeFromSuperview,但在iOS 5上却没有。所以我的dealloc方法都是这样的:

NSLog(@"Dealloc Game Name");
if (([[[UIDevice currentDevice] systemVersion] floatValue] > 4.9)){
    [self removeFromSuperview];
}

每次调用

时,似乎都会调用dealloc方法
currentView = nil;

currentView = someOtherView;

这在iOS4和iOS5之间是一致的。

如果我打电话

也是一致的
[currentView removeFromSuperview]; 

currentView中的视图已取消分配,因此当我按照

进行操作时
'currentView = nil;' or 'currentView = someOtherView;' or even '[self setCurrentView:bacon];'

应用程序崩溃,因为它试图将另一个版本发送到currentView中已发布的视图。

如果我关闭NZZombies,我会从EXC_BAD_ACCESS崩溃中获得此Backtrace。

2012-02-27 15:50:46.631 Keyboard[36378:207] Dealloc Splatter
2012-02-27 15:50:46.718 Keyboard[36378:207] modifying layer that is being finalized - 0x5a17900
(gdb) bt
#0  0x001e7b99 in CALayerCommitIfNeeded ()
#1  0x001e7bc4 in CALayerCommitIfNeeded ()
#2  0x001e7bc4 in CALayerCommitIfNeeded ()
#3  0x0018d4f1 in CA::Context::commit_transaction ()
#4  0x0018e294 in CA::Transaction::commit ()
#5  0x0018e46d in CA::Transaction::observer_callback ()
#6  0x0166889b in __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ ()
#7  0x015fd6e7 in __CFRunLoopDoObservers ()
#8  0x015c61d7 in __CFRunLoopRun ()
#9  0x015c5840 in CFRunLoopRunSpecific ()
#10 0x015c5761 in CFRunLoopRunInMode ()
#11 0x021961c4 in GSEventRunModal ()
#12 0x02196289 in GSEventRun ()
#13 0x005f3c93 in UIApplicationMain ()
#14 0x000027d0 in main (argc=1, argv=0xbfffecc4) at /Users/tjfallon/Documents/iOS Projects/Dropbox/Working Directory/Keyboard/Keyboard/main.m:16

我的结果总是一样的,我尝试过几种不同的方式声明currentView,没有属性,也没有属性。

@property(nonatomic,strong)__strong UIView* currentView;

@property(nonatomic,unsafe_unretained) UIView* currentView;

正如我从搜索文档和SO中所理解的那样,一个不安全的未恢复属性应该在dealloc上使用,就像与iOS 4不兼容的弱引用一样?如果是这样,我一定不能这样做,因为它仍然试图释放自己两次。

这些调用也都是在使用performSelectorOnMainThread调用的方法中进行的,所以我可能在任何时候都不在后台线程中。

我觉得我对ARC的误解程度很低,而我自己也无法解决这个问题。有什么想法吗?

哦,还有一件事。我用-fno-objc-arc标志写的一个游戏在iOS 4上切换得很好,我真的只是希望我不需要返回并将所有小游戏转换为ARC。

修改更多信息: 有时而不是EXC_BAD_ACCES错误,我得到这个,我认为这是指向拥有CALayer的UIView,在“正在最终确定的修改层”中指出警告:

malloc: *** error for object 0xa3012e4: incorrect checksum for freed object - object was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debug

此外,分析工具在分析清洁代码时指出了零错误。

更新

我完全按照k1th的建议做了,并逐步完成了代码。 唯一的区别是我使用了self.currentView而不是currentView,并在Rob Napier的建议中为它的属性声明添加了“readwrite”。 它基本上做了同样的事情。在调用[currentView removeFromSuperview]时,会看到您看到的第一个'Dealloc Splatter'。这是此次运行中的唯一代码行,NSLog(@“Dealloc splatter”);

在调用currentView = someView时,再次调用Dealloc,再次打印'Dealloc Splatter',然后在它到达函数末尾时立即崩溃。这是堆栈跟踪。我已经通过三次逐行执行此代码验证了这一点。

2012-02-27 19:13:58.138 Keyboard[36828:207] call switch
2012-02-27 19:14:00.481 Keyboard[36828:207] SwitchViews
2012-02-27 19:14:13.980 Keyboard[36828:207] Dealloc Splatter
2012-02-27 19:14:20.234 Keyboard[36828:207] Dealloc Splatter
(gdb) bt
#0  0x01a43098 in objc_msgSend ()
#1  0x0061e361 in -[UIView dealloc] ()
#2  0x00025818 in -[CanvasView dealloc] (self=0xa330dd0, _cmd=0x57dfea2) at /Users/tjfallon/Documents/iOS Projects/Dropbox/Working Directory/Keyboard/Keyboard/Paint Splatter/CanvasView.m:54
#3  0x000062db in -[MenuViewController setCurrentView:] (self=0xa305170, _cmd=0x415f2, currentView=0x5c36920) at /Users/tjfallon/Documents/iOS Projects/Dropbox/Working Directory/Keyboard/Keyboard/MenuViewController.m:21
#4  0x00004ee9 in -[MenuViewController launchVisualizer:] (self=0xa305170, _cmd=0x41517, sender=0x0) at /Users/tjfallon/Documents/iOS Projects/Dropbox/Working Directory/Keyboard/Keyboard/MenuViewController.m:180
#5  0x00d6befc in -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] ()
#6  0x00d7e506 in -[NSObject(NSThreadPerformAdditions) performSelectorOnMainThread:withObject:waitUntilDone:] ()
#7  0x0000423a in -[MenuViewController switchViews:] (self=0xa305170, _cmd=0x415d3, number=0x5c2c560) at /Users/tjfallon/Documents/iOS Projects/Dropbox/Working Directory/Keyboard/Keyboard/MenuViewController.m:70
#8  0x00d6c94e in __NSThreadPerformPerform ()
#9  0x016688ff in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#10 0x015c688b in __CFRunLoopDoSources0 ()
#11 0x015c5d86 in __CFRunLoopRun ()
#12 0x015c5840 in CFRunLoopRunSpecific ()
#13 0x015c5761 in CFRunLoopRunInMode ()
#14 0x021961c4 in GSEventRunModal ()
#15 0x02196289 in GSEventRun ()
#16 0x005f3c93 in UIApplicationMain ()
#17 0x00002890 in main (argc=1, argv=0xbfffecc4) at /Users/tjfallon/Documents/iOS Projects/Dropbox/Working Directory/Keyboard/Keyboard/main.m:16

使用NSZombies Enabled执行此操作会产生此结果,CanvasView是Splatter,就像0x5c2f020一样:

2012-02-27 19:26:15.480 Keyboard[36856:207] call switch
2012-02-27 19:26:18.072 Keyboard[36856:207] SwitchViews
2012-02-27 19:26:20.921 Keyboard[36856:207] Dealloc Splatter
2012-02-27 19:26:23.884 Keyboard[36856:207] *** -[CanvasView release]: message sent to deallocated instance 0x5c2f020
2012-02-27 19:26:28.365 Keyboard[36856:207] *** NSInvocation: warning: object 0x5c2f020 of class '_NSZombie_CanvasView' does not implement methodSignatureForSelector: -- trouble ahead
2012-02-27 19:26:28.365 Keyboard[36856:207] *** NSInvocation: warning: object 0x5c2f020 of class '_NSZombie_CanvasView' does not implement doesNotRecognizeSelector: -- abort

这是它的回溯

#0  0x015f8709 in ___forwarding___ ()
#1  0x015f8522 in __forwarding_prep_0___ ()
#2  0x000062db in -[MenuViewController setCurrentView:] (self=0xab0aad0, _cmd=0x415f2, currentView=0x5c1f9e0) at /Users/tjfallon/Documents/iOS Projects/Dropbox/Working Directory/Keyboard/Keyboard/MenuViewController.m:21
#3  0x00004ee9 in -[MenuViewController launchVisualizer:] (self=0xab0aad0, _cmd=0x41517, sender=0x0) at /Users/tjfallon/Documents/iOS Projects/Dropbox/Working Directory/Keyboard/Keyboard/MenuViewController.m:180
#4  0x00d6befc in -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] ()
#5  0x00d7e506 in -[NSObject(NSThreadPerformAdditions) performSelectorOnMainThread:withObject:waitUntilDone:] ()
#6  0x0000423a in -[MenuViewController switchViews:] (self=0xab0aad0, _cmd=0x415d3, number=0x5d2a960) at /Users/tjfallon/Documents/iOS Projects/Dropbox/Working Directory/Keyboard/Keyboard/MenuViewController.m:70
#7  0x00d6c94e in __NSThreadPerformPerform ()
#8  0x016688ff in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ ()
#9  0x015c688b in __CFRunLoopDoSources0 ()
#10 0x015c5d86 in __CFRunLoopRun ()
#11 0x015c5840 in CFRunLoopRunSpecific ()
#12 0x015c5761 in CFRunLoopRunInMode ()
#13 0x021961c4 in GSEventRunModal ()
#14 0x02196289 in GSEventRun ()
#15 0x005f3c93 in UIApplicationMain ()
#16 0x00002890 in main (argc=1, argv=0xbfffec98) at /Users/tjfallon/Documents/iOS Projects/Dropbox/Working Directory/Keyboard/Keyboard/main.m:16

3 个答案:

答案 0 :(得分:2)

您不应该在removeFromSuperview中致电dealloc。如果UIKit这样做,那就是它的业务,我相信Apple知道它在做什么,但在你的dealloc中,这是无稽之谈。如果您目前是其他人的子视图,则永远不应该取消分配(因为他们会保留您)。所以充其量只会做什么,最坏的情况会破坏它。

接下来,关于这个:

  

正如我从搜索文档和SO中所理解的那样,一个不安全的未恢复属性应该在dealloc上使用,就像与iOS 4不兼容的弱引用一样?

这是不正确的。它被称为“不安全”的原因是它 nil本身。当底层对象被释放时,它什么都不做。这就是他们不安全的原因。如果可能,请避免使用它们。

首先,以这种方式声明您的currentView属性:

@property(nonatomic, readwrite, strong) UIView* currentView;

其次,使用属性的访问者(self.currentView,而不是currentView)。在这种情况下,这不会导致您的问题,但这是一个好习惯,可以为您节省其他麻烦。

最后,崩溃看起来像是dealloc Splatter。你在那里做什么?

BT,dealloc在ARC中有点不寻常。从NSNotificationCenter或KVO中删除自己,或者释放malloc内存非常有用,但在视图类中通常不需要它。

答案 1 :(得分:1)

*编辑* 以及进一步的解释。使用self.currentView作为Rob建议也必须完成。

我建议

@property(nonatomic,strong) UIView* currentView;

现在是一个较短的版本,它做同样的

[self.currentView removeFromSuperView];

现在currentView指向的不再是视图层次结构,但尚未解除分配,因为我们对它有强烈的引用。如果我们将其声明为“弱”或“__unsafe_unretained”,它将在未来的未知时间被系统解除分配(或者可能立即 - 取决于iOS版本)。

self.currentView = [[SomeView alloc]initWithFrame:self.bounds];
self.currentView.delegate = self;

这做了一些事情。
旧的self.currentView由系统发布,因为superview不再引用它(已被我们从superview中删除)
SomeView被创建,指针分配给self.currentView并保留 - 当前保留计数:2,没有ARC,我们在这里丢失了一个指针....

[self.view addSubview:self.currentView];   

这会将视图添加到层​​次结构中,这会将保留计数增加到3.(superview将保留一个,一个用于我们的强属性,一个用于dnagling alloc / init)。 在范围的最后,ARC将从alloc / init释放一个,现在保留计数:2

所以没有必要在-dealloc中做任何时髦的事情。即使使用ARC,跟踪(概念上)保留计数也是有意义的。

答案 2 :(得分:0)

因此,之前的答案都没有真正解决问题,但他们确实指出了我正确的方向。

让我说出来 所有的迷你游戏都有一个C函数,必须调用Objc方法。为此,我们必须(_ bridge)对自己进行引用。我从来没有意识到这一点,但在某些时候Xcode建议我使用( _bridge_transfer)强制转换而不是(__bridge)强制转换。

(_ bridge)工作,当调用切换视图方法的方法(包括( _bridge_transfer))结束时,发送了另一个释放消息。这导致了整个问题。