Skip to content

Commit 7e7c291

Browse files
committed
Add a more robust reconnection setup so that even if the Pusher servers
close the websocket connection then reconnection attempts will be made
1 parent 6a6cf72 commit 7e7c291

File tree

3 files changed

+49
-4
lines changed

3 files changed

+49
-4
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ What else would you want? Head over to the example app [ViewController.swift](ht
1717
* [Installation](#installation)
1818
* [Configuration](#configuration)
1919
* [Connection](#connection)
20+
* [Connection state changes](#connection-state-changes)
21+
* [Reconnection](#reconnection)
2022
* [Subscribing to channels](#subscribing)
2123
* [Binding to events](#binding-to-events)
2224
* [Globally](#global-events)
@@ -204,6 +206,23 @@ class ViewController: UIViewController, ConnectionStateChangeDelegate {
204206
}
205207
```
206208

209+
### Reconnection
210+
211+
There are three main ways in which a disconnection can occur:
212+
213+
* The client explicitly calls disconnect and a close frame is sent over the websocket connection
214+
* The client experiences some form of network degradation which leads to a heartbeat (ping/pong) message being missed and thus the client disconnects
215+
* The Pusher server closes the websocket connection; typically this will only occur during a restart of the Pusher socket servers and an almost immediate reconnection should occur
216+
217+
In the case of the first type of disconnection the library will (as you'd hope) not attempt a reconnection.
218+
219+
If there is network degradation that leads to a disconnection then the library has the [Reachability](https://github.com/ashleymills/Reachability.swift) library embedded and will be able to automatically determine when to attempt a reconnect based on the changing network conditions.
220+
221+
If the Pusher servers close the websocket then the library will attempt to reconnect (by default) a maximum of 6 times, with an exponential backoff. The value of `reconnectAttemptsMax` is a public property on the `PusherConnection` and so can be changed if you wish.
222+
223+
All of this is the case if you have the client option of `autoReconnect` set as `true`, which it is by default. If the reconnection strategies are not suitable for your use case then you can set `autoReconnect` to `false` and implement your own reconnection strategy based on the connection state changes.
224+
225+
207226
## Subscribing
208227

209228
### Public channels

Source/PusherConnection.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ public class PusherConnection {
2222
public var userDataFetcher: (() -> PusherUserData)?
2323
public var debugLogger: ((String) -> ())?
2424
public weak var stateChangeDelegate: ConnectionStateChangeDelegate?
25+
public var reconnectAttemptsMax: Int = 6
26+
public var reconnectAttempts: Int = 0
27+
private var reconnectTimer: NSTimer? = nil
2528
internal var reconnectOperation: NSOperation?
2629

2730
public lazy var reachability: Reachability? = {
@@ -185,6 +188,7 @@ public class PusherConnection {
185188
updateConnectionState(.Connecting)
186189
self.socket.connect()
187190
if self.options.autoReconnect {
191+
// can call this multiple times and only one notifier will be started
188192
_ = try? reachability?.startNotifier()
189193
}
190194
}
@@ -278,6 +282,11 @@ public class PusherConnection {
278282
updateConnectionState(.Connected)
279283
self.socketId = socketId
280284

285+
// cancel any other outstanding reconnect attempts
286+
self.reconnectOperation?.cancel()
287+
self.reconnectAttempts = 0
288+
self.reconnectTimer?.invalidate()
289+
281290
for (_, channel) in self.channels.channels {
282291
if !channel.subscribed {
283292
if !self.authorize(channel) {
@@ -356,7 +365,6 @@ public class PusherConnection {
356365
if let jsonData = data, jsonObject = try NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as? [String : AnyObject] {
357366
return jsonObject
358367
} else {
359-
// TODO: Move below
360368
print("Unable to parse string from WebSocket: \(string)")
361369
}
362370
} catch let error as NSError {
@@ -656,6 +664,24 @@ public class PusherConnection {
656664
)
657665
}
658666
}
667+
668+
/**
669+
Attempt to reconnect triggered by a disconnection
670+
*/
671+
@objc internal func attemptReconnect() {
672+
if reconnectAttempts < reconnectAttemptsMax && connectionState != .Connected {
673+
connect()
674+
reconnectAttempts += 1
675+
let timeInterval = Double(reconnectAttempts * reconnectAttempts) * 2.0
676+
reconnectTimer = NSTimer.scheduledTimerWithTimeInterval(
677+
timeInterval,
678+
target: self,
679+
selector: #selector(attemptReconnect),
680+
userInfo: nil,
681+
repeats: false
682+
)
683+
}
684+
}
659685
}
660686

661687
public enum ConnectionState {

Source/PusherWebsocketDelegate.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
//
88

99
extension PusherConnection: WebSocketDelegate {
10-
// MARK: WebSocketDelegate Implementation
1110

1211
/**
1312
Delegate method called when a message is received over a websocket
@@ -31,7 +30,6 @@ extension PusherConnection: WebSocketDelegate {
3130
- parameter error: The error, if one exists, when disconnected
3231
*/
3332
public func websocketDidDisconnect(ws: WebSocket, error: NSError?) {
34-
3533
updateConnectionState(.Disconnected)
3634
for (_, channel) in self.channels.channels {
3735
channel.subscribed = false
@@ -45,7 +43,7 @@ extension PusherConnection: WebSocketDelegate {
4543
print("Websocket is disconnected. Error: \(error.localizedDescription)")
4644

4745
// Reconnect if possible
48-
if self.options.autoReconnect {
46+
if self.options.autoReconnect && reconnectAttempts == 0 {
4947
if let reachability = self.reachability where reachability.isReachable() {
5048
let operation = NSBlockOperation {
5149
self.socket.connect()
@@ -58,6 +56,8 @@ extension PusherConnection: WebSocketDelegate {
5856
self.reconnectOperation?.cancel()
5957
self.reconnectOperation = operation
6058
}
59+
60+
attemptReconnect()
6161
}
6262
}
6363

0 commit comments

Comments
 (0)