【问题标题】:Is it possible to set up KVO notifications for static variables in objective C?是否可以在目标 C 中为静态变量设置 KVO 通知?
【发布时间】:2015-05-23 13:13:59
【问题描述】:

我有 A 类,实例变量从缓存数据派生属性,这个缓存数据表示为单例并且是 A 的一部分(它本质上是一个 NSDictionary)。有时,此缓存会被修改。

发生这种情况时,我希望所有 A 实例在下次访问它们的属性时从缓存中提取新信息,或者换句话说,使它们的属性内容无效。

到目前为止,每个实例都是手动通知的(使用静态数组来跟踪成员)。我不是粉丝。通知中心可能有一个选项,但我宁愿尝试使用 KVO。

有没有人设法从 iOS 上的类变量订阅 KVO 更改? (换句话说,使用 A 的静态变量的变化来告诉 A 实例刷新它们的数据。)

换句话说,我很想拥有

static void* context = &context;
static myHadHocObject* msignal;

及更高版本的 A 类代码

[msignal addObserver:self forKeyPath:@"operation" options:NSKeyValueObservingOptionNew context:context];

并通过

通知类实例中的 msignal 更改
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{

}

我尝试使用各种 had hoc 课程,但没有成功。好像我错过了什么。

欢迎指点,谢谢!

【问题讨论】:

  • 如果它不是静态的,你能观察到它的变化吗?将其设为静态不应影响 KVO,因此我认为该事件更有可能永远不会被触发。
  • Objective-C 没有“类变量”。
  • @user102008 同上。对静态进行了更改。 chedabob,你是对的,但由于某些原因,我看不出问题出在哪里。 meligaletiko 提出了一种比我使用的更清洁的方法。

标签: ios objective-c design-patterns key-value-observing


【解决方案1】:

KVO 表示特定对象观察另一个对象(或自身)的属性的变化。所以我认为你想要实现的目标是不可能的,至少不是这样。或者至少你需要更深入地了解 KVO 机制。

您可以从Apple Key-Value Observing Programming Guide获得回答问题所需的所有信息

与使用 NSNotificationCenter 的通知不同,没有 为所有观察者提供更改通知的中心对象。 相反,通知直接发送到观察对象 进行了更改。 NSObject 提供了 键值观察,你应该很少需要覆盖这些 方法。

您可以使用- willChangeValueForKey:- didChangeValueForKey: 触发KVO。你可以在NSKeyValueObserving Protocol Reference阅读更多相关信息

我建议您使用不同的方法,通过创建一个管理器来管理您的缓存并观察该缓存上的值,这只是一个示例。

CacheManager.h

#import <Foundation/Foundation.h>

@interface CacheManager : NSObject

@property (nonatomic, strong, readonly) NSArray *data;

+ (instancetype)sharedManager;

@end

CacheManager.m

#import "CacheManager.h"

@implementation CacheManager

- (instancetype)init {
    if (self = [super init]) {
        _data = [[NSArray alloc] init];
    }

    return self;
}

+ (instancetype)sharedManager {
    static CacheManager *selfManager;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        selfManager = [[[self class] alloc] init];
    });

    return selfManager;
}

@end

ViewController.m

#import "ViewController.h"

#import "CacheManager.h"

static void *CacheManagerDataChangedContext = &CacheManagerDataChangedContext;

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    CacheManager *cacheManager = [CacheManager sharedManager];

    [cacheManager addObserver:self forKeyPath:NSStringFromSelector(@selector(data)) options:NSKeyValueObservingOptionNew context:CacheManagerDataChangedContext];
}

- (void)dealloc {
    [[CacheManager sharedManager] removeObserver:self forKeyPath:NSStringFromSelector(@selector(data)) context:CacheManagerDataChangedContext];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if (context == CacheManagerDataChangedContext) {
        <# your stuff #>
    }

    else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

@end

如果您从所有其他实例中观察到 CacheManagerdata 属性,则所有该实例都会在更改时收到通知。

希望有所帮助;)

【讨论】:

  • 非常感谢!我有一个缓存管理器,但显然我错过了使用上下文句柄通知适当对象的要点。很好的建议,谢谢!
  • mmm..观察者是'self',接收者是cacheManger,不是吗?
  • 是的,你是对的,对不起。应该是[cacheManager addObserver:self forKeyPath:NSStringFromSelector(@selector(data)) options:NSKeyValueObservingOptionNew context:CacheManagerDataChangedContext];
  • 为了完整起见,我不得不更改 @property (nonatomic, strong, readonly) NSArray *data; @property (nonatomic, strong) NSString *data; 像这样,当做类似 [CacheManager sharedManager].data = @"foobarr"; 通知所有实例,并将它们的属性标记为脏 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 。效果很好,而且比跟踪类实例更强大!!!
【解决方案2】:

以防万一,在不削弱@meligaletiko 优点的情况下,我正在编写与我的问题相对应的完整解决方案的蓝图,以防它对任何人有用。

问题是:给定类实例使用从存储中获取的数据副本,当其中一个修改数据时如何有效地通知所有类实例? (例如:数据来自 NSUserDefaults,但您不必每次都从它获取/保存数据,因为它很慢)此外,数据更改并不频繁。

来自@meligaletiko

#import <Foundation/Foundation.h>

@interface CacheManager : NSObject

@property (nonatomic, strong) NSString *data;

+ (instancetype)sharedManager;

@end

#import "CacheManager.h"

@implementation CacheManager

- (instancetype)init {
    if (self = [super init]) {
        _data = @"x";
    }

    return self;
}

+ (instancetype)sharedManager {
    static CacheManager *selfManager;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        selfManager = [[[self class] alloc] init];
    });

    return selfManager;
}

@end


static void *CacheManagerDataChangedContext = &CacheManagerDataChangedContext;
@interface A:NSObject
-(NSString*)sz;
-(void)setSz:(NSString*)sz;
@end

@interface A ()
@property (nonatomic,copy) NSString* sz;
@property (nonatomic) BOOL shouldFetchDataFromStore;

-(id)fetchDataForKey:(NSString*)key
@end

@implementation A

-(instancetype)init{
    self = [super init];
    if (self) {
        [[CacheManager sharedManager] addObserver:self forKeyPath:NSStringFromSelector(@selector(data))
                                          options:NSKeyValueObservingOptionNew
                                          context:CacheManagerDataChangedContext];
    }
    return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if (context == CacheManagerDataChangedContext) {
        self.shouldFetchDataFromStore = YES;
    }

    else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}
-(void)dealloc{
    [[CacheManager sharedManager] removeObserver:self forKeyPath:NSStringFromSelector(@selector(data)) context:CacheManagerDataChangedContext];
}

-(void)setSz:(NSString*)sz{
    _sz = sz;//sanity checks spared...
    [CacheManager sharedManager].data = @"x";//this line will notify all other instances

}
-(NSString*)sz{
    if(self.shouldFetchDataFromStore) { // in other class instances, this was updated to YES, thanks to KVO
        self.shouldFetchDataFromStore = NO;
        _sz = (NSString*)[self fetchDataForKey:(NSString*)sz];//updates _sz with the correct value.
    }
    return _sz;
}

@end

【讨论】:

  • 哦,我忘记了dealloc 上的removeObserver。它看起来是一个新手错误 :( 抱歉。为什么你不使用管理器来处理数组的集合及其操作?通常我讨厌在管理器上有 readwrite 属性,大多数时候他们仅由经理操纵。但是,这是您的实现。做得好;)
  • 不用担心,我还是添加了dealloc。在这里,管理器充当一个小型的、独立的通知中心,并且不知道缓存的操作。这也使管理器能够重用具有不同更新/插入规则等的不同缓存的管理器......感谢您提出这种方法,非常有帮助!它就像一个魅力!
猜你喜欢
  • 2014-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-09
  • 1970-01-01
  • 2011-03-17
  • 2015-05-20
相关资源
最近更新 更多