3

나는 최근에 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

    + (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; 
    } 
    
    ,369 :

업데이트 CRD로 답변에 따라

, 여기에 업데이트 된 코드입니다

답변

3

당신이 요구하는 것은 수동 변경 알림이며 KVO가 지원합니다.그것은 3 단계 프로세스입니다

  1. 클래스가 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey 당신이 알림을 연기하고자하는 속성에 대한 NO를 반환하고, 그렇지 않으면 super에 연기 무시;
  2. 속성을 변경하기 전에 [self willChangeValueForKey:key]을 호출하십시오. 및
  3. 준비가되면 알림 당신이 아주 쉽게 [self didChangeValueForKey:key]

당신은이 프로토콜을 구축 할 수 있습니다 예를 들어, 전화를 발생하려면 나가기 전에 변경 한 키를 기록하고 모두 트리거하는 것은 쉽습니다. 직접 속성의 후원 변수를 변경하고 KVO를 실행해야하는 경우

또한 자동 알림과 willChangeValueForKey:didChangeValueForKey이에 을 설정 할 수 있습니다.

예제와 함께 프로세스는 Apple의 documentation에 설명되어 있습니다.

+0

이것은 이미 상황을 개선합니다. 그러나 여전히 문제가 있습니다. 제 3 자 라이브러리의 일부이고 자동 알림을 사용하는 클래스가있는 상황을 고려해보십시오. 이 클래스의 인스턴스의 속성을 변경하면 다른 객체가 자신을 관찰자로 등록한 경우 재진입 문제가 다시 발생합니다. 두 번째 문제는 외부에서 속성을 변경하면'willChange'와'didChange'를 호출해야합니다. 코드를 쉽게 잊어 버리기 때문에 오류가 발생하기 쉽습니다. run-to-completion 의미를 지원하는 솔루션이 없습니까? – Etan

+0

@ Etan - 제 3 자 코드의 경우에는 적합하다고 생각되는대로 행동해야합니다. 약간의 작업을하는 코드는 특정 클래스에 대한 알림을 해제 할 때까지 해제 할 수 있습니다. 자동 알림을 끄고 설정자에게'will/didChange '를 추가하면 자동 알림과 동일한 결과가 나타납니다. 그러나 플래그를 추가하면 (set (un)), sethold가 willChange를 큐에 넣고, unset이 대기열에있는 모든 값을 해제 할 때, 당신은 내가 생각하는 의미를가집니다 (1holdNotifications'와 같은 속성을 사용하십시오). 너는 끝이야. – CRD