iOS Notification(二):处理&监听通知事件

时间:2022-08-30 14:48:42

上一章节介绍了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中获取。可以将代码复制下来跑跑看。