在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
答案 0 :(得分:2)
您不应该在removeFromSuperview
中致电dealloc
。如果UIKit这样做,那就是它的业务,我相信Apple知道它在做什么,但在你的dealloc
中,这是无稽之谈。如果您目前是其他人的子视图,则永远不应该取消分配(因为他们会保留您)。所以充其量只会做什么,最坏的情况会破坏它。
接下来,关于这个:
正如我从搜索文档和SO中所理解的那样,一个不安全的未恢复属性应该在dealloc上使用,就像与iOS 4不兼容的弱引用一样?
这是不正确的。它被称为“不安全”的原因是它不 nil本身。当底层对象被释放时,它什么都不做。这就是他们不安全的原因。如果可能,请避免使用它们。
首先,以这种方式声明您的currentView
属性:
@property(nonatomic, readwrite, strong) UIView* currentView;
其次,使用属性的访问者(self.currentView
,而不是currentView
)。在这种情况下,这不会导致您的问题,但这是一个好习惯,可以为您节省其他麻烦。
最后,崩溃看起来像是dealloc
Splatter
。你在那里做什么?
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))结束时,发送了另一个释放消息。这导致了整个问题。