SocketIOClient.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. //
  2. // SocketIOClient.swift
  3. // Socket.IO-Client-Swift
  4. //
  5. // Created by Erik Little on 11/23/14.
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. import Foundation
  25. public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable {
  26. public let socketURL: NSURL
  27. public private(set) var engine: SocketEngineSpec?
  28. public private(set) var status = SocketIOClientStatus.NotConnected {
  29. didSet {
  30. switch status {
  31. case .Connected:
  32. reconnecting = false
  33. currentReconnectAttempt = 0
  34. default:
  35. break
  36. }
  37. }
  38. }
  39. public var forceNew = false
  40. public var nsp = "/"
  41. public var options: Set<SocketIOClientOption>
  42. public var reconnects = true
  43. public var reconnectWait = 10
  44. public var sid: String? {
  45. return nsp + "#" + (engine?.sid ?? "")
  46. }
  47. private let emitQueue = dispatch_queue_create("com.socketio.emitQueue", DISPATCH_QUEUE_SERIAL)
  48. private let logType = "SocketIOClient"
  49. private let parseQueue = dispatch_queue_create("com.socketio.parseQueue", DISPATCH_QUEUE_SERIAL)
  50. private var anyHandler: ((SocketAnyEvent) -> Void)?
  51. private var currentReconnectAttempt = 0
  52. private var handlers = [SocketEventHandler]()
  53. private var ackHandlers = SocketAckManager()
  54. private var reconnecting = false
  55. private(set) var currentAck = -1
  56. private(set) var handleQueue = dispatch_get_main_queue()
  57. private(set) var reconnectAttempts = -1
  58. var waitingPackets = [SocketPacket]()
  59. /// Type safe way to create a new SocketIOClient. opts can be omitted
  60. public init(socketURL: NSURL, options: Set<SocketIOClientOption> = []) {
  61. self.options = options
  62. self.socketURL = socketURL
  63. if socketURL.absoluteString.hasPrefix("https://") {
  64. self.options.insertIgnore(.Secure(true))
  65. }
  66. for option in options {
  67. switch option {
  68. case let .Reconnects(reconnects):
  69. self.reconnects = reconnects
  70. case let .ReconnectAttempts(attempts):
  71. reconnectAttempts = attempts
  72. case let .ReconnectWait(wait):
  73. reconnectWait = abs(wait)
  74. case let .Nsp(nsp):
  75. self.nsp = nsp
  76. case let .Log(log):
  77. DefaultSocketLogger.Logger.log = log
  78. case let .Logger(logger):
  79. DefaultSocketLogger.Logger = logger
  80. case let .HandleQueue(queue):
  81. handleQueue = queue
  82. case let .ForceNew(force):
  83. forceNew = force
  84. default:
  85. continue
  86. }
  87. }
  88. self.options.insertIgnore(.Path("/socket.io/"))
  89. super.init()
  90. }
  91. /// Not so type safe way to create a SocketIOClient, meant for Objective-C compatiblity.
  92. /// If using Swift it's recommended to use `init(socketURL: NSURL, options: Set<SocketIOClientOption>)`
  93. public convenience init(socketURL: NSURL, options: NSDictionary?) {
  94. self.init(socketURL: socketURL, options: options?.toSocketOptionsSet() ?? [])
  95. }
  96. deinit {
  97. DefaultSocketLogger.Logger.log("Client is being released", type: logType)
  98. engine?.disconnect("Client Deinit")
  99. }
  100. private func addEngine() -> SocketEngineSpec {
  101. DefaultSocketLogger.Logger.log("Adding engine", type: logType)
  102. engine = SocketEngine(client: self, url: socketURL, options: options)
  103. return engine!
  104. }
  105. /// Connect to the server.
  106. public func connect() {
  107. connect(timeoutAfter: 0, withTimeoutHandler: nil)
  108. }
  109. /// Connect to the server. If we aren't connected after timeoutAfter, call handler
  110. public func connect(timeoutAfter timeoutAfter: Int, withTimeoutHandler handler: (() -> Void)?) {
  111. assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)")
  112. guard status != .Connected else {
  113. DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket", type: logType)
  114. return
  115. }
  116. status = .Connecting
  117. if engine == nil || forceNew {
  118. addEngine().connect()
  119. } else {
  120. engine?.connect()
  121. }
  122. guard timeoutAfter != 0 else { return }
  123. let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC))
  124. dispatch_after(time, handleQueue) {[weak self] in
  125. if let this = self where this.status != .Connected && this.status != .Disconnected {
  126. this.status = .Disconnected
  127. this.engine?.disconnect("Connect timeout")
  128. handler?()
  129. }
  130. }
  131. }
  132. private func createOnAck(items: [AnyObject]) -> OnAckCallback {
  133. currentAck += 1
  134. return {[weak self, ack = currentAck] timeout, callback in
  135. if let this = self {
  136. this.ackHandlers.addAck(ack, callback: callback)
  137. this._emit(items, ack: ack)
  138. if timeout != 0 {
  139. let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * NSEC_PER_SEC))
  140. dispatch_after(time, this.handleQueue) {
  141. this.ackHandlers.timeoutAck(ack)
  142. }
  143. }
  144. }
  145. }
  146. }
  147. func didConnect() {
  148. DefaultSocketLogger.Logger.log("Socket connected", type: logType)
  149. status = .Connected
  150. // Don't handle as internal because something crazy could happen where
  151. // we disconnect before it's handled
  152. handleEvent("connect", data: [], isInternalMessage: false)
  153. }
  154. func didDisconnect(reason: String) {
  155. guard status != .Disconnected else { return }
  156. DefaultSocketLogger.Logger.log("Disconnected: %@", type: logType, args: reason)
  157. status = .Disconnected
  158. // Make sure the engine is actually dead.
  159. engine?.disconnect(reason)
  160. handleEvent("disconnect", data: [reason], isInternalMessage: true)
  161. }
  162. /// Disconnects the socket. Only reconnect the same socket if you know what you're doing.
  163. /// Will turn off automatic reconnects.
  164. public func disconnect() {
  165. assert(status != .NotConnected, "Tried closing a NotConnected client")
  166. DefaultSocketLogger.Logger.log("Closing socket", type: logType)
  167. didDisconnect("Disconnect")
  168. }
  169. /// Send a message to the server
  170. public func emit(event: String, _ items: AnyObject...) {
  171. emit(event, withItems: items)
  172. }
  173. /// Same as emit, but meant for Objective-C
  174. public func emit(event: String, withItems items: [AnyObject]) {
  175. guard status == .Connected else {
  176. handleEvent("error", data: ["Tried emitting \(event) when not connected"], isInternalMessage: true)
  177. return
  178. }
  179. _emit([event] + items)
  180. }
  181. /// Sends a message to the server, requesting an ack. Use the onAck method of SocketAckHandler to add
  182. /// an ack.
  183. public func emitWithAck(event: String, _ items: AnyObject...) -> OnAckCallback {
  184. return emitWithAck(event, withItems: items)
  185. }
  186. /// Same as emitWithAck, but for Objective-C
  187. public func emitWithAck(event: String, withItems items: [AnyObject]) -> OnAckCallback {
  188. return createOnAck([event] + items)
  189. }
  190. private func _emit(data: [AnyObject], ack: Int? = nil) {
  191. dispatch_async(emitQueue) {
  192. guard self.status == .Connected else {
  193. self.handleEvent("error", data: ["Tried emitting when not connected"], isInternalMessage: true)
  194. return
  195. }
  196. let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: self.nsp, ack: false)
  197. let str = packet.packetString
  198. DefaultSocketLogger.Logger.log("Emitting: %@", type: self.logType, args: str)
  199. self.engine?.send(str, withData: packet.binary)
  200. }
  201. }
  202. // If the server wants to know that the client received data
  203. func emitAck(ack: Int, withItems items: [AnyObject]) {
  204. dispatch_async(emitQueue) {
  205. if self.status == .Connected {
  206. let packet = SocketPacket.packetFromEmit(items, id: ack ?? -1, nsp: self.nsp, ack: true)
  207. let str = packet.packetString
  208. DefaultSocketLogger.Logger.log("Emitting Ack: %@", type: self.logType, args: str)
  209. self.engine?.send(str, withData: packet.binary)
  210. }
  211. }
  212. }
  213. public func engineDidClose(reason: String) {
  214. waitingPackets.removeAll()
  215. if status != .Disconnected {
  216. status = .NotConnected
  217. }
  218. if status == .Disconnected || !reconnects {
  219. didDisconnect(reason)
  220. } else if !reconnecting {
  221. reconnecting = true
  222. tryReconnectWithReason(reason)
  223. }
  224. }
  225. /// error
  226. public func engineDidError(reason: String) {
  227. DefaultSocketLogger.Logger.error("%@", type: logType, args: reason)
  228. handleEvent("error", data: [reason], isInternalMessage: true)
  229. }
  230. public func engineDidOpen(reason: String) {
  231. DefaultSocketLogger.Logger.log(reason, type: "SocketEngineClient")
  232. }
  233. // Called when the socket gets an ack for something it sent
  234. func handleAck(ack: Int, data: [AnyObject]) {
  235. guard status == .Connected else { return }
  236. DefaultSocketLogger.Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data ?? "")
  237. ackHandlers.executeAck(ack, items: data)
  238. }
  239. /// Causes an event to be handled. Only use if you know what you're doing.
  240. public func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, withAck ack: Int = -1) {
  241. guard status == .Connected || isInternalMessage else { return }
  242. DefaultSocketLogger.Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data ?? "")
  243. dispatch_async(handleQueue) {
  244. self.anyHandler?(SocketAnyEvent(event: event, items: data))
  245. for handler in self.handlers where handler.event == event {
  246. handler.executeCallback(data, withAck: ack, withSocket: self)
  247. }
  248. }
  249. }
  250. /// Leaves nsp and goes back to /
  251. public func leaveNamespace() {
  252. if nsp != "/" {
  253. engine?.send("1\(nsp)", withData: [])
  254. nsp = "/"
  255. }
  256. }
  257. /// Joins namespace
  258. public func joinNamespace(namespace: String) {
  259. nsp = namespace
  260. if nsp != "/" {
  261. DefaultSocketLogger.Logger.log("Joining namespace", type: logType)
  262. engine?.send("0\(nsp)", withData: [])
  263. }
  264. }
  265. /// Removes handler(s) based on name
  266. public func off(event: String) {
  267. DefaultSocketLogger.Logger.log("Removing handler for event: %@", type: logType, args: event)
  268. handlers = handlers.filter({ $0.event != event })
  269. }
  270. /// Removes a handler with the specified UUID gotten from an `on` or `once`
  271. public func off(id id: NSUUID) {
  272. DefaultSocketLogger.Logger.log("Removing handler with id: %@", type: logType, args: id)
  273. handlers = handlers.filter({ $0.id != id })
  274. }
  275. /// Adds a handler for an event.
  276. /// Returns: A unique id for the handler
  277. public func on(event: String, callback: NormalCallback) -> NSUUID {
  278. DefaultSocketLogger.Logger.log("Adding handler for event: %@", type: logType, args: event)
  279. let handler = SocketEventHandler(event: event, id: NSUUID(), callback: callback)
  280. handlers.append(handler)
  281. return handler.id
  282. }
  283. /// Adds a single-use handler for an event.
  284. /// Returns: A unique id for the handler
  285. public func once(event: String, callback: NormalCallback) -> NSUUID {
  286. DefaultSocketLogger.Logger.log("Adding once handler for event: %@", type: logType, args: event)
  287. let id = NSUUID()
  288. let handler = SocketEventHandler(event: event, id: id) {[weak self] data, ack in
  289. guard let this = self else { return }
  290. this.off(id: id)
  291. callback(data, ack)
  292. }
  293. handlers.append(handler)
  294. return handler.id
  295. }
  296. /// Adds a handler that will be called on every event.
  297. public func onAny(handler: (SocketAnyEvent) -> Void) {
  298. anyHandler = handler
  299. }
  300. public func parseEngineMessage(msg: String) {
  301. DefaultSocketLogger.Logger.log("Should parse message: %@", type: "SocketIOClient", args: msg)
  302. dispatch_async(parseQueue) {
  303. self.parseSocketMessage(msg)
  304. }
  305. }
  306. public func parseEngineBinaryData(data: NSData) {
  307. dispatch_async(parseQueue) {
  308. self.parseBinaryData(data)
  309. }
  310. }
  311. /// Tries to reconnect to the server.
  312. public func reconnect() {
  313. guard !reconnecting else { return }
  314. engine?.disconnect("manual reconnect")
  315. }
  316. /// Removes all handlers.
  317. /// Can be used after disconnecting to break any potential remaining retain cycles.
  318. public func removeAllHandlers() {
  319. handlers.removeAll(keepCapacity: false)
  320. }
  321. private func tryReconnectWithReason(reason: String) {
  322. if reconnecting {
  323. DefaultSocketLogger.Logger.log("Starting reconnect", type: logType)
  324. handleEvent("reconnect", data: [reason], isInternalMessage: true)
  325. _tryReconnect()
  326. }
  327. }
  328. private func _tryReconnect() {
  329. if !reconnecting {
  330. return
  331. }
  332. if reconnectAttempts != -1 && currentReconnectAttempt + 1 > reconnectAttempts || !reconnects {
  333. return didDisconnect("Reconnect Failed")
  334. }
  335. DefaultSocketLogger.Logger.log("Trying to reconnect", type: logType)
  336. handleEvent("reconnectAttempt", data: [reconnectAttempts - currentReconnectAttempt],
  337. isInternalMessage: true)
  338. currentReconnectAttempt += 1
  339. connect()
  340. let dispatchAfter = dispatch_time(DISPATCH_TIME_NOW, Int64(UInt64(reconnectWait) * NSEC_PER_SEC))
  341. dispatch_after(dispatchAfter, dispatch_get_main_queue(), _tryReconnect)
  342. }
  343. }
  344. // Test extensions
  345. extension SocketIOClient {
  346. var testHandlers: [SocketEventHandler] {
  347. return handlers
  348. }
  349. func setTestable() {
  350. status = .Connected
  351. }
  352. func setTestEngine(engine: SocketEngineSpec?) {
  353. self.engine = engine
  354. }
  355. func emitTest(event: String, _ data: AnyObject...) {
  356. self._emit([event] + data)
  357. }
  358. }