iOS10推送(APNS)App+后端实现

Xcode里的相关配置如下:


Capability设置

APP实现

获取Device Token和处理通知,直接贴代码:

AppDelegate.h:

#ifdef NSFoundationVersionNumber_iOS_9_x_Max
#import 
#endif

@interface AppDelegate : UIResponder 

@property (nonatomic, strong) UIWindow *window;

@end

AppDelegate.m:

#import "AppDelegate.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  if ([[UIDevice currentDevice].systemVersion floatValue] >= 10.0) {
    //iOS10特有
    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
    // 必须写代理,不然无法监听通知的接收与点击
    center.delegate = self;
    [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound) completionHandler:^(BOOL granted, NSError * _Nullable error) {
      if (granted) {
        // 点击允许
        NSLog(@"注册成功");
        [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
          NSLog(@"%@", settings);
        }];
      } else {
        // 点击不允许
        NSLog(@"注册失败");
      }
    }];
  }
  // 注册获得device Token
  [[UIApplication sharedApplication] registerForRemoteNotifications];
  return YES;
}

#pragma mark - RemoteNotifications
// 获得Device Token
 - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
   const unsigned *tokenBytes = [deviceToken bytes];
   NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
                            ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
                            ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
                            ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
   NSLog(@"DeviceToken: %@", hexToken);
//   [[MyModel sharedModel] setApnsToken:hexToken];
}
// 获得Device Token失败
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    NSLog(@"did Fail To Register For Remote Notifications With Error: %@", error);
}


#pragma mark - UNUserNotificationCenterDelegate

// iOS 10收到通知
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{
    NSDictionary * userInfo = notification.request.content.userInfo;
    UNNotificationRequest *request = notification.request; // 收到推送的请求
    UNNotificationContent *content = request.content; // 收到推送的消息内容
    NSNumber *badge = content.badge;  // 推送消息的角标
    NSString *body = content.body;    // 推送消息体
    UNNotificationSound *sound = content.sound;  // 推送消息的声音
    NSString *subtitle = content.subtitle;  // 推送消息的副标题
    NSString *title = content.title;  // 推送消息的标题
    
    if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
      NSLog(@"iOS10 前台收到远程通知:%@", notification);
//      NSLog(@"iOS10 前台收到远程通知:%@", [self logDic:userInfo]);
    } else {
        // 判断为本地通知
        NSLog(@"iOS10 前台收到本地通知:{\\\\nbody:%@,\\\\ntitle:%@,\\\\nsubtitle:%@,\\\\nbadge:%@,\\\\nsound:%@,\\\\nuserInfo:%@\\\\n}",body,title,subtitle,badge,sound,userInfo);
    }
    completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert); // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型可以设置
}

// 通知的点击事件
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler{
    NSDictionary * userInfo = response.notification.request.content.userInfo;
    UNNotificationRequest *request = response.notification.request; // 收到推送的请求
    UNNotificationContent *content = request.content; // 收到推送的消息内容
    NSNumber *badge = content.badge;  // 推送消息的角标
    NSString *body = content.body;    // 推送消息体
    UNNotificationSound *sound = content.sound;  // 推送消息的声音
    NSString *subtitle = content.subtitle;  // 推送消息的副标题
    NSString *title = content.title;  // 推送消息的标题
    if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        NSLog(@"iOS10 收到远程通知:%@", response);
//      NSLog(@"iOS10 收到远程通知:%@", [self logDic:userInfo]);
    }
    else {
        // 判断为本地通知
        NSLog(@"iOS10 收到本地通知:{\\\\nbody:%@,\\\\ntitle:%@,\\\\nsubtitle:%@,\\\\nbadge:%@,\\\\nsound:%@,\\\\nuserInfo:%@\\\\n}",body,title,subtitle,badge,sound,userInfo);
    }
    
    // Warning: UNUserNotificationCenter delegate received call to -userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: but the completion handler was never called.
    completionHandler();  // 系统要求执行这个方法

}

@end

这里的hexTokenDevice Token)是需要记录的,传到后端存起来。处理这个token的时候需要注意,重新安装App后其值发生变化,后端存储的时候要考虑到这种情况。比如更新用户的token信息。

如果不开发后端,想直接测试推送的话,可以使用SmartPush模拟推送,进行测试。里面涉及到一个证书的配置,可以看这篇文章:手把手教你实现iOS 远程推送

后端(PHP,框架CodeIgniter)

有token-based和certificate-based两种方式发送请求。

我这里用的是token-based,这种方式需要以下几个信息:

  1. Key(后缀名是.p8)文件和对应的Key ID
  2. Team ID
  3. App的Bundle ID
  4. Device Token(App里获取,见上文)

1. 创建Key

创建Key

输入Key Name并选择Apple Push Notifications service后 ,点击右上角Continue

点击右上角Register

下载Key文件(只能下一次,妥善保存),记录Key ID

2. Team ID

获取Team ID

后端代码

拿到这些信息后,就可以发送推送请求了。
把下载下来的Key(后缀名是.p8)文件上传到服务器上。

几个重要信息可以存储在其它地方,这里只是为了方便。

define('AUTH_KEY_PATH', '');
define('AUTH_KEY_ID', '');
define('TEAM_ID', '');
define('BUNDLE_ID', '');

private function generateAuthenticationHeader() {
  $header = base64_encode(json_encode([
                'alg' => 'ES256',
                'kid' => AUTH_KEY_ID
            ]));

  $claims = base64_encode(json_encode([
                'iss' => TEAM_ID,
                'iat' => time()
            ]));

  $pkey = openssl_pkey_get_private('file://' . AUTH_KEY_PATH);
  openssl_sign("$header.$claims", $signature, $pkey, 'sha256');

  $signed = base64_encode($signature);
  return "$header.$claims.$signed";
}

private function sendNotification($debug, $token, $payload) {
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'apns-topic: ' . BUNDLE_ID,
      'authorization: bearer ' . $this->generateAuthenticationHeader(),
      'apns-push-type: alert'
  ]);

// 如果正式环境,$debug=false,不然下面$response会出现BadDeviceToken的错误
  $server = $debug ? 'api.development' : 'api';
  $url = 'https://'.$server.'.push.apple.com/3/device/'.$token;
  curl_setopt($ch, CURLOPT_URL, $url);
  
  $response = curl_exec($ch);
  
  if ($response === false) {
      return ["curl_exec failed: " . curl_error($ch)];
  }
  
  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  if ($code === 400 || $code === 410) {
      $json = @json_decode($response);
      return ['Reason: '.$json->reason, $code];
      // if ($json->reason === 'BadDeviceToken') {
      //   //  echo 'BadDeviceToken';
      // }
  }
  
  curl_close($ch);
  return [$response, $code];
}

public function testpush() {
  $deviceToken = '';
  $payload = [
    "aps"=>[
      "alert"=>[
        "title"=>"Game Request",
        "body"=>"Bob wants to play poker",
      ],
      "badge"=>1,
    ],
  ];
  $result = $this->sendNotification(true, $deviceToken, $payload);
  echo json_encode($result);
}

payload的具体设置可见 Creating the Remote Notification Payload

相关参考

  • 手把手教你实现iOS 远程推送
  • iOS10 最新远程通知 详细开发教程
  • Setting Up a Remote Notification Server
  • How can I convert my device token (NSData) into an NSString?
  • SmartPush

你可能感兴趣的:(iOS10推送(APNS)App+后端实现)