0

Summary of what Im trying to do.

I have a NSMutableString property called mailText in my AppDelegate.h, whenever I change the value of this property, I want my viewController to be notified and it will set the value of its local IBOutlet property to the new value. Eventually, APpDelegate will change the string based on a received Push Notification.

In order to test, I am firing a timer in my APpDelegate and changing the value of mailText at timer expiry. However, the addObserver method in my ViewCOntroller is not being called when this happens

Code in my AppDelegate.h

@property (strong, nonatomic) NSMutableString *mailText;

Code in AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    ......

    self.mailText = [NSMutableString string] ;
    self.mailText = (NSMutableString *)@"First text" ;

    [self enableTimer] ;
    ......
}

-(void) enableTimer
{
    NSTimer *timer = nil ;

    timer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:self selector:@selector(updateText) userInfo:nil repeats:NO] ;

    //self.myTimer = timer ;

}

-(void) updateText
{
    self.mailText = (NSMutableString *)@"Changed to second text..aaanjanalnal .. jansjanskanska" ;
    NSLog(@"Timer fired...updating mailtext") ;

}

Observation: The NSLog "Timer fired..." is being printed when I run the app on simulator

Code in my ViewController.h

@interface MailDispViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextView *mailDispText;

@end

Code in my ViewController.m

Within

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    [(AppDelegate *)[[UIApplication sharedApplication] delegate] addObserver:self forKeyPath:@"mailText" options:NSKeyValueObservingOptionNew context:nil];
}

Within

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    NSLog(@"received a KVO") ;

    if ([keyPath isEqual:@"mailText"]) {
        NSLog(@"received a KVO for mailtext") ;
        self.mailDispText.text = [change objectForKey:NSKeyValueChangeNewKey];

     }
    /*
     Be sure to call the superclass's implementation *if it implements it*.
     NSObject does not implement the method.
     */
    [super observeValueForKeyPath:keyPath
                         ofObject:object
                           change:change
                          context:context];
 }

Observation: Neither NSLogs "received a KVO" is not being printed.

Can anyone let me know what Im doing wrong?

A second question, how do I find the value stored in mailText from the debug window in Xcode. I tried po mailText, but that did not work.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
ArvindSN
  • 1
  • 2
  • Found the issue...viewDidLoad was not called.. – ArvindSN Sep 29 '14 at 03:32
  • Put a log in your `-viewDidLoad`. Is it happening before the timer is firing? By the way: you should basically never use a mutable type for a property; value properties should almost always be `copy` instead of `strong`; you can't just cast string literals to `NSMutableString*` and expect that to make them mutable; assigning `[NSMutableString string]` to the property immediately before assigning another value does nothing but was cycles. – Ken Thomases Sep 29 '14 at 03:32
  • Thanks Ken. I think your answer explains the next issue I am seeing (crash when I come back to main screen). Could you please explain the part about mutable string in beginners terms? I am new to ObjectiveC and all this Mutable stuff is new to me since I was mainly a C programmer before. When I tried to use NSString, I got a EXC_BAD_ACCESS, when I tried to change the value after timer expiry. So, how do I change value of string this if I cannot use NSMutableString – ArvindSN Sep 29 '14 at 03:51
  • Update: Changed everything to NSSTring. Still hitting same issue. If I click on second table cell in main view & segue into the textview and then come back, there is a EXC_BAD_ACCESS when the timer expires and NSString is being overridden. One Q: Do I need to have a navigation controller in between the initial Navigation Controller-->Table View Controller and my ViewController with TextView? – ArvindSN Sep 29 '14 at 05:32

2 Answers2

0

Found the issue: the viewDidLoad of MailDispViewController was never called since I never segued into this view.

pkamb
  • 33,281
  • 23
  • 160
  • 191
ArvindSN
  • 1
  • 2
  • You need to remove the key-value observation that you set up in `-viewDidLoad` before letting the view controller be deallocated. – Ken Thomases Sep 29 '14 at 15:12
  • Thanks, but I want to maintain the KVO...I.e whenever AppDelegate gets a new push notification with new text in the payload, I want the new text to be displayed when I move from the "main window" into the "Message Display window". Should I remove the KVO and add it again? – ArvindSN Sep 29 '14 at 16:00
  • If there's a chance that the view controller which is observing may be deallocated, then you have to remove the observation. If you later create a new instance, you can re-add it, yes. – Ken Thomases Sep 29 '14 at 17:11
  • Thanks, very helpful stuff. Will putting the remove code in ViewDidDisappear be sufficient for catching all the "deallocation" cases? – ArvindSN Sep 29 '14 at 18:29
  • I don't know. I don't do iOS development. – Ken Thomases Sep 29 '14 at 20:38
  • Putting remove observer code in viewDidDisappear solved the issue. I don't know if any other location is needed, though. Guess I'll know at the next crash. – ArvindSN Sep 30 '14 at 05:25
  • BTW, this "just in time" enabling and disabling of KVO, brings up a few interesting questions. I thought the notification about changes to the state of a property is a one time broadcast (i.e you lose the info if you are not "subscribed" to it at the time change happens), but I see that even if I enable KVO later I get it. One unknown Q is: what happens if property changes twice and then you enable KVO? Will both the changes reach you or just the last value after the 2nd change? – ArvindSN Sep 30 '14 at 05:30
  • At least with just Initial and New filters ON, I only get the last one if the property changes twice before KVO gets enabled. Is this expected? – ArvindSN Sep 30 '14 at 05:38
  • If you specify the `Initial` option when you add the observer, then you always get a change notification immediately, during the adding of the observer. It doesn't matter if the property changed. It's unconditional. The `New` option only affects what data is provided in the change dictionary. It doesn't affect when or whether you get a notification. Lastly, KVO does not attempt to ensure that you get notified only when the property actually changes value. You get notified whenever a method which might change the property is called. Setting the property to its current value still notifies. – Ken Thomases Sep 30 '14 at 06:33
  • Thanks. With the initial value set in options, Im getting a SIGBRT crash on iPhone but not on iPad. – ArvindSN Oct 04 '14 at 05:20
  • I'm seeing more weirdness that I can't explain and would appreciate some help. \n If I just use the "new" value set in options when I register for KVO, Im seeing that text is no longer updated on the screen when I set them to new values in the AppDelegate. It works if I have both Initial and New filters set. – ArvindSN Oct 04 '14 at 05:59
  • I think I see how this works. Can you pls confirm my understanding? The "new" filter will only kick if the object being observed changes when the registration is active. I.e if I deregister from KVO, object changes and I re-register to KVO, then there will be no notification and thats why I was not seeing the displayed text being updated. Like you pointed out, by having the "initial" filter, I was forcing a notification at the time of registration. Thus, I was forcing the latest value of the object to be read at each registration and displayed. – ArvindSN Oct 04 '14 at 06:02
  • The `New` option is not a filter. It doesn't affect whether or when you get a notification. It only affects what data is contained in the change dictionary. And, yes, you only get notified when the observation is in effect or, if you specify the `Initial` option, when you add the observer. Were you expecting to get notified when you weren't observing? KVO doesn't actually notify you about *changes* to properties. It notifies you about calls to setters and mutation accessors, at the time of those calls — i.e. events which *might* change the property. – Ken Thomases Oct 04 '14 at 06:22
0

The answer is: Do not call

[super observeValueForKeyPath:keyPath
                         ofObject:object
                           change:change
                          context:context];

when super will not handle the keyPath. It means that whenever they key path is yours or the context is yours you should not call super. Only call super to ensure kvo not from your code can be observed regularly.

Nicolas Manzini
  • 8,379
  • 6
  • 63
  • 81