Pusher Channels Objective-C 客户端
libPusher 是 Pusher Channels 实时服务的一个 Objective-C 客户端库。
Pusher Channels 是一个托管服务,位于您的 web 应用程序和浏览器之间,使得您可以使用 WebSockets 实时地传递事件。
libPusher API 尽可能地模仿了 Pusher Channels JavaScript 客户端,同时考虑到了 Objective-C 的约定。特别是,尽管 JavaScript 客户端使用事件绑定来处理所有事件,而事件是预定义的,但 libPusher 使用标准的 Cocoa 委派模式。
实例
订阅 chat
通道,并将 new-message
事件绑定到它。
// self.client is a strong instance variable of class PTPusher
self.client = [PTPusher pusherWithKey:@"YOUR_API_KEY" delegate:self encrypted:YES];
// subscribe to channel and bind to event
PTPusherChannel *channel = [self.client subscribeToChannelNamed:@"chat"];
[channel bindToEventNamed:@"new-message" handleWithBlock:^(PTPusherEvent *channelEvent) {
// channelEvent.data is a NSDictianary of the JSON object received
NSString *message = [channelEvent.data objectForKey:@"text"];
NSLog(@"message received: %@", message);
}];
[self.client connect];
支持的平台
部署目标
- iOS 6.0 以上
- macOS (OS X) 10.9 及以上版本
安装
推荐使用 CocoaPods 进行安装。
pod 'libPusher', '~> 1.6.4'
将 Pusher 库导入您想使用此库的类中。
#import <Pusher/Pusher.h>
如果您想使用 libPusher 的 ReactiveExtensions 版本,请将以下行添加到您的 Podfile 中。
pod 'libPusher/ReactiveExtensions', '~> 1.6.4'
这也会将核心 libPusher 库和 ReactiveCocoa 作为依赖加载。
如果您不使用 CocoaPods,您可以直接将扩展添加到项目中。
用法
注意:在以下示例中,client
是一个强引用属性。根据标准的 Objective-C 返回约定,由 pusherWithKey:*:
方法返回的实例将被自动释放。如果不想这样,您必须保留该客户端,否则它将在任何有用的操作发生之前被自动释放,从而产生静默失败和意外的行为。
创建客户端并与服务器连接
self.client = [PTPusher pusherWithKey:@"YOUR_API_KEY" delegate:self encrypted:YES];
[self.client connect];
注意,客户端不会自动连接(从版本 1.5 开始)。您有责任根据需要调用 connect 方法。
建议实现 PTPusherDelegate 协议,以便在发生重要连接事件时得到通知,如连接错误、断开连接和重试。
当 Pusher 应用程序在不同的集群中创建并在默认集群之外时,您必须指定 cluster
参数。
self.client = [PTPusher pusehrWithKey:@"YOUR_API_KEY" delegate:self encrypt:YES cluster:@"YOUR_APP_CLUSTER"]
[self.client connect];
订阅频道
频道是一种筛选您希望应用程序接收的事件的方式。此外,私有和存在频道还可以用于控制对事件的访问,在存在频道的情况下,查看还有谁在订阅事件。有关频道的更多信息,请参阅文档。
在订阅频道之前不需要等待客户端建立连接。您可以直接订阅频道,并且一旦连接建立,任何订阅都会被创建。
订阅公共频道
PTPusherChannel *channel = [self.client subscribeToChannelNamed:@"chat"];
订阅私有频道
该方法会将适当的 private-
前缀添加到频道名称中,并返回正确转换为 PTPusherChannel 子类 PTPusherPrivateChannel 的频道。
订阅私有频道需要服务器端授权。有关详细信息,请参阅频道授权部分。
// subscribe to private-chat channel
PTPusherPrivateChannel *private = [self.client subscribeToPrivateChannelNamed:@"chat"];
订阅存在频道
该方法会为您将适当的 presence-
前缀添加到频道名称,并返回转换为正确的 PTPusherChannel 子类 PTPusherPresenceChannel 的频道。
订阅存在频道需要服务器端授权。有关详细信息,请参阅频道授权部分。
// subscribe to presence-chat channel
PTPusherPresenceChannel *presence = [client subscribeToPresenceChannelNamed:@"chat" delegate:self];
建议实现 PTPusherPresenceChannelDelegate
协议,以接收成员订阅或退订存在频道的通知。
访问已订阅频道
您可以使用 channelNamed:
方法检索现有的已订阅频道。如果您未订阅请求的频道,它将返回 nil
。
// get the 'chat' channel that you've already subscribed to
PTPusherChannel *channel = [self.client channelNamed:@"chat"];
从频道中退订
如果您不再希望接收指定频道的活动,您可以从该频道中退订。
PTPusherChannel *channel = [self.client channelNamed:@"chat"];
[channel unsubscribe];
通道对象生命周期
当客户端断开连接时,所有已订阅的通道会自动取消订阅(isSubscribed
将返回 NO
),但通道对象将保持存在,因此任何事件绑定也将保持存在。
当客户端重新连接时,所有之前已订阅的通道将被重新订阅(这可能涉及对任何私有/存在通道的另一个身份验证请求)并且你的现有事件绑定将继续按断开连接之前的方式工作。
如果你显式取消订阅一个通道,所有事件绑定将被移除,并且客户端将从订阅通道列表中删除通道对象。如果没有其他代码对通道对象有强引用,它将被销毁。如果你重新订阅该通道,将创建一个新的通道对象。如果你在应用程序代码中维护对通道对象的任何强引用,请注意这一点。
通道授权
私有和存在通道在连接之前需要服务器端授权。
注意:确保您的服务器能够正确响应身份验证请求。有关如何在服务器端实现授权的详细信息和示例,请参见身份验证签名和用户身份验证文档。
为了连接到私有或存在通道,您首先需要配置服务器授权 URL。
self.client.authorizationURL = [NSURL URLWithString:@"https://www.yourserver.com/authorize"];
当您尝试连接到私有或存在通道时,libPusher 将向上述 URL 发送表单编码的 POST 请求,并将 socket_id
和 channel_name
作为参数一同发送。在发送请求之前,Pusher 代理将被通知,并将通道和将要发送的 NSMutableURLRequest 实例传递进去。
自定义授权
为了执行自定义授权,您需要分配 channelAuthorizationDelegate
并实现 PTPusherChannelAuthorizationDelegate
协议,该协议目前包含一个方法。以下示例使用 AFNetworking
库执行基于服务器的授权
- (BOOL)applicationDidFinishLaunching:(UIApplication *)application
{
...
self.pusherClient.channelAuthorizationDelegate = self;
}
- (void)pusherChannel:(PTPusherChannel *)channel requiresAuthorizationForSocketID:(NSString *)socketID completionHandler:(void(^)(BOOL isAuthorized, NSDictionary *authData, NSError *error))completionHandler
{
NSDictionary *authParameters = :@{@"socket_id": socketID, @"channel_name": channel.name};
[[MyHTTPClient sharedClient] postPath:@"/pusher/auth" parameters:authParameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
completionHandler(YES, responseObject, nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
completionHandler(NO, nil, error);
}];
}
事件绑定
绑定事件通常有两种方式:直接在PTPusher客户端本身绑定事件或在特定通道上绑定。
支持两种直接绑定类型:目标和动作绑定以及基于块的绑定。以下示例使用了基于块的绑定。
绑定到通道
一旦创建了PTPusherChannel的实例,就可以设置事件绑定。不需要等待PTPusher客户端连接建立或订阅通道。
仅当您在单个通道上绑定到事件时,如果事件是通过此通道发送的,您才会接收到具有该名称的事件。
PTPusherChannel *channel = [self.client subscribeToChannelNamed:@"chat"];
[channel bindToEventNamed:@"new-message" handleWithBlock:^(PTPusherEvent *channelEvent) {
// channelEvent.data is a NSDictionary of the JSON object received
}];
直接绑定到客户端
当您在客户端上绑定事件时,无论事件来自哪个通道,您都将接收到具有该名称的所有事件。
[self.client bindToEventNamed:@"new-message" handleWithBlock:^(PTPusherEvent *event) {
// event.data is a NSDictionary of the JSON object received
}];
移除绑定
如果您不再想接收具有特定名称的事件,您可以移除绑定。移除绑定就像存储绑定对象的引用,然后稍后在调用 removeBinding:
时将其作为参数传递一样简单。
注意: 绑定对象属于相关联的客户端或通道,并将在绑定的整个生命周期中存在。因此,通常只需存储绑定对象的弱引用,以便移除绑定。如果其他事情移除了绑定(例如,调用 removeAllBindings
或明确退订通道的结果),弱引用将确保绑定对象变为nil,因此在调用 removeBinding:
之前应进行检查。
// _binding is a weak reference
_binding = [self.client bindToEventNamed:@"new-message" target:self action:@selector(handleEvent:)];
// check that nothing else has removed the binding already
if (_binding) {
[self.client removeBinding:_binding];
}
基于块的绑定的内存管理注意事项
与使用基于块的 NSNotificationCenter
观察者类似,适用于基于块的绑定,即在某些情况下,在事件处理器的块中引用 self
有可能在某些情况下创建保留周期或阻止 self
被释放。
当你在你的事件处理器块中引用 self
时,该块将保持对 self
的强引用。这意味着 self
将永远不会被释放,直到绑定(以及相应的处理器块)通过移除绑定而被销毁。因此,你应该小心不要在 dealloc
方法中移除事件绑定,因为如果绑定引用了 self
,则 dealloc
方法永远不会被调用。
例如,你可能在 UINavigationController
栈上推送一个 UIViewController
,然后在那个视图控制器的 viewDidAppear:
方法中创建事件绑定。
- (void)viewDidAppear:(BOOL)animated
{
// _binding is a weak instance variable
_binding = [self.client bindToEventNamed:@"new-message" handleWithBlock:^(PTPusherEvent *event) {
[self doSomethingWithEvent:event];
}];
}
如果你在未移除事件绑定的情况下将从导航栈中弹出的视图控制器,由于绑定块对 self
有强引用,视图控制器将永远不会被释放,你将产生内存泄漏。
你可以用两种方式处理这种情况。第一种是确保在相应的 viewDidDisappear:
中移除绑定。
- (void)viewDidDisappear:(BOOL)animated
{
[self.client removeBinding:_binding];
}
第二种是不让对方一开始就捕捉到对 self
的强引用。
- (void)viewDidAppear:(BOOL)animated
{
__weak typeof(self) weakSelf = self;
// _binding is a weak instance variable
_binding = [self.client bindToEventNamed:@"new-message" handleWithBlock:^(PTPusherEvent *event) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf doSomethingWithEvent:event];
}];
}
最后,如果你在块中引用了 self
并存储了对绑定对象的强引用,你将创建一个保留周期(self
-> binding
-> block
-> self
)。你应该避免保持对绑定对象的强引用,但如果真的需要,你应该确保在块中只捕捉对 self
的弱引用,正如上面的例子。
绑定到所有事件
在某些情况下,绑定到客户端或通道的所有事件可能是有用的。libPusher将为收到的每个事件发布一个NSNotification
。你可以通过添加通知观察者来订阅客户端或通道的所有事件。
使用 NSNotificationCenter 绑定到所有事件
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(didReceiveEventNotification:)
name:PTPusherEventReceivedNotification
object:self.client];
在单个通道上绑定到所有事件
// get chat channel
PTPusherChannel *channel = [self.client channelNamed:@"chat"];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(didReceiveChannelEventNotification:)
name:PTPusherEventReceivedNotification
object:channel];
你可以在回调中从通知的用户信息字典中检索事件。通知的对象将是产生事件的客户端或通道。
- (void)didReceiveEventNotification:(NSNotification *)notification
{
PTPusherEvent *event = [notification.userInfo objectForKey:PTPusherEventUserInfoKey];
}
处理网络连接错误和断开连接
移动设备的本质是连接会产生和消失。你可以做很多事情来确保你的Pusher连接在网络连接持续期间保持活跃,并在网络连接重新建立后重新连接。
自动重新连接行为
libPusher通常会尝试在大多数情况下保持你的连接。
- 如果失败的连接之前已连接,客户端将尝试立即重新连接。
- 如果连接由于推送器错误代码(范围在4200-4299)而断开,客户端将立即尝试重新连接。
- 如果连接由于推送器错误代码(范围在4100-4199)而断开,客户端将尝试以线性退避延迟重新连接。
- 如果连接由于未知原因断开,客户端将在配置的延迟后(默认为5秒,可以使用
reconnectDelay
属性修改)尝试重新连接。
在放弃之前,所有自动重连尝试将重复进行,直到达到最大限制。
以下情况下不会发生自动重连:
- 连接首次尝试失败(即之前未连接过)
- 连接由于推送器错误代码(范围在4000-4099)而断开(表示客户端错误,通常是配置错误)
- 达到自动重连尝试的最大次数
范围在4000-4099的错误代码通常表示客户端配置错误(例如无效的API密钥)或速率限制。有关更多信息,请参阅推送到器协议文档。
其他场景通常表示目前无法连接到推送器服务 - 这可能是由于服务问题,但更有可能是因为没有互联网连接。
在版本1.6之前,即使在明确调用disconnect
之后,也会在配置的reconnectDelay
之后发生自动重连。这种行为是不理想的,并且在后续的所有版本中,这种情况都不会再发生,并且需要显式调用connect
才能在此情况下重新连接。
处理断开连接
如果客户端完全无法连接,则会调用代理方法pusher:connection:failedWithError:
,并且不会尝试自动重连。
如果客户端断开,则会调用代理方法pusher:connection:didDisconnectWithError:willAttemptReconnect:
。如果willAttemptReconnect
是YES
,则不需要执行任何其他操作。
如果willAttemptReconnect
是NO
,应首先检查错误以确定是否存在客户端配置错误。如果客户端由于推送器错误代码而拒绝自动重新连接,则NSError
将具有PTPusherFatalErrorDomain
的域。
您如何处理断开连接取决于您,但一般建议是检查是否存在网络连接,并且如果没有连接,则在重新连接之前等待。
建议让操作系统处理连接管理。从实际角度来看,这意味着如果您想确保客户端在应用处于后台时不接收任何消息,那么您应该对相关的频道(s)调用unsubscribe
而不是调用disconnect
。最终,连接将由操作系统清理(因此关闭)。在应用再次变为前景时尝试明确调用disconnect
可能会导致意外的行为,此时将打开多个连接。
示例:使用Reachability库处理断开连接
在这个示例中,我们首先检查任何致命的Pusher错误,然后使用Reachability等待网络连接可用,然后再手动重新连接。
- (void)pusher:(PTPusher *)pusher connection:(PTPusherConnection *)connection failedWithError:(NSError *)error
{
[self handleDisconnectionWithError:error];
}
- (void)pusher:(PTPusher *)pusher connection:(PTPusherConnection *)connection didDisconnectWithError:(NSError *)error willAttemptReconnect:(BOOL)willAttemptReconnect
{
if (!willAttemptReconnect) {
[self handleDisconnectionWithError:error];
}
}
handleDisconnectionWithError
的实现执行错误检查并等待Reachability变化
- (void)handleDisconnectionWithError:(NSError *)error
{
Reachability *reachability = [Reachability reachabilityWithHostname:self.client.connection.URL.host];
if (error && [error.domain isEqualToString:PTPusherFatalErrorDomain]) {
NSLog(@"FATAL PUSHER ERROR, COULD NOT CONNECT! %@", error);
}
else {
if ([reachability isReachable]) {
// we do have reachability so let's wait for a set delay before trying again
[self.client performSelector:@selector(connect) withObject:nil afterDelay:5];
}
else {
// we need to wait for reachability to change
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_reachabilityChanged:)
name:kReachabilityChangedNotification
object:reachability];
[reachability startNotifier];
}
}
}
- (void)_reachabilityChanged:(NSNotification *note)
{
Reachability *reachability = [note object];
if ([reachability isReachable]) {
// we're reachable, we can try and reconnect, otherwise keep waiting
[self.client connect];
// stop watching for reachability changes
[reachability stopNotifier];
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:kReachabilityChangedNotification
object:reachability];
}
}
为了更复杂地处理客户端断开连接,并了解如何将其与应用程序集成,您可以查看ClientDisconnectionHandler
类,该类位于官方Pusher iOS Diagnostics应用程序中。
许可协议
所有代码均根据MIT许可证授权。有关更多详细信息,请参阅LICENSE文件。