Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to set up KVO notifications for static variables in objective C?

I have class A, with instance variables deriving attributes from cached data, this cached data is represented as a singleton and is part of A (it's essentially a NSDictionary). From time to time, this cache is modified.

When this happens, I would like to have all A instances pull new information from the cache the next time they access their attribute , or in other words, invalidate their attribute content.

Up to now, each instances is notified manually (using a static array to track members). I'm not a fan. There's might be an option with notification centre, but I'd rather try with KVO.

Has anyone ever managed to subscribe to KVO changes from a class variable on iOS ? (in other words, use changes from a static variable of A to tell A-instances to refresh their data.)

in otherwords, I'd love to have

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

and later in Class A code

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

and be notified of msignal changes in a class instance via

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

}

I've tried to use various had hoc classes, with no luck. Seems I'm missing something.

pointers are welcome, thanks !

like image 915
Alex Avatar asked May 23 '15 13:05

Alex


2 Answers

KVO says that a specific object observes changes on a property of another object (or itself). So i think what you're trying to achieve is not possible, at least not like this. Or at least you need to go deeper on the KVO mechanism.

You can get all information you need to answer your question from Apple Key-Value Observing Programming Guide

Unlike notifications that use NSNotificationCenter, there is no central object that provides change notification for all observers. Instead, notifications are sent directly to the observing objects when changes are made. NSObject provides this base implementation of key-value observing, and you should rarely need to override these methods.

You can fire KVO, with - willChangeValueForKey: and - didChangeValueForKey:. You can read more about this at NSKeyValueObserving Protocol Reference

I suggest that you use a different approach, by creating a manager to manage your cache with and observe the values on that cache, just an example.

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

If you observe the data property of CacheManager from all the other instances, all that instance will get notified when that changes.

Hope that helps ;)

like image 124
portella Avatar answered Nov 02 '22 11:11

portella


Just in case, and without diminishing the merits of @meligaletiko, I'm writing the blue print of the full solution, corresponding to my problem, in case it can be useful to anyone.

Problem was: given class instances using a copy of data fetched from a store, how to efficiently notify all class instances when one of them modifies data ? (ex: data comes from NSUserDefaults, but you don't necessarily want to fetch/save data from/to it, each time, because it's slow) Also, data changes are unfrequent.

from @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
like image 41
Alex Avatar answered Nov 02 '22 11:11

Alex