MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C推送通知(APNs)集成与优化

2024-06-237.0k 阅读

一、Objective-C 中 APNs 基础概念

1.1 APNs 简介

APNs 即 Apple Push Notification service(苹果推送通知服务),它允许第三方应用程序向 iOS、iPadOS、watchOS、macOS 和 tvOS 设备发送推送通知。APNs 作为一个桥梁,在服务器和用户设备之间传递通知信息。当应用处于非活动状态(比如后台运行或者完全关闭)时,APNs 能够让用户及时知晓应用相关的重要信息,如消息提醒、活动通知等。

在 Objective-C 开发环境中,与 APNs 集成可以为应用增加实时性和交互性,提升用户体验。例如,即时通讯应用通过 APNs 推送新消息通知,让用户即使没有打开应用也能及时获取信息;新闻类应用通过 APNs 推送热门新闻头条,吸引用户打开应用查看详细内容。

1.2 APNs 架构

APNs 架构主要由三部分组成:应用服务器、APNs 服务器和用户设备。

  • 应用服务器:这是开发者自己搭建的服务器,负责生成推送通知内容,并将其发送给 APNs 服务器。应用服务器需要维护用户设备的令牌(device token),这些令牌是每个设备在 APNs 服务器上的唯一标识。
  • APNs 服务器:由苹果公司运营,负责接收来自应用服务器的推送通知,并将其发送到目标用户设备。APNs 服务器会验证应用服务器的证书,确保推送来源合法。同时,APNs 服务器会管理设备的在线状态,对于不在线的设备,会在一定时间内保留推送通知,待设备上线后再进行推送。
  • 用户设备:运行 iOS 等操作系统的设备,如 iPhone、iPad 等。设备在安装应用后,会向 APNs 服务器注册,并获取一个 device token。设备通过这个 token 与 APNs 服务器进行通信,接收推送通知。

二、Objective-C 中 APNs 集成步骤

2.1 配置推送证书

  • 生成 CSR 文件:首先,在 Mac 电脑上打开“钥匙串访问”应用程序。选择“钥匙串访问”>“证书助理”>“从证书颁发机构请求证书”。在弹出的窗口中,填写您的电子邮件地址和常用名称。选择“存储到磁盘”,然后点击“继续”。这将生成一个证书签名请求(CSR)文件。
  • 创建推送证书:登录到苹果开发者中心(developer.apple.com),在“Certificates, Identifiers & Profiles”中,选择“Certificates”。点击“+”按钮创建新证书,选择“Apple Push Notification service SSL (Sandbox & Production)”(开发阶段使用沙盒环境,发布阶段使用生产环境)。上传刚才生成的 CSR 文件,苹果会生成推送证书。下载并双击该证书,将其安装到钥匙串中。
  • 导出证书:在钥匙串访问中,找到安装的推送证书。右键点击证书,选择“导出”。选择保存位置并设置密码。导出的文件格式为.p12,这个文件将用于配置应用服务器。

2.2 注册推送通知

在应用的 AppDelegate.m 文件中,注册推送通知。首先,导入必要的头文件:

#import <UserNotifications/UserNotifications.h>

然后,在 application:didFinishLaunchingWithOptions: 方法中添加以下代码:

if (@available(iOS 10.0, *)) {
    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
    center.delegate = self;
    [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound + UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) {
        if (granted) {
            NSLog(@"用户授权推送通知");
            dispatch_async(dispatch_get_main_queue(), ^{
                [[UIApplication sharedApplication] registerForRemoteNotifications];
            });
        } else {
            NSLog(@"用户拒绝推送通知");
        }
    }];
} else {
    UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge) categories:nil];
    [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
    [[UIApplication sharedApplication] registerForRemoteNotifications];
}

这段代码首先检查当前系统版本是否为 iOS 10.0 及以上。如果是,使用 UNUserNotificationCenter 来请求用户授权推送通知。如果用户授权,应用将注册远程通知。如果系统版本低于 iOS 10.0,则使用旧的 UIUserNotificationSettings 方式进行注册。

2.3 获取设备令牌

当应用成功注册远程通知后,系统会调用 AppDelegateapplication:didRegisterForRemoteNotificationsWithDeviceToken: 方法。在这个方法中,可以获取设备令牌,并将其发送到应用服务器。代码如下:

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
    token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSLog(@"设备令牌: %@", token);
    // 这里将 token 发送到应用服务器
}

上述代码将设备令牌转换为字符串格式,并去除了尖括号和空格。实际应用中,需要通过网络请求将这个设备令牌发送到应用服务器,以便应用服务器向该设备发送推送通知。

2.4 处理推送通知

当应用接收到推送通知时,会调用 AppDelegate 中的不同方法,具体取决于应用的状态。

  • 应用处于前台:调用 application:didReceiveRemoteNotification:fetchCompletionHandler: 方法。代码示例如下:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    NSLog(@"前台收到推送通知: %@", userInfo);
    // 处理推送通知内容
    completionHandler(UIBackgroundFetchResultNewData);
}
  • 应用处于后台或关闭状态:当用户点击推送通知打开应用时,调用 application:didFinishLaunchingWithOptions: 方法,在 launchOptions 字典中可以获取推送通知的 UIApplicationLaunchOptionsRemoteNotificationKey 键对应的值,即推送通知内容。代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
        NSDictionary *userInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
        NSLog(@"后台或关闭状态通过推送通知打开应用,通知内容: %@", userInfo);
        // 处理推送通知内容
    }
    // 其他应用启动相关代码
    return YES;
}

在这些方法中,可以根据推送通知的内容进行相应的处理,比如更新 UI、获取新数据等。

三、Objective-C 中 APNs 优化策略

3.1 优化推送频率

频繁的推送通知可能会让用户感到厌烦,甚至导致用户关闭应用的推送功能。因此,需要合理控制推送频率。

  • 合并推送:如果应用有多个相关的事件需要推送通知,可以将这些事件合并为一个推送通知。例如,一个电商应用可能有多个商品降价信息需要通知用户,可以将这些商品信息合并在一个推送通知中,而不是发送多条单独的通知。
  • 设置推送时间:分析用户的使用习惯,选择合适的时间进行推送。比如,对于一款健身类应用,在早上或晚上用户可能更倾向于接收健身计划提醒,而在工作时间推送可能会打扰用户。可以通过用户设置或者大数据分析来确定最佳推送时间。

3.2 优化推送内容

推送内容应该简洁明了,吸引用户点击。

  • 个性化推送:根据用户的偏好和行为,发送个性化的推送通知。例如,音乐应用可以根据用户平时喜欢的音乐类型,推送新发布的相关音乐专辑通知。
  • 避免冗长内容:推送通知的标题和正文应该简洁,突出重点。如果需要展示更多内容,可以引导用户点击通知进入应用查看详细信息。

3.3 处理推送失败

APNs 推送可能会因为各种原因失败,如设备离线、token 无效等。应用服务器需要处理这些推送失败情况,以确保重要通知能够成功送达用户。

  • 监控推送状态:应用服务器可以通过 APNs 的反馈服务获取推送失败的设备令牌。APNs 会定期向应用服务器发送一个包含无效设备令牌的文件。应用服务器收到这个文件后,需要从自己的数据库中删除这些无效的设备令牌,避免向这些设备再次发送无效推送。
  • 重试机制:对于一些临时性的推送失败情况,如网络问题导致的推送失败,可以设置重试机制。应用服务器在检测到推送失败后,等待一段时间后重试推送,直到达到最大重试次数。

3.4 提高网络性能

推送通知的及时送达依赖良好的网络性能。

  • 优化网络请求:在将推送通知发送到 APNs 服务器时,应用服务器应该优化网络请求,减少请求时间和带宽消耗。可以使用高效的网络库,如 AFNetworking,并且对请求进行适当的缓存和压缩。
  • 处理网络波动:在设备端,应用需要能够处理网络波动情况。当网络不稳定时,应用应该暂时缓存推送通知相关数据,待网络恢复后再进行处理,确保推送通知的接收不受网络问题的严重影响。

四、APNs 高级特性与技巧

4.1 静默推送

静默推送允许应用在后台获取新数据,而不会向用户显示通知。在 iOS 7 及以上版本,可以使用静默推送来更新应用数据,保持应用内容的实时性。

  • 配置 Capabilities:在 Xcode 项目的 Capabilities 中,启用 Background Modes,并勾选 Remote notifications
  • 注册远程通知:在 AppDelegate.m 中注册远程通知时,添加 UIUserNotificationTypeRemoteNotification 类型(对于 iOS 10.0 以下),或者在 UNUserNotificationCenter 请求授权时添加 UNAuthorizationOptionRemoteNotification 选项(对于 iOS 10.0 及以上)。
  • 处理静默推送:当应用接收到静默推送时,会调用 application:didReceiveRemoteNotification:fetchCompletionHandler: 方法。在这个方法中,可以进行数据更新等操作,如从服务器获取最新的消息列表、更新应用配置等。代码示例如下:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    if ([[userInfo objectForKey:@"aps"] objectForKey:@"content-available"]) {
        // 这是静默推送
        NSLog(@"收到静默推送,开始更新数据");
        // 执行数据更新操作
        completionHandler(UIBackgroundFetchResultNewData);
    } else {
        // 普通推送处理
        NSLog(@"前台收到普通推送通知: %@", userInfo);
        completionHandler(UIBackgroundFetchResultNewData);
    }
}

上述代码通过检查推送通知中的 content-available 字段来判断是否为静默推送。如果是静默推送,则执行数据更新操作。

4.2 通知扩展

iOS 8 及以上版本支持通知扩展,允许开发者自定义推送通知的外观和行为。通知扩展可以提供更丰富的交互方式,如在通知中添加按钮、输入框等。

  • 创建通知扩展目标:在 Xcode 中,选择 File > New > Target,然后选择 Notification Service ExtensionNotification Content ExtensionNotification Service Extension 可以在通知到达设备时修改通知内容,如添加图片、修改文本等;Notification Content Extension 可以自定义通知的界面布局。
  • 配置扩展:在扩展的 Info.plist 文件中,可以设置扩展的相关属性,如扩展的显示名称、支持的通知类型等。
  • 实现扩展逻辑:对于 Notification Service Extension,在 NotificationService.m 文件中,可以重写 didReceiveNotificationRequest:withContentHandler: 方法来修改通知内容。例如,以下代码在通知中添加一张图片:
#import "NotificationService.h"
#import <ImageIO/ImageIO.h>
#import <UIKit/UIKit.h>

@interface NotificationService ()

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];

    NSURL *imageURL = [NSURL URLWithString:@"https://example.com/image.jpg"];
    NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
    if (imageData) {
        UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:imageURL options:nil error:nil];
        if (attachment) {
            self.bestAttemptContent.attachments = @[attachment];
        }
    }

    self.contentHandler(self.bestAttemptContent);
}

- (void)serviceExtensionTimeWillExpire {
    self.contentHandler(self.bestAttemptContent);
}

@end

对于 Notification Content Extension,需要在 ViewController.m 文件中设计和实现自定义的通知界面。可以使用 Interface Builder 来创建界面布局,并通过代码来处理用户交互。

4.3 基于位置的推送

基于位置的推送可以根据用户所在的地理位置发送相关的推送通知。例如,当用户进入某个商场时,商场的应用可以推送附近店铺的优惠信息。

  • 请求位置权限:在应用中,需要请求用户的位置权限。在 AppDelegate.m 中导入 CoreLocation 框架,并在 application:didFinishLaunchingWithOptions: 方法中请求权限:
#import <CoreLocation/CoreLocation.h>

@interface AppDelegate () <CLLocationManagerDelegate>

@property (nonatomic, strong) CLLocationManager *locationManager;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;
    if ([self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
        [self.locationManager requestWhenInUseAuthorization];
    }
    // 其他应用启动代码
    return YES;
}

- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
    if (status == kCLAuthorizationStatusAuthorizedWhenInUse) {
        [self.locationManager startUpdatingLocation];
    }
}

@end
  • 设置地理围栏:使用 CLRegion 类来设置地理围栏。当用户进入或离开某个地理围栏区域时,应用可以发送推送通知。例如:
- (void)startMonitoringRegion {
    CLLocationCoordinate2D center = CLLocationCoordinate2DMake(37.7749, -122.4194);
    CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:100 identifier:@"MyRegion"];
    [self.locationManager startMonitoringForRegion:region];
}

- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
    if ([region.identifier isEqualToString:@"MyRegion"]) {
        // 发送推送通知
        UILocalNotification *notification = [[UILocalNotification alloc] init];
        notification.alertBody = @"您已进入指定区域,附近有优惠活动!";
        [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
    }
}

- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
    if ([region.identifier isEqualToString:@"MyRegion"]) {
        // 也可以发送离开区域的通知
    }
}

上述代码首先设置了一个以指定经纬度为中心、半径为 100 米的地理围栏区域。当用户进入该区域时,应用会发送一条本地推送通知。

通过以上对 Objective-C 中 APNs 的集成与优化的详细介绍,开发者可以为应用添加高效、用户友好的推送通知功能,提升应用的用户体验和竞争力。在实际开发中,需要根据应用的特点和用户需求,灵活运用这些技术和策略。