Advertise here




Advertise here

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Slick's Helpers - Notifications

BrianSlickBrianSlick Treadmill Desk NinjaPosts: 10,689Tutorial Authors, Registered Users @ @ @ @ @ @ @ @
edited September 2012 in iOS SDK Development
Mostly throwing this out there for comments, but feel free to take this and run with it. I've been trying to simplify notification handling in view controllers, and this is something I'm going to do moving forward.

We start with a helper class (this is using ARC, so add memory management for non-ARC cases):
//  BTINotificationHelper.h

// Public Constants
typedef enum {
   BTINotificationRegistrationLifetime = 0,
   BTINotificationRegistrationVisible,
} BTINotificationRegistrationLifeSpan;

@interface BTINotificationHelper : NSObject
{
}

// Public Properties
@property (nonatomic, assign, readonly) BTINotificationRegistrationLifeSpan lifeSpan;

// Misc Public Methods
+ (id)notificationHelperWithObserver:(id)observer
     selector:(SEL)selector
     name:(NSString *)name
     object:(id)object
     lifeSpan:(BTINotificationRegistrationLifeSpan)lifeSpan;
- (void)register;
- (void)unregister;

@end
//  BTINotificationHelper.m

#import "BTINotificationHelper.h"

@interface BTINotificationHelper ()

// Private Properties
@property (nonatomic, weak) id observer;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, weak) id object;
@property (nonatomic, assign) BTINotificationRegistrationLifeSpan lifeSpan;

@end

@implementation BTINotificationHelper

#pragma mark - Synthesized Properties

// Public

// Private
@synthesize observer = ivObserver;
@synthesize selector = ivSelector;
@synthesize name = ivName;
@synthesize object = ivObject;
@synthesize lifeSpan = ivLifeSpan;

#pragma mark - Misc Methods

+ (id)notificationHelperWithObserver:(id)observer
     selector:(SEL)selector
     name:(NSString *)name
     object:(id)object
     lifeSpan:(BTINotificationRegistrationLifeSpan)lifeSpan
{
   BTINotificationHelper *helper = [[BTINotificationHelper alloc] init];
	
   [helper setObserver:observer];
   [helper setSelector:selector];
   [helper setName:name];
   [helper setObject:object];
   [helper setLifeSpan:lifeSpan];

   return helper;
}

- (void)register
{
   // Error handling should possibly be moved to the +class method, returning nil if everything isn't valid	
   if ([self observer] == nil)
   {
      return;
   }
	
   if ([self name] == nil)
   {
      return;
   }
	
   [[NSNotificationCenter defaultCenter] addObserver:[self observer]
      selector:[self selector]
      name:[self name]
      object:[self object]];
}

- (void)unregister
{
   [[NSNotificationCenter defaultCenter] removeObserver:[self observer]
      name:[self name]
      object:[self object]];
}

@end
Basically this captures everything you would normally do for a notification, but adds to it an intended life span. Right now, I typically only have 2 life spans in mind, but it won't be hard to add more if needed:
1. Life time: As long as the VC is alive, it needs the notification
2. Visible: The VC only needs the notification when it is on screen

In order to make this relatively painless, I've made a new view controller subclass that automates a few of the necessary steps. Other VC's can inherit from this rather than UIViewController directly. Looks like this:
//  BTIViewController.h

// Libraries
#import <UIKit/UIKit.h>

// Forward Declarations and Classes
#import "BTINotificationHelper.h"

@interface BTIViewController : UIViewController
{
}

// Other Public Properties
@property (nonatomic, strong, readonly) NSMutableSet *notificationHelpers;


#pragma mark BTINotificationHelper Support

// Intended for subclass override
- (void)populateNotificationHelpers;

// Subclasses should NOT override
- (void)registerNotificationsWithLifeSpan:(BTINotificationRegistrationLifeSpan)lifeSpan;
- (void)unregisterNotificationsWithLifeSpan:(BTINotificationRegistrationLifeSpan)lifeSpan;

@end
//  BTIViewController.m

#import "BTIViewController.h"

@interface BTIViewController ()

// Private Properties
@property (nonatomic, strong) NSMutableSet *notificationHelpers;

@end

@implementation BTIViewController

#pragma mark - Synthesized Properties

// Public
@synthesize notificationHelpers = ivNotificationHelpers;

#pragma mark - Dealloc and Memory Methods

- (void)dealloc
{
   [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (id)init
{
   self = [self initWithNibName:nil bundle:nil];
   if (self)
 {
      [self populateNotificationHelpers];
      [self registerNotificationsWithLifeSpan:BTINotificationRegistrationLifetime];
   }
   return self;
}

- (NSMutableSet *)notificationHelpers
{
   if (ivNotificationHelpers == nil)
   {
      ivNotificationHelpers = [[NSMutableSet alloc] init];
   }
   return ivNotificationHelpers;
}

- (void)viewWillAppear:(BOOL)animated
{
   [super viewWillAppear:animated];
	
   [self registerNotificationsWithLifeSpan:BTINotificationRegistrationVisible];
}

- (void)viewWillDisappear:(BOOL)animated
{
   [super viewWillDisappear:animated];
	
   [self unregisterNotificationsWithLifeSpan:BTINotificationRegistrationVisible];
}

- (void)populateNotificationHelpers
{
   // Deliberately empty.  Subclasses should override.  No need to call super.
}

- (void)registerNotificationsWithLifeSpan:(BTINotificationRegistrationLifeSpan)lifeSpan
{
   for (BTINotificationHelper *notificationHelper in [self notificationHelpers])
   {
      if ([notificationHelper lifeSpan] == lifeSpan)
      {
         [notificationHelper register];
      }
   }
}

- (void)unregisterNotificationsWithLifeSpan:(BTINotificationRegistrationLifeSpan)lifeSpan
{
   for (BTINotificationHelper *notificationHelper in [self notificationHelpers])
   {
      if ([notificationHelper lifeSpan] == lifeSpan)
      {
         [notificationHelper unregister];
      }
   }
}

@end
A given VC subclass would inherit from this class, and then override the indicated method to do something like this:
- (void)populateNotificationHelpers
{
   BTINotificationHelper *helper = [BTINotificationHelper notificationHelperWithObserver:self
      selector:@selector(textFieldDidChange:)
      name:UITextFieldTextDidChangeNotification
      object:nil
      lifeSpan:BTINotificationRegistrationVisible];

   [[self notificationHelpers] addObject:helper];
}
So what this means is that all (or at least, most) notifications are defined in one place. No more of this putting some in init, putting some in viewWillAppear, maybe putting some in viewDidLoad, etc. All of them go into this method. This method is called during init, and then init immediately registers for any lifetime notifications. In viewWillAppear, the visible ones are registered, then in viewWillDisappear they are unregistered. You don't have to individually unregister for each notification like you normally would. It tidies things up a bit, and makes sure that the behaviors are appropriate.

Public Service Announcement:

You aren't doing this:
[[NSNotificationCenter defaultCenter] removeObserver:self];
...anywhere OTHER than dealloc, are you? You are? Uh oh, that's a problem. When you do that, you also unregister for the ones that UIViewController registers, such as listening for memory warning notifications. Anything you register in viewWill/DidAppear you need to INDIVIDUALLY unregister in viewWill/DidDisappear, otherwise you can cause problems. This "nuclear" unregister should only be done in dealloc.
Professional iOS App Development. Available for hire.
BriTer Ideas LLC - WWW | Facebook | Twitter | LinkedIn

BTIKit | BTICoreDataKit | SlickShopper 2 | Leave a PayPal donation

Replies

  • Duncan CDuncan C Posts: 9,114Tutorial Authors, Registered Users @ @ @ @ @ @ @
    Brian,

    Looks interesting.

    A few comments:

    What if you add a "startListening" and "stopListening" method to the category so you can start and stop listening based on reasons you haven't thought of yet? (Like say you don't want notifications while an alert is being displayed, or a modal is on-screen on iPad {since modals don't have to cover the whole screen on iPad, so the underlying VC is still visible.)

    Another thing: I've switched to using the new block-based notification method ( addObserverForName:object:queue:usingBlock: ) whenever possible. That method lets you pass in a block rather than a selector, so you don't have to write all these selectors that are specific to handling notifications.

    One tricky bit in using that method: It passes back an observer object (type id) and you have to keep it around so you can stop listening later.
    Regards,
    Duncan C
    WareTo

    widehead.gif
    Animated GIF created with Face Dancer, available for free in the app store.

    I'm available for one-on-one help at CodeMentor
  • BrianSlickBrianSlick Treadmill Desk Ninja Posts: 10,689Tutorial Authors, Registered Users @ @ @ @ @ @ @ @
    Hrm, I hadn't considered the possibility of sort of "pausing" the notifications. You could just call the register/unregister methods again I suppose, but that might get a bit messy with mixed life spans.

    I haven't played with the block-based notifications yet, so that didn't enter my thinking. Offhand I'd say the mutableSet could be used to store the tokens, but otherwise I'm not sure that this techniques would add much benefit in this case. Although considering the amount of extra code the blocks would involve when defining the notifications, moving notifications into a separate method anyway would probably be one of the first things I would do.

    Thanks for the comments.
    Professional iOS App Development. Available for hire.
    BriTer Ideas LLC - WWW | Facebook | Twitter | LinkedIn

    BTIKit | BTICoreDataKit | SlickShopper 2 | Leave a PayPal donation
  • BrianSlickBrianSlick Treadmill Desk Ninja Posts: 10,689Tutorial Authors, Registered Users @ @ @ @ @ @ @ @
    Arise, dead thread.... arise!

    I've updated and streamlined this concept from my older BTIConcepts library into my newer BTIKit library. The sample project shows how much simpler they are to use now with some helper methods.

    Duncan, I didn't address the "pausing" concept that you mentioned, mostly because I've never had the need. But I did add the ability to handle block-based notification responders.
    Professional iOS App Development. Available for hire.
    BriTer Ideas LLC - WWW | Facebook | Twitter | LinkedIn

    BTIKit | BTICoreDataKit | SlickShopper 2 | Leave a PayPal donation
Sign In or Register to comment.