PusherSwiftWithEncryption 10.1.5

PusherSwiftWithEncryption 10.1.5

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 或以上版本。这里有完整的说明(包括如何迁移到 XCFrameworks,如果您已经使用 Carthage 进行了集成)。

另外,如果您正在使用英特尔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请求,其中包括客户端的socket 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 对象,以便完成订阅过程。

如果由于任何原因,认证过程失败,只需将 nil 作为唯一参数调用 completionHandler

请注意,如果您想要指定要连接的集群,则可以使用如下方式使用 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 - 如果您想使用连接状态变化来执行不同的操作/ UI 更新,请使用此函数。
  • 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整数枚举示例)

  • 连接中(0) - 连接正尝试建立
  • 连接成功(1) - 连接已成功建立
  • 断开中(2) - 连接已被指示断开,并且正准备进行断开
  • 已断开(3) - 连接已断开,不会自动尝试重新连接
  • 重新连接中(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的演示端点

用于解密事件的共享密钥从用于授权订阅的同一身份验证端点请求中加载。还有一个机制在加密主密钥变化时重新加载共享密钥。如果遇到无法解密的事件,将向您的身份验证端点发出请求,尝试加载新的共享密钥。如果该请求失败或返回的密钥仍然无法解密事件,则将跳过该事件,调用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-”的通道。

订阅存在通道的建议方式是使用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];

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

要查找当某个Channel成功订阅时的方式,最简单的方法是绑定到感兴趣Channel上的名为 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对象,或者当您实例化Pusher对象时,会向您提供的认证端点发送一个认证请求。

在大多数情况下,我们建议您使用认证端点,而不是将您的应用程序的秘密包含在应用程序中。如果您完全确信在应用程序中包含您的应用程序的秘密不会带来风险,例如,如果您的应用程序仅用于您公司的内部使用,那么设置认证端点可能比设置认证端点更容易一些。

使用自提供的认证值订阅

您可以在调用subscribesubscribeToPresenceChannel时提供认证信息来订阅需要认证的通道。这如下所示完成:

Swift

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

This PusherAuth object can be initialized with just an auth (String) value if the subscription is to a private channel, or both an auth (String) and channelData (String) pair of values if the subscription is to a presence channel.

These auth and channelData values are the values that you received if the json object created by a call to pusher.authenticate(...) in one of our various server libraries.

Keep in mind that in order to generate a valid auth value for a subscription the socketId (i.e. the unique identifier for a web socket connection to the Pusher servers) must be present when the auth value is generated. As such, the likely flow for using this is something like this would involve checking for when the connection state becomes connected before trying to subscribe to any channels requiring authentication.

绑定到事件

事件绑定可以在两个级别上操作;全局和按频道。当绑定到事件时,您可以选择保存返回值,这是创建的事件处理器的唯一标识符。唯一需要保存这个的原因是如果您在将来某个时刻想要解绑事件。以下是一个示例。

频道事件

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

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。这仅在触发于存在频道的客户端事件时可用。
函数 参数 返回类型 描述
属性 withKey: String - 属性键 Any? 用于从websocket事件中访问原始属性的帮助函数。此函数返回的数据不应被认为是稳定的,建议您使用上述属性。

解析事件数据

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

例如,以下可能是股票跟踪应用发布公司价格更新的例子。您可以将 "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 Channels 接收的错误可以通过 连接代理 访问。这以前是通过绑定回调完成的。

查看旧方法

错误以事件名 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 Action

扩展

通信

  • 如果你发现了一个bug,请提交问题。
  • 如果你有一个功能请求,请提交问题。
  • 如果你想贡献,请提交一个pull request(最好附带一些测试🙂 ).

致谢

PusherSwift由Pusher拥有并维护。它最初由Hamilton Chapman创建。

它使用了以下仓库的代码

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

许可协议

PusherSwift采用MIT许可协议发布。详情请参见LICENSE