在 iOS5 上,我的应用程序运行一整天,没有任何问题。在 iOS 4.3 上,只要我尝试切换 View ,应用程序就会开始喷出“修改正在完成的层”,指向刚刚解除分配的 View 的 CALayer,直到我终止程序或它自己崩溃。
我正在构建的应用有大约 8 个小游戏,每个游戏都包含一个 View 。我有一个主视图 Controller ,在那个类中我保留对当前游戏 View 的引用。
UIView* currentView;
主视图完全是空的。通过本质上调用此 View 将 View 加载到其中:
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 中的 View 被释放,所以当我跟随它时
'currentView = nil;' or 'currentView = someOtherView;' or even '[self setCurrentView:bacon];'
应用程序崩溃,因为它试图向 currentView 中已发布的 View 发送另一个版本。
如果我关闭 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 上应该为 nil,就像与 iOS 4 不兼容的弱引用一样?如果是这样,我一定是做错了,因为它仍然试图释放自己两次。
这些调用也都是在使用 performSelectorOnMainThread 调用的方法中进行的,因此我可能在任何时候都不在后台线程中。
我觉得我对 ARC 有一些低层次的误解,我自己无法解决这个问题。有什么想法吗?
哦,还有一件事。我用 -fno-objc-arc 标志编写的一款游戏在 iOS 4 上运行良好,我真的只是希望我不需要返回并将所有迷你游戏从 ARC 转换。
编辑更多信息: 有时我得到的不是 EXC_BAD_ACCES 错误,而是我认为它指向拥有 CALayer 的 UIView,该 CALayer 在“修改正在完成的图层”警告中指向:
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 这样做,那是它的事,我相信 Apple 知道它在做什么,但在你的 dealloc
中,这是胡说八道。如果你目前是其他人的 subview ,你永远不应该被释放(因为他们正在保留你)。所以充其量这不会做任何事情,而在最坏的情况下它会破坏事情。
接下来,关于这个:
As I understand from scouring the docs and SO, an unsafe_unretained property should nil itself on dealloc, just like the weak reference that isn't compatible with iOS 4?
这是不正确的。它被称为“不安全”的原因是它本身不是 nil。当底层对象被释放时,它什么也不做。这就是让他们不安全的原因。尽可能避免使用它们。
首先,以这种方式声明您的 currentView
属性:
@property(nonatomic, readwrite, strong) UIView* currentView;
其次,为您的属性使用访问器(self.currentView
,而不是currentView
)。在这种情况下,这不会引起您的问题,但这是一个好习惯,可以让您避免其他麻烦。
最后,崩溃看起来像是在 Splatter
的 dealloc
中。你在那做什么?
顺便说一句,dealloc
在 ARC 中有点不寻常。它对于从 NSNotificationCenter
或 KVO 中删除自己很有用,或者对于释放 malloc 内存很有用,但在 View 类中通常不需要它。
关于objective-c - 仅在 iOS 4 上的 ARC 中为 "Modifying layer that is being finalized",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9473127/