(两个编辑跟随这个问题的原始主体,两者都非常彻底地修改了这个问题。不要挂在第一部分 - 虽然有用于上下文目的,但我已经排除了我问的原始问题。)
正如你从标题中看到的那样,我已经把自己编程到一个角落,我有几件事情对我不利......
在UIViewController子类中管理大而复杂的视图。其中一部分是UIWebView,它包含我必须构建和执行的Web请求的输出,并从中手动汇编HTML。由于运行需要一两秒钟,因此我通过调用self performSelectorInBackground:
将其放入后台。然后从我调用那个方法,我使用self performSelectorOnMainThread:
返回到线程堆栈的表面,用我刚刚得到的更新UIWebView。
像这样(我已经减少只显示相关问题):
-(void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
//then get mapquest directions
NSLog(@"Got called to handle new location!");
[manager stopUpdatingLocation];
[self performSelectorInBackground:@selector(getDirectionsFromHere:) withObject:newLocation];
}
- (void)getDirectionsFromHere:(CLLocation *)newLocation
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CLLocationCoordinate2D here = newLocation.coordinate;
// assemble a call to the MapQuest directions API in NSString *dirURL
// ...cut for brevity
NSLog(@"Query is %@", dirURL);
NSString *response = [NSString stringWithContentsOfURL:[NSURL URLWithString:dirURL] encoding:NSUTF8StringEncoding error:NULL];
NSMutableString *directionsOutput = [[NSMutableString alloc] init];
// assemble response into an HTML table in NSString *directionsOutput
// ...cut for brevity
[self performSelectorOnMainThread:@selector(updateDirectionsWithHtml:) withObject:directionsOutput waitUntilDone:NO];
[directionsOutput release];
[pool drain];
[pool release];
}
- (void)updateDirectionsWithHtml:(NSString *)directionsOutput
{
[self.directionsWebView loadHTMLString:directionsOutput baseURL:nil];
}
这一切都非常好,除非我在CLLocationManager命中它的委托方法之前退出了这个视图控制器。如果在我离开此视图后发生这种情况,我会得到:
2010-06-07 16:38:08.508 EverWondr[180:760b] bool _WebTryThreadLock(bool), 0x1b6830: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
尽管如此,但是当我太早退出时,我可以重复地导致这次崩溃。我完全不相信从后台线程尝试UI更新是真正的问题;我认为我的UIWebView已经解除分配。我怀疑我只是在后台线程这一事实让运行时怀疑某些事情,但我觉得不是这样。
那么当我退出该视图时,如何告诉CLLocationManager不要担心它?我在viewWillDisappear方法中尝试了[self.locationManager stopUpdatingLocation]
,但是没有这样做。
(顺便提一下,MapQuest的apis非常棒。比Google提供的任何东西都好。我不能高度推荐它们。)
编辑1:
这只是更奇怪了。我实际上已经隔离了崩溃的地方,它位于-(void) dealloc
的底部。如果我评论[super dealloc]
,我不会崩溃!但我似乎无法“进入”[super dealloc]
看到超类中发生了什么,它只是崩溃并停止与我交谈。
这是一个堆栈跟踪:
#0 0x3018c380 in _WebTryThreadLock ()
#1 0x3018cac8 in _WebThreadAutoLock ()
#2 0x3256e4f0 in -[UITextView dealloc] ()
#3 0x323d7640 in -[NSObject release] ()
#4 0x324adb34 in -[UIView(Hierarchy) removeFromSuperview] ()
#5 0x3256e4a8 in -[UIScrollView removeFromSuperview] ()
#6 0x3256e3e4 in -[UITextView removeFromSuperview] ()
#7 0x324ffa24 in -[UIView dealloc] ()
#8 0x323d7640 in -[NSObject release] ()
#9 0x3256dd64 in -[UIViewController dealloc] ()
#10 0x0000c236 in -[EventsDetailViewController dealloc] (self=0x1c8360, _cmd=0x3321ff2c) at /Users/danielray/Documents/Xcode Projects/EverWondr svn/source/obj-c/Classes/EventsDetailViewController.m:616
#11 0x323d7640 in -[NSObject release] ()
#12 0x336e0352 in __NSFinalizeThreadData ()
#13 0x33ad8e44 in _pthread_tsd_cleanup ()
#14 0x33ad8948 in _pthread_exit ()
#15 0x33731f02 in +[NSThread exit] ()
#16 0x336dfd2a in __NSThread__main__ ()
#17 0x33ad8788 in _pthread_body ()
#18 0x00000000 in ?? ()
此外,现在很清楚,当[self getDirectionsFromHere]
正在运行时触发后退按钮时会发生错误。我们正在等待stringWithContentsOfURL
调用返回。
编辑2:
好的,所以注意到该堆栈顶部附近的[UITextView dealloc]
,我意识到我在这个视图层次结构上只有一个UITextView,所以我在-(void) dealloc
中注释掉了该UITextView对象的发布。接下来的崩溃,在跟踪点上有[UIWebView dealloc]
。哼!变化!所以我在dealloc方法中注释掉了我的一个webView版本......现在我没有崩溃。这不是一个解决方案,因为据我所知,我现在只是泄漏这两个对象,但它确实很有趣。或者至少,我希望是对某人,因为我完全不知所措。
答案 0 :(得分:1)
我认为你的诊断可能是错误的,但让我们想象一下它不是,看看你会对它做些什么。
如果问题是在解除分配后访问的Web视图,为了响应上面显示的调用序列,那么该调用将来自updateDirectionsWithHtml:
self.directionsWebView
中的引用。在这种情况下,您应该确保不要保持陈旧指针,而是将其设置为nil
。但是,我认为你已经做这样做是公平的,所以这不太可能是问题所在。
如果问题是在解除分配后访问控制器,由位置管理器调用委托方法,那么您应该确保先断开链接。像[locationManager setDelegate:nil]
之类的东西应该停止不必要的呼叫,即使位置管理器本身比你的控制器更长。但同样,我怀疑你已经在控制器死之前释放了位置管理器,这也不是问题。
在这种情况下,问题很可能在其他地方,并且可能值得考虑面值的错误消息:是否有可能 从某个后台线程调用UIKit?说运行时对此感到困惑,因为你之前的后台线程对我来说只是一个小问题。这个错误看起来像来自某个相当具体的地方。那个地方可能是错误的,但通常最好先假设操作系统比自己的代码更好。
如果没有看到其余的代码,很难知道这个真正的问题在哪里,如果确实存在的话,可能就是这样。让人想起的一种可能性是,你可能会对某个属性进行一些无辜的使用,而这个属性实际上已经在某个地方触及了UI;但我承认这只是空闲的猜测。
<强>更新强>
如果将后台调用放到前台会阻止您按下后退按钮,那么耗时的操作必须已经在进行中,这意味着您的委托方法必须已经被调用 - 日志记录告诉您所以只是'通过。
我在这里遇到的一件事是performSelectorInBackground
和performSelectorOnMainThread
都保留了接收器,直到选择器运行完毕。因此,控制器释放的时间可能因此被搞砸了(除非你在某个地方通过过度释放强制它)。
从堆栈跟踪中,它几乎看起来好像dealloc
实际上是从后台线程中调用的,作为其自身清理的一部分。然后调用各种UIKit事件,这将解释线程锁定错误。但是如果调用performSelectorOnMainThread
那么应该延长控制器的生命周期,直到它回到主线程上。那么也许 毕竟是过度释放?
更新2:
这个讨论有点分散,但让我试着总结一下我的想法:
[self performSelectorInBackground:...]
时,会创建一个新主题并通过主题向self
发送retain
条消息。release
。但是,因为它是由后台线程保留的,所以它还没有被释放。release
消息。控制器的保留计数降至0,并调用其dealloc
方法。但由于此消息来自后台线程,当它尝试执行UI内容(删除子视图)时,会出现线程锁定错误。在最后一点的理由中,我理解这看起来有点奇怪,请注意崩溃中的调用堆栈显示来自线程完成的调用,而不是来自与后退按钮相关的任何事件处理。
现在,这里有趣的问题不是后台线程发送release
的原因,这是其生命周期的一个自然部分,但为什么这会将保留计数减少到0.如果线程实际上是远至performSelectorOnMainThread
,应该还有另一条retain
消息,并且你的控制器应该存活足够长时间以释放主线程,这应该没问题。
在我看来,有两个可能的原因:要么是线程提前退出,要么是过度释放。但如果是后者,那么在线程终止之前,你会期望self
已经命中dealloc
。
所以我想问题是,updateDirectionsWithHtml
是否会在不调用performSelectorOnMainThread
的情况下退出?或者,是否有其他东西会导致线程过早终止?
答案 1 :(得分:1)
您已将自己设置为CLLocationManager
上的委托。代表几乎总是被实现为弱引用,这意味着它们不会被保留。这意味着当您的视图控制器弹出导航堆栈并释放时,CLLocationManager
现在有一个错误的指针,您将遇到崩溃和各种可怕的难以调试的问题。
标准做法是在CLLocationManager
方法中将dealloc
的委托设置为nil,从而确保在您离开后不会试图给您打电话。即:
- (void)dealloc {
locationManager.delegate = nil;
[locationManager stopUpdatingLocation];
[locationManager release];
// ...
[super dealloc];
}
据我所知,performSelectorInBackground
等代码没问题,所有这些调用都会保留接收器,直到选择器返回,所以我不认为这是你问题的根源。
答案 2 :(得分:1)
好的,我会将此作为另一个答案发布,因为我之前的答案仍然有效。您过度释放了自动释放池。
[pool drain];
[pool release];
排水与释放相同。来自documentation:
在引用计数环境中,此方法的行为与发布相同。由于无法保留自动释放池(请参阅保留),因此会导致接收器被解除分配。
你应该只调用释放或消耗,而不是两者。过度释放池将导致双重免费且随机难以调试的问题。
您可能需要尝试NSZombieEnabled进一步诊断。
答案 3 :(得分:1)
问题是最终版本(因此)dealloc是在主线程以外的线程上调用的。有几种方法可以处理这种情况。一种是对具有对视图控制器的弱引用的不同对象进行后台处理。它可能看起来像:
@interface MyAppViewController : UIViewController {
MyAppLocationUpdater *locationUpdater;
}
@property MyAppLocationUpdater* locationUpdater;
@end
@implementation MyAppViewController
@synthesize locationUpdater;
-(void)alloc
{
[super alloc];
locationUpdater = [[MyAppLocationUpdator alloc] init];
locationUpdater.viewController = self;
}
-(void)dealloc
{
locationUpdater.viewController = nil;
[locationUpdater release];
}
-(void)locationManager:(CLLocationManager*)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
[manager stopUpdatingLocation];
[locationUpdater performSelectorInBackgroundThread: @selector(getDirectionsFromHere:) withObject:newLocation];
}
@end
@interface MyAppLocationUpdater : NSObject {
// this reference is not retained or released, the view controller is responsible for clearing the field when it is deallocating
volatile MyAppViewControler *viewController;
}
-(void)getDirectionsFromHere:(CLLocation *)newLocation;
@end
@implementation MyAppLocationUpdater
-(void)getDirectionsFromHere:(CLLocation *)newLocation
{
// same stuff as before except
if (viewController != nil) {
[viewController performSelectorOnMainThread:@selector(updateDirectionsWithHtml:) withObject:directionsOutput waitUntilDone:NO];
}
// release pool as before
}
@end
可能会简化使用NSOperation和NSOperationQueue的事情。在这种情况下,您将设置一个操作来创建HTML字符串,第二个操作依赖于第一个操作并将HTML放入Web视图中。当视图控制器取消分配时,您应该取消第二个操作。请记住,第一个操作仍然无法保留对视图控制器的任何引用。
答案 4 :(得分:0)
你试过打开僵尸检查吗?你说“[不释放dealloc中的对象]不是一个解决方案,因为据我所知,我现在只是泄漏这两个对象”,但它确实描述了你正在向已经发布的对象发送释放。
检查此问题的一种方法是覆盖故障对象中的alloc / dealloc retain / release并记录这些事件。然后你可以看到每个被调用的地方,也许还可以发现迄今未知的版本。
答案 5 :(得分:0)
当我太早点击后退按钮时,我也遇到了这个问题。
我所做的是在函数体中添加一个[self retain]
,它将在一个单独的线程上调用,并在viewDidUnload
中添加[self release]
。简单。希望这也有帮助。