libPusher 1.6.4

libPusher 1.6.4

测试已测试
Lang语言 Obj-CObjective C
许可证 NOASSERTION
发布上次发布2020年1月

Luke RedpathPusher 维护。



libPusher 1.6.4

  • Luke Redpath

Pusher Channels Objective-C 客户端

Build Status

libPusher 是 Pusher Channels 实时服务的一个 Objective-C 客户端库。

Pusher Channels 是一个托管服务,位于您的 web 应用程序和浏览器之间,使得您可以使用 WebSockets 实时地传递事件。

libPusher API 尽可能地模仿了 Pusher Channels JavaScript 客户端,同时考虑到了 Objective-C 的约定。特别是,尽管 JavaScript 客户端使用事件绑定来处理所有事件,而事件是预定义的,但 libPusher 使用标准的 Cocoa 委派模式。

API 文档

实例

订阅 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_idchannel_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:。如果willAttemptReconnectYES,则不需要执行任何其他操作。

如果willAttemptReconnectNO,应首先检查错误以确定是否存在客户端配置错误。如果客户端由于推送器错误代码而拒绝自动重新连接,则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文件。