上一章节介绍了Notification的注册和发送,在常规的notification API中,需要制定一个对象来发送通知,然后另一个注册了该通知的对象会接收到该通知,并用selector来进行处理。这个通知是可以最多携带一个参数的,这个参数是一个指向NSNotification的对象指针。
下面给出一个注册通知代码:
[code]
NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserver: self selector: @selector(networkByeBye:) name: kNetworkWentByeByeNotification object: self.networkMonitor];
selector可以写成这种形式:
[code]
- (void) networkByeBye: (NSNotification *) notification { // handle the notification } // networkByeBye
当使用block型的notification注册通知时,你需要提供一个queue,当通知被发送的时候,这个block就放入到这个queue中去执行。因为执行代码已经被封装到block中了,所以就不再需要一个selector去处理该通知了,也不需要关联某一个对象。当然,如果这个处理过程需要完成很多事情的话,你还是很希望有一个参数能够传进来。
例如,下面是一个block的注册方式,notification block持有一个notification的对象:
[code]
_token = [center addObserverForName: kNetworkWentByeByeNotification object: nil queue: [NSOperationQueue mainQueue] usingBlock: ^(NSNotification *notification) { NSLog (@"Network went down: %@", notification); }];
如果要注销该notification话,需要回调token变量。
Notification对象
notification的名字是一个NSString类型的,前面提到的代码中名字叫做kNetworkWentByeByeNotification。
可以将不同的通知来源,用同一个selector来处理,这样的话,会有一个问题就是那个处理函数的函数体会比较大,但只需要增加一点额外的代码来处理不同的case。
[code]
- (void) networkChanged: (NSNotification *) notification { // do stuff if ([notification.name isEqualToString: kNetworkWentByeByeNotification]) { NSLog (@"Network went away"); // update timestamp label } } // networkChanged
object是notification的第二个参数,这个参数是持有该Notification的对象。下面是一个通知notification的代码:
[code]
// NetworkMonitor [center postNotificationName: kNetworkWentByeByeNotification object: self userInfo: userInfo];
可以通过object来区别对待同一个nofication
[code]
- (void) tapcasterTapped: (NSNotification *) notification { if (notification.object == self.shoesizeTapcaster) { // Upload the shoe size. } else if (notification.object == self.bloodtypeTapcaster) { // Take a blood sample from the user. } } // tapcasterTapped
useriinfo是第三个参数,这是一个NSDictionary类型的参数,里面包含着健值对,当通知被推送的时候,任何接收到该通知的都能收到这个参数。userinfo没有预定义里面要存什么数据,所以你可以传送任何你想要传送的数据。例如在网络监视器中,当数据下载完毕时,可以传送这些内容:IP地址,端口号,上次连接时间和连接设备名称等信息。
可以定义好常量符号来表示这些数据:
[code]
extern NSString *const kNetworkInterfaceKey; extern NSString *const kNetworkDownTimestampKey; extern NSString *const kNetworkAddressKey; extern NSString *const kNetworkPortKey; extern NSString *const kNetworkUptimeSecondsKey; extern NSString *const kNetworkBonjourKey;
上面这种方式,可以使得程序看上去更加易读。
当传送通知时,用下面这种方式封装userinfo dictionary:
[code]
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: self.interface, kNetworkInterfaceKey, [NSDate date], kNetworkDownTimestampKey, self.connectionAddress, kNetworkAddressKey, self.connectionPort, kNetworkPortKey, [NSNumber numberWithInt: timeDelta], kNetworkUptimeSecondsKey, self.bonjourName, kNetworkBonjourKey, nil]; // Post notification. NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center postNotificationName: kNetworkWentByeByeNotification object: self userInfo: userInfo];
当接收到通知之后,就可以从userinfo中取出自己需要的信息。
[code]
NSString *interface = [notification.userInfo objectForKey: kNetworkInterfaceKey]; NSLog (@"interface is %@", interface);
代码风格
当我声明一个notification和keys的时候,我会把他们放在一起,这样可以保持好良好的编码风格,非常有必要建立一些编码规则使得程序看上去更加易读和清晰。
[code]
extern NSString *const kNetworkWentByeByeNotification; extern NSString *const kNetworkInterfaceKey; extern NSString *const kNetworkDownTimestampKey; extern NSString *const kNetworkAddressKey; extern NSString *const kNetworkPortKey; extern NSString *const kNetworkUptimeSecondsKey; extern NSString *const kNetworkBonjourKey;
常量个后缀用“Notifgication”和“Keys”来表示他们之间的关系和所代表的变量。
对于变量时实际数值,我一般有两种赋值方式。
一种是让数值看上去非常易读:
[code]
NSString *const kNetworkWentByeByeNotification = @"network went bye bye"; NSString *const kNetworkInterfaceKey = @"network interface"; NSString *const kNetworkDownTimestampKey = @"network down timestamp"; NSString *const kNetworkAddressKey = @"network address"; NSString *const kNetworkPortKey = @"network port"; NSString *const kNetworkUptimeSecondsKey = @"network uptime seconds"; NSString *const kNetworkBonjourKey = @"bonjour key";
用这种方式,可以在打印的时候,看上去更加易读
[code]
{ "bonjour key" = Nerdinalia; "network address" = "192.168.254.13"; "network down timestamp" = "2012-07-16 19:52:19 +0000"; "network interface" = en1; "network port" = 666; "network uptime seconds" = 10; }
另一种方式是,把数值和变量名称写成一样的
[code]
NSString *const kNetworkWentByeByeNotification = @"kNetworkWentByeByeNotification"; NSString *const kNetworkInterfaceKey = @"kNetworkInterfaceKey"; NSString *const kNetworkDownTimestampKey = @"kNetworkDownTimestampKey"; NSString *const kNetworkAddressKey = @"kNetworkAddressKey"; NSString *const kNetworkPortKey = @"kNetworkPortKey"; NSString *const kNetworkUptimeSecondsKey = @"kNetworkUptimeSecondsKey"; NSString *const kNetworkBonjourKey = @"kNetworkBonjourKey";
这种赋值方式的好处是和自己定义的API相像,看到数值就知道是哪一个通知发出来的数值。
[code]
{ kNetworkBonjourKey = Nerdinalia; kNetworkAddressKey = "192.168.254.13"; kNetworkDownTimestampKey = "2012-07-16 19:52:49 +0000"; kNetworkInterfaceKey = en1; kNetworkPortKey = 666; kNetworkUptimeSecondsKey = 10; }
监视Notification
监视代码中的notification发送流非常的简单。notification的名字一般都是预定义好的,尤其是来自cocoa的通知。
最近我需要查看我的app中的都有哪些notification流。尤其是,我很好奇为什么我获取不到来自iCloud文件容器的BSMetadataQuery通知。我已经设置好所有的事情,检查了我们的cloud document设置,里面的文件也在,但是却收不到任何metadata的数据。我知道肯定是有一些metadata的通知的,所以我想查看一下这些通知信息。
监视所有的通知非常的简单。只需要注册一个通知,然后object和name的参数都传nil即可。传送nil参数,就是告诉notification center可以接受任意类型的通知。下面是监视所有notification的代码:
[code]
NSNotificationCenter *center; center = [NSNotificationCenter defaultCenter]; token = [center addObserverForName: nil object: nil queue: nil usingBlock: ^(NSNotification *notification) { <a href="/blog/a-quieter-log/">QuietLog</a> (@"NOTIFICATION %@ -> %@", notification.name, notification.userInfo); }];
下面是输出的信息:
[code]
NOTIFICATION NSWillBecomeMultiThreadedNotification -> (null) NOTIFICATION _UIWindowDidCreateWindowContextNotification -> { "_UIWindowContextIDKey" = "-464166441"; } ... NOTIFICATION UIWindowDidBecomeVisibleNotification -> (null) NOTIFICATION UIDeviceOrientationDidChangeNotification -> { UIDeviceOrientationRotateAnimatedUserInfoKey = 1; } NOTIFICATION UIWindowDidBecomeKeyNotification -> (null) NOTIFICATION UIApplicationDidFinishLaunchingNotification -> (null) NOTIFICATION UIApplicationDidBecomeActiveNotification -> (null) NOTIFICATION _UIApplicationDidEndIgnoringInteractionEventsNotification -> (null) ... NOTIFICATION UINavigationControllerDidShowViewControllerNotification -> { UINavigationControllerLastVisibleViewController = "<GRMainMenuViewController: 0x255320>"; UINavigationControllerNextVisibleViewController = "<GRClassChooserViewController: 0x2e0d80>";
从输出结果中果然没有看到metadata的通知,然后我才意识到我们没有调用startQuery函数,这个错误太低级了。。
Distributed Notifications
我们一般使系统默认的notification center,你也可以创建自己的notification center(NSNotificationCenter并不是一个一个单例类)。在Mac中,我们可以访问NSDistirbutedNotificationCenter,它是一个方便与进行通信的机制。一个进程向该center中推送一个通知,其他的进程都可以监听到,如果要监视这个center发出来的所有通知,你只需要在注册通知时,将name,object的参数设置为nil即可:
[code]
center = [NSDistributedNotificationCenter defaultCenter]; token = [center addObserverForName: nil object: nil queue: nil usingBlock: ^(NSNotification *notification) { QuietLog (@"DISTRIBUTED %@ -> %@", notification.name, notification.userInfo); }];
当你设置系统中的按钮时,你可以从notification center中看到改通知:
[code]
DISTRIBUTED com.apple.HIToolbox.beginMenuTrackingNotification -> { ToolboxMessageEventData = 145; } DISTRIBUTED com.apple.HIToolbox.endMenuTrackingNotification -> (null) DISTRIBUTED AppleShowScrollBarsSettingChanged -> (null) DISTRIBUTED com.apple.HIToolbox.beginMenuTrackingNotification -> { ToolboxMessageEventData = 25921; } DISTRIBUTED com.apple.HIToolbox.endMenuTrackingNotification -> (null) DISTRIBUTED AppleSideBarDefaultIconSizeChanged -> (null)
Workspace的通知
最后,我们来看一下Workspace中的通知,NSWorkspace是Mac开发中使用的类,它是一个单例的类,拥有自己的notification center,通过NSWorkpace你可以捕捉到,程序的启动,退出事件,程序跳转,机器休眠等事件。监听改center的通知,和上面的方式类似,不同的是被监听的center不同。
[code]
center = [[NSWorkspace sharedWorkspace] notificationCenter]; token = [center addObserverForName: nil object: nil queue: nil usingBlock: ^(NSNotification *notification) { QuietLog (@"WORKSPACE %@ -> %@", notification.name, notification.userInfo); }];
下面是被监听到的workspace中的通知事件:
[code]
WORKSPACE NSWorkspaceDidActivateApplicationNotification -> { NSWorkspaceApplicationKey = "<NSRunningApplication: 0x7ff56aa01230 (com.literatureandlatte.scrivener2 - 25588)>"; } WORKSPACE NSWorkspaceDidDeactivateApplicationNotification -> { NSWorkspaceApplicationKey = "<NSRunningApplication: 0x7ff568d09970 (com.apple.Terminal - 145)>"; } WORKSPACE NSWorkspaceWillSleepNotification -> (null) WORKSPACE NSWorkspaceDidWakeNotification -> (null) WORKSPACE NSWorkspaceDidTerminateApplicationNotification -> { NSApplicationName = "backupd-helper"; NSApplicationPath = "/System/Library/CoreServices/backupd.bundle/Contents/Resources/backupd-helper"; NSApplicationProcessIdentifier = 25947; NSApplicationProcessSerialNumberHigh = 0; NSApplicationProcessSerialNumberLow = 3703688; NSWorkspaceApplicationKey = "<NSRunningApplication: 0x7ff568e12da0 ((null) - 25947)>"; NSWorkspaceExitStatusKey = 0; }
改章节中所用到的代码,可以在改gist中获取。可以将代码复制下来跑跑看。