第十一章:推送机制

本章将会详细介绍iOS本地通知和远程通知相关的知识。本地通知和远程推送通知都可以向不在前台运行的应用发送消息,他们在程序界面上的显示效果相同,都可能显示为一段警告信息或应用程序图标上的徽标。

不管是本地通知还是远程推送通知,都可对用户进行提醒,提醒用户即将要做的事情,也可将服务器数据发送给iOS客户端。本地通知和推送通知的基本目的都是让应用程序能够通知用户某些事情,而且不需要应用程序在前台运行。

一、使用NSNotificationCenter通信

NSNotificationCenter实现了观察者模式,允许应用的不同对象之间以松耦合的方式进行通信。

NSNotificationCenter就是iOS SDK为开发者实现的观察者模式。这种设计模式的示意图如下:


NSNotificationCenter相当于一个『消息中心』,首先由Observer组件向NSNotificationCenter进行注册——表明该Observer组件对哪些NSNotification感兴趣。在咩有组件发送NSNotification的情况下,这些组件都处于静止状态,不会执行任何代码。
当Poster向NSNotificationCenter发送NSNotification后,所有在NSNotificationCenter上注册过、对该NSNotification感兴趣的Observer都会被激发。

NSNotification代表Poster与Pbserver之间的信息载体,该对象包含如下只读属性:

//①、
@property (readonly, copy) NSString *name;
//该属性代表该通知的名字。程序将Poster注册到指定通知中心时,就是根据该名称进行注册的。
//②、
@property (nullable, readonly, retain) id object;
//该属性代表该通知的Poster。
//③、
@property (nullable, readonly, copy) NSDictionary *userInfo;
//该属性该表是一个NSDictionary对象,用于携带通知的附加信息。

NSNotificationCenter是整个通知系统的中心,Observer向NSNotificationCenter注册自己感兴趣的通知,Poster向NSNotificationCenter发送通知。

NSNotificationCenter提供了defaultCenter类方法获取系统默认的通知中心对象,获取NSNotificationCenter对象之后,接下来即可通过如下方法注册和删除监听者。

①、
- (id <NSObject>)addObserverForName:(nullable NSString *)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block NS_AVAILABLE(10_6, 4_0);
//该方法将指定代码块注册为监听者,监听object:参数代表的对象(Poster)发出的通知(由第1个参数指定通知名称)。该方法直接使用指定代码块作为监听者,当Poster向NSNotificationCenter发送通知时,将会触发、执行该代码块。如果object:参数为nil,则用于监听任何对象发出的通知。
②、
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;
//该方法将第1个参数注册为监听者,监听bject:参数代表的对象(Poster)发出的通知(由name:参数指定通知名称),其中selector:参数用于指定监听者的监听方法——当Poster向NSNotificationCenter发送通知时,将会触发、执行object:参数代表的对象的selector方法。
③、
- (void)removeObserver:(id)observer;
//删除一个监听者;
④、
- (void)removeObserver:(id)observer name:(nullable NSString *)aName object:(nullable id)anObject;
//取消监听者对指定Poster、指定通知的监听;
⑤、
- (void)postNotification:(NSNotification *)notification;
//发送通知。该方法需要一个NSNotification对象作为通知;
⑥、
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject;
//发送通知。该方法的第1个参数指定通知名称,第2个参数指定通知的Poster。
⑦、
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
//发送通知。该方法的第1个参数指定通知名称,第2个参数指定通知的Poster,第3个参数指定通知的userInfo。

1、使用NSNotificationCenter监听系统组件的通知

- (void)viewDidLoad {
    [super viewDidLoad];
    //监听UIAlication的UIApplicationDidFinishLaunchingNotification通知
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(lauch:) name:UIApplicationDidFinishLaunchingNotification object:[UIApplication sharedApplication]];
    //监听UIAlication的UIApplicationDidEnterBackgroundNotification通知
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(back:) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]];
    //监听UIAlication的UIApplicationWillEnterForegroundNotification通知
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(fore:) name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]];
}
- (void)lauch:(NSNotification *)notification{
    NSLog(@"应用程序加载完成!");
}
- (void)back:(NSNotification *)notification{
    NSLog(@"应用程序进入后台!");
}
- (void)fore:(NSNotification *)notification{
    NSLog(@"应用程序进入前台!");
}

2、使用NSNotificationCenter监听自定义通知

#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];
    [self start];
}
- (void)start{
    __block int progStatus = 0;
    //以传入的代码快作为执行体,创建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 *)notification{
    //userInfo属性获取耗时任务的进度信息
    NSNumber* progStatus = notification.userInfo[@"prog"];
    NSLog(@"%d",progStatus.intValue);
    dispatch_async(dispatch_get_main_queue(), ^{
        //做一个视图改变
    });
}

@end

二、iOS本地通知

本地通知和远程推送通知不同于前面介绍的NSNotification,这种通知属于应用界面编程的内容,本地通知和远程推送通知都可以向不在前台运行的应用发送消息,这种消息即可能是即将发生的事件,也可能是服务器的新数据。不管是本地通知还是远程推送通知,它们在程序界面的显示效果相同,都可能显示一段警告信息或应用程序图标上的徽标。

当用户点击本地通知或远程推送通知时,可以启动相应的应用程序来查看详情,也可以选择忽略通知,此时应用程序将不会被激活。

本地通知和远程推送通知的基本目的都是让应用程序能够通知用户某些事情,而且不需要应用程序在前台运行。二者的区别在于:本地通知由本应用负责调用,只能从当前设备上iOS发出;而远程推送通知由远程服务器上的程序(可由任意语言编写)发送至Apple Push Notification service(APNs),再由APNs把消息推送至设备上对应的程序。

本地通知(仅在iOS中有效)适用于基于时间的程序,包括简单的日历程序或者to-do列表类型的应用程序。那些在有限时间内运行的后台程序(iOS系统许可的)也能接收到本地通知。例如:需要从服务端获取数据的应用程序,可以在后台运行并查询服务端最新的数据,如果有消息或数据需要通知用户更新或下载,程序可以立即发送一个本地通知来通知用户。

本地通知是一个UILocalNotification对象,它有如下常用属性:

①、
@property(nullable, nonatomic,copy) NSDate *fireDate;
//指定通知将在什么时间触发。
②、
@property(nonatomic) NSCalendarUnit repeatInterval; 
//设置本地通知重复发送的时间间隔;
③、
@property(nullable, nonatomic,copy) NSString *alertBody;
//设置本地通知的消息体;
④、
@property(nullable, nonatomic,copy) NSString *alertAction;
//设置当设备处于锁屏状态时,显示通知的警告框下方的title;
⑤、
@property(nonatomic) BOOL hasAction;
//设置是否显示Action;
⑥、
@property(nullable, nonatomic,copy) NSString *alertLaunchImage;
//当用户通过该通知启动对应的应用时,该属性设置为加载图片;
⑦、
@property(nonatomic) NSInteger applicationIconBadgeNumber;
//设置显示在应用程序上红色徽标中的数字;
⑧、
@property(nullable, nonatomic,copy) NSString *soundName;
//设置通知的声音;
⑨、
@property(nullable, nonatomic,copy) NSDictionary *userInfo;
//设置该通知携带的附加信息;

创建了UILocalNotification对象之后,接下来就可以通过UIApplication的如下两个方法发送通知了:

①、
- (void)scheduleLocalNotification:(UILocalNotification *)notification NS_AVAILABLE_IOS(4_0) __TVOS_PROHIBITED;  // copies notification
//该方法指定调度通知。通知将会在于fireDate指定的时间触发,而且会按repeatInterval指定的时间间隔重复触发。
②、
- (void)presentLocalNotificationNow:(UILocalNotification *)notification NS_AVAILABLE_IOS(4_0) __TVOS_PROHIBITED;
//该方法指定立即发送通知。该方法忽略UILocalNotification的fireDate属性。

每个应用程序最多只能发送最近(新)的64个本地通知,超过该限制的通知将被操作系统自动放弃。重复出现的通知会被认为是一个通知。

除此之外,如果系统发出通知时,应用程序处于前台运行,系统将会触发应用程序委托类的方法

 - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification NS_AVAILABLE_IOS(4_0) __TVOS_PROHIBITED;

在iOS应用中发送本地通知的步骤如下:

  1. 创建UILocalNotification对象;
  2. 设置UILocalNotification的属性;
  3. 调用UIApplication的方法发送或调用通知;
  4. 如果希望应用程序在前台运行时可以对通知进行相应的处理,则需要重写应用程序委托类application:didReceiveLocalNotification:方法;
  5. 当应用需要取消本地通知,可调用UIApplication的cancelLocalNotification:方法取消指定通知,或调用cancelLocalNotifications方法取消所有通知。

例如:

@interface ViewController ()
{
    UIApplication* app;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    app = [UIApplication sharedApplication];

    UISwitch* uiSwitch = [[UISwitch alloc]initWithFrame:CGRectMake(100, 100, 100, 40)];
    [uiSwitch addTarget:self action:@selector(clickSwitch:) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:uiSwitch];
}
- (void)clickSwitch:(UISwitch *)sender{
    if (sender.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.alertLaunchImage = @"logo.png";
        //设置通知内容
        notification.alertBody = @"唉,我很尴尬你知道不";
        //设置显示在应用程序上红色徽标中的数字
        notification.applicationIconBadgeNumber = 1;
        //设置userInfo,用于携带额外的附加信息
        NSDictionary* info = @{@"key":@"fkjava.org"};
        notification.userInfo = info;
        //调度通知
        [app scheduleLocalNotification:notification];
    }else{
        //获取所有处于调度中的本地通知数组
        NSArray* localArray = [app scheduledLocalNotifications];
        NSLog(@"%@",localArray);
        if (localArray) {
            for (UILocalNotification* noti in localArray) {
                [app cancelLocalNotification:noti];
            }
        }
        //取消所有的通知
        [app cancelAllLocalNotifications];
    }
}
@end
//只有当应用程序在前台运行时,该方法才会被调用
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
    //如果应用程序在前台运行,则将应用程序图标上的红色徽标中数字设为0
    application.applicationIconBadgeNumber = 0;
    NSLog(@"收到通知:%@",notification.alertBody);
}

三、iOS远程推送通知

iOS远程推送通知由远程服务器上的程序(可由任意语言编写)发送至APNs,再由APNs把消息推送至设备上对应的程序。

iOS远程推送通知的过程如下:

Provider指远程服务器上的Push服务端应用,这种Push服务端应用可以使用任何语言编写,如Java,PHP等。APNs由Apple公司提供,APNs负责把通知发送到对应的iOS设备,该设备再把通知转发给Client App——即我们的iOS应用。

该过程可分为3个阶段:

  • 第一阶段:Provider程序把要发送的通知、目标iPhone的device token(相当于该设备的第一标识)打包,发送给APNs。
  • 第二阶段:APNs通过已注册Push服务iPhone列表查找具有对应的device token的iPhone,并把Push通知发送给对应的iPhone。
  • 第三阶段:iPhone将收到的Push通知传递给相应的应用程序,并且按照设定弹出Puch通知。

(1)、Push客户端应用需要3个组件

  • App ID(应用程序唯一标识,这个必须到Apple网站注册来获得);
  • Provisioning Profile(到Apple网站下载);
  • device token(当Push客户端应用注册Push推送通知成功时,APNs将会返回该设备device token);

(2)、Push服务端程序需要如下两个组件

  • SSL Certificate SSL连接证书,这个必须从Apple网站下载;
  • Private Key(私钥,这个可通过开发这电脑导出);

1、开发Push客户端应用

开发Push客户端应用需要到Apple网站注册一个App ID,而且该App ID不允许使用配符。通过Apple网站注册App ID的步骤如下。
①、打开OS X系统上的『钥匙串访问』应用,单击该应用的主菜单『钥匙串访问』->『证书助理』->『从证书颁发机构请求证书』,如下图

②、单击『从证书发布机构申请证书』将会出现如图的对话框

第十一章:推送机制_第1张图片
③、输入电子邮件地址和常用名称,并选中『存储到磁盘』单选项,然后单击『继续』按钮,该程序将会创建一个『Certificate Signing Request(证书签名请求)』文件,系统弹出如图所示的保存文件对话框;
第十一章:推送机制_第2张图片
将证书签名请求文件保存到磁盘上,此处将该文件保存为『Push.certSigningRequest』。
④、使用浏览器打开https//:developer.apple.com/ios/manage/overview/index.action站点,页面上将会提示用户输入开发者帐号、密码。登录成功将会看到如下所示的页面(现在已改版,大体差不多)

⑤、单击『iOS App』->『Identifiers』栏目下的『App IDs』链接,系统将会显示如下
第十一章:推送机制_第3张图片
⑥、点击页面右上角的『+』按钮(添加App ID),系统打开如下页面:
第十一章:推送机制_第4张图片
对于Push通知而言,App ID不能带通配符的ID,因此在上图中选择『Explicit App ID』分类,按图所示输入App ID的描述字符串和唯一标识。需要说明的是,App ID的描述字符串可以随便填,但该App ID的唯一标识必须要记住,通常采用『公司域名+应用名』的格式。
填写完成后,单击页面下方的『Continue』按钮。
⑦、单击刚刚注册的App ID,可以看到该App ID支持的各种服务,如下:

⑧、从上图所示的页面可以看出,该App ID病不支持Push Notifications(推送通知)和iCloud服务。单击『Edit』按钮,进入编辑该App ID的页面:
第十一章:推送机制_第5张图片
⑨、勾选Push Notifications右边的复选框——这回启用该App ID的Push通知功能。勾选该复选框之后,Push Notification下面的两个『Create Certificate…』按钮将会变成可用状态,其中第1个按钮用于创建开发阶段的证书,第2个按钮用于创建产品化阶段的证书。此处只需要创建开发阶段的证书,当需要正式发布该iOS应用时,还需要创建产品化阶段的证书。
⑩、单击第1个『Create Certificate』按钮,系统将会显示一个提示页面,告诉用户去创建一个CSR文件——也就是我们前面已经创建的『Certificate Signing Request』文件,由于我们已经创建了该文件,因此直接单击『Continue』按钮,系统会显示如图页面:
第十一章:推送机制_第6张图片
⑪、单击『Choose File』按钮选择前面创建的Push.certSigningRequest文件,然后单击『Generate』按钮,系统将会为该App ID生成开发证书。成功生成开发证书之后,可以看到如下页面:
第十一章:推送机制_第7张图片
⑫、成功生成App ID的开发证书之后,即可通过该页面中的『Download』按钮下载证书,也可以返回系统证书列表也米娜去下载证书。此处先将下载并保存到本地磁盘,该证书的文件名为『apd_development.cer』。
⑬、双击上一步下载得到的『apd_development.cer』文件,OS X系统将会自动打开『钥匙串访问』应用程序,并将该证书添加到系统中。此时将可以在『钥匙串访问』应用程序中看到如下一行:

⑭、通过证书列表页面也可以下载指定的证书(只要点击指定的证书,页面就会显示『Revoke』、『Download』两个按钮,其中『Revoke』按钮用于删除证书,『Download』按钮用于下载证书)。
第十一章:推送机制_第8张图片

经过上面步骤,我们已经成功为Push客户端创建一个支持Push通知的App ID,并下载,安装了该App ID的开发证书。

现在开始开发Push客户端应用。新建一个Single View Application,该应用的『Bundle Identifier』必须与前面注册的App ID完全相同。
第十一章:推送机制_第9张图片

接下来通过修改应用程序委托类来注册远程Push通知,并重写对应的方法来处理远程Push通知。
UIApplication提供了如下方法来注册远程Push通知:

①、
- (void)registerForRemoteNotificationTypes:(UIRemoteNotificationType)types NS_DEPRECATED_IOS(3_0, 8_0, "Please use registerForRemoteNotifications and registerUserNotificationSettings: instead") __TVOS_PROHIBITED; //注册远程推送通知iOS8后由registerForRemoteNotifications和registerUserNotificationSettings:代替; ②、 - (void)unregisterForRemoteNotifications NS_AVAILABLE_IOS(3_0); //取消注册远程推送通知;

UIApplicationDelegate中则定义了如下与远程推送通知相关方法:

①、
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo NS_AVAILABLE_IOS(3_0); //应用程序收到远程Push通知时自动执行该方法; ②、 - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken NS_AVAILABLE_IOS(3_0); //该应用成功注册远程推送通知时激发该方法; ③、 - (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error NS_AVAILABLE_IOS(8_0); //该应用注册远程推送通知失败时激发该方法;

iOS应用处理远程通知比较简单:

  • ①、在应用程序委托类的application:didFinishLaunchingWithOptions:方法中调用UIApplication的registerForRemoteNotificationTypes:注册远程Push通知(iOS 8以后调用registerForRemoteNotifications和registerUserNotificationSettings:)
  • ②、重写应用程序委托类的如上3个方法。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {


#ifdef __IPHONE_8_0
    //IOS8 新的通知机制category注册
    UIUserNotificationType types = (UIUserNotificationTypeAlert|
                                    UIUserNotificationTypeSound|
                                    UIUserNotificationTypeBadge);
    UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
    [[UIApplication sharedApplication] registerForRemoteNotifications];
    [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
#else
    UIRemoteNotificationType apn_type = (UIRemoteNotificationType)(UIRemoteNotificationTypeAlert|
                                                                   UIRemoteNotificationTypeSound|
                                                                   UIRemoteNotificationTypeBadge);
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:apn_type];
#endif
    // Override point for customization after application launch.
    return YES;
}
#pragma mark -- UIApplicationDelegate
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken{
    NSLog(@"注册成功:%@", pToken);
    //注册成功,应该将该device token 发送到Push服务端程序,
    //Push服务端程序应该将该Token保存到数据库中,以备以后重复使用
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    NSLog(@"注册失败:%@",error);
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{

    NSLog(@"收到通知:userInfo == %@",userInfo);
    NSString *message = [[userInfo objectForKey:@"aps"]objectForKey:@"alert"];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:message delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定",nil];
    [alert show];
}

你可能感兴趣的:(ios,推送,通知)