Catapush iOS SDK-pod 2.2.4

catapush-ios-sdk-pod 2.2.4

测试已测试
语言语言 Obj-CObjective C
许可协议 MIT
发布上次发布2024 年 4 月

Matteo Corradin 维护。



  • Alessandro Chiarotto 和 Felice De Luca

Catapush Logo

Catapush 是一个简单、可靠且可扩展的交易推送通知 API,适用于网站和应用程序。非常适合发送数据驱动的交易通知,包括针对性电商和个性化的点对点消息。

获取每个发送的消息的实时状态确认,并在您的应用程序中构建智能消息逻辑

目录

设置

Catapush iOS SDK 通过 CocoaPods 提供。要安装它,只需将以下行添加到您的 Podfile 中即可

pod "catapush-ios-sdk-pod"

警告:由于我们的库不是框架,您不能在Podfile中使用use_frameworks!,如果您必须使用此标志,您必须按照以下步骤手动包含库:参阅此指南!

最低iOS版本为11。

以下是使用Catapush设置您应用程序的先决条件

创建Catapush应用程序并获取应用密钥

  1. 从左侧菜单中的“您的应用程序” -> 应用详情获取您的应用密钥:Catapush仪表板
  2. 从“您的应用程序” -> 用户创建第一个用户

创建和配置身份验证密钥

本节说明了如何为启用了推送通知的App ID生成一个身份验证密钥。如果您已经有一个现有密钥,您可以使用该密钥而不是生成新密钥。

要创建一个身份验证密钥:

  1. 在您的Apple开发者会员中心中,转到证书、标识符和配置文件,并选择密钥。
  2. 点击添加按钮(+)或点击“创建密钥”按钮。

3) 输入APNs Auth Key的描述。4) 在密钥服务下,选择Apple推送通知服务(APNs)复选框,然后点击继续。

5) 点击注册,然后下载并安全保存您的密钥。这是一个一次性下载,密钥无法稍后检索。

下载后,您必须配置您的Catapush应用程序。

  1. 转到https://www.catapush.com/panel/apps/YOUR_APP_ID/platforms
  2. 点击iOS Token Based以启用它。
  3. 填写iOS Team Id、iOS Key Id、iOS AuthKey和iOS Topic。

iOS Team Id 可在此处找到https://developer.apple.com/account/#/membership,在“会员信息”部分。

iOS Key Id 可在此处检索https://developer.apple.com/account/resources/authkeys/list,点击您创建的密钥,您可以在“查看密钥详细信息”部分找到它。

iOS AuthKey 是密钥文件的内容。

示例

-----BEGIN PRIVATE KEY-----
...........................
          AUTH_KEY
...........................
-----END PRIVATE KEY-----

iOS Topic 是您的iOS应用程序的bundle标识符。

添加通知服务扩展

为了处理推送通知,需要一个通知服务扩展。通过 Xcode 文件 -> 新建 -> 目标... 添加一个通知服务扩展,该扩展继承自 CatapushNotificationServiceExtension

@interface CatapushNotificationServiceExtension : UNNotificationServiceExtension
- (void)handleMessage:(MessageIP * _Nullable) message withContentHandler:(void (^_Nullable)(UNNotificationContent * _Nullable))contentHandler  withBestAttemptContent: (UNMutableNotificationContent* _Nullable) bestAttemptContent;
- (void)handleError:(NSError * _Nonnull) error withContentHandler:(void (^_Nullable)(UNNotificationContent * _Nullable))contentHandler  withBestAttemptContent: (UNMutableNotificationContent* _Nullable) bestAttemptContent;
@end

示例

NotificationService.h

#import "Catapush.h"

@interface NotificationService : CatapushNotificationServiceExtension

@end

NotificationService.m

#import "NotificationService.h"

@interface NotificationService ()

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    [super didReceiveNotificationRequest:request withContentHandler:contentHandler];
}

- (void)handleError:(NSError * _Nonnull) error withContentHandler:(void (^_Nullable)(UNNotificationContent * _Nullable))contentHandler  withBestAttemptContent: (UNMutableNotificationContent* _Nullable) bestAttemptContent{
    if (contentHandler != nil && bestAttemptContent != nil){
        if (error.code == CatapushCredentialsError) {
            bestAttemptContent.body = @"User not logged in";
        }
        if (error.code == CatapushNetworkError) {
            bestAttemptContent.body = @"Network error";
        }
        if (error.code == CatapushNoMessagesError) {
            bestAttemptContent.body = @"No new message";
        }
        if (error.code == CatapushFileProtectionError) {
            bestAttemptContent.body = @"Unlock the device at least once to receive the message";
        }
        if (error.code == CatapushConflictErrorCode) {
            bestAttemptContent.body = @"Connected from another resource";
        }
        if (error.code == CatapushAppIsActive) {
            bestAttemptContent.body = @"Please open the app to read the message";
        }
        contentHandler(bestAttemptContent);
    }
}

- (void)handleMessage:(MessageIP * _Nullable) message withContentHandler:(void (^_Nullable)(UNNotificationContent * _Nullable))contentHandler  withBestAttemptContent: (UNMutableNotificationContent* _Nullable) bestAttemptContent{
    if (contentHandler != nil && bestAttemptContent != nil){
        if (message != nil) {
            bestAttemptContent.body = message.body.copy;
        }else{
            bestAttemptContent.body = @"No new message";
        }
        contentHandler(bestAttemptContent);
    }
}

@end

如果用户在启动后至少一次没有解锁设备就接收到了推送通知,将返回 CatapushFileProtectionError。用户数据以加密格式存储在磁盘上,使用 NSFileProtectionCompleteUntilFirstUserAuthentication 策略。

https://developer.apple.com/documentation/foundation/nsfileprotectioncompleteuntilfirstuserauthentication

注意:确保服务的部署目标与应用的部署目标相同。

应用组

Catapush 需要通知服务扩展和主应用程序能够共享资源。为此,您必须为应用程序和扩展都创建并启用一个特定的应用组。应用程序和扩展必须在同一应用组中。

您还应在应用 plist 和扩展 plist 中添加这些信息(如示例中的 group.catapush.test,则应使用 group.example.group)。

    <key>Catapush</key>
    <dict>
        <key>AppGroup</key>
        <string>group.example.group</string>
    </dict>

AppDelegate

将应用程序生命周期事件与相应的 Catapush 方法绑定

+ (void)applicationDidEnterBackground:(UIApplication *)application;
+ (void)applicationDidBecomeActive:(UIApplication *)application; 
+ (void)applicationWillEnterForeground:(UIApplication *)application withError:(NSError **) error;
+ (void)applicationWillTerminate:(UIApplication *)application;

配置 Catapush 应用密钥用户凭据

    [Catapush setAppKey:@"YOUR_APP_KEY"];
    [Catapush setIdentifier:@"test" andPassword:@"test"];

遵循 UNUserNotificationCenterDelegate

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler{
    //Here you can handle tap on the push notification
    completionHandler();
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
    //UNNotificationPresentationOptionNone is required to avoid alert when the app is in foreground
    completionHandler(UNNotificationPresentationOptionNone);
}

完整示例

#import "AppDelegate.h"
#import "Catapush.h"

@interface AppDelegate () <CatapushDelegate,MessagesDispatchDelegate, UNUserNotificationCenterDelegate>

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [Catapush setAppKey:@"YOUR_APP_KEY"];
    [Catapush setIdentifier:@"test" andPassword:@"test"];
    [Catapush setupCatapushStateDelegate:self andMessagesDispatcherDelegate:self];
    [Catapush registerUserNotification:self];
    NSError *error;
    [Catapush start:&error];
    if (error != nil) {
        // Handle error...
    }
    [UNUserNotificationCenter currentNotificationCenter].delegate = self;
    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    [Catapush applicationDidEnterBackground:application];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    
    NSError *error;
    [Catapush applicationWillEnterForeground:application withError:&error];
    if (error != nil) {
        // Handle error...
    }
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    [Catapush applicationDidBecomeActive:application];
}

- (void)applicationWillTerminate:(UIApplication *)application {
    [Catapush applicationWillTerminate:application];
}

#pragma mark Standard Push Notification Delegate
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
   // Custom code (can be empty)
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    // Custom code (can be empty)
}

- (void)catapushDidConnectSuccessfully:(Catapush *)catapush
{
    UIAlertView *connectedAV = [[UIAlertView alloc] initWithTitle:@"Connected"
                                                          message:@"Catapush Connected!" delegate:self
                                                cancelButtonTitle:@"Ok" otherButtonTitles:nil];
    [connectedAV show];
}

- (void)catapush:(Catapush *)catapush didFailOperation:(NSString *)operationName withError:(NSError *)error {
    if ([error.domain isEqualToString:CATAPUSH_ERROR_DOMAIN]) {
        switch (error.code) {
            case WRONG_AUTHENTICATION:
                break;
            case INVALID_APP_KEY:
                break;
            case USER_NOT_FOUND:
                break;
            case GENERIC:
                break;
            default:
                break;
        }
    }
    
    NSString *errorMsg = [NSString stringWithFormat:@"The operation %@ is failed with error:\n%@", operationName, [error localizedDescription]];
    
    UIAlertView *flowErrorAlertView = [[UIAlertView alloc] initWithTitle:@"Error"
                                                                 message:errorMsg
                                                                delegate:self
                                                       cancelButtonTitle:@"Ok"
                                                       otherButtonTitles:nil];
    [flowErrorAlertView show];
}

-(void)libraryDidReceiveMessageIP:(MessageIP *)messageIP {
    [MessageIP sendMessageReadNotification:messageIP];
    [[Catapush allMessages] enumerateObjectsUsingBlock:^(MessageIP *m, NSUInteger idx, BOOL * stop) {
        NSLog(@"Message: \(%@)",m.body);
    }];
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler{
    //Here you can handle tap on the push notification
    completionHandler();
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
    //To avoid alert when the app is in foreground
    completionHandler(UNNotificationPresentationOptionNone);
}

@end

推送通知

#pragma mark - End developer user must  declare in order to let AOP to inject catapush library code
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
   // Custom code (can be empty)
}

Catapush会在上述方法中注入代码,以在一次设备令牌可用时检索它。

您可以通过调用以下方法来检查一个UNNotificationRequest是否来自Catapush:[Catapush isCatapushNotificationRequest:request]如果您需要管理在有效载荷中有mutable-content : 1的自定义推送通知,这将非常有用。示例(在您的UNNotificationServiceExtension中)

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    if (![Catapush isCatapushNotificationRequest:request]) {
        // Custom code to properly handle the notification
    }else{
        [super didReceiveNotificationRequest:request withContentHandler:contentHandler];
    }
}

示例项目

Obj-C Swift

事件处理

为了接收事件,设置这两个代理:<CatapushDelegate><MessagesDispatchDelegate>,例如您的应用程序代理本身。

@interface AppDelegate () <CatapushDelegate, MessagesDispatchDelegate>

然后在您的应用程序代理中,例如在application:didFinishLaunchingWithOption

[Catapush setupCatapushStateDelegate:self andMessagesDispatcherDelegate:self];

CatapushDelegate负责处理连接事件,通知连接状态,以及MessagesDispatchDelegate将消息发送到您的应用程序认证

CatapushDelegate负责通知连接状态或与库相关的任何错误

如果连接成功,则会触发此委托

- (void)catapushDidConnectSuccessfully:(Catapush *)catapush
{
    UIAlertView *connectedAV = [[UIAlertView alloc] initWithTitle:@"Connected"
                                                          message:@"Catapush Connected!" delegate:self
                                                cancelButtonTitle:@"Ok" otherButtonTitles:nil];
    [connectedAV show];
}

错误处理

主应用程序

此委托具有错误处理功能

- (void)catapush:(Catapush *)catapush didFailOperation:(NSString *)operationName withError:(NSError *)error;

错误代码

回调函数可能执行的以下错误代码:

错误代码 描述 建议策略
-10404 = INVALID_APP_KEY 应用密钥不正确。 请检查应用ID并重试。
-11404 = USER_NOT_FOUND 标识符或密码未设置 请检查您是否已通过此方法向Catapush提供了有效的用户名和密码

[Catapush setIdentifier:username andPassword:password];
-10403 = WRONG_AUTHENTICATION 凭证错误 请验证您的标识符和密码的有效性。用户可能已被从Catapush应用中删除(通过API或仪表板)或密码已更改。

请不要重复尝试,删除已存储的凭证。
为该安装提供一个新标识符以解决问题。
-11000 = GENERIC 远程消息服务的内部错误 远程消息服务出现意外内部错误。
这可能是由于暂时性服务中断造成的。
请稍后再试。
11 = XMPP_MULTIPLE_LOGIN 相同的用户标识符已在另一台设备上登录,该设备上的消息服务将停止。 请确保在每个设备上使用唯一的标识符,即使是在同一个用户拥有的设备上。
如果您收到此错误,表示您在不同设备上使用了相同的用户名/密码。
为该安装提供一个新标识符以解决问题。
14011 = API_UNAUTHORIZED 凭证已被拒绝 请验证您的标识符和密码的有效性。用户可能已被从Catapush应用中删除(通过API或仪表板)或密码已更改。

请不要重复尝试,删除已存储的凭证。
为该安装提供一个新标识符以解决问题。
15001 = API_INTERNAL_ERROR 远程消息服务的内部错误 远程消息服务出现意外内部错误。
这可能是由于暂时性服务中断造成的。
请稍后再试。
24001 = REGISTRATION_BAD_REQUEST 远程消息服务的内部错误 远程消息服务出现意外内部错误。
这可能是由于暂时性服务中断造成的。
请稍后再试。
24031 = REGISTRATION_FORBIDDEN_WRONG_AUTH 错误的认证 请验证您的标识符和密码的有效性。用户可能已被从Catapush应用中删除(通过API或仪表板)或密码已更改。

请不要重复尝试,删除已存储的凭证。
为该安装提供一个新标识符以解决问题。
24041 = REGISTRATION_NOT_FOUND_APPLICATION 找不到应用程序 您的应用程序未找到或未激活。
请不要重复尝试。
24042 = REGISTRATION_NOT_FOUND_USER 找不到用户 用户可能已被从Catapush应用中删除(通过API或从仪表板)。

请不要重复尝试。
为该安装提供一个新标识符以解决问题。
25001 = REGISTRATION_INTERNAL_ERROR 远程消息服务的内部错误 远程消息服务出现意外内部错误。
这可能是由于暂时性服务中断造成的。
请稍后再试。
34001 = OAUTH_BAD_REQUEST 远程消息服务的内部错误 远程消息服务出现意外内部错误。
这可能是由于暂时性服务中断造成的。
请稍后再试。
34002 = OAUTH_BAD_REQUEST_INVALID_CLIENT 远程消息服务的内部错误 远程消息服务出现意外内部错误。
这可能是由于暂时性服务中断造成的。
请稍后再试。
34003 = OAUTH_BAD_REQUEST_INVALID_GRANT 远程消息服务的内部错误 远程消息服务出现意外内部错误。
这可能是由于暂时性服务中断造成的。
请稍后再试。
35001 = OAUTH_INTERNAL_ERROR 远程消息服务的内部错误 远程消息服务出现意外内部错误。
这可能是由于暂时性服务中断造成的。
请稍后再试。
44031 = UPDATE_PUSH_TOKEN_FORBIDDEN_WRONG_AUTH 凭证错误 请验证您的标识符和密码的有效性。用户可能已被从Catapush应用中删除(通过API或仪表板)或密码已更改。

请不要重复尝试,删除已存储的凭证。
为该安装提供一个新标识符以解决问题。
44032 = UPDATE_PUSH_TOKEN_FORBIDDEN_NOT_PERMITTED 凭证错误 请验证您的标识符和密码的有效性。用户可能已被从Catapush应用中删除(通过API或仪表板)或密码已更改。

请不要重复尝试,删除已存储的凭证。
为该安装提供一个新标识符以解决问题。
44041 = UPDATE_PUSH_TOKEN_NOT_FOUND_CUSTOMER 应用程序错误 您的应用程序未找到或未激活。
请不要重复尝试。
44042 = UPDATE_PUSH_TOKEN_NOT_FOUND_APPLICATION 找不到应用程序 您的应用程序未找到或未激活。
请不要重复尝试。
44043=UPDATE_PUSH_TOKEN_NOT_FOUND_USER 找不到用户 请验证您的标识符和密码的有效性。用户可能已被从Catapush应用中删除(通过API或仪表板)或密码已更改。

请不要重复尝试,删除已存储的凭证。
为该安装提供一个新标识符以解决问题。
45001 = UPDATE_PUSH_TOKEN_INTERNAL_ERROR 远程消息服务更新推送令牌时的内部错误。 无,由SDK自动处理。
远程消息服务出现意外内部错误。
这可能是由于暂时性服务中断造成的。
55001 = UPLOAD_FILE_ERROR 在发送附件时出现问题。

设备未连接到互联网或可能被防火墙阻止或远程消息服务可能暂时中断。
您可以通过检查underlyingError来获取仅用于调试的错误。
if (error.userInfo != nil) {
NSError* underlyingError = error.userInfo[NSUnderlyingErrorKey];
}

请稍后再试发送消息。
54001 = UPLOAD_FILE_MAX_SIZE_EXCEEDED 附件太大,不得大于8MB。 请减小附件大小。
10 = NETWORK_ERROR SDK无法连接到Catapush远程消息服务。
设备未连接到互联网或可能被防火墙阻止或远程消息服务可能暂时中断。
请检查您的网络连接并重新连接。
13 = INTERNAL_NETWORK_ERROR SDK无法连接到Catapush远程消息服务。
设备未连接到互联网或可能被防火墙阻止或远程消息服务可能暂时中断。
无,由SDK自动处理。仅用于调试。

您可以通过检查underlyingError来获取仅用于调试的错误。
if (error.userInfo != nil) {
NSError* underlyingError = error.userInfo[NSUnderlyingErrorKey];
}
12 = PUSH_TOKEN_UNAVAILABLE 推送令牌不可用。 无,由SDK自动处理。
14 = DISK_IS_FULL 设备上没有空间。 删除临时文件或通知用户。

服务

错误可以通过此方法处理

- (void)handleError:(NSError * _Nonnull) error withContentHandler:(void (^_Nullable)(UNNotificationContent * _Nullable))contentHandler  withBestAttemptContent: (UNMutableNotificationContent* _Nullable) bestAttemptContent;
错误代码 描述 建议策略
CatapushCredentialsError 应用密钥、用户名或密码未设置 检查应用ID、用户名和密码后重试。
CatapushNetworkError SDK无法连接到Catapush远程消息服务。
设备未连接到互联网或可能被防火墙阻止或远程消息服务可能暂时中断。
检查是否有底层错误。
if (error.userInfo != nil) {
NSError*底层错误 = error2.userInfo[NSUnderlyingErrorKey];
}

底层错误代码可能为以下之一:https://github.com/Catapush/catapush-ios-sdk-pod#error-handling
CatapushNoMessagesError 没有收到消息 没有新消息,这可能是由于消息已经被处理。
CatapushFileProtectionError 用户收到推送通知,但设备在启动后至少未解锁一次。
用户数据存储在磁盘上通过NSFileProtectionCompleteUntilFirstUserAuthentication策略加密格式。
请参阅https://developer.apple.com/documentation/foundation/nsfileprotectioncompleteuntilfirstuserauthentication了解详细信息。
CatapushConflictErrorCode 相同的用户标识已在其他设备或另一个进程中登录。 请确保在每个设备上使用唯一的标识符,即使是在同一个用户拥有的设备上。
CatapushAppIsActive 应用程序正在运行,通知将由主应用程序处理。
在主应用程序稍后进入后台的罕见情况下,通知可能仍然显示。
在主应用程序稍后进入后台的罕见情况下,通知可能仍然显示且不可处理,因此建议将通知文本更改为如下处理这种情况
bestAttemptContent.body = @"请打开应用程序以读取消息";

否则,它将在主应用程序此处处理
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler;

接收消息

MessagesDispatchDelegate是负责消息分发的代理。消息由MessageIP对象表示,并到达此代理

-(void)libraryDidReceiveMessageIP:(MessageIP *)messageIP
{
    NSLog(@"Did receive IP Message with identifier: %@ and body: %@", messageIP.identifier, messageIP.body);
}

读取消息

当您消费接收到的消息时,您可以标记它们为已读,如果用户打开了推送通知

-(void)libraryDidReceiveMessageIP:(MessageIP *)messageIP
{
    NSLog(@"Did receive IP Message with identifier: %@ and body: %@", messageIP.identifier, messageIP.body);
    UIAlertView *connectedAV = [[UIAlertView alloc] initWithTitle:@"MyApp"
                                                          message:messageIP.body
                                                         delegate:self
                                                cancelButtonTitle:@"Ok"
                                                otherButtonTitles:nil];

    [connectedAV show];
    [MessageIP sendMessageReadNotification:messageIP];
}

发送消息

您可以使用以下方法向Catapush服务器发送文本消息

[Catapush sendMessageWithText:text];

您还可以使用MessagesDispatchDelegate协议的新两个可选方法来监视消息传递状态

- (void)libraryDidSendMessage:(MessageIP *)message;
- (void)libraryDidFailToSendMessage:(MessageIP *)message;

消息的传递状态存储在MessageIP类的status属性中。可能的值为枚举列出的

typedef NS_ENUM(NSInteger, MESSAGEIP_STATUS)
{
    MessageIP_NOT_READ = 0,
    MessageIP_READ = 1,
    MessageIP_READ_SENT_ACK = 2,

    MessageIP_NOT_SENT = 3,
    MessageIP_SENT = 4,
    MessageIP_SENDING = 5
};

如果消息传递失败,您可以使用其messageID重新发送消息

+ (MessageIP *)sendMessageWithMessageId:(NSString *) messageId;

您还可以发送带有图片、文件的消息,或者指定一个通道或replyTo(回复的消息ID)。

+ (MessageIP *)sendMessageWithText:(NSString *)text andChannel:(NSString *) channel;
+ (MessageIP *)sendMessageWithText:(NSString *)text andImage:(UIImage *)image;
+ (MessageIP *)sendMessageWithText:(NSString *)text andChannel:(NSString *) channel andImage:(UIImage *)image;
+ (MessageIP *)sendMessageWithText:(NSString *)text andData:(NSData *)data ofType:(NSString *)mediaType;
+ (MessageIP *)sendMessageWithText:(NSString *)text andChannel:(NSString *) channel andData:(NSData *)data ofType:(NSString *)mediaType;
+ (MessageIP *)sendMessageWithText:(NSString *)text replyTo:(NSString *) messageId;
+ (MessageIP *)sendMessageWithText:(NSString *)text andChannel:(NSString *) channel replyTo:(NSString *) messageId;
+ (MessageIP *)sendMessageWithText:(NSString *)text andImage:(UIImage *)image replyTo:(NSString *) messageId;
+ (MessageIP *)sendMessageWithText:(NSString *)text andChannel:(NSString *) channel andImage:(UIImage *)image replyTo:(NSString *) messageId;
+ (MessageIP *)sendMessageWithText:(NSString *)text andData:(NSData *)data ofType:(NSString *)mediaType replyTo:(NSString *) messageId;
+ (MessageIP *)sendMessageWithText:(NSString *)text andChannel:(NSString *) channel andData:(NSData *)data ofType:(NSString *)mediaType replyTo:(NSString *) messageId;

获取附件

MessageIP提供了各种方法和属性

@interface MessageIP : NSManagedObject
// ....
@property (readonly) NSData *  mm;
@property (readonly) NSString * mmType;
@property (readonly) NSString * filename;
- (bool)hasMedia;
- (bool)isMediaReady;
- (bool)hasMediaPreview;
- (UIImage * _Nullable)imageMediaPreview;
- (void)downloadMediaWithCompletionHandler:(void (^)(NSError *error,
NSData *data))completionHandler;
// ....
@end
- (bool)hasMedia;

如果消息包含多媒体则为真。

- (bool)isMediaReady;

如果多媒体已被下载则为真。

@property (readonly) NSData *  mm;

如果已经下载,包含多媒体。

@property (readonly) NSString * mmType;

媒体的媒体类型(例如,image/png、application/msword)

@property (readonly) NSString * filename;

媒体的文件名。

- (void)downloadMediaWithCompletionHandler:(void (^)(NSError *error,
NSData *data))completionHandler;

允许您下载多媒体(如果尚未下载)。回调包含下载的多媒体和错误(如网络错误)。

如果多媒体已下载,调用此方法将没有效果。

获取可选数据

如果消息包含可选数据,您可以通过此方法获取它们。

@interface MessageIP : NSManagedObject
//....
- (NSDictionary *)optionalData;
//....
@end

多人用户

如果您的客户端应用程序需要多人用户体验,您必须注销当前会话,特别是调用此静态方法

+ (void)logoutStoredUser;

的SDK

  1. 断开XMPP连接
  2. 从本地存储中删除用户数据
  3. 通过从Catapush服务器中去除设备令牌来禁用推送通知

此操作是异步的,因此如果您需要立即登录新用户,请使用基于块的的方法,此方法提供了一个完成块和失败(如果发生某些错误)。

[Catapush logoutStoredUserWithCompletion:^{
   // Configure a new user
    NSError *error;
    [Catapush start:&error];
    
    if (error != nil) {
        // Handle login/start error...
    }
} failure:^{

    /*
     *
     * XMPP NOT disconnected
     * User data NOT removed from local storage
     * Device token NOT removed from Catapush Server
     *
     */

}];

强制登出

您可以通过强制登出清除用户数据并关闭连接,即使调用Catapush服务器失败(例如没有可用的互联网连接,超时)。

    [Catapush logoutStoredUser:true];


    [Catapush logoutStoredUser:true withCompletion:^{
        /*
         * Success
         *
         * XMPP disconnected
         * User data removed from local storage
         * Removed device token from Catapush Server
         *
         */
    } failure:^{
        /*
         * Device token NOT removed from Catapush Server (no internet connection available, timeout ...)
         *
         * XMPP disconnected
         * User data removed from local storage
         *
         */
    }];

高级

让库知道用户在您的视图中读取了消息调用此方法

[MessageIP sendMessageReadNotification:messageIP];

您可以通过使用以下方法,通过连接状态的观察者始终查询库当前的连接状态:

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(checkLibraryStatus)
                                             name:kCatapushStatusChangedNotification
                                           object:nil];

可以获取当前连接状态

[Catapush status];

该函数返回状态枚举

DISCONNECTED
CONNECTING
CONNECTED

在您的选择器中,如上所述

- (void)checkLibraryStatus
{
 NSString *statusString;
    switch ([Catapush status]) {
        case CONNECTED:
            statusString = @"Connected";
           break;
        case DISCONNECTED:
            statusString = @"Disconnected";
           break;
        case CONNECTING:
            statusString = @"Connecting..";
            break;              
      }

    NSLog(@"%@", statusString);
}

消息管理器

Catapush iOS SDK利用CoreData框架的强大功能来管理消息。这使得库的集成以及管理MessageIP对象的生命周期变得容易。

CatapushCoreData提供了以下对象:

    managedObjectContext
    managedObjectModel
    persistentStoreCoordinator

这些三个对象可以使用协议。

例如,要获取存储的所有消息及其更改,可以使用期望的NSPredicate(如下所示)设置NSFetchedResultsControllerDelegate

@property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController; //handles all Messages
@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;

- (void)perfomFetch
{
    self.fetchedResultsController.delegate = self;

    NSError *error;
    BOOL success = [self.fetchedResultsController performFetch:&error];
    //Now all messages are stored in [self.fetchedResultsController objectAtIndexPath:indexPath];
}

- (NSFetchedResultsController *)fetchedResultsController{

if (!_fetchedResultsController) {

    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"MessageIP"];
    request.predicate = nil;
    request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"sentTime" ascending:YES]];

    _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                    managedObjectContext:self.managedObjectContext
                                                                      sectionNameKeyPath:nil
                                                                               cacheName:nil];
}

return _fetchedResultsController;
}

- (NSManagedObjectContext *)managedObjectContext{

if (!_managedObjectContext) {
    _managedObjectContext = [CatapushCoreData managedObjectContext];
}

return _managedObjectContext;
}

然后观察MessageIP的变化

    Insertion
    Updating
    Deletion

使用NSFetchedResultsControllerDelegate的方法

#pragma mark - NSFetchedResultsControllerDelegate

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
//If needed, prepare for content changes
}


- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type)
    {
        case NSFetchedResultsChangeInsert:
        //New object fetched
        break;

        case NSFetchedResultsChangeDelete:
        //Object deleted
        break;
        default:
        break;
    }
}

- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
    switch(type)
    {
        case NSFetchedResultsChangeInsert:
        //New object fetched
        break;

        case NSFetchedResultsChangeDelete:
        //Object deleted
        break;

        case NSFetchedResultsChangeUpdate:
        //Object updated
        break;

        case NSFetchedResultsChangeMove:
        //Object changes index in the array
        break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    //After the changes of the fetched content
}

使用NSFetchedResultsController可以轻松地获取满足条件的消息。例如,当创建用于检索所有消息的NSFetchRequest时,只需设置

request.predicate = nil;

即。

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"MessageIP"];
request.predicate = nil;
request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"sentTime" ascending:YES]];

如果您需要未读消息,则只需写:

request.predicate = [NSPredicate predicateWithFormat:@"status = %i", MessageIP_NOT_READ];

即。

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"MessageIP"];
request.predicate = [NSPredicate predicateWithFormat:@"status = %i", MessageIP_NOT_READ];
request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"sentTime" ascending:YES]];

当满足定义的NSPredicate的对象存在时,您将在NSFetchedResultsControllerDelegate方法中接收通知,如上所示。

日志记录

要启用控制台日志记录

[Catapush enableLog:true];

要禁用

[Catapush enableLog:false];

使用 use_frameworks! 时手动库集成!

由于我们库不是一个框架,你无法在 Podfile 中使用 use_frameworks!,如果你必须使用此标志,你必须手动按照以下步骤包含库

注意:此步骤不适用于我们的示例项目,你必须创建一个新的空项目或者将其包含到你的项目中

  1. git clone https://github.com/Catapush/catapush-ios-sdk-pod.git

  2. 在 XCode 中打开你的项目

  3. 从 Finder 中将以下文件拖拽到你的项目根目录,当被询问时选择 "如果需要则复制项目" 并将它们添加到 应用程序和服务目标

    • catapush-ios-sdk-pod/CatapushKit/libCatapushLib.a
    • catapush-ios-sdk-pod/CatapushKit/Catapush.h
    • catapush-ios-sdk-pod/CatapushKit/CatapushLibBundle.bundle
  4. 从项目设置中添加依赖项 为应用和服务的目标,选择你的目标,然后转到“构建阶段”标签。在“与二进制链接库”下使用加号按钮添加以下框架到你的项目

    • Security.framework
    • CFNetwork.framework
    • SystemConfiguration.framework
    • CoreData.framework
    • libresolv
  5. 仅 Swift:从 XCode 创建一个新文件 -> 头文件,命名为 <projectname>-Bridging-Header.h,并添加以下指令,如在此 Bridging-Header.h 示例中所示

#import "Catapush.h"
  1. 设置构建设置前往“构建设置”,选择“全部”标签
    • 查找“链接”部分,并在“其他链接器标志”中填写:-ObjC -lxml2
    • 仅 Swift:找到“Swift 编译器 - 一般”,并在“Objective-C 桥接头”中填写:<projectname>-Bridging-Header.h

处理徽章计数

你可以通过设置徽章为未读消息的数量来管理徽章,当用户打开应用程序时重置它们。

//NotificationService

override func handleMessage(_ message: MessageIP?, withContentHandler contentHandler: ((UNNotificationContent?) -> Void)?, withBestAttempt bestAttemptContent: UNMutableNotificationContent?) {
    if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
        if (message != nil) {
            bestAttemptContent.body = message!.body;
        }else{
            bestAttemptContent.body = NSLocalizedString("no_message", comment: "");
        }
        
        //Set the badge to the value equal to the number of messages still to be read
        let request = NSFetchRequest<NSFetchRequestResult>(entityName: "MessageIP")
        request.predicate = NSPredicate(format: "status = %i", MESSAGEIP_STATUS.MessageIP_NOT_READ.rawValue)
        request.includesSubentities = false
        do {
            let msgCount = try CatapushCoreData.managedObjectContext().count(for: request)
            bestAttemptContent.badge = NSNumber(value: msgCount)
        } catch _ {
        }
            
        contentHandler(bestAttemptContent);
    }
}
//AppDelegate

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    FirebaseApp.configure()
    Catapush.enableLog(false)
    Catapush.setAppKey(Const.appKey)
    Catapush.registerUserNotification(self)
    let firstTime = Defaults[\.firstTime]
    if firstTime {
        Catapush.logoutStoredUser(true)
        Defaults[\.firstTime] = false
    }
    Defaults[\.didFinishLaunchingWithOptions] = true

    application.applicationIconBadgeNumber = 0;
    UNUserNotificationCenter.current().delegate = self
        
    return true
}

故障排除

原始通知

通知服务扩展负责生成用户将要看到的详细通知内容,详见 添加通知服务扩展

通常情况下,服务将接收到消息,并使用消息内容填充通知内容,或者向用户显示适当错误消息(例如用户未登录、消息已被接收等)

然而有时,通知内容未经修改直接通过服务。这可能是以下两个原因导致的:

  1. 配置错误
  2. 崩溃
配置错误

在这种情况下,通知服务扩展将被完全忽略,而通知将不经修改地通过。

常见原因是服务的发布目标设置错误。请检查服务与应用的发布目标是否相同,您可以通过以下步骤进行检查:添加通知服务扩展

崩溃

如果通知服务扩展由于任何原因崩溃

  1. 它不会阻止将通知显示给用户。
  2. 未经修改的推送通知将显示给用户。

要捕获崩溃,您可以实现 Firebase Crashlytics,这是一个轻量级、实时的崩溃报告工具,或者您可以手动跟踪它们,通过添加未捕获异常处理器Objective-C异常和SignalHandlers来处理。以下是一个适用于objective-c和swift的示例:CrashEye

服务器端覆盖

在发送消息时,您可以通过设置自定义消息,并在devicesData.ios.alert字段中填充,以防止向用户显示原始消息。该字段是当通知服务扩展出现意外行为时,如果iOS忽略通知扩展并且只显示原生推送通知文本时显示给用户的文本。(API文档)

调试期间禁用通知服务

为了正确调试库,有必要禁用通知服务的行为,防止其在调试期间处理消息。这是必要的,因为如果在接收消息时触发断点,通知服务会被调用,并认为应用程序已关闭,因为它无法与主应用程序通信。为了处理这种情况,我们可以通过覆盖 didReceive 方法绕过通知服务中的所有逻辑。

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    contentHandler(request.content);
}
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    contentHandler(request.content)
}

额外信息

使用 Console.app 查看日志

Console 是由 Apple Inc. 开发并随 macOS 一同提供的日志查看器。它允许用户搜索系统中的所有已记录消息。

您可以通过 Spotlight 或直接从终端使用以下命令打开控制台

% open /System/Applications/Utilities/Console.app

在左侧选择所需的设备以访问其实时日志。

如果您已启用日志记录(https://github.com/Catapush/catapush-ios-sdk-pod/blob/master/README.md#logging),您可以很容易地通过在右上角搜索 --- CATAPUSHLIB --- 前缀来过滤它们。

点击通知后检索 MessageIP

如果您需要在点击通知后访问 MessageIP,您必须将通知与特定的 MessageIP 关联。

UNNotificationRequest 对象的标识符属性,即通知请求的唯一标识符,可以用来创建此链接。

使用消息 IP 标识符和请求标识符在 MessageIP 和通知之间创建链接,并将其保存到共享 UserDefaults 中,以便主应用程序访问数据。

NotificationService (保存链接)

@implementation NotificationService

- (void)handleMessage:(MessageIP * _Nullable) message withContentHandler:(void (^_Nullable)(UNNotificationContent * _Nullable))contentHandler  withBestAttemptContent: (UNMutableNotificationContent* _Nullable) bestAttemptContent{
    if (contentHandler != nil && bestAttemptContent != nil){
        if (message != nil) {
            bestAttemptContent.body = message.body.copy;
            //We will save the link in the user defaults shared by both the app and the extension: https://github.com/Catapush/catapush-ios-sdk-pod/blob/master/README.md#app-groups
            NSUserDefaults* ud = [[NSUserDefaults alloc] initWithSuiteName:[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"Catapush"] objectForKey:@"AppGroup"]];
            NSString* pendingMessagesKey = @"pendingMessages";
            NSDictionary* pendingMessages = [ud objectForKey:pendingMessagesKey];
            NSMutableDictionary* newPendingMessages;
            if (pendingMessages == nil) {
                newPendingMessages = [[NSMutableDictionary alloc] init];
            }else{
                newPendingMessages = [pendingMessages mutableCopy];
            }
            [newPendingMessages setValue:message.messageId forKey:[self.receivedRequest identifier]];
            [ud setValue:newPendingMessages forKey:pendingMessagesKey];
            [ud synchronize];
        }else{
            bestAttemptContent.body = @"No new message";
        }
        contentHandler(bestAttemptContent);
    }
}

@end

AppDelegate(获取MessageIP)

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler{
    //tap on the notification
    NSUserDefaults* ud = [[NSUserDefaults alloc] initWithSuiteName:[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"Catapush"] objectForKey:@"AppGroup"]];
    NSString* pendingMessagesKey = @"pendingMessages";
    NSDictionary* pendingMessages = [ud objectForKey:pendingMessagesKey];
    if (pendingMessages != nil && [pendingMessages objectForKey:response.notification.request.identifier] != nil) {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"messageId == %@", [pendingMessages objectForKey:response.notification.request.identifier]];
        NSArray *matches = [Catapush messagesWithPredicate:predicate];
        if (matches.count){
            //Exists one occurence of that Message IP
            MessageIP* messageIP = [matches firstObject];
        }
    }
    completionHandler();
}

备注

Catapush静态库对IPA大小的贡献为650KB。编译时启用ENABLE_BITCODE = 1的静态库存档文件大小为60MB(包含了不同架构的对象文件)。

作者

亚历山德罗·恰罗托,《[email protected],《[email protected]》,《[email protected]>

许可证

catapush-ios-sdk-pod处于商业许可证之下。有关更多信息,请参阅LICENSE文件。