2015-06-19 19 views
8

Trong các thử nghiệm đơn vị của tôi, tôi đang sử dụng phương pháp -[XCTestCase keyValueObservingExpectationForObject:keyPath:handler:] để đảm bảo rằng NSOperation của tôi kết thúc, đây là code from my XCDYouTubeKit project:ngoại lệ XCTest khi sử dụng keyValueObservingExpectationForObject: keyPath: handler:

- (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]; 
} 

thử nghiệm này luôn đi khi tôi chạy nó cục bộ trên máy Mac của tôi nhưng đôi khi nó fails on Travis với lỗi này:

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."

tôi có làm điều gì sai?

+1

@ Cœur [Sự đồng thuận chung] (https://meta.stackoverflow.com/questions/274906/should-questions-that-violate-api-terms-of-service-be-flagged) là nó không phải là trách nhiệm của người dùng stackoverflow hoặc người kiểm duyệt để thực thi các ToS của các trang web khác. –

Trả lời

10

Mã của bạn là chính xác, bạn đã tìm thấy lỗi trong khung XCTest. Đây là một lời giải thích sâu sắc, bạn có thể bỏ qua đến cuối câu trả lời này nếu bạn chỉ đang tìm cách giải quyết.

Khi bạn gọi keyValueObservingExpectationForObject:keyPath:handler:, một đối tượng _XCKVOExpectation được tạo bên dưới nắp. Nó có trách nhiệm quan sát đối tượng/keyPath bạn đã vượt qua. Khi thông báo KVO đã được kích hoạt, phương thức _safelyUnregister được gọi, đây là nơi mà người quan sát bị loại bỏ. Đây là cách thực hiện (đảo ngược được thiết kế) của phương thức _safelyUnregister.

@implementation _XCKVOExpectation 

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

@end 

Phương pháp này được gọi là một lần nữa vào cuối waitForExpectationsWithTimeout:handler: và khi đối tượng _XCKVOExpectation được deallocated. Lưu ý rằng thao tác chấm dứt trên một chuỗi nền nhưng thử nghiệm được chạy trên luồng chính. Vì vậy, bạn có một điều kiện chủng tộc: nếu _safelyUnregister được gọi trên chủ đề chính trước khi thuộc tính được đặt thành YES trên chủ đề nền, người quan sát bị xóa hai lần, gây ra Không thể xóa ngoại lệ người quan sát.

Vì vậy, để giải quyết vấn đề này, bạn phải bảo vệ phương pháp _safelyUnregister bằng khóa. Dưới đây là một đoạn mã để bạn biên dịch trong mục tiêu thử nghiệm của bạn mà sẽ chăm sóc sửa lỗi này.

#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); 
     } 
    })); 
} 

EDIT

Lỗi này đã được fixed in Xcode 7 beta 4.

Các vấn đề liên quan