Search code examples
objective-ckey-value-observing

Key Value Observing context not work


In Objective-C, when using Key-Value Observing, I have a Bank class with a accountDomestic property and a person property. The person is added to observe the accountDomestic property. I have a static void *bankContext = & bankContext in Bank class as its context. However, after I change the accountDomestic property, the old and new value are not shown correctly due to mismatch of context and bankContext in the -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)contextmethod in Person.

the code is as following, first is the Bank Class:

Bank.h

#import <Foundation/Foundation.h>
#import "Person.h"

static void * const bankContext = &bankContext;
@class Person;

@interface Bank : NSObject
@property (nonatomic, strong) NSNumber* accountDomestic;
@property (nonatomic, strong) Person* person;
-(instancetype)initWithPerson:(Person *)person;
@end



Bank.m

@implementation
-(instancetype)initWithPerson:(Person *)person{
    if(self = [super init]){
        _person = person;
        [self addObserver:_person 
               forKeyPath:NSStringFromSelector(@selector(accountDomestic)) 
                  options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew
                  context:bankContext];
}
    return self;
}

-(void)dealloc{
    [self removeObserver:_person forKeyPath:NSStringFromSelector(@selector(accountDomestic))];
}

@end

then is the Person Class:

Person.h

#import <Foundation/Foundation.h>
#import "Bank.h"
@interface Person : NSObject
@end


Person.m

#import "Person.h"
@implementation Person

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
    NSLog(@"context: %p",context);
    NSLog(@"bankContext: %p",bankContext);
    if(context == bankContext){
        if([keyPath isEqualToString:NSStringFromSelector(@selector(accountDomestic))]){
            NSString *oldValue = change[NSKeyValueChangeOldKey];
            NSString *newValue = change[NSKeyValueChangeNewKey];
            NSLog(@"--------------------------");
            NSLog(@"accountDomestic old value: %@", oldValue);
            NSLog(@"accountDomestic new value: %@", newValue);
        }
    }
}

@end

at last is the ViewController Class

ViewController.h

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end


ViewController.m

#import "ViewController.h"
#import "Bank.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic, strong) Bank *bank;
@property (nonatomic, strong) Person *person;
@property (nonatomic, strong) NSNumber *delta;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.person = [[Person alloc] init];
    self.delta = @10;
    self.bank = [[Bank alloc] initWithPerson:self.person];
}

- (IBAction)accountDomesticIncreaseButtonDidTouch:(id)sender {
    self.bank.accountDomestic = self.delta;
    int temp = [self.delta intValue];
    temp += 10;
    self.delta = [NSNumber numberWithInt:temp];
}
@end

after I click the button, the new and old value of accountDomestic not shown. You can see the context and bankContext value are not equal as the picture shown below:

enter image description here

Does anyone have any idea of that?


Solution

  • The reason is there are two bankContexts. In Bank.h, you have

    static void * const bankContext = &bankContext;

    This file is then included in both Bank.m and Person.m, so both files (compilation units) define a pointer bankContext, which is marked as static so generates no external linkage (so you can have two with same name).

    The most direct solution is to ensure there is only one bankContext. In Bank.h:

    extern void * const bankContext;

    and in Bank.m:

    void * const bankContext = &bankContext;

    More about static and extern.

    That said, I think it would be better to restructure the code so this isn’t necessary. Your setup of responsibilities makes me wary (one object telling another to become an observer), and I’m surprised it compiles correctly since there appear to be cyclic imports (Bank.h and Person.h import each other).