2011-10-20 9 views
7

La mia app si arresta in modo anomalo in iOS 5 perché ho un codice che chiama istanze UIKit da un thread secondario. Sai che hai questo problema quando vedi il seguente errore:Come posso trovare le chiamate alle istanze UIKit da un thread secondario?

bool _WebTryThreadLock (bool), 0x811bf20: blocchi multipli su thread Web non consentiti! Si prega di presentare un bug. Crashing now ...

Quindi la mia domanda è quali sono alcuni modi in cui riesco a trovare il codice che chiama le istanze UIKit da un thread secondario?

Qui ci sono alcune cose che ho già provato:

  1. commentata blocchi che potrebbero violare la regola
  2. Aggiunto assert([NSThread isMainThread]) in posti che potrebbe essere l'elaborazione in thread secondario
  3. Aggiunto un punto di interruzione simbolica per _WebTryThreadLock

Queste cose mi hanno aiutato a trovare aree problematiche. Tuttavia, nel mio arresto finale il punto di interruzione _WebTryThreadLock non ha traccia di stack in nessuno degli altri thread. Quindi, come posso trovare il codice che causa il problema senza traccia dello stack?

Grazie per il vostro tempo!

+0

Questo potrebbe essere un bug simulatore.. Il problema si verifica anche durante l'esecuzione su un dispositivo iOS reale? –

+0

Sì, si verifica in iOS 5 Simulator e dispositivi iOS con iOS 5. – johnnieb

+0

Ah, va bene.Una rapida ricerca su Google di quel messaggio di errore mi ha portato a credere che fosse un bug del simulatore, ma se si verifica altrove, non ne sono del tutto sicuro. Per caso hai 2+ UIWebViews visualizzate sullo schermo/caricate nello stesso momento? –

risposta

3

Il tuo assert() è probabilmente lo strumento più prezioso in questo. Sono stato conosciuto per mettere un'asserzione simile all'inizio di ogni metodo nelle mie lezioni di Controller. Se questo non lo trova, aggiungo l'asserzione alle mie classi View. Se non lo trova, lo aggiungo a qualsiasi classe Model che penso sia solo per main-thread.

Per il commento di @ craig, il fatto che si tratti di un bug interno potrebbe essere accurato. Ma penso che tu sia sulla strada giusta per esaminare da vicino il tuo codice.

1

Questo problema è dovuto al fatto che si desidera accedere all'interfaccia utente dal thread secondario in qualche modo, dal webview di qualsiasi altra cosa. Non è permesso perché UIKit non è thread-safe e può essere consultato solo da MainThread. La prima cosa che si può fare è cambiare la chiamata filo a [self performSelectorOnMainThread:@selector(myMethod) withObject:nil waitUntilDone:NO]; (cercare documentazione). Nel caso in cui non si ha altra scelta è possibile utilizzare GCD (Grand Central Dispathc) ...

1

Questo codice (basta aggiungere al progetto e compilare questo file senza ARC) fa sì che affermazioni sulla UIKit accesso esterno il filo conduttore: https://gist.github.com/steipete/5664345

L'ho appena usato per raccogliere numerosi problemi relativi a UIKit/thread principale in un codice che ho appena raccolto.

3

Ho adattato PSPDFUIKitMainThreadGuard.m per consentire a uno di non doversi preoccupare di queste cose. Qui: https://gist.github.com/k3zi/98ca835b15077d11dafc:

#import <objc/runtime.h> 
#import <objc/message.h> 

// Compile-time selector checks. 

#define PROPERTY(propName) NSStringFromSelector(@selector(propName)) 

// A better assert. NSAssert is too runtime dependant, and assert() doesn't log. 
// http://www.mikeash.com/pyblog/friday-qa-2013-05-03-proper-use-of-asserts.html 
// Accepts both: 
// - PSPDFAssert(x > 0); 
// - PSPDFAssert(y > 3, @"Bad value for y"); 
#define PSPDFAssert(expression, ...) \ 
do { if(!(expression)) { \ 
NSLog(@"%@", [NSString stringWithFormat: @"Assertion failure: %s in %s on line %s:%d. %@", #expression, __PRETTY_FUNCTION__, __FILE__, __LINE__, [NSString stringWithFormat:@"" __VA_ARGS__]]); \ 
abort(); }} while(0) 

/////////////////////////////////////////////////////////////////////////////////////////// 
#pragma mark - Helper for Swizzling 

BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) { 
    PSPDFAssert(c && origSEL && newSEL && block); 
    Method origMethod = class_getInstanceMethod(c, origSEL); 
    const char *encoding = method_getTypeEncoding(origMethod); 

    // Add the new method. 
    IMP impl = imp_implementationWithBlock(block); 
    if (!class_addMethod(c, newSEL, impl, encoding)) { 
     NSLog(@"Failed to add method: %@ on %@", NSStringFromSelector(newSEL), c); 
     return NO; 
    }else { 
     // Ensure the new selector has the same parameters as the existing selector. 
     Method newMethod = class_getInstanceMethod(c, newSEL); 
     PSPDFAssert(strcmp(method_getTypeEncoding(origMethod), method_getTypeEncoding(newMethod)) == 0, @"Encoding must be the same."); 

     // If original doesn't implement the method we want to swizzle, create it. 
     if (class_addMethod(c, origSEL, method_getImplementation(newMethod), encoding)) { 
      class_replaceMethod(c, newSEL, method_getImplementation(origMethod), encoding); 
     }else { 
      method_exchangeImplementations(origMethod, newMethod); 
     } 
    } 
    return YES; 
} 

// This installs a small guard that checks for the most common threading-errors in UIKit. 
// This won't really slow down performance but still only is compiled in DEBUG versions of PSPDFKit. 
// @note No private API is used here. 
__attribute__((constructor)) static void PSPDFUIKitMainThreadGuard(void) { 
    @autoreleasepool { 
     for (NSString *selStr in @[PROPERTY(setNeedsLayout), PROPERTY(setNeedsDisplay), PROPERTY(setNeedsDisplayInRect:)]) { 
      SEL selector = NSSelectorFromString(selStr); 
      SEL newSelector = NSSelectorFromString([NSString stringWithFormat:@"pspdf_%@", selStr]); 
      if ([selStr hasSuffix:@":"]) { 
       PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self, CGRect r) { 
        if(!NSThread.isMainThread){ 
         dispatch_async(dispatch_get_main_queue(), ^{ 
          ((void (*)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r); 
         }); 
        }else{ 
         ((void (*)(id, SEL, CGRect))objc_msgSend)(_self, newSelector, r); 
        } 
       }); 
      }else { 
       PSPDFReplaceMethodWithBlock(UIView.class, selector, newSelector, ^(__unsafe_unretained UIView *_self) { 
        if(!NSThread.isMainThread){ 
         dispatch_async(dispatch_get_main_queue(), ^{ 
          ((void (*)(id, SEL))objc_msgSend)(_self, newSelector); 
         }); 
        }else 
         ((void (*)(id, SEL))objc_msgSend)(_self, newSelector); 
       }); 
      } 
     } 
    } 
} 

calci automaticamente le chiamate verso il thread principale e quindi non sarebbe nemmeno bisogno di fare altro che plop il codice in

+0

il tuo istinto non è più lì –

+0

@AdamMendoza mi dispiace, appena aggiornato il link – kezi

Problemi correlati