2010-06-07 3 views
1

(Два изменения следуют исходному телу этого вопроса, оба из которых очень сильно изменяют вопрос. Не зацикливайтесь на первой части этого - в то время как полезно для контекстных целей , Я почти исключил исходную проблему, о которой я просил.)Обновление освобожденного UIWebView из фоновой темы

Как вы можете видеть из названия, я запрограммировал себя в угол, и у меня есть несколько вещей, которые работают против меня ...

В подклассе UIViewController, который управляет большим и сложным видом. Одной из них является UIWebView, который содержит вывод веб-запроса, который мне нужно было создать и выполнить, и вручную собрать 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... 

Несмотря на то, что это говорит о том, что я могу повторяемостью вызвать этот сбой, когда я вернулся слишком рано. Я совсем не убежден, что попытка обновления пользовательского интерфейса из фонового потока действительно является проблемой; Я думаю, что мой UIWebView освобожден. Я подозреваю, что тот факт, что я был только в фоновом потоке, заставляет предположить, что что-то случилось, но я уверен, что это не так.

Итак, как я могу сообщить CLLocationManager, чтобы не беспокоиться об этом, когда я отказываюсь от этой точки зрения? Я пробовал [self.locationManager stopUpdatingLocation] внутри моего метода просмотраWillDisappear, но этого не делал.

(.. Кстати, APIs Mapquest являются Классно WAY лучше, чем что Google предоставляет я не могу рекомендовать их достаточно высоко.)

EDIT 1:

Это только что получил страннее. Я на самом деле изолирован, где он падает, и он находится внизу -(void) dealloc. Если я прокомментирую [super dealloc], я не сбой! Но я не могу «вступить» в [super dealloc], чтобы увидеть, что происходит в суперклассе, он просто падает и перестает разговаривать со мной.

Вот StackTrace:

#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 для возврата.

EDIT 2:

Итак, заметив [UITextView dealloc] в верхней части этого стека, я понял, что есть только один UITextView с этой точки зрения иерархии, так что я закомментирована выпуска этого UITextView объекта в -(void) dealloc. Следующий крушение, было [UIWebView dealloc] в том пятне следа. Гм! Изменение! Поэтому я прокомментировал выпуск одного моего webView в методе dealloc ... и теперь я не сбой. Это не решение, потому что, насколько я могу судить, я просто утечка этих двух объектов сейчас, но это, безусловно, интересно.Или, по крайней мере, я надеюсь, что это кому-то, потому что я полностью в недоумении.

+0

Как уже упоминалось в моем обновлении ниже, я уверен, что ваша проблема на самом деле немного дальше трассировки стека: ваш контроллер быть dealloced, когда он освобождается от фонового потока на доработку. – walkytalky

+0

Еще раз, как указано ниже: thread completes (# 17- # 12), освобождает контроллер (# 11), счетчик удержания переходит в 0, контроллер освобождается (# 10), материал UIKit освобождается по очереди (# 8- # 2), kaboom (# 0). – walkytalky

+0

Я действительно не хочу спорить с кем-то, кто так полезен, но ... Этот след произошел от вызова моего метода dealloc, который произошел, когда я нажал кнопку назад на навигационной панели. Я не понимаю, как это можно уволить в фоновом режиме. Но эй: ты начал с ума меня, и оказалось, что ты совершенно прав, поэтому я заберу свою очередь, чтобы пошутить над вами, и, может быть, вы снова будете правы. Почему черт может быть фоновым потоком быть освобождением от контроллера представления, членом которого он является? И что можно сделать по этому поводу? –

ответ

1

Я думаю, что ваш диагноз, вероятно, неверен, но давайте представим на тот момент, что это не так, и посмотрим, что вы можете с этим поделать.

Если проблема связана с просмотром веб-страницы после освобождения, в ответ на последовательность вызовов, показанных выше, тогда этот вызов будет поступать от updateDirectionsWithHtml: по ссылке в self.directionsWebView. В этом случае вы должны просто убедиться, что вы не держите устаревший указатель, но установите его на nil. Тем не менее, я думаю, что это справедливая ставка, вы делаете это do, так что это маловероятно, что это проблема.

Если проблема связана с доступом к контроллеру после освобождения, диспетчером местоположения, вызывающим метод делегата, необходимо сначала отключить ссылку. Что-то вроде [locationManager setDelegate:nil] должно прекратить нежелательный звонок, даже если менеджер местоположений сам оживляет ваш контроллер. Но опять же, я подозреваю, что вы уже освобождаете менеджера местоположений до того, как контроллер умрет, и это тоже не проблема.

В этом случае проблема наиболее вероятна в другом месте, и, возможно, стоит рассмотреть сообщение об ошибке по номиналу: возможно ли, что где-то вы :, вызывающий UIKit из фонового потока? Говоря о том, что среда исполнения путается по этому поводу, потому что вы были в фоновом потоке раньше, мне кажется, что это просто немного сомнительно для меня. Эта ошибка выглядит так, будто она исходит откуда-то довольно конкретная. Это место могло бы быть ошибкой, но обычно лучше начинать с предположения, что ОС работает лучше, чем собственный код.

Не видя остальной части кода, трудно понять, где эта реальная проблема, если она действительно существует, может быть. Одной из возможностей, которая приходит на ум, является то, что у вас может быть какое-то невиновное использование свойства, которое на самом деле оказывается в интерфейсе пользователя; но я признаю, что это просто праздные спекуляции.

Update:

Если положить фоновый вызов на первый план предотвращает вас от нажатия на кнопку назад, то много времени операции должно быть уже в процессе, который означает, что ваш метод делегата уже должен был называться - - журнал, рассказывающий вам, что просто не проходит.

Одна вещь, которая возникает у меня здесь, заключается в том, что оба performSelectorInBackground и performSelectorOnMainThread сохраняют приемник до тех пор, пока селектор не закончит работать. Таким образом, время освобождения вашего контроллера, вероятно, завинчивается в результате (если вы не заставляете его перевыпускать где-нибудь).

От вашей трассировки стека почти выглядит так, как будто dealloc фактически вызывается из фоновой нити как часть собственной очистки. Затем он вызывает различные вещи UIKit, что объясняет ошибку блокировки потока. Но если вызывается performSelectorOnMainThread, это должно продлить срок службы контроллера до тех пор, пока он не вернется в основной поток. Так, может быть, - перевыпуск в конце концов?

Update 2:

Это обсуждение становится немного рассеянной об этом, но позвольте мне попытаться суммировать то, что я думаю, что происходит:

  • При вызове [self performSelectorInBackground:...], новый поток создается и self отправлено сообщение retainпо теме.
  • Когда-нибудь позже вы нажмете кнопку «Назад» и заставите ваш контроллер получить сообщение release. Однако, поскольку он был сохранен фоновым потоком, он еще не освобождается.
  • Так или иначе фоновый поток заканчивается, и в этот момент он отправляет сообщение release на контроллер. Количество удержаний контроллера уменьшается до 0 и вызывается его метод dealloc. Но поскольку это сообщение исходит из фонового потока, когда он пытается выполнить UI-файл (удаление subviews), вы получаете ошибку блокировки потока.

В оправдание этого последнего бита, который, как я понимаю, может показаться немного странным, обратите внимание, что стек вызовов из аварии показывает вызовы, исходящие из финишной нити, а не из любой обработки событий, связанной с вашей кнопкой возврата ,

Теперь интересный вопрос здесь не почему фоновый поток посылает release, которая является естественной частью его жизненного цикла, но почему это сокращение сохранить счетчик на 0. Если нить на самом деле получить, насколько performSelectorOnMainThread , должно появиться еще одно сообщение retain, и ваш контроллер должен выжить достаточно долго, чтобы dealloc на главной нити, что должно быть хорошо.

Мне кажется, что есть две возможные причины: либо поток выходит преждевременно, либо переизбыточен. Но если бы это было последнее, вы ожидали бы, что self уже достигнет dealloc, прежде чем поток завершится.

Так что я думаю, вопрос в том, есть ли способ, которым updateDirectionsWithHtml выйдет без звонка performSelectorOnMainThread? Или иначе, есть ли что-то еще, что может привести к преждевременному завершению потока?

+0

Я слышу вас громко и ясно обо всем этом @walkytalky. Единственное, что я не сделал, это явно указать делегату моего менеджера на ноль, поэтому позвольте мне попробовать, чтобы удовлетворить мои мысли о том, что я думаю о проблеме. В то же время, любые мысли о полной повторяемости этого сбоя?Я вхожу в мои методы делегата, которые показывают мне в консоли, когда менеджер местоположений меня обновляет, и ясно, что если я удалю свою кнопку назад на navController до того, как это произойдет, мы продолжим бум. Я не вижу, как это может иметь какое-то отношение к фоновым потокам. –

+0

Кстати, я предполагаю, что во фразе «Пробовал получить веб-блокировку» «сеть» относится к моему UIWebView. Я хочу сделать это предположение явным, потому что на самом деле я НЕ ЗНАЮ, что это элемент моего представления, о котором говорится в этом сообщении. –

+0

Моя единственная мысль о повторяемости заключается в том, что она должна * облегчить поиск проблемы, но я ценю, что это не может быть большим утешением прямо сейчас. Еще одна вещь: если вы поместите вызов getDirectionsFromHere в основной поток - или вообще удалите его - все еще проблема? – walkytalky

1

Вы назначили себя делегатом на CLLocationManager. Делегаты почти всегда реализуются как слабые ссылки, то есть они не сохраняются. Это означает, что когда ваш контроллер просмотра выскочил из стека навигации и освобожден, CLLocationManager теперь имеет плохой указатель, и вы получите сбои и всевозможные ужасные проблемы с отладкой.

Стандартная практика заключается в том, чтобы установить делегата CLLocationManager на ноль в вашем методе dealloc, поэтому убедитесь, что он не пытается позвонить вам после того, как вы ушли. то есть:

- (void)dealloc { 
    locationManager.delegate = nil; 
    [locationManager stopUpdatingLocation]; 
    [locationManager release]; 
    // ... 
    [super dealloc]; 
} 

Насколько я могу сказать, ваш performSelectorInBackground т.д. код ОК, все эти вызовы сохраняют приемник, пока селектор не возвращается, так что я не думаю, что есть источник ваших проблем.

+0

Спасибо, но я немного уточнил свой вопрос с той части, на которую вы отвечаете. Взгляните на мои два изменения. (BTW, я вернусь к вершине и заметлю, что OP перед редактированием - это немного goosechase. Извините за это.) –

0

Вы пробовали проверять зомби? Вы говорите: «[не выпускать объекты в dealloc] не является решением, потому что, насколько я могу судить, я просто утечка этих двух объектов сейчас», но он точно описывает, как вы отправляете выпуск уже выпущенному объекту.

Один из способов проверить это переопределить Alloc/dealloc сохранить/релиз в ваших объектах неприятности и протоколировать эти события. Затем вы можете увидеть, где каждый звонит, и, возможно, обнаружите ранее неизвестный выпуск.

1

ОК, я отправлю это как еще один ответ, поскольку мой предыдущий все еще действителен. Вы перевыпускаете пул автоматического выпуска.

[pool drain]; 
[pool release]; 

дренаж идентичен выпуску. От documentation:

В среде с подсчетом ссылок этот метод ведет себя так же, как и выпуск. Поскольку пул авторесурсов не может быть сохранен (см. Сохранение), это приводит к тому, что приемник будет освобожден.

Вы должны только вызывать выпуск или слив, а не оба. Сверхвыдача пула приведет к возникновению двойных и случайных проблем с отладкой.

Вы можете попробовать NSZombieEnabled для дальнейшей диагностики этого.

+0

Спасибо за это, я думал, что должен был сделать оба. Я теперь исправил это, но это не решило крах. –

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 в веб-представление. Когда диспетчер просмотра отключается, вы должны отменить эту вторую операцию. Имейте в виду, что все еще первая операция не может содержать никаких ссылок на контроллер вида.

0

У меня также возникает проблема, когда я нажимаю кнопку «Назад» слишком рано. То, что я сделал это я добавляю [self retain] в теле функции, которая будет вызываться в отдельном потоке и в viewDidUnload добавить [self release]. Просто. Надеюсь, это тоже поможет.

Смежные вопросы