参考来源:http://www.tuicool.com/articles/MNjamm
NSNotifaction使用的是观察者设计模式
1>创建
1.创建一个通知对象:使用notificationWithName:object: 或者 notificationWithName:object:userInfo:
NSNotification* notification = [NSNotification notificationWithName:kImageNotificationLoadFailed(connection.imageURL)
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:error,@"error",connection.imageURL,@"imageURL",nil]];
这里需要注意的是,创建自己的通知并不是必须的。更多的使用NSNotificationCenter
2.+ (NSNotificationCenter *)defaultCenter
一个NSNotificationCenter对象(通知中心)提供了在程序中广播消息的机制,它实质上就是一个通知分发表。这个分发表负责维护为各个通知注册的观察者,并在通知到达时,去查找相应的观察者,将通知转发给他们进行处理。
注意:NSNotification对象是不可变的,因为一旦创建,对象是不能更改的。
如果想让对象监听某个通知,则需要在通知中心中将这个对象注册为通知的观察者。早先,NSNotificationCenter提供了以下方法来添加观察者:
- (void)addObserver:(id)notificationObserver
selector:(SEL)notificationSelector
name:(NSString *)notificationName
object:(id)notificationSender
因此为了进行防御式编程,最好先检查观察者是否定义了该方法。例如:添加观察者代码有
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(aWindowBecameMain:)
name:NSWindowDidBecomeMainNotification object:nil];
这里保证了self定义了aWindowBecameMain:方法。而对于一个任意的观察者observer,不能保证其对应的selector有aWindowBecameMain:,可采用[observer respondsToSelector:@selector(aWindowBecameMain:)]] 进行检查。所以完整的添加观察者过程为:
if([observer respondsToSelector:@selector(aWindowBecameMain:)]) {
[[NSNotificationCenter defaultCenter] addObserver:observer selector:@selector(aWindowBecameMain:) name:NSWindowDidBecomeMainNotification object:nil];
}
这个方法带有4个参数,分别指定了通知的观察者、处理通知的回调、通知名及通知的发送对象。这里需要注意几个问题:
对于以上几点,我们来重点关注一下第3条。以下代码演示了当我们的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。
在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。
这个方法的优点在于添加观察者的操作与回调处理操作的代码更加紧凑,不需要拼命滚动鼠标就能直接找到处理代码,简单直观。这个方法也有几个地方需要注意:
下面我们重点说明一下第2点和第3点。
关于第2点,当我们指定一个Operation Queue时,不管通知是在哪个线程中post的,都会在Operation Queue所属的线程中进行转发,如代码清单2所示:
@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,所以需要注意的就是避免引起循环引用的问题,如代码清单3所示:
@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:一定要注意这个问题。
与注册观察者相对应的,NSNotificationCenter为我们提供了两个移除观察者的方法。它们的定义如下:
- (void)removeObserver:(id)notificationObserver
- (void)removeObserver:(id)notificationObserver name:(NSString *)notificationName object:(id)notificationSender
前一个方法会将notificationObserver从通知中心中移除,这样notificationObserver就无法再监听任何消息。而后一个会根据三个参数来移除相应的观察者。
这两个方法也有几点需要注意:
关于注册监听者,还有一个需要注意的问题是,每次调用addObserver时,都会在通知中心重新注册一次,即使是同一对象监听同一个消息,而不是去覆盖原来的监听。这样,当通知中心转发某一消息时,如果同一对象多次注册了这个通知的观察者,则会收到多个通知,如代码清单4所示:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil];
}
- (void)handleNotification:(NSNotification *)notification
{
NSLog(@"notification = %@", notification.name);
}
@end
其输出结果如下所示:
notification = TestNotification
notification = TestNotification
可以看到对象处理了两次通知。所以,如果我们需要在viewWillAppear监听一个通知时,一定要记得在对应的viewWillDisappear里面将观察者移除,否则就可能会出现上面的情况。
最后,再特别重点强调的非常重要的一点是,在释放对象前,一定要记住如果它监听了通知,一定要将它从通知中心移除。如果是用 – addObserverForName:object:queue:usingBlock:,也记得一定得移除这个匿名观察者。说白了就一句话,添加和移除要配对出现。
注册了通知观察者,我们便可以随时随地的去post一个通知了(当然,如果闲着没事,也可以不注册观察者,post通知随便玩,只是没人理睬罢了)。NSNotificationCenter提供了三个方法来post一个通知,如下所示:
- postNotification:
– postNotificationName:object:
– postNotificationName:object:userInfo:
我们可以根据需要指定通知的发送者(object)并附带一些与通知相关的信息(userInfo),当然这些发送者和userInfo可以封装在一个NSNotification对象中,由- postNotification:来发送。注意,- postNotification:的参数不能为空,否则会引发一个异常,如下所示:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSNotificationCenter postNotification:]: notification is nil'
每次post一个通知时,通知中心都会去遍历一下它的分发表,然后将通知转发给相应的观察者。
另外,通知的发送与处理是同步的,在某个地方post一个消息时,会等到所有观察者对象执行完处理操作后,才回到post的地方,继续执行后面的代码。如代码清单5所示:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil];
NSLog(@"continue");
}
- (void)handleNotification:(NSNotification *)notification
{
NSLog(@"handle notification");
}
@end
运行后输出结果是:
handle notification
continue
iOS中的通知
每一个应用程序都有一个通知中心(NSNotificationCenter)实例, 专门负责协助不同对象之间的消息通信.
任何一个对象都可以向通知中心发布通知(NSNotification), 描述自己在做什么. 其他感兴趣的对象(Observe)可以申请在某个特定通知发布的时候(或在某个特定对象发布通知时)收到这个通知.
通知(NSNotification)
一个完整的通知一般包含三个属性:
1
2
3
|
-(NSString *)name;
// 通知的名称
-(id)object
// 通知发布者
-(NSDictionary *) userInfo ;
// 一些额外的信息(通知发布者传递给通hi接收者的信息内容).
|
初始化一个通知(NSNotification)对象:
1
2
3
|
+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject;
+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
- (instancetype)initWithName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo;
|
通知中心(NSNotificationCenter)提供了相应的方法来帮助发布通知.
1
|
- (
void
)postNotification:(NSNotification *)notification;
|
发布一个notification通知,可在notification对象中设置通知的名称, 通知发布者, 额外信息等.
1
|
- (
void
)postNotificationName:(NSString *)aName object:(id)anObject;
|
发布一个名称为aName的通知, anObject为这个通知的发布者.
1
|
- (
void
)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
|
发布一个名称为aName的通知, anObject为这个通知的发布者, aUserInfo为额外信息.
通知中心(NSNotificationCenter)提供了方法来注册一个监听通知的监听器(Observe).
1
|
- (
void
)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject;
|
observer: 监听器, 即谁要接收这个通知 .
aSelector: 收到通知后, 回调监听器的这个方法 ,并且把通知对象当做参数传入 .
aName: 通知的名称. 如果为 nil ,那么无论通知的名称是什么 ,监听器都能收到这个通知 .
anObject: 通知发布者. 如果 anObject 和 aName 都为 nil ,监听器能收到所有通知 .
1
|
- (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(
void
(^)(NSNotification *note))block;
|
name : 通知的名称 .
obj : 通知的发布者 .
block : 收到对应的通知时, 会回调这个block .
queue : 决定了block在哪个操作队列中执行, 如果传nil ,默认在当前操作队列中同步执行 .
通知中心不会保留(retain)监听器对象, 在通知中心注册过的对象 ,必须在该对象释放前取消注册. 否则, 当相应的通知再次出现时, 通知中心仍然会向该监听器发送消息. 因为, 相应的监听器对象已经被释放了, 所以可能会导致应用崩溃 .
通知中心提供了相应的方法来取消注册监听器
1
2
|
- (
void
)removeObserver:(id)observer;
- (
void
)removeObserver:(id)observer name:(NSString *)aName object:(id)anObject;
|
一般在监听器销毁之前取消注册(如在监听器中加入下列代码) :
1
2
3
4
|
- (
void
)dealloc {
//[super dealloc]; 非ARC中需要调用此句
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
|
UIDevice类提供了一个单例对象, 它代表着设备, 通过它可以获得一些设备相关的信息, 比如电池电量值(batteryLevel), 电池状态(batteryState), 设备的类型(model ,比如iPad, iPhone等), 设备的系统(systemVersion).
通过[UIDevice currentDevice]可以获取这个单例对象 ;
UIDevice对象会不间断地发布一些通知, 下列是UIDevice对象所发布通知的名称常量:
1
2
3
4
|
UIDeviceOrientationDidChangeNotification
// 设备旋转
UIDeviceBatteryStateDidChangeNotification
// 电池状态改变
UIDeviceBatteryLevelDidChangeNotification
// 电池电量改变
UIDeviceProximityStateDidChangeNotification
// 近距离传感器(比如设备贴近了使用者的脸部)
|
我们经常需要在键盘弹出或者隐藏的时候做一些特定的操作, 因此需要监听键盘的状态 .
键盘状态改变的时候 , 系统会发出一些特定的通知
1
2
3
4
5
6
|
UIKeyboardWillShowNotification
// 键盘即将显示
UIKeyboardDidShowNotification
// 键盘显示完毕
UIKeyboardWillHideNotification
// 键盘即将隐藏
UIKeyboardDidHideNotification
// 键盘隐藏完毕
UIKeyboardWillChangeFrameNotification
// 键盘的位置尺寸即将发生改变
UIKeyboardDidChangeFrameNotification
// 键盘的位置尺寸改变完毕
|
系统发出键盘通知时, 会附带一下跟键盘有关的额外信息(字典),字典常见的key如下:
1
2
3
4
|
UIKeyboardFrameBeginUserInfoKey
// 键盘刚开始的frame
UIKeyboardFrameEndUserInfoKey
// 键盘最终的frame(动画执行完毕后)
UIKeyboardAnimationDurationUserInfoKey
// 键盘动画的时间
UIKeyboardAnimationCurveUserInfoKey
// 键盘动画的执行节奏(快慢)
|