使用NSNotificationCenter通信
NSNotificationCenter实现了观察者模式,允许应用的不同对象之间以松耦合的方式进行通信。
NSNotification代表Poster与Observer之间的信息载体,该对象包含如下只读属性。
|
name:该属性代表该通知的名字,程序将Poster注册到指定通知中心时,就是根据该名称进行注册的 |
|
object:该属性代表该通知的Poster。 |
|
userInfo:该属性是一个NSDictionary对象,用于携带通知的附加信息。 |
NSNotificationCenter是整个通知系统的中心,Observer向NSNotificationCenter注册自己感兴趣的通知,Poster向NSNotificationCenter发送通知。
|
- addObserverForName:object:queue:usingBlock:该方法将指定代码块注册为监听者,监听object:参数代表的对象(Poster)发出的通知(由第1个参数指定通知名称)。该方法直接使用指定代码块作为监听者,当Poster向NSNotificationCenter发送通知时,将会触发、执行该代码块。如果object:参数为nil,则用于监听任何对象发出的通知。 |
|
示例说明 |
1 ViewController.m
2
3 @implementation ViewController
4
5 - (void)viewDidLoad
6
7 {
8
9 [super viewDidLoad];
10
11 // 监听UIApplicatiob的 UIApplicationDidFinishLaunchingNotification通知
12
13 [[NSNotificationCenter defaultCenter] addObserver:self
14
15 selector:@selector(launch:)
16
17 name:UIApplicationDidFinishLaunchingNotification
18
19 object:[UIApplication sharedApplication]];
20
21 // 监听UIApplicatiob的 UIApplicationDidEnterBackgroundNotification通知
22
23 [[NSNotificationCenter defaultCenter] addObserver:self
24
25 selector:@selector(back:)
26
27 name:UIApplicationDidEnterBackgroundNotification
28
29 object:[UIApplication sharedApplication]];
30
31 // 监听UIApplicatiob的 UIApplicationWillEnterForegroundNotification通知
32
33 [[NSNotificationCenter defaultCenter] addObserver:self
34
35 selector:@selector(fore:)
36
37 name:UIApplicationWillEnterForegroundNotification
38
39 object:[UIApplication sharedApplication]];
40
41 }
42
43 - (void)back: (NSNotification*)Notification
44
45 {
46
47 self.showLabel.text = [NSString stringWithFormat:@”应用程序加载完成!”];
48
49 }
50
51 - (void)launch: (NSNotification*)Notification
52
53 {
54
55 self.showLabel.text = [NSString stringWithFormat:@”%@\n应用程序进入后台!”,
56
57 self.showLabel.text];
58
59 }
60
61 - (void) fore: (NSNotification*)Notification
62
63 {
64
65 self.showLabel.text = [NSString stringWithFormat:@”%@\n应用程序进入前台!”,
66
67 self.showLabel.text];
68
69 }
70
71 @end
|
使用NSNotificationCenter监听自定义通知
使用NSNotificationCenter除了可以监听系统组件发出的通知之外,也可以监听程序自己发出的通知.下面示例将使用异步操作来模拟执行一个耗时任务,并在界面上使用UIProgressView显示耗时任务的执行进度.
|
示例代码 |
ViewController.m
@import “ViewController.h”
#define PROGRESS_CHANGED @”down_progress_changed”
@interface ViewController()
{
NSNotificationCenter* nc;
NSOperationQueue* queue;
}
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
nc = [NSNotificationCenter defaultCenter];
queue = [[NSOperationQueue alloc] init];
// 设置该队列最多支持10个并发线程
queue.maxConcurrentOperationCount = 10;
// 使用视图控制器监听任何对象发出的 PROGRESS_CHANGED 通知
[nc addObserver:self selector:@selector(update:)
name: PROGRESS_CHANGED object:nil]; // ①
}
- (IBAction)start: (id)sender
{
__block int progStatus = 0;
[sender setEnabled:NO];
// 以传入的代码块作为执行体,创建NSOperation
NSBlockOperation* operation = [NSBlockOperation
blockOperationWithBlock:^{
for(int I = 0; i < 100; i++)
{
// 暂停0.5秒模拟耗时任务
[NSThread sleepForTimeInterval: 0.5] ;
// 创建NSNotification,并指定userInfo信息
NSNotification* noti = [NSNotification notificationWithName: PROGRESS_CHANGED object:nil userInfo: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt: ++ progStatus]
,@”prog” , nil]];
// 发送通知
[nc postNotification : noti]; // ②
}
}];
// 将NSOperation添加给NSOperationQueue
[queue addOperation: operation];
}
- (void)update: (NSNotification*) noti
{
// 通过userInfo属性获取耗时任务的进度信息
NSNumber* progStatus = noti.userInfo[@”prog”];
NSLog(@”%d”, progStatus.intValue);
dispatch_async(dispatch_get_main_queue(), ^{
self.prog.progress = progStatus.intValue / 100.0;
// 当任务执行进度执行到100时,启用按钮
if(100 == progStatus.intValue)
{
[self.bn setEnabled: YES];
}
});
}
@end
|
|
上面程序中的第1行代码将视图控制器注册为通知监听者,用于监听任何对象的PROGRESS_CHANGED通知;接下来的第②行代码先创建了一个NSNotification,并使用NSNotificationCenter发送该通知。当该异步代码块向NSNotificationCenter发送通知之后,通知监听者的方法(update:)将会被触发执行,这个update:方法将会更新界面上UIProgressView的进度. |
iOS本地通知
本地通知属于应用界面编程的内容,本地通知和远程推送通知都可以向不在前台运行的应用发送消息,这种消息既可能是即将发生的事件,也可以是服务器的新数据,都可能显示为一段警告信息信息或应用程序图标上的徽标。
本地通知和远程推送通知的基本目的都死让应用程序能够通知用户某些事情,而且不需要应用程序在前台运行。二者的区别在于:本地通知由本应用负责调用,只能从当前设备上的 iOS 发出;而远程推送通知由远程服务器上的程序(可由任意语言编写)发送至 Apple Push Notification service(APNs), 再由 APNs 把消息推送至设备上对应的程序.[BL1]
|
本地通知是一个 UILocalNotification对象,它有如下常用属性. |
|
fireData: 指定通知将在什么时间触发. |
|
repeatInterval:设置本地通知重复发送的时间间隔. |
|
alertBody: 设备本地通知的消息体. |
|
alertAction: 设置当设备处于锁屏状态时,显示通知的警告框下方的 title. |
|
has Action: 设置是否显示 Action. |
|
alertLaunchImage: 当用户通过该通知启动对应的应用时, 该属性设置为加载图片. |
|
applicatonIconBadgeNumber: 设置显示在应用程序上红色徽标中的数字. |
|
soundName: 设置通知的声音. |
|
userInfo: 设置该通知携带的附加信息. |
|
|
|
创建了 UILocalNotification对象之后,接下来就可以通过 UIApplication 的如下两个方法发送通知了 |
|
l - scheduleLocalNotification: 该方法指定调度通知。通知将会于 fireDate 指定的时间触发,而且会按 repeatInterval 指定的时间间隔重复触发。 l - presentLocalNotificationNow: 该方法指定立即发送通知。该方法会忽略UILocalNotification的fireDate属性。 |
|
如果系统发出通知时,应用程序处于前台运行,系统将会触发应用程序委托类的application:didReceiveLocalNotification:方法。 |
|
|
|
在iOS应用中发送本地通知的步骤很简单,只要如下几步即可。
|
|
示例示范如何开发本地通知 |
ViewController.m
@interface ViewController()
{
UIApplication* app;
}
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
app = [UIApplication sharedApplication];
}
- (IBAction)changed: (id)sender
{
UISwitch* sw = (UISwitch*)sender;
if(sw.on)
{
// 创建一个本地通知
UILocalNotification* notification = [[UILocalNotification alloc] init]; // ①
// 设置通知的触发时间
notification.fireDate = [NSDate dateWithTimeIntervalSinceNow:10];
// 设置通知的时区
notification.timeZone = [NSTimeZone defaultTimeZone];
// 设置通知重复发送的时间间隔
notification.repeatInterval = kCFCalendarUnitMinute;
// 设置通知的声音
notification.soundName = @”gu.mp3”;
// 设置当设备处于锁屏状态时, 显示通知的警告框下方的 title
notification.alertAction = @”打开”;
// 设置通知是否显示 Action
notification.hasAction = YES;
// 设置通过通知加载应用时显示的图片
notification.alertLaunchingImage = @”logo.png”;
// 设置通知内容
notification.alertBody = @”轮到你下棋了, 赶快走棋!”;
// 设置显示在应用程序上红色徽标中的数字
notification.applicationIconBadgeNumber = 1;
// 设置 userInfo, 用于携带额外的附加信息
NSDictionary* info = @{@”123456”: @”key”};
notification.userInfo = info;
// 调度通知
[app scheduleLocalNotification:Notification]; // ①
}
else
{
// 获取所有处于调度中的本地通知数组
NSArray* localArray = [app scheduledLocalNotifications];
if(localArray)
{
for(UILocalNotification * noti in localArray)
{
NSDictionary* dict = noti.userInfo;
if(dict)
{
// 如果找到要取消的通知
NSString* inKey = [dict objectForKey:@”key”];
if([inKey isEqualToString:@”123456”])
{
// 取消调度该通知
[app cancelLocalNotification: noti]; // ②
}
}
}
}
}
}
@end
|
|
上面程序中的第①段代码创建了一个UILocalNotification对象,并为该对象设置了相关属性,接下来在①号代码处调用了UIApplication的scheduleLocalNotification:方法来调度通知,这样该通知将会在指定事件触发,并按相应的周期重复执行。当用户把UISwitch控件切换到关闭状态时,②号代码将会取消调度该通知。 为了让程序处于前台运行时也能看到本地通知,还重写了应用程序委托类的application:didReceiveLocalNotification:方法。 |
|
AppDelegate.m
@implementation AppDelegate
// 只有当应用程序在前台运行时,该方法才会被调用
- (void)application: (UIApplication*)application
didReceiveLocalNotification: (UILocalNotification*) notification
{
// 如果应用程序在前台运行,则将应用程序图标上的红色徽标中的数字设为0
application.applicationIconBadgeNumber = 0; // ①
// 使用UIAlertView显示本地通知的信息
[[[UIAlertView alloc] initWithTitle:@” 收到通知 ”
message:notification.alertBody
delegate:nil cancelButtonTitle:@”确定”
otherButtonTitles: nil] show]; // ②
}
- (void)applicationWillEnterForeground: (UIApplication*) application
{
// 当应用程序再次进入前台运行时,将应用程序徽标中数字设为0
application.applicationIconBadgeNumber = 0;
}
…
@end
|
|
上面程序中的第①②段代码控制当应用程序处于前台运行时,即使程序收到了本地通知,也依然会将应用程序图标上的红色徽标中数字设为0。 |
iOS远程推送通知
iOS远程推送通知由远程服务器上的程序(可由任意语言编写)发送至APNs,再由APNs把消息推送至设备上对应的程序。
iOS远程推送通知的过程可用下图进行描述

在上面中,Provider指远程服务器上的Push服务端应用,这种Push服务端应用可以使用任意语言编写,如Java、PHP等。
APNs由Apple公司提供,APNs负责把通知发送到对应的iOS设备,该设备再把通知转发给ClientApp-----即我们的iOS应用。
|
上面所示的过程分为3个阶段 |
|
第一个阶段:Provider程序把要发送的通知、目标iPhone的device token (相当于该设备的唯一标识)打包,发给APNs. |
|
第二阶段:APNs通过已注册Push服务的iPhone列表查找具有对应device token的iPhone,并把Push通知发送给对应的iPhone。 |
|
第三阶段:iPhone将收到的Push通知传递给相应的应用程序,并且按照设定弹出Push通知。 |
|
实际上,Push服务端程序可以通过APNs将一条通知发送给多个iPhone 上的客户端应用;与此同时,iPhone上的客户端应用也可接收多个Push服务端程序发送过来的推送通知。下图显示了这种示意图 |
对于开发iOSPush服务而言,完整的过程如下。
|
|
从上图可以看出,Push客户端应用需要3个组件。
Push服务端程序则需要如下两个组件
|
开发Push客户端应用
开发Push客户端应用需要到Apple网站注册一个App ID,而且该App ID不允许使用通配符。通过Apple网站注册App ID的步骤如下。
|
|
|
将证书签名请求文件保存到磁盘上,此处将该文件保存为“Push。cerSigningRequest”。
在上图所示页面中,可以看到在“iOS Apps”栏目下包含了Certificates、Identifiers、Device、ProvisioningProfiles-----这些只有登录账号已经加入iOS Developer Program(iDP)的原因。 |
|
|
|
将证书签名请求文件保存到磁盘上,此处将该文件保存为“Push。cerSigningRequest”。
在上图所示页面中,可以看到在“iOS Apps”栏目下包含了Certificates、Identifiers、Device、ProvisioningProfiles-----这些只有登录账号已经加入iOS Developer Program(iDP)的原因。 |
上图列出了该开发者账号当前拥有的所有App ID。 |
App ID的描述字符串可以随便填,但该App ID的唯一标识必须要记住,通常采用“公司域名+应用名”的格式 |
|
|
|
|
通过证书列表页面也可以下载指定的证书(只要点击指定的证书,页面就会显示“Revoke”、“Download”两个按钮,其中“Revoke”按钮用于删除证书,“Download”按钮用于下载证书)。 经过上面步骤,我们已经成功为Push客户端创建了一个支持Push通知的App ID,并下载、安装了该App ID的开发证书 |
|
现在开始开发Push‘客户端应用。新建一个Single View Application, 该应用的”Bundle Identifier”必须与前面注册的App ID完全相同.如下图所示是新建应用对话框. 该对话框的填写非常关键,各位务必保证该用用的“Bundle Identifier”与前面注册的App ID完全相同。
|
|
接下来通过修改应用程序委托类来注册远程Push通知,并重写对应的方法来处理远程Push通知.
|
|
|
|
通过上面介绍不难看出,iOS应用处理远程通知比较简单。 |
|
|
该示例的应用程序委托类实现部分代码 |
AppDelegate.m
- (BOOL)application: (UIApplication*) application
didFinishLauchingWithOptions: (NSDictionary*)LaunchingOptions
{
// 注册远程推送通知
[[UIApplication shareApplication] registerForRemoteNotificationTypes:
UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound
| UIRemoteNotificationTypeAlert ];
return YES; // ①
}
- (void)application: (UIApplication *) application
didRegisterForRemoteNotificationsWithDeviceToken: (NSData*)pToken
{
NSLog(@”注册成功: %@”, pToken);
// 注册成功,应该将该device token发送给Push服务端程序
// Push服务端程序应该将该token保存到数据库中, 以备以后重复使用
}
- (void)application: (UIApplication*)application didFailToRegisterForRemoteNotifications
WithError: (NSError*) error
{
NSLog(@”注册失败: %@”, error);
}
- (void)application: (UIApplication*)application didReceiveRemoteNotification: (NSDictionary*) userInfo
{
// 处理推送消息
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@” 通 知 ”
message:@”我的消息” delegate:self cancelButtonTitle:@”取消 ”
otherButtontitles: nil];
[alert show];
NSLog(@”%@”, userInfo);
}
…
@end
|
|
上面程序的第1行代码注册了远程Push通知,接下来程序实现了UIApplicationDelegate协议中与远程推送通知相关的3个方法-----注册Push通知成功、失败时激发的方法,以及收到远程Push通知时激发的方法 |
|
远程推送通知的应用必须在真机上测试,而且客户端应用还需要Provisioning Profile,这个东西也必须通过Apple网站创建、下载。为远程推送通知应用创建、下载Provisioning Profile请按如下步骤进行。
其中第1个Provisioning Profile是测试其他普通程序的Provisioning Profile,由于其他程序所谓App ID可支持通配符, 因此只要一个Provisioning Profile即可。但远程推送通知应用的App ID不能使用通配符,因此此处必须重新创建一个Provisioning Profile。
编译运行该应用(在真机上运行该应用),将可以看到如下图所示的提示框。
用户单击“好”按钮,将可以在Xcode的底部看到如下图所示的输出
此处iOS应用成功注册了远程推送通知,APNs返回了该设备的device token.在实际应用中, iOS应用应该通过网络将该device token发送给Push服务端程序, 此处我们为了简化编程就不增加网络传输的代理代码了,而是直接将该device token复制到Push服务端程序. |
|
|
开发Push服务端程序
Push服务端程序需要包含SSL连接证书和私钥。因此在开发Push服务端程序之前,需要先将它们准备好。
|
导入远程推送通知应用的私钥 |
此处之所以列有名为“Push”的公用密钥和专用密钥,是因为前面开发iOS客户端应用时使用证书助理创建了名字为Push的Certificate Signing Request文件,如果之前没有创建该Certificate Signing Request文件,或创建Certificate Signing Request时指定的名称不同,那么此处将不会显示名为“Push”的公用密钥和专用密钥。
|
|
此时我们已经得到了Push服务端程序所需要的私钥和SSL连接证书(前面开发推送通知客户端时已经从Apple网站下载过开发证书,当时保存的开发证书名为aps_development.ver)。 |
|
接下来还需要将两个文件合并为一个文件,请按如下步骤进行:
上面命令的作用是将aps_development.cer证书文件转换为PEM格式的证书文件,该命令将会生成一个PushCert.pem文件.
上面命令的作用是将Push.p12私钥文件转换为PEM格式的私钥文件,该命令将会生成一个PushKey.pem文件。 运行上面命令一共会提示输入3次密码:第1次输入Push.p12私钥文件的密码(也就是我们在“为Push输入密码”图片中输入的密码),只有输入该密码才可让openssl命令读取该Push.p12私钥文件的内容;第2次输入的密码将会作为PushKey.pem文件的密码;第3次输入的密码用于确认第2次输入的密码-----此处依然输入”user”.
上面命令的作用是将PushCert.pem、PushKey.pem 合并为一个aps_developer_identity.p12 运行上面命令一共会提示输入3次密码:第1次输入PushKey.pem文件的密码(也就是我们在第2 步中为PushKey.pem文件输入的密码),只有输入该密码才可让openssl命令读取该PushKey.pem文件的内容;第2次输入的密码将会作为aps_developer_identity.p12文件的密码;第3次输入的密码用于确认第 2次输入的密码-----此处依然输入“user”。 整个运行过程如下图所示
|
|
Java领域有一个JavaPNS开源项目用于开发Apple Push Notification Service Provider,借助于该项目即可非常方便地开发远程推送通知的服务端程序。下载和安装JavaPNS的步骤如下:
JavaPNS_2.2.jar: 该选项只是下载JavaPNS项目的核心JAR包. JavaPNS_2.2_javadoc.zip: 该选项只是下载JavaPNS项目的API文档 JavaPNS_2.2_complete.zip: 该选项下载JavaPNS的完整压缩包,包括JavaPNS的核心JAR包、API文档和示例。
doc:该文件夹下包含JavaPNS项目的各种文档。 lib:该文件下包含bcprov-jdk15-146.jar、log4j-1.2.15.jar两个JAR包。这两个JAR包是 JavaPNS所依赖的JAR包。 src:该文件夹下包含JavaPNS项目的源代码。 JavaPNS_2.2.jar:这是JavaPNS项目的核心JAR包。
|
如下编写简单的Java源文件,即可实现远程推送通知的服务端程序
PushSever.java
import javapns.Push;
import javapns.notification.PushNotificationPayload;
public class PushServer
{
public static void main (String[] args)
{
// 向执行设备发送Push通知的device token
// 不同设备的device token应该由iOS应用通过网络发送给服务端程序
// 服务端程序应该将这些device token保存在服务器中
// 然后通过循环向每个device token发送Push通知
String deviceToken = “60c70bb185cd50edfaa430”; // ①
try
{
// 创建PushNotificationPayload
PushNotificationPayload payload = new PushNotificationPayload();
|
|
上面程序中的第1行红色字代码就是指定iOS设备的device token----在实际应用中, 该device token应该由iOS客户端通过网络发送给服务端程序,此处为了简单起见,我们直接将iOS设备的device token复制、粘贴到此处。 上面程序中的第2段红色字代码使用了Push类的payload()类方法来发送远程推送通知,发送推送通知指定了包含证书和私钥的文件、aps_developer_identity.p12文件的导出密码。 将JavaPNS解压路径中包含的JavaPNS_2.2.jar、bcprov-jdk15-146.jar、log4j-1.2.15.jar复制到该Java源文件的同一路径下,然后使用如下命令来编译该Java源文件: java –encoding utf-8 –cp JavaPNS_2.2.jar PushServer.java // ① 上面命令用于编译PushServer.java文件,该命令使用了-cp选项将JavaPNS_2.2.jar临时添加到类加载路径中. 使用如下命令运行PushServer: java –cp .: JavaPNS_2.2.jar:bcprov-jdk15-146.jar:log4j-1.2.15.jar PushServer // ② 上面命令用于运行PushServer,并使用了-cp选项将JavaPNS_2.2.jar、bcprov-jdk15-146.jar和log4j-1.2.15.jar临时添加到类加载路径中 /* 注意: 若在UNIX、Linux、OS X系统上执行上面的java命令,-cp选项值中的各JAR包的分隔符为英文冒号;如果在Windows系统中执行上面的java命令,则应将-cp选项值中的各JAR包的分隔符改为英文分号。而且,不要忘记了-cp选项值开始的一点(.),这个点(.)代表了当前路径,用于告诉系统在当前路径下搜索PushServer类文件。 */ |
















浙公网安备 33010602011771号