iOS实现消息推送及原理分析

iOS实现消息推送及原理分析

什么是消息推送?

消息推送是指在无需打开应用程序的情况下,向手机用户发送通知消息。消息推送可以通过苹果官方提供的APNs(Apple Push Notification service,苹果推送服务)完成。

APNs的工作原理

APNs与苹果设备之间的通信是基于一种专门为该服务设计的二进制协议,这个协议被称为APNs协议。APNs协议工作的必要前提是设备必须向APNs注册。注册的过程分为三步:

  1. 应用程序向APNs服务器发送一个请求,表示应用程序想要获取推送通知。
  2. APNs服务器向设备发送一条唯一的标识该设备的令牌。
  3. 应用程序将此令牌与设备相关联上传至自己开发的服务器上,推送消息时,APNs服务就需要知道要推送给哪些设备。

注册成功后,当服务器需要推送消息时,它会将要发送的消息及设备令牌一起发送给APNs服务器。APNs接收到消息及令牌后,就会对每个设备进行验证并确定该消息最终使用的传输通道。成功验证并传输过程中,APNs则会将消息推送到设备上。

如何实现消息推送?

在iOS中实现消息推送其实很简单,关键在于对APNs协议的理解和应用。以下是具体步骤:

  1. 注册开通APNs服务(这里不再赘述)。
  2. 在项目中引用PushKit框架,并在Capabilities选项卡中开启Push Notifications和Background Modes功能。Programmatic Only和Remote notifications两个选项都要勾选并关联对应的代码。
  3. 获取设备的DeviceToken:在AppDelegate.swift中实现didRegisterForRemoteNotificationsWithDeviceToken代理方法,并将获取到的DeviceToken上传到服务器上。
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 注册APNs服务
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, error in
            if granted == true {
                DispatchQueue.main.async {
                    application.registerForRemoteNotifications()
                }
            }
        }

        // Override point for customization after application launch.
        return true
    }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        // 发送设备的Token给服务器(这里省略具体实现代码)
    }

}
  1. 发送消息到服务器:将要向设备展示的消息以指定格式(例如JSON)发送到自己的服务端。同时,还需要将对应的设备令牌上传到服务端,并确保服务器与APNs服务器进行了注册和通信以获取对应的令牌。具体消息推送内容和格式可以根据自己的需求进行定制。
  2. 服务端向APNs服务器发送消息和设备令牌,APNs服务器根据令牌进行验证并推送消息到指定设备。

示例1:APNs本地通知

APNs不仅可以实现远程通知,还可以实现本地推送。本地推送不需要与服务端进行通信,只需要在应用程序中添加本地通知即可。

以下是一个简单的示例代码,可以实现5秒后通知用户一个本地通知。需要注意的是,在XS及以上设备中,会自动弹出通知,但是在较老的设备上,需要在AppDelegate.swift中实现UNUserNotificationCenterDelegate代理方法并手动展示通知。

import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        UNUserNotificationCenter.current().delegate = self
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
            if granted {
                let content = UNMutableNotificationContent()
                content.title = "通知标题"
                content.body = "通知内容"
                content.sound = UNNotificationSound.default

                let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
                let request = UNNotificationRequest(identifier: "localNotification", content: content, trigger: trigger)
                UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
            }
        }

        return true
    }

    // xs以上设备会自动弹出通知,较旧的设备需要手动展示通知。
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.sound, .alert])
    }

}

示例2:APNs远程推送

下面是一个APNs远程推送的示例代码。服务器端可以使用HTTP/2 推送通知 API 或者是向 APNs 服务器发送HTTP/2 请求发送推送通知,此处我们仅提供单独调用APNs函数的简单代码。

设备端通过苹果官方提供的SDK向APNs服务器注册自己的token,APNs 将提供一个唯一的deviceToken,当有推送通知需要发送给你的 app 时,服务器就会用这个 token 来标识你的 app。

import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        UNUserNotificationCenter.current().delegate = self
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
            if granted {
                DispatchQueue.global().async {
                    // 向APNs服务器发送推送消息
                    let apnsHost = "gateway.push.apple.com"
                    let apnsPort: Int32 = 2195

                    let certificatePath = Bundle.main.path(forResource: "certificate", ofType: "p12")
                    let certificateData = try! Data(contentsOf: URL(fileURLWithPath: certificatePath!))

                    let options: [String: Any] = [kSecImportExportPassphrase as String: "your_password_here"]
                    var imported = CFArrayCreate(nil, nil, 0, nil)
                    let importStatus = SecPKCS12Import(certificateData as CFData, options as CFDictionary, &imported)
                    guard importStatus == errSecSuccess else {
                        fatalError()
                    }

                    let importArray = imported as! Array<Dictionary<String, Any>>
                    let identity = importArray[0][kSecImportItemIdentity as String] as! SecIdentity

                    var cert: UnsafeMutablePointer<Cert>? = nil
                    cert = useCertificate(identity: identity)

                    connectAPNs(certificate: cert!, apnsHost: apnsHost, apnsPort: apnsPort)
                }
            }
        }

        return true
    }

    struct Cert {
        let identity: SecIdentity
        let certificate: SecCertificate
    }

    func useCertificate(identity: SecIdentity) -> UnsafeMutablePointer<Cert> {
        var certificate: SecCertificate?
        SecIdentityCopyCertificate(identity, &certificate)
        let cert = UnsafseMutablePointer<Cert>.allocate(capacity: 1)
        cert.pointee = Cert(identity: identity, certificate: certificate!)
        return cert
    }

    func connectAPNs(certificate: UnsafeMutablePointer<Cert>, apnsHost: String, apnsPort: Int32) {
        let sock = openAPNsConnection(host: apnsHost, port: apnsPort, cert: certificate.pointee.certificate, key: certificate.pointee.identity, chain: nil)
        let deviceToken = "device_token_here"

        let message = "Hello, World!"
        let payload = "{\"aps\": {\"alert\": \"\(message)\", \"sound\": \"default\"}}"

        var buffer = [UInt8]()
        buffer.append(0) // payload 的通用头
        buffer.append(0)
        buffer.append(32) // deviceToken 长度
        buffer.append(contentsOf: deviceToken.hexadecimalToBytes())
        buffer.append(0) // payload 的长度
        buffer.append(UInt8(payload.utf8.count))
        buffer.append(contentsOf: payload.utf8)

        write(sock: sock, data: buffer)
        close(sock)
    }

    func openAPNsConnection(host: String, port: Int32, cert: SecCertificate, key: SecIdentity, chain: [SecCertificate]?) -> Int32 {
        var result: Int32 = -1
        let clientContext = SSLClientContext(cert: cert, key: key, chain: chain)
        var inputStream: CFReadStream!
        var outputStream: CFWriteStream!
        CFStreamCreatePairWithSocketToHost(nil, host as CFString, port, &inputStream, &outputStream)
        CFReadStreamSetProperty(inputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext), clientContext)
        CFWriteStreamSetProperty(outputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext), clientContext)
        if CFReadStreamOpen(inputStream) == true && CFWriteStreamOpen(outputStream) == true {
            result = outputStream as! Int32
        }
        return result
    }

    func close(_ sock: Int32) {
        #if os(iOS) || os(tvOS) || os(watchOS)
        _ = Darwin.close(sock)
        #elseif os(macOS)
        _ = Darwin.close(sock)
        #endif
    }

    func write(sock: Int32, data: [UInt8]) {
        if data.isEmpty == false {
            var index = 0
            repeat {
                let written = Darwin.write(sock, data, data.count - index)
                // -1 表示写入失败
                if written == -1 {
                    throw NSError(domain: "com.company.app", code: 1, userInfo: nil)
                }
                index += written
            } while index != data.count
        }
    }

}

结论

APNs是一个十分方便且稳定的消息推送服务。在实现过程中,关键在于对APNs协议的掌握和服务端的编写,每个部分都有其特定的要求和考虑点。在使用PushKit框架时,需要注意权限及背景运行的相关设置,并在实现远程推送时,需要对APNs服务器进行认证和验证,确保推送的消息是合法、可靠的。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:iOS实现消息推送及原理分析 - Python技术站

(0)
上一篇 2023年6月26日
下一篇 2023年6月26日

相关文章

  • AD常用命令以及概念

    AD常用命令以及概念 AD(Active Directory)是微软推出的一种基于LDAP(Lightweight Directory Access Protocol)协议的目录服务,用于管理网络系统的用户、计算机、群组、权限等信息。在日常的系统管理工作中,掌握AD的基本知识和常用命令是非常有必要的。 概念介绍 域(Domain) AD中的域指的是逻辑上的一…

    其他 2023年3月28日
    00
  • Android ViewFlipper的详解及实例

    Android ViewFlipper的详解及实例攻略 什么是ViewFlipper? ViewFlipper是Android中的一个布局容器,它可以让你在同一个位置上显示多个子视图,并且可以通过滑动或者自动切换的方式进行切换。 ViewFlipper的使用步骤 在XML布局文件中添加ViewFlipper控件: <ViewFlipper androi…

    other 2023年8月21日
    00
  • oracle数据库解析json格式

    Oracle数据库解析JSON格式 随着Web应用程序的日益普及,JavaScript生成的JSON格式成为了主要的数据交换格式。这里我们将介绍如何在Oracle数据库中解析存储的JSON格式数据。 JSON的基本结构 首先让我们来看一下JSON的基本结构: { "name": "Jane", "age&qu…

    其他 2023年3月29日
    00
  • 电脑进水无法开机怎么办 电脑进水开不了机的解决方法

    电脑进水无法开机的解决方法 如果你不小心让电脑进水了,那么电脑无法开机就成了一个非常严重的问题。不过不用担心,下面给出了几条具体的解决方法。 第一步:断电 首先,必须立刻断电。如果电脑还在运转的状态下,强制关机是很危险的,因为它可能会导致数据损坏或者电脑硬件故障。所以,我们需要断开电源线和电池(如果电脑是笔记本的话)。这样做可以防止进一步损害电脑。 第二步:…

    other 2023年6月27日
    00
  • 浅析Python面向对象编程

    浅析Python面向对象编程 什么是面向对象编程 面向对象编程(Object Oriented Programming, OOP) 是一种程序设计的思想方式,是以对象为基础来构建程序的编程范式。 在面向对象编程中,一切程序实体都是对象,对象之间通过消息传递进行交互。每个对象都是一个可以执行任务、处理数据的独立体,由一个或多个方法构成。方法是属于对象的,只有该…

    other 2023年6月27日
    00
  • vim替换操作

    Vim替换操作 如果你是一位开发者或者写作人员,你肯定会时常遇到需要替换文件中文本的情况。在 Vim 编辑器中,使用替换操作可以方便地对文件进行批量修改。下面是一些基本的替换操作技巧。 替换命令 在 Vim 编辑器中,使用 :s 命令进行替换操作。例如,要将文本中的 “foo” 替换为 “bar”,可以使用以下命令: :%s/foo/bar/g 其中,% 表…

    其他 2023年3月28日
    00
  • 关于python:如何去掉空格?

    以下是关于“Python中如何去掉空格”的完整攻略,包含两个示例。 Python中如何去掉空格 在Python中,我们可以使用多种方法去掉字符串的空格。以下是关于如何去掉空格的详细攻略。 1. 使用strip()方法 strip()方法可以去掉字符串开头和结尾的空格。以下是一个示例: str = " hello world " new_s…

    other 2023年5月9日
    00
  • PHP英文字母大小写转换函数小结

    PHP英文字母大小写转换函数小结 在PHP中,我们可以使用内置的函数来实现英文字母的大小写转换。下面是一些常用的函数及其用法的详细说明。 strtolower() strtolower()函数用于将字符串中的所有英文字母转换为小写。它的语法如下: strtolower(string $string): string 示例: $input = \"H…

    other 2023年8月17日
    00
合作推广
合作推广
分享本页
返回顶部