Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/objective-c/22.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/cocoa/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Objective c KVO具有从运行到完成的语义-可能吗?_Objective C_Cocoa_Architecture_Key Value Observing_Reentrancy - Fatal编程技术网

Objective c KVO具有从运行到完成的语义-可能吗?

Objective c KVO具有从运行到完成的语义-可能吗?,objective-c,cocoa,architecture,key-value-observing,reentrancy,Objective C,Cocoa,Architecture,Key Value Observing,Reentrancy,我最近遇到了KVO的重入问题。为了将问题形象化,我想展示一个最小的示例。考虑 AppReaveT//Cube类的接口 @interface AppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @property (nonatomic) int x; @end 意外地,此程序将43打印到控制台 原因如下: @interface BigBugS

我最近遇到了KVO的重入问题。为了将问题形象化,我想展示一个最小的示例。考虑<代码> AppReaveT//Cube类

的接口
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic) int x;
@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
如您所见,某些不同的类(可能在您无权访问的第三方代码中)可能会向属性注册一个不可见的观察者。然后,只要属性的值发生更改,就会同步调用该观察者

由于调用发生在另一个函数的执行过程中,因此尽管程序在单个线程上运行,但会引入各种并发/多线程错误。更糟糕的是,在客户端代码中没有明确的通知就发生了更改(好的,您可以预期在设置属性时会出现并发问题…)

在Objective-C中解决此问题的最佳实践是什么

  • 是否有一些通用的解决方案可以自动恢复从运行到完成的语义,这意味着在当前方法完成执行并恢复不变量/后置条件后,KVO观察消息将通过事件队列

  • 不暴露任何属性

  • 用布尔变量保护对象的每个关键函数以确保不可能重入? 例如:
    assert(!opInProgress);opInProgress=是
    opInProgress=NO。这至少会在运行时直接暴露这些bug

  • 还是有可能以某种方式选择退出KVO

更新

根据CRD的回答,以下是更新的代码:

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
}

您所要求的是手动更改通知,并且由KVO支持。这是一个三阶段的过程:

  • 您的类会自动覆盖
    +(BOOL)notifiesobserversforkey:(NSString*)键
    为您希望延迟通知的任何属性返回
    NO
    ,否则将延迟到
    super
  • 在更改属性之前,请调用
    [self-willChangeValueForKey:key]
    ;及
  • 当您准备好发出通知时,请调用
    [self-didChangeValueForKey:key]
  • 您可以非常轻松地基于此协议进行构建,例如,很容易保存您更改的密钥的记录,并在退出之前触发所有密钥

    如果直接更改属性的支持变量并需要触发KVO,还可以使用
    willChangeValueForKey:
    didChangeValueForKey
    并启用自动通知


    这个过程和一个例子在苹果的文章中有描述。

    这已经改善了这种情况。然而,仍然存在一些问题。考虑这样一种情况,其中有一个类,它是第三方库的一部分,它使用自动通知。当您更改此类实例的属性时,如果另一个对象已将自身注册为观察者,则再次出现可重入性问题。第二个问题是,如果从外部更改属性,还必须调用
    willChange
    didChange
    ,这使得代码很容易出错,因为它很容易被忘记。没有真正支持从运行到完成语义的解决方案吗?@Etan-对于第三方代码,您真的必须让他们按照自己认为合适的方式进行操作。对于您的代码,只需做一点工作,您就可以保留特定类的通知,直到您发布它们-关闭自动通知并将
    will/didChange
    添加到您的setters中,这将使您回到自动通知的等效状态;但是,如果您现在添加一个标志(使用一个属性,比如1holdNotifications`,来(取消)设置它),当设置该标志时,将导致您的设置程序对willChange进行排队,而当unset触发任何排队的willChange时,您就得到了我认为您需要的语义。
    - (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
    }
    
    + (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;
    }