在Cocoa Touch框架中,观察者模式的具体应用有两个——通知(notification)机制和KVO(Key-Value-Observing)机制。
1、通知机制。
在iOS中通知主要有以下三种:广播通知、本地通知和推送通知。本文所说的通知是广播通知,广播通知除了名字和后两者相似之外其他完全不同。广播通知是Cocoa Touch框架中实现观察者模式的一种机制,它可以在一个应用内部的多个对象之间发送消息;本地通知和推送通知是给用户一种提示,比如警告对话框、发出声音、震动或者在应用图标上面显示数字等。本地通知由本地iOS发出,推送通知由第三方程序发送给苹果的远程服务器,再由远程服务器推送给iOS的制定应用。
通知机制有一个非常重要的类——NSNotificationCenter(通知中心),这是一个单例类。在通知机制中,对某个通知感兴趣的的对象都可以向通知中心发出addObserver:selector:name:object:消息注册为接受者,在投送对象投送给通知中心时,通知中心就会把通知广播给注册过的接收者,投送对象与接收者是一对多的关系。接收者可以给通知中心发送removeObserver:name:object消息解除注册,以后不再接收通知。
下面的代码是测试接收自定义通知和接收系统通知:
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. //将当前的controller注册为通知接收者 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"TestNotification" object:nil]; //添加一个发送通知的按钮 UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; [button setTitle:@"Send" forState:UIControlStateNormal]; button.frame = CGRectMake(50, 300, 220, 25); [button addTarget:self action:@selector(sendNotification) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button]; //监听系统通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. //解除通知 [[NSNotificationCenter defaultCenter] removeObserver:self]; } /** * 发送通知 */ - (void)sendNotification { NSDictionary *dic = [NSDictionary dictionaryWithObject:@"Test" forKey:@"key"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"TestNotification" object:nil userInfo:dic]; } /** * 接收自定义通知 * * @param notification 接收到的通知 */ - (void)receiveNotification:(NSNotification *)notification { NSDictionary *dic = notification.userInfo; NSString *value = [dic objectForKey:@"key"]; NSLog(@"Receive notification:%@", value); } /** * 接收应用进入后台的通知 * * @param notification 接收到的通知 */ - (void)handleEnterBackground:(NSNotification *)notification { NSLog(@"应用进入到后台。"); } /** * 接收应用进入前台的通知 * * @param notification 接收到的通知 */ - (void)handleEnterForeground:(NSNotification *)notification { NSLog(@"应用进入前台。"); }
NSNotificationCenter是一个单例类,defaultCenter方法创建并获得一个实例。NSNotificationCenter的addObserver:selector:name:object方法用来注册通知,selector指定的方法是接收并处理通知的方法,name是通知的名字,object是投送通知时传递过来的对象,userInfo是投送通知时传递的字典对象,可以用它来传递一些数据。
postNotificationName:object:userInfo方法是用来发送通知的,第一个参数是通知的名字,object是发送通知时传递的一个对象,如果接收者不需要,可以将其设为nil。
UIApplicationDidEnterBackgroundNotification
UIApplicationWillEnterForegroundNotification
这两个是系统提供的通知的名字,用来接收系统发送的一些通知。除了应用生命周期的不同阶段有不同的通知外,很多控件也会在某些事件发生时投送通知,例如UITextField控件,在编辑过程的不同阶段,UITextField控件会分别发送如下通知:UITextFieldTextDidBeginEditingNotification、UITextFieldTextDidChangeNotification和UITextFieldTextDidEndEditingNotification。
2、KVO机制
KVO不像通知机制那样通过一个通知中心通知所有通知接收者,而是在对象属性变化时通知会被直接发送给观察者对象。
在KVO机制中,属性发生变化的对象需要发出消息addObserver:forKeyPath:options:context给注册观察者,使得观察者关注它的某个属性的变化。观察者需要重写observerValueForKeyPath:ofObject:change:context方法来响应属性的变化。
下面是KVO的一个demo的部分实现代码:
// // WBViewController.h // KVODemo // // Created by 韩学鹏 on 15/6/26. // Copyright (c) 2015年 韩学鹏. All rights reserved. // #import <UIKit/UIKit.h> @interface WBViewController : UIViewController @property (nonatomic, strong)NSString *textString; @end // // WBViewController.m // KVODemo // // Created by 韩学鹏 on 15/6/26. // Copyright (c) 2015年 韩学鹏. All rights reserved. // #import "WBViewController.h" #import "WBTextObserver.h" @interface WBViewController () { UITextField *_textField; WBTextObserver *_observer; } @end @implementation WBViewController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization } return self; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. //输入框 _textField = [[UITextField alloc] initWithFrame:CGRectMake(10, 200, 200, 25)]; [_textField setBorderStyle:UITextBorderStyleRoundedRect]; [self.view addSubview:_textField]; //按钮,改变字符串 UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; [button setTitle:@"Change" forState:UIControlStateNormal]; button.frame = CGRectMake(200, 200, 80, 25); [button addTarget:self action:@selector(textChangeFunc) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button]; //观察者 _observer = [[WBTextObserver alloc] init]; [self addObserver:_observer forKeyPath:@"textString" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"context"]; } - (void)textChangeFunc { self.textString = _textField.text; }
观察者类:
// // WBTextObserver.h // KVODemo // // Created by 韩学鹏 on 15/6/26. // Copyright (c) 2015年 韩学鹏. All rights reserved. // #import <Foundation/Foundation.h> @interface WBTextObserver : NSObject @end // // WBTextObserver.m // KVODemo // // Created by 韩学鹏 on 15/6/26. // Copyright (c) 2015年 韩学鹏. All rights reserved. // #import "WBTextObserver.h" @implementation WBTextObserver - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"WBTextObserver %@---%@", keyPath, [change objectForKey:NSKeyValueChangeNewKey]); } @end
textSting属性是要观察的属性,WBTextObserver是自定义的观察者类,它负责观察textString属性的变化,_textField是测试需要添加的一个输入框,用来改变textString的值。
[self addObserver:_observer forKeyPath:@"textString" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"context"];
这一行代码是关键代码,它用来告诉观察者对象_observer开始观察属性textString的变化,self是被观察的对象,_observer是观察者对象,forKeyPath是设置被关注对象的属性,options是为属性变化设置的选项,本例中New和Old表示把属性的新旧两个值都传递给观察者。
因为NSObject类实现了NSKeyValueObserving协议,所以WBTextObserver只需继承NSObject类。
observeValueForKeyPath:ofObject:change:context方法的observeValueForKeyPath参数是被关注的属性,ofObject是被关注的对象,change是字典类,包含了属性变化的内容,这些内容与注册时属性变化设置的选项(options参数)有关,context是注册时传递的上下文内容。
最后附上通知机制和KVO机制的测试工程:通知机制和KVO机制Demo