1

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?

Johnson
  • 491
  • 1
  • 3
  • 13
  • If you use a static then this class if effectively reduced to being a singleton. Why would you restrict the class like that? – Droppy Mar 17 '16 at 10:12
  • This is suggested in this article http://nshipster.com/key-value-observing/ . as a token. If I don't care about the different instances as long as the same class, I think it is fine. The point is why the contex and bankContext are not equal. – Johnson Mar 17 '16 at 10:17
  • Ah OK - my mistake. I take it back :) – Droppy Mar 17 '16 at 10:36

1 Answers1

2

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).

Community
  • 1
  • 1
Douglas Hill
  • 1,537
  • 10
  • 14
  • What you said is correct! The purpose of using bankContext = &bankContext is as a token for a certain class (http://nshipster.com/key-value-observing/). For this purpose, I think may be using `if(object isKindOfClass:[Bank class]])` is better than `if([context == bankContext) `without additional bankContext variable declaration. – Johnson Mar 17 '16 at 11:04
  • I recommend you keep using a context pointer as suggested in the NSHipster article. You need to learn more about variable storage in C to apply this properly to your situation. – Douglas Hill Mar 17 '16 at 11:07
  • Thanks for your suggestion, I have seen the difference between static and extern. Isn't it unnecessary to declare context pointer just as a token to identify the situation? I don't see other advantage due to my knowledge. – Johnson Mar 17 '16 at 11:11
  • It isn’t strictly necessary but will be more robust, since superclasses or subclasses (or potentially even other objects) might also set up observations that could interfere if you just check the object and key path. – Douglas Hill Mar 18 '16 at 16:49