0xc*_*ced 8 macos key-value-observing ios xctest
在我的单元测试中,我使用的-[XCTestCase keyValueObservingExpectationForObject:keyPath:handler:]方法是为了确保我的NSOperation完成,这是我的XCDYouTubeKit项目的代码:
- (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];
}
Run Code Online (Sandbox Code Playgroud)
当我在Mac上本地运行它时,此测试总是通过,但有时它在Travis上失败并出现此错误:
失败:捕获"NSRangeException","无法从<XCDYouTubeVideoOperation 0x1001b9510>中删除关键路径"isFinished"的观察者<_XCKVOExpectation 0x1001846c0>,因为它未注册为观察者."
难道我做错了什么?
0xc*_*ced 10
您的代码是正确的,您在XCTest框架中发现了一个错误.这是一个深入的解释,如果您只是寻找一个解决方法,您可以跳到这个答案的结尾.
当你打电话时keyValueObservingExpectationForObject:keyPath:handler:,_XCKVOExpectation会在引擎盖下创建一个对象.它负责观察您传递的对象/ keyPath.一旦KVO通知被触发,_safelyUnregister就会调用该方法,这是删除观察者的地方.这是该方法的(逆向工程)实现_safelyUnregister.
@implementation _XCKVOExpectation
- (void) _safelyUnregister
{
if (!self.hasUnregistered)
{
[self.observedObject removeObserver:self forKeyPath:self.keyPath];
self.hasUnregistered = YES;
}
}
@end
Run Code Online (Sandbox Code Playgroud)
此方法在结束时再次调用waitForExpectationsWithTimeout:handler:以及当所述_XCKVOExpectation对象被释放.请注意,操作在后台线程上终止,但测试在主线程上运行.所以你有一个竞争条件:如果_safelyUnregister在hasUnregistered属性设置为YES后台线程之前在主线程上调用,则观察者被移除两次,导致无法删除观察者异常.
因此,为了解决此问题,您必须_safelyUnregister使用锁保护该方法.这是一个代码片段供您在测试目标中进行编译,该代码片段将负责修复此错误.
#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);
}
}));
}
Run Code Online (Sandbox Code Playgroud)
编辑
此错误已在Xcode 7 beta 4中修复.
| 归档时间: |
|
| 查看次数: |
1425 次 |
| 最近记录: |