Objective c KVO具有从运行到完成的语义-可能吗?
我最近遇到了KVO的重入问题。为了将问题形象化,我想展示一个最小的示例。考虑<代码> AppReaveT//Cube类的接口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
@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
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]
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;
}