问题的背景

IOS中委托模式和消息机制基本上开发中用到的比较多,一般最开始页面传值通过委托实现的比较多,类之间的传值用到的比较多,不过委托相对来说只能是一对一,比如说页面A跳转到页面B,页面的B的值改变要映射到页面A,页面C的值改变也需要映射到页面A,那么就需要需要两个委托解决问题。NSNotificaiton则是一对多注册一个通知,之后回调很容易解决以上的问题。

概念

iOS消息通知机制算是同步的,观察者只要向消息中心注册, 即可接受其他对象发送来的消息,消息发送者和消息接受者两者可以互相一无所知,完全解耦。这种消息通知机制可以应用于任意时间和任何对象,观察者可以有多个,所以消息具有广播的性质,只是需要注意的是,观察者向消息中心注册以后,在不需要接受消息时需要向消息中心注销,属于典型的观察者模式。

消息通知中重要的两个类:

(1)NSNotificationCenter: 实现NSNotificationCenter的原理是一个观察者模式,获得NSNotificationCenter的方法只有一种,那就是[NSNotificationCenter defaultCenter] ,通过调用静态方法defaultCenter就可以获取这个通知中心的对象了。NSNotificationCenter是一个单例模式,而这个通知中心的对象会一直存在于一个应用的生命周期。

(2) NSNotification: 这是消息携带的载体,通过它,可以把消息内容传递给观察者。

使用

1.通过NSNotificationCenter注册通知NSNotification,viewDidLoad中代码如下:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationFirst:) name:@"First" object:nil];
 
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationSecond:) name:@"Second" object:nil];

第一个参数是观察者为本身,第二个参数表示消息回调的方法,第三个消息通知的名字,第四个为nil表示表示接受所有发送者的消息~

回调方法:

-(void)notificationFirst:(NSNotification *)notification{
    NSString  *name=[notification name];
    NSString  *object=[notification object];
    NSLog(@"名称:%@----对象:%@",name,object);
}
 
-(void)notificationSecond:(NSNotification *)notification{
    NSString  *name=[notification name];
    NSString  *object=[notification object];
    NSDictionary  *dict=[notification userInfo];
    NSLog(@"名称:%@----对象:%@",name,object);
    NSLog(@"获取的值:%@",[dict objectForKey:@"key"]);
}

2.消息传递给观察者

[[NSNotificationCenter defaultCenter] postNotificationName:@"First" object:@"博客园-Fly_Elephant"];
 
NSDictionary  *dict=[[NSDictionary alloc]initWithObjects:@[@"keso"] forKeys:@[@"key"]];
 
[[NSNotificationCenter defaultCenter] postNotificationName:@"Second" object:@"" userInfo:dict];

3.销毁观察者

-(void)dealloc{
    NSLog(@"观察者销毁了");
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

也可以通过name单个删除:

[[NSNotificationCenter defaultCenter] removeObserver:self name:@"First" object:nil];

4.运行结果

2015-04-26 15:08:25.900 CustoAlterView[2169:148380] 观察者销毁了
2015-04-26 15:08:29.222 CustoAlterView[2169:148380] 名称:First----对象:博客园-Fly_Elephant
2015-04-26 15:08:29.222 CustoAlterView[2169:148380] 名称:Second----对象:
2015-04-26 15:08:29.223 CustoAlterView[2169:148380] 获取的值:keso

深入分析观察者


如果想让对象监听某个通知,则需要在通知中心中将这个对象注册为通知的观察者。早先,NSNotificationCenter提供了以下方法来添加观察者:

- (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender

这个方法带有4个参数,分别指定了通知的观察者、处理通知的回调、通知名及通知的发送对象。这里需要注意几个问题

  1. notificationObserver不能为nil。
  2. notificationSelector回调方法有且只有一个参数(NSNotification对象)。
  3. 如果notificationName为nil,则会接收所有的通知(如果notificationSender不为空,则接收所有来自于notificationSender的所有通知)。如代码清单1所示。
  4. 如果notificationSender为nil,则会接收所有notificationName定义的通知;否则,接收由notificationSender发送的通知。
  5. 监听同一条通知的多个观察者,在通知到达时,它们执行回调的顺序是不确定的,所以我们不能去假设操作的执行会按照添加观察者的顺序来执行。

对于以上几点,我们来重点关注一下第3条。以下代码演示了当我们的notificationName设置为nil时,通知的监听情况。

测试代码如下

添加一个Observer,其中notificationName为nil

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:nil object:nil];

    [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil];
}

- (void)handleNotification:(NSNotification *)notification
{
    NSLog(@"notification = %@", notification.name);
}

@end

运行后的输出结果如下:

notification = TestNotification
notification = UIWindowDidBecomeVisibleNotification
notification = UIWindowDidBecomeKeyNotification
notification = UIApplicationDidFinishLaunchingNotification
notification = _UIWindowContentWillRotateNotification
notification = _UIApplicationWillAddDeactivationReasonNotification
notification = _UIApplicationDidRemoveDeactivationReasonNotification
notification = UIDeviceOrientationDidChangeNotification
notification = _UIApplicationDidRemoveDeactivationReasonNotification
notification = UIApplicationDidBecomeActiveNotification

可以看出,我们的对象基本上监听了测试程序启动后的所示消息。当然,我们很少会去这么做。

而对于第4条,使用得比较多的场景是监听UITextField的修改事件,通常我们在一个ViewController中,只希望去监听当前视图中的UITextField修改事件,而不希望监听所有UITextField的修改事件,这时我们就可以将当前页面的UITextField对象指定为notificationSender。

NSNotification Block


在iOS 4.0之后,NSNotificationCenter为了跟上时代,又提供了一个以block方式实现的添加观察者的方法,如下所示:

- (id<NSObject>)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block

大家第一次看到这个方法时是否会有这样的疑问:观察者呢?参数中并没有指定具体的观察者,那谁是观察者呢?实际上,与前一个方法不同的是,前者使用一个现存的对象作为观察者,而这个方法会创建一个匿名的对象作为观察者(即方法返回的id<NSObject>对象),这个匿名对象会在指定的队列(queue)上去执行我们的block。

这个方法的优点在于添加观察者的操作与回调处理操作的代码更加紧凑,不需要拼命滚动鼠标就能直接找到处理代码,简单直观。这个方法也有几个地方需要注意:

  1. name和obj为nil时的情形与前面一个方法是相同的。
  2. 如果queue为nil,则消息是默认在post线程中同步处理,即通知的post与转发是在同一线程中;但如果我们指定了操作队列,情况就变得有点意思了,我们一会再讲。
  3. block块会被通知中心拷贝一份(执行copy操作),以在堆中维护一个block对象,直到观察者被从通知中心中移除。所以,应该特别注意在block中使用外部对象,避免出现对象的循环引用,这个我们在下面将举例说明。
  4. 如果一个给定的通知触发了多个观察者的block操作,则这些操作会在各自的Operation Queue中被并发执行。所以我们不能去假设操作的执行会按照添加观察者的顺序来执行。
  5. 该方法会返回一个表示观察者的对象,记得在不用时释放这个对象。

下面我们重点说明一下第2点和第3点。

关于第2点,当我们指定一个Operation Queue时,不管通知是在哪个线程中post的,都会在Operation Queue所属的线程中进行转发:

代码如下:
在指定队列中接收通知

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserverForName:TEST_NOTIFICATION object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {

        NSLog(@"receive thread = %@", [NSThread currentThread]);
    }];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSLog(@"post thread = %@", [NSThread currentThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil];
    });
}

@end

在这里,我们在主线程里添加了一个观察者,并指定在主线程队列中去接收处理这个通知。然后我们在一个全局队列中post了一个通知。我们来看下输出结果:

post thread = <NSThread: 0x7ffe0351f5f0>{number = 2, name = (null)}
receive thread = <NSThread: 0x7ffe03508b30>{number = 1, name = main}

可以看到,消息的post与接收处理并不是在同一个线程中。如上面所提到的,如果queue为nil,则消息是默认在post线程中同步处理,大家可以试一下。

对于第3点,由于使用的是block,所以需要注意的就是避免引起循环引用的问题,如下:

block引发的循环引用问题

@interface Observer : NSObject

@property (nonatomic, assign) NSInteger i;
@property (nonatomic, weak) id<NSObject> observer;

@end

@implementation Observer

- (instancetype)init
{
    self = [super init];

    if (self)
    {
        NSLog(@"Init Observer");

        // 添加观察者
        _observer =  [[NSNotificationCenter defaultCenter] addObserverForName:TEST_NOTIFICATION object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {

            NSLog(@"handle notification");

            // 使用self
            self.i = 10;
        }];
    }

    return self;
}

@end

·#pragma mark - ViewController

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self createObserver];

    // 发送消息
    [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil];
}

- (void)createObserver {

    Observer *observer = [[Observer alloc] init];
}

@end

运行后的输出如下:

Init Observer
handle notification

我们可以看到createObserver中创建的observer并没有被释放。所以,使用 – addObserverForName:object:queue:usingBlock:一定要注意这个问题。