2015-06-19 33 views
8

, benim NSOperation bitirir sağlamak için -[XCTestCase keyValueObservingExpectationForObject:keyPath:handler:] yöntemi kullanıyorum olduğunu code from my XCDYouTubeKit project:XCTest istisna keyValueObservingExpectationForObject kullanırken burada, sipariş benim birim testlerinde

- (void) testStartingOnBackgroundThread 
{ 
    XCDYouTubeVideoOperation *operation = [[XCDYouTubeVideoOperation alloc] initWithVideoIdentifier:nil languageIdentifier:nil]; 
    [self keyValueObservingExpectationForObject:operation keyPath:@"isFinished" handler:^BOOL(id observedObject, NSDictionary *change) 
    { 
     XCTAssertNil([observedObject video]); 
     XCTAssertNotNil([observedObject error]); 
     return YES; 
    }]; 

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
     XCTAssertFalse([NSThread isMainThread]); 
     [operation start]; 
    }); 
    [self waitForExpectationsWithTimeout:5 handler:nil]; 
} 

Bu test her zaman zaman geçer Ben bu hata ile yerel olarak benim Mac üzerinde ama bazen fails on Travis çalıştırmak: yanlış bir şey yapıyor muyum

failed: caught "NSRangeException", "Cannot remove an observer <_XCKVOExpectation 0x1001846c0> for the key path "isFinished" from <XCDYouTubeVideoOperation 0x1001b9510> because it is not registered as an observer."

?

+1

@ Cœur [Genel Konsensus] (https://meta.stackoverflow.com/questions/274906/should-questions-that-violate-api-terms-of-service-be-flagged) bunun stackoverflow kullanıcılarının veya moderatörlerinin diğer web sitelerinin ToS'sini zorlama sorumluluğu. –

cevap

10

Sizin kodunuz doğru, XCTest çerçevesinde bir hata buldunuz. İşte derinlemesine bir açıklama, sadece bir çözüm arıyorsanız, bu cevabın sonuna atlayabilirsiniz.

keyValueObservingExpectationForObject:keyPath:handler: numaralı telefonu aradığınızda, kapağın altında bir _XCKVOExpectation nesnesi oluşturulur. Geçirdiğiniz nesne/keyPath gözlemlemek için sorumludur. KVO bildirimi tetiklendiğinde, _safelyUnregister yöntemi çağrılır, burada gözlemci kaldırılır. İşte _safelyUnregister yönteminin (ters mühendislik) uygulamasıdır.

@implementation _XCKVOExpectation 

- (void) _safelyUnregister 
{ 
    if (!self.hasUnregistered) 
    { 
     [self.observedObject removeObserver:self forKeyPath:self.keyPath]; 
     self.hasUnregistered = YES; 
    } 
} 

@end 

Bu yöntem

waitForExpectationsWithTimeout:handler: sonunda bir kere daha adlandırılır ve _XCKVOExpectation nesne ayırmanın. İşlemin bir arka plan iş parçacığı üzerinde sona erdiğini ancak testin ana iş parçacığı üzerinde çalıştırıldığını unutmayın. Yani bir yarış durumunuz var: özelliği özelliği arka iş parçacığı için YES olarak ayarlanmadan önce ana iş parçacığı üzerinde çağrılırsa, gözlemci iki kez kaldırılır, bir gözlemcinin bir özel durum kaldırılamıyor. Bu soruna geçici çözüm bulmak için _safelyUnregister yöntemini bir kilitle korumanız gerekir. Bu hatayı düzeltmek için test hedefinizde derlenmeniz için bir kod snippet'i.

#import <objc/runtime.h> 

__attribute__((constructor)) void WorkaroundXCKVOExpectationUnregistrationRaceCondition(void); 
__attribute__((constructor)) void WorkaroundXCKVOExpectationUnregistrationRaceCondition(void) 
{ 
    SEL _safelyUnregisterSEL = sel_getUid("_safelyUnregister"); 
    Method safelyUnregister = class_getInstanceMethod(objc_lookUpClass("_XCKVOExpectation"), _safelyUnregisterSEL); 
    void (*_safelyUnregisterIMP)(id, SEL) = (__typeof__(_safelyUnregisterIMP))method_getImplementation(safelyUnregister); 
    method_setImplementation(safelyUnregister, imp_implementationWithBlock(^(id self) { 
     @synchronized(self) 
     { 
      _safelyUnregisterIMP(self, _safelyUnregisterSEL); 
     } 
    })); 
} 

DÜZENLEME

Bu hata fixed in Xcode 7 beta 4 olmuştur.