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

yizhihongxing

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日

相关文章

  • python核心编程–学习笔记–6–序列(上)字符串

    以下是“Python核心编程–学习笔记–6–序列(上)字符串”的完整攻略,包括两个示例说明。 Python核心编程–学习笔记–6–序列(上)字符串 在Python中,字符串是一种常见的序列类型。本文将介绍Python中字符串的基础知识、常用操作和两个示例说明。 1. 字符串的基础知识 字符串是由一系列字符组成的序列,可以使用单引号、双引号或三引号…

    other 2023年5月10日
    00
  • 电脑散热器一直响而且声音大怎么办 电脑散热器声音很大的解决方法

    电脑散热器声音很大的解决方法 电脑散热器声音很大通常是因为CPU使用率高或者散热器工作不正常导致的,下面是一些可能的解决方法。 检查散热器安装 散热器安装不正确很可能导致散热器声音很大,因此我们需要检查它是否被正确安装在CPU上。有时散热器会有松动,或者散热器风扇与CPU风扇混淆。检查这些情况可以很好地解决散热器声音很大的问题。 清洁散热器 散热器上积聚的灰…

    other 2023年6月27日
    00
  • Windows XP超强50招

    Windows XP超强50招完整攻略 概述 Windows XP超强50招是一本介绍Windows XP系统优化、加速、配置的相关技巧的书籍,其中包含了50条实用的技巧和建议,可以帮助用户更好地利用Windows XP系统。本文将详细讲解书中所有的50条技巧和建议,并通过两个示例说明这些技巧的实际应用。 技巧1:加快启动速度 Windows XP启动时默认…

    other 2023年6月27日
    00
  • SwiftUI自定义导航的方法实例

    下面我来详细讲解一下“SwiftUI 自定义导航的方法实例”的攻略。 一、导航栏 1.1 导航栏的实现 在 SwiftUI 中,我们可以使用 NavigationView 来创建导航栏。导航栏可以通过修改导航栏标题、添加导航按钮和自定义导航按钮来进行自定义。例如,下面的代码展示了如何使用 NavigationView 创建具有特定标题和按钮的导航栏。 str…

    other 2023年6月25日
    00
  • Java中的接口多继承机制

    Java中的接口多继承机制,是指一个接口可以同时继承多个父接口的方法定义。这可以使得接口更加灵活和可扩展,使得我们可以更好地进行代码设计和重用。撰写完整攻略的过程中,我将包含以下内容: 什么是Java中的接口多继承机制? 接口多继承的语法和使用方法 示例说明 示例一:解释接口继承多个其他接口的用法和适用场景。 示例二:展示如何在实现类中实现多个继承的接口。 …

    other 2023年6月27日
    00
  • 电脑常见问题与解决方案第2/2页

    下面我就详细讲解一下“电脑常见问题与解决方案第2/2页”的完整攻略。 电脑常见问题与解决方案第2/2页攻略 背景 随着电脑的广泛应用,用户常常会遇到各种问题,使得电脑无法正常使用。我们整理了电脑常见问题与解决方案的攻略,分为两页。这里是第2/2页,包含更多的问题及其解决方案。为了帮助用户更好地理解与使用,我们特别使用Markdown格式编写。 步骤 打开“电…

    other 2023年6月26日
    00
  • Java子类对象的实例化过程分析

    Java子类对象的实例化过程分析 概述 在Java中,当我们创建一个子类对象时,其实会经历一系列的步骤。本文将通过分析Java子类对象的实例化过程,帮助读者更好地理解Java面向对象编程中一些重要的概念和机制。 具体步骤 Java子类对象的实例化过程包含以下几个步骤: 继承父类:子类继承了父类的所有属性和方法; 初始化父类属性:子类构造方法首先会调用父类的构…

    other 2023年6月26日
    00
  • Ubuntu中添加应用程序快速启动器的方法

    下面是完整的“Ubuntu中添加应用程序快速启动器的方法”攻略。 1. 打开“主菜单” 在Ubuntu的左侧“Dock栏”上,点击Ubuntu图标,打开“主菜单”。 2. 选择应用程序 在“主菜单”中,找到需要添加快速启动器的应用程序,选择该应用程序。 3. 复制应用程序的启动命令 在应用程序的菜单中,右击该应用程序并选择“添加到收藏夹”。然后打开“收藏夹”…

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