PusherSwift 10.1.5

PusherSwift 10.1.5

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布上次发布2024年2月
SPM支持 SPM

Pusher 维护。



 
依赖项
TweetNacl~> 1.0.0
NWWebSocket~> 0.5.4
 

Pusher Channels Swift 客户端(也支持 Objective-C)

Build Status codecov Latest Release CocoaPods API Docs Supported Platforms Swift Versions Twitter GitHub license

这是 Pusher Channels 的 WebSocket 客户端,名为 PusherSwift,它支持 iOS、macOS (OS X) 和 tvOS。它使用 Swift 和 Objective-C。

有关 Pusher Channels 的教程和更多信息,请访问我们的 官方文档

支持的平台

  • Swift 5.0 及以上
  • Xcode 12.0 及以上
  • 可使用 Objective-C

部署目标

  • iOS 13.0 及以上
  • macOS (OS X) 10.15 及以上
  • tvOS 13.0 及以上

旧版操作系统支持

如果您需要iOS、macOS或tvOS旧版本的支持,请使用SDK的最新v8.x版本。

我只想要复制粘贴一些代码来开始

您还需要什么?请转到我们的示例应用之一

目录

安装

CocoaPods

CocoaPods 是 Cocoa 项目的依赖管理器,并且是我们推荐用来安装 PusherSwift 及其依赖项的方法。

如果您还没有安装 Cocoapods,请运行以下命令:

$ gem install cocoapods

要使用 CocoaPods 将 PusherSwift 集成到您的 Xcode 项目中,请在您的 Podfile 中指定它。

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

pod 'PusherSwift', '~> 10.1.0'

然后,运行以下命令:

$ pod install

如果您发现运行 pod install 时的安装版本不是最新,那么尝试运行以下命令:

$ pod cache clean
$ pod repo update PusherSwift
$ pod install

此外,您还需要确保您没有在您的 Podfile.lock 文件中将 PusherSwift 锁定到旧版本。

Carthage

Carthage 是一个去中心化的依赖管理器,自动化将框架添加到您的 Cocoa 应用程序的过程。

您可以使用以下命令通过 Homebrew 安装 Carthage:

$ brew update
$ brew install carthage

要使用 Carthage 将 PusherSwift 集成到您的 Xcode 项目中,请在您的 Cartfile 中指定它。

github "pusher/pusher-websocket-swift"

Carthage 将生成多个框架。您需要从 Carthage/Build 目录中将以下框架二进制文件包含到您的项目中:PusherSwiftNWWebSocketTweetNacl

Xcode 12 考虑事项

在 Xcode 12.0 和更高版本中构建通用框架时,包含的架构已进行了更改,这是为了支持引入苹果硅处理器系列。

强烈建议您使用 --use-xcframeworks 标志来使用 PusherSwift,运行 Carthage 0.37.0 或更高版本。有关(this)(以及如果您已经使用 Carthage 进行集成,则有关迁移到 XCFrameworks的说明)的完整说明(。

或者,如果您正在使用英特尔mac构建,且不想通过XCFrameworks迁移到构建Carthage依赖项,有一个解决方案可以成功构建。您可以在此处找到这个解决方案的示例,它用于运行'Consumption-Tests' 这里

Swift Package Manager

要使用Swift Package Manager将PusherSwift集成到您的项目中,您可以在Xcode中将库作为依赖项添加 - 请参阅文档。包仓库URL是

https://github.com/pusher/pusher-websocket-swift.git

或者,您可以在Package.swift文件中将PusherSwift作为依赖项添加。例如

// swift-tools-version:5.1
import PackageDescription

let package = Package(
    name: "YourPackage",
    products: [
        .library(
            name: "YourPackage",
            targets: ["YourPackage"]),
    ],
    dependencies: [
        .package(url: "https://github.com/pusher/pusher-websocket-swift.git", from: "10.1.0"),
    ],
    targets: [
        .target(
            name: "YourPackage",
            dependencies: ["PusherSwift"]),
    ]
)

然后,您需要在任何想要使用SDK的源文件中包含一个import PusherSwift声明。

配置

对于Pusher客户端,可以设置多项配置参数。对于Swift,它们是

  • authMethod (AuthMethod) - 客户端用于对需要身份验证的通道进行身份验证的请求的认证方法(以下有更多详细信息)
  • useTLS (Bool) - 是否希望使用TLS加密的传输,默认为true
  • autoReconnect (Bool) - 设置是否希望库在断开连接后尝试自动重新连接(如果可能)。有关更多信息,请参阅重新连接
  • host (PusherHost) - 为您想要连接到的主机设置自定义值,例如PusherHost.host("ws-test.pusher.com")
  • port (Int) - 设置您想要连接到的端口号
  • activityTimeout (TimeInterval) - 在此时间(以秒为单位)没有从服务器接收到任何消息后,将发送ping消息以检查连接是否仍在工作;默认值由服务器提供,较低值会导致不必要的流量。
查看传统的配置选项
  • attemptToReturnJSONObject (Bool) - 是否希望库尝试将数据解析为JSON(或者不,仅返回字符串)

authMethod参数的类型必须是AuthMethod。这是一个枚举,定义为

public enum AuthMethod {
    case endpoint(authEndpoint: String)
    case authRequestBuilder(authRequestBuilder: AuthRequestBuilderProtocol)
    case inline(secret: String)
    case authorizer(authorizer: Authorizer)
    case noMethod
}
  • endpoint(authEndpoint: String) - 客户端将向您指定的端点发送POST请求,其中包含客户端的套接字ID和想要订阅的通道名称
  • authRequestBuilder(authRequestBuilder: AuthRequestBuilderProtocol) - 您指定一个符合 AuthRequestBuilderProtocol(以下定义)的对象,必须生成一个将要用于进行认证请求的 URLRequest 对象
  • inline(secret: String) - 您应用的密钥,以便认证请求无需发送到认证端点,而是可以在库内部直接进行订阅认证(这主要是为了开发使用)
  • authorizer(authorizer: Authorizer) - 您指定一个符合 Authorizer 协议的对象,该对象必须能够提供适当的认证信息
  • noMethod - 如果您仅使用公共通道,则不需要设置 authMethod(这是默认值)

这是 AuthRequestBuilderProtocol 的定义

public protocol AuthRequestBuilderProtocol {
    func requestFor(socketID: String, channelName: String) -> URLRequest?
}

这是 Authorizer 协议的定义

public protocol Authorizer {
    func fetchAuthValue(socketID: String, channelName: String, completionHandler: (PusherAuth?) -> ())
}

其中 PusherAuth 定义为

public class PusherAuth: NSObject {
    public let auth: String
    public let channelData: String?
    public let sharedSecret: String?

    public init(auth: String, channelData: String? = nil, sharedSecret: String? = nil) {
        self.auth = auth
        self.channelData = channelData
        self.sharedSecret = sharedSecret
    }
}

如果认证过程成功,需要调用提供的 completionHandler 并传入一个 PusherAuth 对象,以便订阅过程可以完成。

如果出于任何原因认证过程失败,只需将 completionHandler 的唯一参数设置为 nil

注意,如果您想要指定要连接的集群,则可以使用以下方式使用 host 属性

Swift

let options = PusherClientOptions(
    host: .cluster("eu")
)

Objective-C

OCAuthMethod *authMethod = [[OCAuthMethod alloc] initWithAuthEndpoint:@"https://your.authendpoint/pusher/auth"];
OCPusherHost *host = [[OCPusherHost alloc] initWithCluster:@"eu"];
PusherClientOptions *options = [[PusherClientOptions alloc]
                                initWithOcAuthMethod:authMethod
                                autoReconnect:YES
                                ocHost:host
                                port:nil
                                useTLS:YES
                                activityTimeout:nil];

所有这些配置选项需要传递给一个 PusherClientOptions 对象,然后将该对象在创建对象时传递给 Pusher 对象,例如

Swift

let options = PusherClientOptions(
    authMethod: .endpoint(authEndpoint: "https://:9292/pusher/auth")
)

let pusher = Pusher(key: "APP_KEY", options: options)

Objective-C

OCAuthMethod *authMethod = [[OCAuthMethod alloc] initWithAuthEndpoint:@"https://your.authendpoint/pusher/auth"];
OCPusherHost *host = [[OCPusherHost alloc] initWithCluster:@"eu"];
PusherClientOptions *options = [[PusherClientOptions alloc]
                                initWithOcAuthMethod:authMethod
                                autoReconnect:YES
                                ocHost:host
                                port:nil
                                useTLS:YES
                                activityTimeout:nil];
pusher = [[Pusher alloc] initWithAppKey:@"YOUR_APP_KEY" options:options];

如您注意到的,对于Objective-C的使用,略有不同。主要变化是您需要使用OCAuthMethodOCPusherHost代替AuthMethodPusherHost。在Objective-C代码中,您可以在OCAuthMethod类中调用以下函数。

public init(authEndpoint: String)

public init(authRequestBuilder: AuthRequestBuilderProtocol)

public init(secret: String)

public init()
OCAuthMethod *authMethod = [[OCAuthMethod alloc] initWithSecret:@"YOUR_APP_SECRET"];
PusherClientOptions *options = [[PusherClientOptions alloc] initWithAuthMethod:authMethod];

OCPusherHost的情况类似。您有以下函数可用

public init(host: String)

public init(cluster: String)
[[OCPusherHost alloc] initWithCluster:@"YOUR_CLUSTER_SHORTCODE"];

认证通道示例

Swift

class AuthRequestBuilder: AuthRequestBuilderProtocol {
    func requestFor(socketID: String, channelName: String) -> URLRequest? {
        var request = URLRequest(url: URL(string: "https://:9292/builder")!)
        request.httpMethod = "POST"
        request.httpBody = "socket_id=\(socketID)&channel_name=\(channel.name)".data(using: String.Encoding.utf8)
        request.addValue("myToken", forHTTPHeaderField: "Authorization")
        return request
    }
}

let options = PusherClientOptions(
    authMethod: AuthMethod.authRequestBuilder(authRequestBuilder: AuthRequestBuilder())
)
let pusher = Pusher(
  key: "APP_KEY",
  options: options
)

Objective-C

@interface AuthRequestBuilder : NSObject <AuthRequestBuilderProtocol>

- (NSURLRequest *)requestForSocketID:(NSString *)socketID channelName:(NSString *)channelName;

@end

@implementation AuthRequestBuilder

- (NSURLRequest *)requestForSocketID:(NSString *)socketID channelName:(NSString *)channelName {
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:@"https://:9292/pusher/auth"]];
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL: [[NSURL alloc] initWithString:@"https://:9292/pusher/auth"]];

    NSString *dataStr = [NSString stringWithFormat: @"socket_id=%@&channel_name=%@", socketID, channelName];
    NSData *data = [dataStr dataUsingEncoding:NSUTF8StringEncoding];
    mutableRequest.HTTPBody = data;
    mutableRequest.HTTPMethod = @"POST";
    [mutableRequest addValue:@"myToken" forHTTPHeaderField:@"Authorization"];

    request = [mutableRequest copy];

    return request;
}

@end

OCAuthMethod *authMethod = [[OCAuthMethod alloc] initWithAuthRequestBuilder:[[AuthRequestBuilder alloc] init]];
PusherClientOptions *options = [[PusherClientOptions alloc] initWithAuthMethod:authMethod];

其中"Authorization""myToken"是服务器在请求头中期望的字段和值。

连接

通过向构造函数提供您的API密钥来建立WebSocket连接。

Swift

let pusher = Pusher(key: "APP_KEY")
pusher.connect()

Objective-C

Pusher *pusher = [[Pusher alloc] initWithAppKey:@"YOUR_APP_KEY"];
[pusher connect];

这会返回一个客户端对象,然后可以使用它来订阅频道,之后调用 connect() 触发连接过程开始。

重要:您必须保留对 Pusher 客户端的强引用。您可以通过将 pusher 设置为您的应用代理的属性来实现这一点,例如。

您也可以在连接对象上设置 userDataFetcher

  • userDataFetcher (() -> PusherPresenceChannelMember) - 如果您正在订阅一个认证频道,并且希望提供一个函数返回用户数据

设置方式如下

Swift

let pusher = Pusher(key: "APP_KEY")

pusher.connection.userDataFetcher = { () -> PusherPresenceChannelMember in
    return PusherPresenceChannelMember(userId: "123", userInfo: ["twitter": "hamchapman"])
}

Objective-C

Pusher *pusher = [[Pusher alloc] initWithAppKey:@"YOUR_APP_KEY"];

pusher.connection.userDataFetcher = ^PusherPresenceChannelMember* () {
    NSString *uuid = [[NSUUID UUID] UUIDString];
    return [[PusherPresenceChannelMember alloc] initWithUserId:uuid userInfo:nil];
};

连接代理

存在一个可以用来接收连接相关信息的PusherDelegate,当遵守PusherDelegate协议时,你可以选择实现以下功能

@objc optional func changedConnectionState(from old: ConnectionState, to new: ConnectionState)
@objc optional func subscribedToChannel(name: String)
@objc optional func failedToSubscribeToChannel(name: String, response: URLResponse?, data: String?, error: NSError?)
@objc optional func debugLog(message: String)
@objc(receivedError:) optional func receivedError(error: PusherError)
@objc optional func failedToDecryptEvent(eventName: String, channelName: String, data: String?)

函数的命名在很大程度上已经揭示了它们的作用,但为了完整性,以下是一些信息

  • changedConnectionState - 如果想要使用连接状态变化来执行不同的操作或界面更新,则可以使用此功能
  • subscribedToChannel - 如果想在被成功订阅到一个频道时得到通知,这非常有用,如果要执行仅在订阅成功后才相关的操作(例如,注销存在频道的成员),则可以此功能
  • failedToSubscribeToChannel - 如果想被通知尝试订阅频道失败,可以使用此功能。例如,您可以使用该功能再次尝试订阅或调用您用于跟踪错误的服务的调用
  • debugLog - 如果想记录与Pusher相关的事件(例如,底层的websocket接收到的消息),则可以使用此功能
  • receivedError - 如果想被通知从Pusher频道接收到的错误(例如,“应用已超过连接配额”),则可以使用此功能。您可以在以下链接找到一些可能的错误列表:这里
  • failedToDecryptEvent - 仅用于私有加密频道 - 如果想被通知任何消息解析失败,则可以使用此功能

设置代理的示例如下

Swift

class ViewController: UIViewController, PusherDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        let pusher = Pusher(key: "APP_KEY")
        pusher.connection.delegate = self
        // ...
    }
}

Objective-C

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.client = [[Pusher alloc] initWithAppKey:@"YOUR_APP_KEY"];

    self.client.connection.delegate = self;
    // ...
}

以下是一些示例,展示如何设置一个类,其中包含实现所有可选协议函数的功能

Swift

class DummyDelegate: PusherDelegate {
    func changedConnectionState(from old: ConnectionState, to new: ConnectionState) {
        // ...
    }

    func debugLog(message: String) {
        // ...
    }

    func subscribedToChannel(name: String) {
        // ...
    }

    func failedToSubscribeToChannel(name: String, response: URLResponse?, data: String?, error: NSError?) {
        // ...
    }

    func receivedError(error: PusherError) {
        let message = error.message
        if let code = error.code {
            // ...
        }
    }

    func failedToDecryptEvent(eventName: String, channelName: String, data: String?) {
      // ...
    }

}

Objective-C

@interface DummyDelegate : NSObject <PusherDelegate>

- (void)changedConnectionState:(enum ConnectionState)old to:(enum ConnectionState)new_
- (void)debugLogWithMessage:(NSString *)message
- (void)subscribedToChannelWithName:(NSString *)name
- (void)failedToSubscribeToChannelWithName:(NSString *)name response:(NSURLResponse *)response data:(NSString *)data error:(NSError *)error
- (void)receivedError:(PusherError *)error
- (void)failedToDecryptEventWithEventName:(NSString *)eventName channelName:(NSString *)channelName data:(NSString *)data

@end

@implementation DummyDelegate

- (void)changedConnectionState:(enum ConnectionState)old to:(enum ConnectionState)new_ {
    // ...
}

- (void)debugLogWithMessage:(NSString *)message {
    // ...
}

- (void)subscribedToChannelWithName:(NSString *)name {
    // ...
}

- (void)failedToSubscribeToChannelWithName:(NSString *)name response:(NSURLResponse *)response data:(NSString *)data error:(NSError *)error {
    // ...
}

- (void)receivedError:(PusherError *)error {
    NSNumber *code = error.codeOC;
    NSString *message = error.message;
    // ...
}

- (void)failedToDecryptEventWithEventName:(NSString *)eventName channelName:(NSString *)channelName data:(NSString *)data {
  // ...
}

@end

连接可以处于的不同状态有(括号中为Objective-C整数枚举案例)

  • connecting (0) - 连接即将尝试建立
  • connected (1) - 连接已成功建立
  • disconnecting (2) - 连接已被指示断开,并即将进行断开操作
  • disconnected (3) - 连接已断开,且不会尝试自动重新连接
  • reconnecting (4) - 将尝试重新建立连接

你可以调用位于ConnectionState对象上的stringValue()函数来获取状态的字符串表示,例如"connecting"

重新连接

断开连接主要有三种情况

  • 客户端显式调用断开连接并发送WebSocket连接的关闭帧
  • 客户端遇到某种形式的网络降级,导致心跳(ping/pong)消息丢失,从而使客户端断开连接
  • Pusher服务器关闭WebSocket连接;通常这只会发生在Pusher socket服务器重启期间,并且应立即尝试重新连接

对于第一种断开情况,库不会(如你所希望的那样)尝试重新连接。

库使用NWWebSocket尝试检测导致断开连接的网络降级事件。如果检测到这种情况,则库将尝试重新连接(默认情况下)以指数退避方式进行无限尝试(默认情况下,重新连接尝试之间的最大时间限制为120秒)。reconnectAttemptsMax的值是PusherConnection上的一个公共属性,因此可以根据需要设置最大重连尝试次数。

如果Pusher服务器关闭WebSocket,或者由于NWWebSocket未涵盖的网络事件而导致断开连接,则库仍然会尝试按上述方式重新连接。

如果你将客户端选项autoReconnect设置为true,这是默认设置,那么这些都适用。如果你的重连策略不适合你的用例,则可以将autoReconnect设置为false并实现基于连接状态变更的自定义重连策略。

注意:如果Pusher服务器使用通道协议关闭代码关闭WebSocket,则忽略autoReconnect选项,重连策略由接收到的特定关闭代码确定。

连接(PusherConnection)上有一些属性可以设置,这些属性会影响重新连接的行为。它们包括:

  • public var reconnectAttemptsMax: Int? = 6 - 如果将此设置为 nil,则不会设置最大重连尝试次数,尝试将会继续以指数退避的方式(基于尝试次数)进行,否则只会进行此属性值指定的重连尝试次数,在连接状态变为 .disconnected 之前。
  • public var maxReconnectGapInSeconds: Double? = nil - 如果您想要设置重连尝试之间的最大时间长度(以秒为单位),适当设置此属性。

注意,一旦连接成功,重连尝试的次数将会重置为 0。

订阅

公开频道

订阅频道的默认方法是通过调用客户端对象的 subscribe 方法实现。

Swift

let myChannel = pusher.subscribe("my-channel")

Objective-C

PusherChannel *myChannel = [pusher subscribeWithChannelName:@"my-channel"];

这将返回 PusherChannel 对象,可以将事件绑定到它上面。

对于非存在频道,您还可以提供一个函数,当客户端订阅或退订包含订阅者数量的频道时会调用该函数。此外,此函数作为参数也可用于subscribe函数。

let onSubscriptionCountChanged = { (count: Int) in
    print("\(count) subscriptions")
}

let channel = pusher.subscribe(
    channelName: "my-channel",
    onSubscriptionCountChanged: onSubscriptionCountChanged
)

私有频道

私有频道与公共频道的创建方式完全相同,只是它们位于 'private-' 命名空间中。这意味着需要使用频道名的前缀

Swift

let myPrivateChannel = pusher.subscribe("private-my-channel")

Objective-C

PusherChannel *myPrivateChannel = [pusher subscribeWithChannelName:@"private-my-channel"];

订阅私有频道需要客户端进行身份验证。有关身份验证频道的示例,请参阅配置部分以获取更多信息。

私有加密频道

与私有频道类似,您还可以订阅一个私有加密频道。此库现在完全支持端到端加密。这意味着只有您和连接的客户端才能阅读您的消息。Pusher无法解密它们。

与私有频道一样,您必须提供一个身份验证端点。该端点必须使用支持端到端加密的服务器客户端。这里有一个演示端点示例,使用nodejs实现

用于解密事件的共享密钥是从用于授权订阅的同一认证端点请求加载的。如果您的加密主密钥发生变化,还有一个机制可以重新加载共享密钥。如果遇到无法解密的 eventos,则会向您的认证端点发出请求,尝试加载新的共享密钥。如果此请求失败或者返回的密钥仍无法解密该事件,则将跳过该事件,调用failedToDecryptEvent连接代理函数,并处理下一个接收的事件。

由于需要按需重新加载共享密钥,您只能使用以下认证方法endpointauthRequestBuilderauthorizer。如果您正在订阅加密通道,则无法将PusherAuth实例传递给subscribe函数。

限制

  • 不适合用于扩展
  • 加密通道不支持客户端事件

Swift

let privateEncryptedChannel = pusher.subscribe(channelName: "private-encrypted-my-channel")

Objective-C

PusherChannel *privateEncryptedChannel = [pusher subscribeWithChannelName:@"private-encrypted-my-channel"];

当您希望能监听任何解密失败事件时,连接代理还包含可选的回调

optional func failedToDecryptEvent(eventName: String, channelName: String, data: String?)

存在(Presence)通道

存在(Presence)通道是以presence-为前缀的通道。

推荐订阅存在性通道的方法是使用 subscribeToPresenceChannel 函数,而不是标准 subscribe 函数。使用 subscribeToPresenceChannel 函数意味着你将得到一个 PusherPresenceChannel 对象,而不是标准的 PusherChannel。这个 PusherPresenceChannel 对象有一些额外的存在性通道特定功能可供使用,例如 membersmefindMember

Swift

let myPresenceChannel = pusher.subscribeToPresenceChannel(channelName: "presence-my-channel")

Objective-C

PusherPresenceChannel *myPresenceChannel = [pusher subscribeToPresenceChannelWithChannelName:@"presence-my-channel"];

正如所提及的那样,您仍然可以使用 subscribe 方法订阅存在性通道,但是返回的通道对象将无法访问存在性通道特定功能,除非您选择将通道对象转换为 PusherPresenceChannel

Swift

let myPresenceChannel = pusher.subscribe("presence-my-channel")

Objective-C

PusherChannel *myPresenceChannel = [pusher subscribeWithChannelName:@"presence-my-channel"];

您还可以提供在成员被添加到或从通道中删除时调用的函数。这些在 subscribesubscribeToPresenceChannel 都可以作为参数提供。

Swift

let onMemberChange = { (member: PusherPresenceChannelMember) in
    print(member)
}

let chan = pusher.subscribeToPresenceChannel("presence-channel", onMemberAdded: onMemberChange, onMemberRemoved: onMemberChange)

Objective-C

void (^onMemberChange)(PusherPresenceChannelMember*) = ^void (PusherPresenceChannelMember *member) {
    NSLog(@"%@", member);
};

PusherChannel *myPresenceChannel = [pusher subscribeWithChannelName:@"presence-my-channel" onMemberAdded:onMemberChange onMemberRemoved:onMemberChange];

注意:只有在成功订阅频道后,PusherPresenceChannel对象的membersmyId属性(以及获取这些属性值的函数)才会被设置。

了解何时成功订阅了频道的最简单方法是在感兴趣的频道上绑定名为pusher:subscription_succeeded的事件。它看起来可能是这样的

Swift

let pusher = Pusher(key: "YOUR_APP_KEY")

let chan = pusher.subscribeToPresenceChannel("presence-channel")

chan.bind(eventName: "pusher:subscription_succeeded", eventCallback: { event in
    print("Subscribed!")
    print("I can now access myId: \(chan.myId)")
    print("And here are the channel members: \(chan.members)")
})

Objective-C

Pusher *pusher = [[Pusher alloc] initWithAppKey:@"YOUR_APP_KEY"];
PusherPresenceChannel *chan = [pusher subscribeToPresenceChannelWithChannelName:@"presence-channel"];

[chan bindWithEventName:@"pusher:subscription_succeeded" eventCallback: ^void (PusherEvent *event) {
    NSLog(@"Subscribed!");
    NSLog(@"I can now access myId: %@", chan.myId);
    NSLog(@"And here are my channel members: %@", chan.members);
}];

您还可以使用PusherDelegate协议中的subscriptionDidSucceed代理方法来通知订阅成功。

以下是如何使用代理的示例

Swift

class DummyDelegate: PusherDelegate {
    func subscribedToChannel(name: String) {
        if channelName == "presence-channel" {
            if let presChan = pusher.connection.channels.findPresence(channelName) {
                // in here you can now have access to the channel's members and myId properties
                print(presChan.members)
                print(presChan.myId)
            }
        }
    }
}

let pusher = Pusher(key: "YOUR_APP_KEY")
pusher.connection.delegate = DummyDelegate()
let chan = pusher.subscribeToPresenceChannel("presence-channel")

Objective-C

@implementation DummyDelegate

- (void)subscribedToChannelWithName:(NSString *)name {
    if ([channelName isEqual: @"presence-channel"]) {
        PusherPresenceChannel *presChan = [self.client.connection.channels findPresenceWithName:@"presence-channel"];
        NSLog(@"%@", [presChan members]);
        NSLog(@"%@", [presChan myId]);
    }
}

@implementation ViewController

- (void)viewDidLoad {
    // ...

    Pusher *pusher = [[Pusher alloc] initWithAppKey:@"YOUR_APP_KEY"];
    pusher.connection.delegate = [[DummyDelegate alloc] init];
    PusherChannel *chan = [pusher subscribeToPresenceChannelWithChannelName:@"presence-channel"];

注意,无论是私有频道还是存在频道,用户都需要进行身份验证才能订阅频道。此身份验证可以发生在图书馆内部,如果您配备了应用密钥,或者当您创建Pusher对象时,会向您提供的身份验证端点发出身份验证请求。

在大多数情况下,我们建议您使用身份验证端点,而不是将应用密钥包含在您的应用中。如果您完全确信包含应用密钥没有风险,例如如果您的应用仅限于公司内部使用,那么这会比设置身份验证端点更容易。

使用自提供身份验证值进行订阅

可以通过在调用subscribesubscribeToPresenceChannel时提供身份验证信息来订阅需要身份验证的频道。这如下所示

Swift

let pusherAuth = PusherAuth(auth: yourAuthString, channelData: yourOptionalChannelDataString)
let chan = self.pusher.subscribe(channelName, auth: pusherAuth)

如果订阅到的是私有频道,可以通过仅使用auth (String) 值来初始化PusherAuth对象,如果订阅到的是存在频道,则可以同时提供auth (String) 和channelData (String) 值对。

这些authchannelData值是在我们的各种服务器库中对pusher.authenticate(...)进行调用时创建的json对象所收到的值。

请注意,为了生成一个有效的身份验证值进行订阅,生成身份验证值时必须存在socketId(即与Pusher服务器建立的Web socket连接的唯一标识符)。因此,使用此功能时可能涉及在尝试订阅任何需要身份验证的频道之前先检查连接状态变为connected

绑定到事件

事件可以在两个级别上进行绑定;全局和按频道。在绑定事件时,您可以选择保存返回值,该值为创建的事件处理程序的唯一标识符。保留此值的唯一原因是在稍后时间点要从事件中取消绑定。下面有此示例。

按频道事件

这些事件绑定到特定的频道,这意味着您可以在客户端应用程序的不同部分重用事件名称。

Swift

let pusher = Pusher(key: "YOUR_APP_KEY")
let myChannel = pusher.subscribe("my-channel")

myChannel.bind(eventName: "new-price", eventCallback: { (event: PusherEvent) -> Void in
    if let data: String = event.data {
        // `data` is a string that you can parse if necessary.
    }
})

回调函数传递一个 PusherEvent(请参见文档)。

查看旧版方法
let pusher = Pusher(key: "YOUR_APP_KEY")
let myChannel = pusher.subscribe("my-channel")

myChannel.bind(eventName: "new-price", callback: { (data: Any?) -> Void in
    if let data = data as? [String : AnyObject] {
        if let price = data["price"] as? String, company = data["company"] as? String {
            print("\(company) is now priced at \(price)")
        }
    }
})

Objective-C

Pusher *pusher = [[Pusher alloc] initWithAppKey:@"YOUR_APP_KEY"];
PusherChannel *chan = [pusher subscribeWithChannelName:@"my-channel"];

[chan bindWithEventName:@"new-price" eventCallback:^void (PusherEvent *event) {
    NSString *data = event.data;
    // `data` is a string that you can parse if necessary.
}];
查看旧版方法
Pusher *pusher = [[Pusher alloc] initWithAppKey:@"YOUR_APP_KEY"];
PusherChannel *chan = [pusher subscribeWithChannelName:@"my-channel"];

[chan bindWithEventName:@"new-price" callback:^void (NSDictionary *data) {
    NSString *price = data[@"price"];
    NSString *company = data[@"company"];

    NSLog(@"%@ is now priced at %@", company, price);
}];

全局事件

无论事件广播到哪个渠道,您都可以将这些事件的行为附加到它们。

Swift

let pusher = Pusher(key: "YOUR_APP_KEY")
pusher.subscribe("my-channel")

pusher.bind(eventCallback: { (event: PusherEvent) -> Void in
    if let data: String = event.data {
        // `data` is a string that you can parse if necessary.
    }
})

回调函数传递一个 PusherEvent(请参见文档)。

查看旧版方法
let pusher = Pusher(key: "YOUR_APP_KEY")
pusher.subscribe("my-channel")

pusher.bind(callback: { (event: Any?) -> Void in
    if let data = event["data"] as? [String : AnyObject] {
        if let commenter = data["commenter"] as? String, message = data["message"] as? String {
            print("\(commenter) wrote \(message)")
        }
    }
})

Objective-C

Pusher *pusher = [[Pusher alloc] initWithAppKey:@"YOUR_APP_KEY"];
PusherChannel *chan = [pusher subscribeWithChannelName:@"my-channel"];

[pusher bindWithEventCallback: ^void (PusherEvent *event) {
    // `data` is a string that you can parse if necessary.
    NSString *data = event.data;
}];
查看旧版方法
Pusher *pusher = [[Pusher alloc] initWithAppKey:@"YOUR_APP_KEY"];
PusherChannel *chan = [pusher subscribeWithChannelName:@"my-channel"];

[pusher bind: ^void (NSDictionary *event) {
    NSDictionary *data = event[@"data"];
    NSString *commenter = data[@"commenter"];
    NSString *message = data[@"message"];

    NSLog(@"%@ wrote %@", commenter, message);
}];

回调参数

PusherEvent

您绑定的回调函数将接收一个 PusherEvent

属性 类型 描述
eventName String 事件名称。
channelName String? 触发事件的频道名称。
data String? 传递给 trigger 的数据,作为字符串编码。如果您传递了一个对象,那么它已经被序列化为 JSON 字符串,您可以按照需要进行解析。参见 解析事件数据
userId String? 触发事件的用户 ID。这仅适用于在 presence 频道上触发的事件。
函数 参数 返回类型 描述
property withKey: String - 属性的键 Any? 用于访问WebSocket事件的原始属性的辅助函数。此函数返回的数据不应被视为稳定的,建议您使用上述属性。

解析事件数据

PusherEventdata属性包含了您在触发事件时传入的数据的字符串表示。如果您传入了一个对象,那么该对象将被序列化为JSON。您可以按适当的方式解析这个JSON。您可以利用JSONSerialization,或者使用JSONDecoder将JSON解码为可编码的类或结构体。请参阅Apple文档:编码和自定义类型的解码

例如,以下可能是一个追踪股票应用发布公司价格更新的示例。您可以将“price-update”事件解码为Swift中的结构体

struct PriceUpdate: Codable {
    public let company: String,
    public let price: Int,
}

let pusher = Pusher(key: "YOUR_APP_KEY")
let myChannel = pusher.subscribe("my-channel")
let decoder = JSONDecoder()

myChannel.bind(eventName: "price-update", eventCallback: { (event: PusherEvent) -> Void in
    guard let json: String = event.data,
        let jsonData: Data = json.data(using: .utf8)
    else{
        print("Could not convert JSON string to data")
        return
    }

    let decoded = try? decoder.decode(PriceUpdate.self, from: jsonData)
    guard let priceUpdate = decoded else {
        print("Could not decode price update")
        return
    }

    print("\(priceUpdate.company) is now priced at \(priceUpdate.price)")
})

另一种方法是使用JSONSerialization将JSON解码为Swift数据类型

Swift

let pusher = Pusher(key: "YOUR_APP_KEY")
let myChannel = pusher.subscribe("my-channel")

myChannel.bind(eventName: "price-update", eventCallback: { (event: PusherEvent) -> Void in
    guard let json: String = event.data,
        let jsonData: Data = json.data(using: .utf8)
    else{
        print("Could not convert JSON string to data")
        return
    }

    let decoded = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any]
    guard let priceUpdate = decoded else {
        print("Could not decode price update")
        return
    }

    if let company = priceUpdate["company"] as? String, let price = priceUpdate["price"] as? String {
        print("\(company) is now priced at \(price)")
    }
})

Objective-C

Pusher *pusher = [[Pusher alloc] initWithAppKey:@"YOUR_APP_KEY"];
PusherChannel *chan = [pusher subscribeWithChannelName:@"my-channel"];

[chan bindWithEventName:@"price-update" eventCallback:^void (PusherEvent *event) {
    NSString *dataString = event.data;
    NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];

    NSError *error;
    NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];

    NSString *price = jsonObject[@"price"];
    NSString *company = jsonObject[@"company"];

    NSLog(@"%@ is now priced at %@", company, price);
}];

接收错误

可以通过连接代理访问从Pusher频道接收到的错误。以前是通过绑定回调来完成的。

查看旧版方法

相关的错误会将事件名称为 pusher:error 的错误发送到客户端。可以使用以下代码来接收和处理这些错误。显然,具体如何处理它们留给开发者决定,但这里展示了通用的模式。

Swift

pusher.bind({ (message: Any?) in
    if let message = message as? [String: AnyObject], eventName = message["event"] as? String where eventName == "pusher:error" {
        if let data = message["data"] as? [String: AnyObject], errorMessage = data["message"] as? String {
            print("Error message: \(errorMessage)")
        }
    }
})

Objective-C

[pusher bind:^void (NSDictionary *data) {
    NSString *eventName = data[@"event"];

    if ([eventName isEqualToString:@"pusher:error"]) {
        NSString *errorMessage = data[@"data"][@"message"];
        NSLog(@"Error message: %@", errorMessage);
    }
}];

你可能遇到的错误类型包括

# if attempting to subscribe to an already subscribed-to channel

"{\"event\":\"pusher:error\",\"data\":{\"code\":null,\"message\":\"Existing subscription to channel presence-channel\"}}"

# if the auth signature generated by your auth mechanism is invalid

"{\"event\":\"pusher:error\",\"data\":{\"code\":null,\"message\":\"Invalid signature: Expected HMAC SHA256 hex digest of 200557.5043858:presence-channel:{\\\"user_id\\\":\\\"200557.5043858\\\"}, but got 8372e1649cf5a45a2de3cd97fe11d85de80b214243e3a9e9f5cee502fa03f880\"}}"

你可以看到,它们的通用形式是

{
  "event": "pusher:error",
  "data": {
    "code": null,
    "message": "Error message here"
  }
}

解除事件处理器

你可以使用 unbind 函数从对象中移除之前绑定的处理器。例如,

Swift

let pusher = Pusher(key: "YOUR_APP_KEY")
let myChannel = pusher.subscribe("my-channel")

let eventHandlerId = myChannel.bind(eventName: "new-price", eventCallback: { (event: PusherEvent) -> Void in
  //...
})

myChannel.unbind(eventName: "new-price", callbackId: eventHandlerId)

Objective-C

Pusher *pusher = [[Pusher alloc] initWithAppKey:@"YOUR_APP_KEY"];
PusherChannel *chan = [pusher subscribeWithChannelName:@"my-channel"];

NSString *callbackId = [chan bindWithEventName:@"new-price" eventCallback:^void (PusherEvent *event) {
    //...
}];

[chan unbindWithEventName:@"new-price" callbackId:callbackId];

您可以在全局和频道级别解除事件绑定。对于这两个对象,您还可以选择调用unbindAll,正如您所猜测的,它将解除对象上所有事件句柄的绑定。

触发事件

一旦授权私有订阅存在频道(见认证用户)且订阅成功,就可以在那些频道上触发事件。

chan.trigger(eventName: "client-myEvent", data: ["myName": "Bob"])

由客户端触发的事件称为客户端事件。因为它们来自可能不受信任的客户端,因此在使用它们时有一系列强制规则。其中一些规则包括

  • 事件名称必须具有client-前缀
  • 速率限制
  • 只能在订阅成功时触发事件

有关详细信息,请参阅客户端事件文档

测试

可以运行标准方法(Xcode中的Command-U)来运行库的一组测试。

这些测试也在GitHub Actions上运行,请参阅CI动作

扩展

通信

  • 如果你发现了错误,请创建一个问题。
  • 如果你有功能请求,请创建一个问题。
  • 如果你想贡献,请提交一个拉取请求(最好包含一些测试🙂 ).

致谢

PusherSwift 由 Pusher 拥有和管理。它最初由 Hamilton Chapman 创建。

它使用了以下存储库中的代码

这些库的单独许可证包含在相应的 Swift 文件中。

许可证

PusherSwift 采用 MIT 许可证发布。有关详情,请参阅 LICENSE