接入 iOS
本文將向您詳細(xì)介紹將消息推送服務(wù)接入 iOS 客戶端的接入流程。
前提條件
您已將工程接入到 mPaaS。更多信息,請(qǐng)參見以下內(nèi)容:
操作步驟
要使用消息推送服務(wù),您需要完成以下接入步驟:
添加消息推送 SDK。
根據(jù)您接入 mPaaS 采用的接入方式,添加消息推送 SDK。
在接入 mPaaS 的三種接入方式中,均可以添加消息推送 SDK。
當(dāng)采用 基于 mPaaS 框架接入 或 基于已有工程且使用 mPaaS 插件接入 的接入方式時(shí),在編輯工程頁(yè)面,選擇 Push(消息推送),即可添加消息推送 SDK,完成消息推送 SDK 接入。
當(dāng)采用 基于已有工程且使用 CocoaPods 接入 的接入方式時(shí),在 Podfile 文件中,使用
mPaaS_pod "mPaaS_Push"
添加消息推送組件依賴,然后執(zhí)行pod install
完成消息推送 SDK 接入。
配置工程。需要在工程的 TARGETS 設(shè)置中開啟以下兩項(xiàng)配置:
Capabilities > Push Notifications
Capabilities > Background Modes > Remote notifications
使用 SDK。下面對(duì)不同接入方式的 SDK 使用進(jìn)行說(shuō)明。
基于 mPaaS 框架接入
注冊(cè) deviceToken(非必需)。
消息推送 SDK 在應(yīng)用啟動(dòng)完成時(shí),會(huì)自動(dòng)請(qǐng)求注冊(cè) deviceToken,一般情況下您無(wú)需請(qǐng)求注冊(cè) deviceToken。但是當(dāng)特殊情況下(比如啟動(dòng)時(shí)有隱私管控,阻止一切網(wǎng)絡(luò)請(qǐng)求時(shí))您需要在管控授權(quán)后,再次觸發(fā)注冊(cè) deviceToken,示例代碼如下:
- (void)registerRemoteNotification { // 注冊(cè)推送 if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0) {// 10.0+ UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = self; [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert|UNAuthorizationOptionSound|UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) { // Enable or disable features based on authorization. if (granted) { dispatch_async(dispatch_get_main_queue(), ^{ [[UIApplication sharedApplication] registerForRemoteNotifications]; }); } }]; }]; } else {// 8.0,9.0 UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge |UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil]; [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; [[UIApplication sharedApplication] registerForRemoteNotifications]; } }
獲取 deviceToken 并綁定 userId。
與基于 iOS 原生框架相比,基于 mPaaS 框架的應(yīng)用的生命周期被 mPaaS 框架接管,應(yīng)用獲取 deviceToken 的回調(diào)方法有所不同,代碼示例如下:
// import <PushService/PushService.h> // 在 DTFrameworkInterface 分類中重寫如下方法 - (DTFrameworkCallbackResult)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [[PushService sharedService] setDeviceToken:deviceToken]; [[PushService sharedService] pushBindWithUserId:@"your userid(需替換)" completion:^(NSException *error) { }]; return DTFrameworkCallbackResultContinue; }
說(shuō)明消息推送 SDK 同時(shí)提供了解綁的接口
- (void)pushUnBindWithUserId:(NSString *)userId completion:(void (^)(NSException *error))completion;
,用于解除設(shè)備的 deviceToken 與當(dāng)前應(yīng)用的 userId 的綁定。如在用戶切換賬號(hào)后,可以調(diào)用解綁接口。綁定用戶手機(jī)號(hào)。
重要目前,僅杭州非金融區(qū)提供短信補(bǔ)充服務(wù)。
將 deviceToken 和用戶的手機(jī)號(hào)碼綁定。綁定手機(jī)號(hào)碼后,用戶就可以通過該手機(jī)號(hào)碼收到相關(guān)推送短信。
// 若在綁定接口上傳手機(jī)號(hào)碼,則可以使用短信補(bǔ)充服務(wù) - (void)pushBindWithUserId:(NSString *)userId phoneNumber:(NSString *)phoneNumber completion:(void (^)(NSException *error))completion;
接收推送的消息。
基于 mPaaS iOS 框架的應(yīng)用,由于其生命周期被 mPaaS 框架接管,與基于 iOS 原生框架相比,收到消息的回調(diào)方法不同,代碼示例如下:
// import <PushService/PushService.h> // 在 DTFrameworkInterface 分類中重寫如下方法 - (DTFrameworkCallbackResult)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { // userInfo 為推送消息內(nèi)容,業(yè)務(wù)解析處理 return DTFrameworkCallbackResultContinue; }
統(tǒng)計(jì)消息的打開率。
為了統(tǒng)計(jì)消息在客戶端的打開率,您需要在 App 消息被用戶打開時(shí),調(diào)用
PushService
的pushOpenLogReport
接口(10.1.32 及以上版本可用)上報(bào)消息打開事件。該事件上報(bào)后,您可以在 mPaaS 控制臺(tái)中的 消息推送 > 概覽 頁(yè)面中查看消息打開率的統(tǒng)計(jì)數(shù)據(jù)。/** * 打開推送消息的上報(bào)接口,用于統(tǒng)計(jì)推送消息的打開率 * @param userInfo 消息的 userInfo * @return */ - (void)pushOpenLogReport:(NSDictionary *)userInfo;
基于已有工程接入
在基于已有工程且使用 mPaaS 插件或 CocoaPods 接入 iOS 客戶端的情況下,您需要完成以下操作。
注冊(cè) deviceToken(非必需)。
消息推送 SDK 在應(yīng)用啟動(dòng)完成時(shí),會(huì)自動(dòng)請(qǐng)求注冊(cè) deviceToken,一般情況下您無(wú)需請(qǐng)求注冊(cè) deviceToken。但是當(dāng)特殊情況下(比如啟動(dòng)時(shí)有隱私管控,阻止一切網(wǎng)絡(luò)請(qǐng)求時(shí))您需要在管控授權(quán)后,再次觸發(fā)注冊(cè) deviceToken,示例代碼如下:
- (void)registerRemoteNotification { // 注冊(cè)推送 if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0) {// 10.0+ UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = self; [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert|UNAuthorizationOptionSound|UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) { // Enable or disable features based on authorization. if (granted) { dispatch_async(dispatch_get_main_queue(), ^{ [[UIApplication sharedApplication] registerForRemoteNotifications]; }); } }]; }]; } else {// 8.0,9.0 UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge |UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil]; [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; [[UIApplication sharedApplication] registerForRemoteNotifications]; } }
獲取 deviceToken 并綁定 userId。
mPaaS 提供的消息推送 SDK 中封裝了向 APNs 服務(wù)器注冊(cè)的邏輯,在程序啟動(dòng)后,Push SDK 自動(dòng)向 APNs 服務(wù)器注冊(cè)。您可在注冊(cè)成功的回調(diào)方法中獲取 APNs 下發(fā)的 deviceToken,然后調(diào)用
PushService
的接口方法,上報(bào)綁定 userId 至移動(dòng)推送核心。// import <PushService/PushService.h> - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [[PushService sharedService] setDeviceToken:deviceToken]; [[PushService sharedService] pushBindWithUserId:@"your userid(需替換)" completion:^(NSException *error) { }]; }
消息推送 SDK 同時(shí)提供了解綁的接口
- (void)pushUnBindWithUserId:(NSString *)userId completion:(void (^)(NSException *error))completion;
,用于解除設(shè)備的 deviceToken 與當(dāng)前應(yīng)用的 userId 的綁定。如在用戶切換賬號(hào)后,可以調(diào)用解綁接口。綁定用戶手機(jī)號(hào)。
重要目前,僅杭州非金融區(qū)提供短信補(bǔ)充服務(wù)。
將 deviceToken 和用戶的手機(jī)號(hào)碼綁定。綁定手機(jī)號(hào)碼后,用戶就可以通過該手機(jī)號(hào)碼收到相關(guān)推送短信。
// 若在綁定接口上傳手機(jī)號(hào)碼,則可以使用短信補(bǔ)充服務(wù) - (void)pushBindWithUserId:(NSString *)userId phoneNumber:(NSString *)phoneNumber completion:(void (^)
接收推送的消息。
客戶端收到推送的消息后,如果用戶點(diǎn)擊查看,系統(tǒng)將啟動(dòng)相應(yīng)應(yīng)用。可在
AppDelegate
的回調(diào)方法中完成收到 push 消息后的邏輯處理。在 iOS 10 以下系統(tǒng)中,通知欄消息或靜默消息的處理方法如下:
// iOS 10 以下 Push 冷啟動(dòng)處理 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSDictionary *userInfo = [launchOptions objectForKey: UIApplicationLaunchOptionsRemoteNotificationKey]; if ([[[UIDevice currentDevice] systemVersion] doubleValue] < 10.0) { // iOS 10 以下 Push 冷啟動(dòng)處理 } return YES; } // App 在前臺(tái)時(shí),普通推送的處理方法;App 在前臺(tái)或后臺(tái)時(shí),靜默推送的處理方法;iOS 10 以下系統(tǒng),通知欄消息處理方法 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { //處理接受到的消息 }
在 iOS 10 及以上系統(tǒng)中,您需要實(shí)現(xiàn)以下代理方法來(lái)監(jiān)聽通知欄消息:
// 注冊(cè) UNUserNotificationCenter delegate if ([[[UIDevice currentDevice] systemVersion] doubleValue] >= 10.0) { UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = self; } //應(yīng)用處于前臺(tái)時(shí)的遠(yuǎn)程推送接收 - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { NSDictionary *userInfo = notification.request.content.userInfo; if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) { //應(yīng)用處于前臺(tái)時(shí)的遠(yuǎn)程推送接收 } else { //應(yīng)用處于前臺(tái)時(shí)的本地推送接收 } completionHandler(UNNotificationPresentationOptionNone); } //應(yīng)用處于后臺(tái)或者活冷啟動(dòng)時(shí)遠(yuǎn)程推送接收 - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler { NSDictionary *userInfo = response.notification.request.content.userInfo; if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) { //應(yīng)用處于后臺(tái)或者活冷啟動(dòng)時(shí)遠(yuǎn)程推送接收 } else { //應(yīng)用處于前臺(tái)時(shí)的本地推送接收 } completionHandler(); }
統(tǒng)計(jì)消息的打開率。
基于已有工程統(tǒng)計(jì)消息打開率的操作方法和基于 mPaaS 框架完全相同。
配置推送證書。
要使用 mPaaS 消息推送控制臺(tái)推送消息,您需要在控制臺(tái)中配置 APNs 推送證書。該證書必須是與客戶端簽名對(duì)應(yīng)的推送證書,否則客戶端會(huì)收不到推送消息。有關(guān)詳細(xì)的配置說(shuō)明,查看 配置 iOS 推送通道。
后續(xù)操作
在 mPaaS 消息推送控制臺(tái)配置完 APNs 證書后,可以按設(shè)備維度向應(yīng)用推送消息。消息推送服務(wù)使用蘋果的 APNs 服務(wù)向客戶端推送消息,更多信息請(qǐng)參見 蘋果及國(guó)外安卓設(shè)備推送流程。
上報(bào)用戶 ID 并由服務(wù)端綁定用戶和設(shè)備后,可以按用戶維度向應(yīng)用推送消息。
代碼示例
點(diǎn)擊此處 下載示例代碼包。
相關(guān)鏈接
Live Activity 消息推送
iOS 在 16.1 版本中推出了一個(gè)新功能:Live Activity(實(shí)時(shí)活動(dòng))。該功能可以將實(shí)時(shí)活動(dòng)展示在鎖屏界面上,幫助用戶從鎖定的屏幕實(shí)時(shí)獲知各種活動(dòng)的進(jìn)展。在主工程中,可以使用 ActivityKit 框架來(lái)開啟、更新、結(jié)束實(shí)時(shí)活動(dòng)。其中,更新和結(jié)束實(shí)時(shí)活動(dòng)還可以使用遠(yuǎn)程推送來(lái)實(shí)現(xiàn)。在 widget extension 中,可以使用 SwiftUI 和 WidgetKit 來(lái)創(chuàng)建 Live Activity 的界面。其中,Live Activity 遠(yuǎn)程推送更新功能,不支持 .p12
證書,因此需要用戶配置 .p8
證書。
同一個(gè)項(xiàng)目中可以同時(shí)開啟多個(gè) Live Activity,不同的 Live Activity,其 token 是不同的。
Live Activity 蘋果官方文檔
Live Activity 使用限制
只能在 iOS 16.1 版本以上的系統(tǒng)上運(yùn)行。
只支持 iPhone 設(shè)備,不支持 iPadOS,macOS,tvOS,watchOS。
單個(gè) Live Activity 最多可運(yùn)行 8 個(gè)小時(shí),超過 8 小時(shí)系統(tǒng)會(huì)自動(dòng)停止 Live Activity 的運(yùn)行,但是不會(huì)立即從屏幕上被移除。
停止運(yùn)行超過 4 小時(shí)后系統(tǒng)會(huì)自動(dòng)將其從屏幕上移除。
資源文件尺寸需符合要求,詳情請(qǐng)參見 蘋果開發(fā)者文檔。
推送的內(nèi)容不能超過 4KB。
接入客戶端
配置工程支持 Live Activity
在主工程的
Info.plist
文件中添加一個(gè)鍵值對(duì),key 為NSSupportsLiveActivities
,值為YES
。新建 Widget Extension,如果項(xiàng)目中已有,可跳過此步驟。
代碼實(shí)現(xiàn)
創(chuàng)建 model。
在主工程代碼里新建一個(gè) swift 文件,在其中定義
ActivityAttributes
以及Activity.ContentState
。以下代碼為示例代碼,請(qǐng)按照實(shí)際業(yè)務(wù)編寫。
import SwiftUI import ActivityKit struct PizzaDeliveryAttributes: ActivityAttributes { public typealias PizzaDeliveryStatus = ContentState public struct ContentState: Codable, Hashable { var driverName: String var estimatedDeliveryTime: ClosedRange<Date> init(driverName: String, estimatedDeliveryTime: ClosedRange<Date>) { self.driverName = driverName self.estimatedDeliveryTime = estimatedDeliveryTime } init(from decoder: Decoder) throws { let container: KeyedDecodingContainer<PizzaDeliveryAttributes.ContentState.CodingKeys> = try decoder.container(keyedBy: PizzaDeliveryAttributes.ContentState.CodingKeys.self) self.driverName = try container.decode(String.self, forKey: PizzaDeliveryAttributes.ContentState.CodingKeys.driverName) if let deliveryTime = try? container.decode(TimeInterval.self, forKey: PizzaDeliveryAttributes.ContentState.CodingKeys.estimatedDeliveryTime) { self.estimatedDeliveryTime = Date()...Date().addingTimeInterval(deliveryTime * 60) } else if let deliveryTime = try? container.decode(String.self, forKey: PizzaDeliveryAttributes.ContentState.CodingKeys.estimatedDeliveryTime) { self.estimatedDeliveryTime = Date()...Date().addingTimeInterval(TimeInterval.init(deliveryTime)! * 60) } else { self.estimatedDeliveryTime = try container.decode(ClosedRange<Date>.self, forKey: PizzaDeliveryAttributes.ContentState.CodingKeys.estimatedDeliveryTime) } } } var numberOfPizzas: Int var totalAmount: String }
主工程 target 和 Activity 都要選上。
收到的推送消息由系統(tǒng)處理,開發(fā)者不能攔截。
ContentState
中為可以動(dòng)態(tài)更新的數(shù)據(jù),推送 Live Activity 通知時(shí),動(dòng)態(tài)更新的參數(shù)名和類型要和ContentState
里配置的對(duì)應(yīng)上。如果有些數(shù)據(jù)需要經(jīng)過加工,需要重寫
ActivityAttributes.ContentState
的decoder
方法。
創(chuàng)建界面。
在 Widget Extension 中創(chuàng)建實(shí)時(shí)活動(dòng)的界面。創(chuàng)建 Widget 并返回一個(gè)
Activity Configuration
。具體 UI 請(qǐng)按照自己的業(yè)務(wù)編寫。
使用 WidgetBundle。
如果目標(biāo) App 既支持小組件也支持實(shí)時(shí)活動(dòng),請(qǐng)使用 WidgetBundle。
import WidgetKit import SwiftUI @main structIslandBundle: WidgetBundle { varbody: someWidget { Island() IslandLiveActivity() } }
開啟實(shí)時(shí)活動(dòng)。
func startDeliveryPizza() { let pizzaDeliveryAttributes = PizzaDeliveryAttributes(numberOfPizzas: 1, totalAmount:"$99") let initialContentState = PizzaDeliveryAttributes.PizzaDeliveryStatus(driverName: "TIM", estimatedDeliveryTime: Date()...Date().addingTimeInterval(15 * 60)) do { let deliveryActivity = try Activity<PizzaDeliveryAttributes>.request( attributes: pizzaDeliveryAttributes, contentState: initialContentState, pushType: .token) } catch (let error) { print("Error requesting pizza delivery Live Activity \(error.localizedDescription)") } }
提交 Token。
開啟實(shí)時(shí)活動(dòng)成功后,通過
pushTokenUpdates
方法拿到系統(tǒng)返回的 Live Activity 的推送 Token。 調(diào)用 PushService 的liveActivityBindWithActivityId:pushToken:filter:completion:
方法上報(bào)。在上報(bào) Token 的同時(shí),需要將該實(shí)時(shí)活動(dòng)的標(biāo)識(shí)一起上報(bào)。實(shí)時(shí)活動(dòng)推送時(shí)需要用到該標(biāo)識(shí),服務(wù)器根據(jù)該標(biāo)識(shí)確認(rèn)推送目標(biāo)。該實(shí)時(shí)活動(dòng)的標(biāo)識(shí)請(qǐng)自定義,不同 Live Activity,其 id 不同(如果唯一會(huì)導(dǎo)致推送出現(xiàn)問題),同一個(gè) Live Activity,在 Token 更新時(shí)不要更換 id。
說(shuō)明ActivityKit 為 swift 語(yǔ)言框架,且不支持直接 OC 調(diào)用,使用該框架 API 的時(shí)候,請(qǐng)?jiān)?swift 文件里面調(diào)用。由于 MPPushSDK 是 OC 語(yǔ)言,涉及到 swift 調(diào)用 OC,需要?jiǎng)?chuàng)建橋接文件。并在橋接文件里導(dǎo)入:
#import <MPPushSDK/MPPushSDK.h>
。let liveactivityId = UserDefaults.standard.string(forKey: "pushTokenUpdates_id") ?? "defloutliveactivityId" Task { for await tokenData in deliveryActivity.pushTokenUpdates { let newToken = tokenData.map { String(format: "%02x", $0) }.joined() PushService.shared().liveActivityBind(withActivityId: liveactivityId, pushToken: newToken, filter: .call) { excpt in guard let excpt = excpt else { ///上報(bào)成功 return } if "callRepeat" == excpt.reason { ///重復(fù)調(diào)用,請(qǐng)忽略 print("pushTokenUpdates_id——重復(fù)調(diào)用") } else { ///上報(bào)失敗 } } } }
上報(bào)成功后,則可以使用實(shí)時(shí)活動(dòng)的標(biāo)識(shí)推送更新。
說(shuō)明由于 iPhone 的
pushTokenUpdates
會(huì)同時(shí)被調(diào)用兩次,即在多個(gè) Live Activity 的場(chǎng)景中,新創(chuàng)建 Live Activity 時(shí)之前的LiveActivity pushTokenUpdates
又會(huì)被重新喚醒一次,所以 SDK 提供了過濾功能,并由參數(shù) filter 控制:filter 為
MPPushServiceLiveActivityFilterAbandon
時(shí),SDK 會(huì)自動(dòng)直接拋棄重復(fù)的調(diào)用,不給回調(diào)。filter 為
MPPushServiceLiveActivityFilterCall
時(shí),SDK 會(huì)自動(dòng)過濾掉本次請(qǐng)求,給失敗回調(diào)(callRepeat),此時(shí)error.reason
為@"callRepeat"
,請(qǐng)忽略。filter 為
MPPushServiceLiveActivityFilterReRefuse
時(shí),SDK 內(nèi)部不做過濾。重復(fù)地調(diào)用相同的 activityId,相同的 pushToken 時(shí),如果上報(bào)失敗,客戶端重新上報(bào)不會(huì)被認(rèn)為是相同的調(diào)用。
下面是
MPPushServiceLiveActivityFilterType
的定義:typedef NS_ENUM(NSInteger, MPPushServiceLiveActivityFilterType){ MPPushServiceLiveActivityFilterAbandon,//直接拋棄,不給回調(diào) MPPushServiceLiveActivityFilterCall,//過濾掉本次請(qǐng)求,給失敗回調(diào)(callRepeat) MPPushServiceLiveActivityFilterRefuse//不做過濾 };