仅在 iOS 4 上的 ARC 中"Modifying layer that is being finalized"



在iOS5上,我的应用程序全天运行,没有问题。在 iOS 4.3 上,一旦我尝试切换视图,该应用程序就会开始喷出"正在修改正在完成的图层",指向刚刚被解除分配的视图的 CALayer,直到我杀死程序或它自己崩溃。

我正在构建的应用程序大约有 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 = nil;' or 'currentView = someOtherView;' or even '[self setCurrentView:bacon];'

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

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

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 中了解到的那样,unsafe_unretained属性应该在 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"被打印出来。这是 dealloc 中唯一的代码行,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 的情况下执行此操作会产生以下结果,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

你不应该在dealloc中调用removeFromSuperview。如果UIKit这样做,那就是它的业务,我相信苹果知道它在做什么,但在你dealloc,这是无稽之谈。如果你目前是其他人的子视图,你永远不应该被释放(因为他们会留住你(。所以充其量这不会做任何事情,最坏的情况是它会破坏东西。

接下来,关于这一点:

正如我从搜索文档和 SO 中了解到的那样,unsafe_unretained属性应该在 dealloc 上为零,就像与 iOS 4 不兼容的弱引用一样?

这是不正确的。它被称为"不安全"的原因是它不会自我归零。释放基础对象时,它根本不执行任何操作。这就是使它们不安全的原因。如果可以的话,避免使用它们。

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

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

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

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

顺便说一句,dealloc在 ARC 中有些不寻常。它对于将自己从 NSNotificationCenter 或 KVO 中删除或释放 malloc 内存很有用,但您通常不需要在视图类中使用它。

*

编辑 * 和进一步的解释。按照Rob的建议使用self.currentView也必须这样做。

我建议

@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。(一个是超级视图将持有的,一个是我们强大的属性,一个来自 dnagling 分配/初始化(。在范围结束时,ARC 将从 alloc/init 中释放一个,现在保留计数:2

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

所以前面的答案都没有真正解决这个问题,但它们确实为我指明了正确的方向。

让我来阐述一下所有小游戏都有一个C函数,必须调用Objc方法。为此,我们必须(_bridge(对自身进行引用。我从来没有意识到这一点,但在某些时候,Xcode 建议我使用 (_bridge_transfer( 强制转换而不是 (__bridge( 强制转换。

(_bridge( 工作,当调用切换视图方法的方法(包括 (_bridge_transfer( 强制转换(结束时,将发送另一条发布消息。这导致了整个问题。

相关内容

最新更新