나는 최근에 KVO에서 재진입 문제를 접했습니다. 문제를 시각화하기 위해 최소한의 예를 보여주고 싶습니다. 콘솔은 An AppDelegate
클래스Run-to-Completion 의미를 가진 KVO - 가능합니까?
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic) int x;
@end
의 인터페이스뿐만 아니라 구현 예기치
@implementation AppDelegate
- (BOOL) application:(__unused UIApplication *)application
didFinishLaunchingWithOptions:(__unused NSDictionary *)launchOptions
{
__unused BigBugSource *b = [[BigBugSource alloc] initWithAppDelegate:self];
self.x = 42;
NSLog(@"%d", self.x);
return YES;
}
@end
이 프로그램 인쇄 43을 고려한다. 보시다시피
@interface BigBugSource : NSObject {
AppDelegate *appDelegate;
}
@end
@implementation BigBugSource
- (id)initWithAppDelegate:(AppDelegate *)anAppDelegate
{
self = [super init];
if (self) {
appDelegate = anAppDelegate;
[anAppDelegate addObserver:self
forKeyPath:@"x"
options:NSKeyValueObservingOptionNew
context:nil];
}
return self;
}
- (void)dealloc
{
[appDelegate removeObserver:self forKeyPath:@"x"];
}
- (void)observeValueForKeyPath:(__unused NSString *)keyPath
ofObject:(__unused id)object
change:(__unused NSDictionary *)change
context:(__unused void *)context
{
if (appDelegate.x == 42) {
appDelegate.x++;
}
}
@end
이 어떤 다른 클래스 (즉, 타사 코드에있을 수 있습니다 당신이 액세스 할 수없는) 속성에 보이지 않는 관찰자를 등록 할 수 있습니다
는 그 이유는. 이 관찰자는 속성 값이 변경 될 때마다 동기식으로 호출됩니다.
다른 함수를 실행하는 동안 호출이 발생하기 때문에 프로그램이 단일 스레드에서 실행 되더라도 모든 종류의 동시성/다중 스레드 버그가 발생합니다. 더욱이 클라이언트 코드에 명시 적으로주의를 기울이지 않아도 변경이 발생합니다 (OK, 속성을 설정할 때마다 동시성 문제가 발생할 수 있음).
Objective-C에서이 문제를 해결하는 가장 좋은 방법은 무엇입니까?
현재의 방법을 실행하고 불변 완료되면 KVO-관측 메시지, 이벤트 큐를 통해 이동한다는 것을 의미 자동 실행에 완성 의미를 회복하기 위해 몇 가지 일반적인 해결책은 있는가/사후 조건이 복원됩니다?
속성을 나타내지 않습니까?
부울 변수를 가진 객체의 모든 중요한 기능을 보호하여 재진입을 보장 할 수 없습니까? 예를 들어 방법 시작 부분에
assert(!opInProgress); opInProgress = YES;
, 끝 부분에opInProgress = NO;
입니다. 이것은 최소한 실행 중에 직접적으로 그런 종류의 버그를 드러 낼 것입니다.또는 KVO를 어떻게해서든지 거부 할 수 있습니까?
BigBugSource
- (void)observeValueForKeyPath:(__unused NSString *)keyPath ofObject:(__unused id)object change:(__unused NSDictionary *)change context:(__unused void *)context { if (appDelegate.x == 42) { [appDelegate willChangeValueForKey:@"x"]; // << Easily forgotten appDelegate.x++; // Also requires knowledge of [appDelegate didChangeValueForKey:@"x"]; // whether or not appDelegate } // has automatic notifications }
AppDelegate
,369 :+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { if ([key isEqualToString:@"x"]) { return NO; } else { return [super automaticallyNotifiesObserversForKey:key]; } } - (BOOL) application:(__unused UIApplication *)application didFinishLaunchingWithOptions:(__unused NSDictionary *)launchOptions { __unused BigBugSource *b = [[BigBugSource alloc] initWithAppDelegate:self]; [self willChangeValueForKey:@"x"]; self.x = 42; NSLog(@"%d", self.x); // now prints 42 correctly [self didChangeValueForKey:@"x"]; NSLog(@"%d", self.x); // prints 43, that's ok because one can assume that // state changes after a "didChangeValueForKey" return YES; }
업데이트 CRD로 답변에 따라
, 여기에 업데이트 된 코드입니다
이것은 이미 상황을 개선합니다. 그러나 여전히 문제가 있습니다. 제 3 자 라이브러리의 일부이고 자동 알림을 사용하는 클래스가있는 상황을 고려해보십시오. 이 클래스의 인스턴스의 속성을 변경하면 다른 객체가 자신을 관찰자로 등록한 경우 재진입 문제가 다시 발생합니다. 두 번째 문제는 외부에서 속성을 변경하면'willChange'와'didChange'를 호출해야합니다. 코드를 쉽게 잊어 버리기 때문에 오류가 발생하기 쉽습니다. run-to-completion 의미를 지원하는 솔루션이 없습니까? – Etan
@ Etan - 제 3 자 코드의 경우에는 적합하다고 생각되는대로 행동해야합니다. 약간의 작업을하는 코드는 특정 클래스에 대한 알림을 해제 할 때까지 해제 할 수 있습니다. 자동 알림을 끄고 설정자에게'will/didChange '를 추가하면 자동 알림과 동일한 결과가 나타납니다. 그러나 플래그를 추가하면 (set (un)), sethold가 willChange를 큐에 넣고, unset이 대기열에있는 모든 값을 해제 할 때, 당신은 내가 생각하는 의미를가집니다 (1holdNotifications'와 같은 속성을 사용하십시오). 너는 끝이야. – CRD