diff --git a/.travis.yml b/.travis.yml index 6afadee..ef88eb6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,13 @@ language: objective-c xcode_project: Socket.IO-Client-Swift.xcodeproj # path to your xcodeproj folder xcode_scheme: SocketIO-iOS -osx_image: xcode7.3 +osx_image: xcode8 branches: only: - master - development -script: xctool -project Socket.IO-Client-Swift.xcodeproj -scheme SocketIO-Mac build test -parallelize +before_install: + - brew update + - brew outdated xctool || brew upgrade xctool +# script: xctool -project Socket.IO-Client-Swift.xcodeproj -scheme SocketIO-Mac build test -parallelize +script: xcodebuild -project Socket.IO-Client-Swift.xcodeproj -scheme SocketIO-Mac build test diff --git a/Package.swift b/Package.swift index f7d8304..04e8fbb 100644 --- a/Package.swift +++ b/Package.swift @@ -1,5 +1,5 @@ import PackageDescription let package = Package( - name: "SocketIOClientSwift" + name: "SocketIO" ) diff --git a/README.md b/README.md index c58abb4..95a6b8d 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,7 @@ Socket.IO-client for iOS/OS X. ##Example ```swift import SocketIO - -let socket = SocketIOClient(socketURL: NSURL(string: "http://localhost:8080")!, config: [.Log(true), .ForcePolling(true)]) +let socket = SocketIOClient(socketURL: NSURL(string: "http://localhost:8080")!, config: [.log(true), .forcePolling(true)]) socket.on("connect") {data, ack in print("socket connected") @@ -58,13 +57,15 @@ SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:url config:@{ - Can be used from Objective-C ##Installation -Requires Swift 2.2/Xcode 7.3 +Requires Swift 3/Xcode 8.x -If you need Swift 2.1/Xcode 7.2 use v5.5.0 (Pre-Swift 2.2 support is no longer maintained) +If you need swift 2.2 use 7.x (Pre-Swift 3 support is no longer maintained) -If you need Swift 1.2/Xcode 6.3/4 use v2.4.5 (Pre-Swift 2 support is no longer maintained) +If you need Swift 2.1 use v5.5.0 (Pre-Swift 2.2 support is no longer maintained) -If you need Swift 1.1/Xcode 6.2 use v1.5.2. (Pre-Swift 1.2 support is no longer maintained) +If you need Swift 1.2 use v2.4.5 (Pre-Swift 2 support is no longer maintained) + +If you need Swift 1.1 use v1.5.2. (Pre-Swift 1.2 support is no longer maintained) Manually (iOS 7+) ----------------- @@ -151,43 +152,43 @@ Options All options are a case of SocketIOClientOption. To get the Objective-C Option, convert the name to lowerCamelCase. ```swift -case ConnectParams([String: AnyObject]) // Dictionary whose contents will be passed with the connection. -case Cookies([NSHTTPCookie]) // An array of NSHTTPCookies. Passed during the handshake. Default is nil. -case DoubleEncodeUTF8(Bool) // Whether or not to double encode utf8. If using the node based server this should be true. Default is true. -case ExtraHeaders([String: String]) // Adds custom headers to the initial request. Default is nil. -case ForcePolling(Bool) // `true` forces the client to use xhr-polling. Default is `false` -case ForceNew(Bool) // Will a create a new engine for each connect. Useful if you find a bug in the engine related to reconnects -case ForceWebsockets(Bool) // `true` forces the client to use WebSockets. Default is `false` -case HandleQueue(dispatch_queue_t) // The dispatch queue that handlers are run on. Default is the main queue. -case Log(Bool) // If `true` socket will log debug messages. Default is false. -case Logger(SocketLogger) // Custom logger that conforms to SocketLogger. Will use the default logging otherwise. -case Nsp(String) // The namespace to connect to. Must begin with /. Default is `/` -case Path(String) // If the server uses a custom path. ex: `"/swift/"`. Default is `""` -case Reconnects(Bool) // Whether to reconnect on server lose. Default is `true` -case ReconnectAttempts(Int) // How many times to reconnect. Default is `-1` (infinite tries) -case ReconnectWait(Int) // Amount of time to wait between reconnects. Default is `10` -case SessionDelegate(NSURLSessionDelegate) // Sets an NSURLSessionDelegate for the underlying engine. Useful if you need to handle self-signed certs. Default is nil. -case Secure(Bool) // If the connection should use TLS. Default is false. -case Security(SSLSecurity) // Allows you to set which certs are valid. Useful for SSL pinning. -case SelfSigned(Bool) // Sets WebSocket.selfSignedSSL. Use this if you're using self-signed certs. -case VoipEnabled(Bool) // Only use this option if you're using the client with VoIP services. Changes the way the WebSocket is created. Default is false +case connectParams([String: AnyObject]) // Dictionary whose contents will be passed with the connection. +case cookies([NSHTTPCookie]) // An array of NSHTTPCookies. Passed during the handshake. Default is nil. +case doubleEncodeUTF8(Bool) // Whether or not to double encode utf8. If using the node based server this should be true. Default is true. +case extraHeaders([String: String]) // Adds custom headers to the initial request. Default is nil. +case forcePolling(Bool) // `true` forces the client to use xhr-polling. Default is `false` +case forceNew(Bool) // Will a create a new engine for each connect. Useful if you find a bug in the engine related to reconnects +case forceWebsockets(Bool) // `true` forces the client to use WebSockets. Default is `false` +case handleQueue(dispatch_queue_t) // The dispatch queue that handlers are run on. Default is the main queue. +case log(Bool) // If `true` socket will log debug messages. Default is false. +case logger(SocketLogger) // Custom logger that conforms to SocketLogger. Will use the default logging otherwise. +case nsp(String) // The namespace to connect to. Must begin with /. Default is `/` +case path(String) // If the server uses a custom path. ex: `"/swift/"`. Default is `""` +case reconnects(Bool) // Whether to reconnect on server lose. Default is `true` +case reconnectAttempts(Int) // How many times to reconnect. Default is `-1` (infinite tries) +case reconnectWait(Int) // Amount of time to wait between reconnects. Default is `10` +case sessionDelegate(NSURLSessionDelegate) // Sets an NSURLSessionDelegate for the underlying engine. Useful if you need to handle self-signed certs. Default is nil. +case secure(Bool) // If the connection should use TLS. Default is false. +case security(SSLSecurity) // Allows you to set which certs are valid. Useful for SSL pinning. +case selfSigned(Bool) // Sets WebSocket.selfSignedSSL. Use this if you're using self-signed certs. +case voipEnabled(Bool) // Only use this option if you're using the client with VoIP services. Changes the way the WebSocket is created. Default is false ``` Methods ------- -1. `on(event: String, callback: NormalCallback) -> NSUUID` - Adds a handler for an event. Items are passed by an array. `ack` can be used to send an ack when one is requested. See example. Returns a unique id for the handler. -2. `once(event: String, callback: NormalCallback) -> NSUUID` - Adds a handler that will only be executed once. Returns a unique id for the handler. +1. `on(_ event: String, callback: NormalCallback) -> NSUUID` - Adds a handler for an event. Items are passed by an array. `ack` can be used to send an ack when one is requested. See example. Returns a unique id for the handler. +2. `once(_ event: String, callback: NormalCallback) -> NSUUID` - Adds a handler that will only be executed once. Returns a unique id for the handler. 3. `onAny(callback:((event: String, items: AnyObject?)) -> Void)` - Adds a handler for all events. It will be called on any received event. -4. `emit(event: String, _ items: AnyObject...)` - Sends a message. Can send multiple items. -5. `emit(event: String, withItems items: [AnyObject])` - `emit` for Objective-C -6. `emitWithAck(event: String, _ items: AnyObject...) -> (timeoutAfter: UInt64, callback: (NSArray?) -> Void) -> Void` - Sends a message that requests an acknowledgement from the server. Returns a function which you can use to add a handler. See example. Note: The message is not sent until you call the returned function. -7. `emitWithAck(event: String, withItems items: [AnyObject]) -> (UInt64, (NSArray?) -> Void) -> Void` - `emitWithAck` for Objective-C. Note: The message is not sent until you call the returned function. +4. `emit(_ event: String, _ items: AnyObject...)` - Sends a message. Can send multiple items. +5. `emit(_ event: String, withItems items: [AnyObject])` - `emit` for Objective-C +6. `emitWithAck(_ event: String, _ items: AnyObject...) -> (timeoutAfter: UInt64, callback: (NSArray?) -> Void) -> Void` - Sends a message that requests an acknowledgement from the server. Returns a function which you can use to add a handler. See example. Note: The message is not sent until you call the returned function. +7. `emitWithAck(_ event: String, withItems items: [AnyObject]) -> (UInt64, (NSArray?) -> Void) -> Void` - `emitWithAck` for Objective-C. Note: The message is not sent until you call the returned function. 8. `connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. 9. `connect(timeoutAfter timeoutAfter: Int, withTimeoutHandler handler: (() -> Void)?)` - Connect to the server. If it isn't connected after timeoutAfter seconds, the handler is called. 10. `disconnect()` - Closes the socket. Reopening a disconnected socket is not fully tested. 11. `reconnect()` - Causes the client to reconnect to the server. -12. `joinNamespace(namespace: String)` - Causes the client to join namespace. Shouldn't need to be called unless you change namespaces manually. +12. `joinNamespace(_ namespace: String)` - Causes the client to join namespace. Shouldn't need to be called unless you change namespaces manually. 13. `leaveNamespace()` - Causes the client to leave the nsp and go back to / -14. `off(event: String)` - Removes all event handlers for event. +14. `off(_ event: String)` - Removes all event handlers for event. 15. `off(id id: NSUUID)` - Removes the event that corresponds to id. 16. `removeAllHandlers()` - Removes all handlers. diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index 47c0b88..b7e4897 100644 --- a/Socket.IO-Client-Swift.podspec +++ b/Socket.IO-Client-Swift.podspec @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.license = { :type => 'MIT' } s.author = { "Erik" => "nuclear.ace@gmail.com" } s.ios.deployment_target = '8.0' - s.osx.deployment_target = '10.10' + s.osx.deployment_target = '10.9' s.tvos.deployment_target = '9.0' s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v7.0.3' } s.source_files = "Source/**/*.swift" diff --git a/Socket.IO-Client-Swift.xcodeproj/project.pbxproj b/Socket.IO-Client-Swift.xcodeproj/project.pbxproj index 8ad9641..de5755c 100644 --- a/Socket.IO-Client-Swift.xcodeproj/project.pbxproj +++ b/Socket.IO-Client-Swift.xcodeproj/project.pbxproj @@ -531,8 +531,9 @@ 572EF20E1B51F12F00EEBB58 /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftMigration = 0730; LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 0800; TargetAttributes = { 572EF2181B51F16C00EEBB58 = { CreatedOnToolsVersion = 6.4; @@ -542,9 +543,11 @@ }; 572EF2371B51F18A00EEBB58 = { CreatedOnToolsVersion = 6.4; + LastSwiftMigration = 0800; }; 572EF2411B51F18A00EEBB58 = { CreatedOnToolsVersion = 6.4; + LastSwiftMigration = 0800; }; }; }; @@ -771,15 +774,31 @@ isa = XCBuildConfiguration; buildSettings = { BITCODE_GENERATION_MODE = bitcode; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "Developer ID Application"; ENABLE_BITCODE = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = SocketIO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; TVOS_DEPLOYMENT_TARGET = 9.0; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; @@ -789,12 +808,28 @@ isa = XCBuildConfiguration; buildSettings = { BITCODE_GENERATION_MODE = bitcode; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "Developer ID Application"; ENABLE_BITCODE = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_NAME = SocketIO; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; TVOS_DEPLOYMENT_TARGET = 9.0; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; @@ -851,6 +886,7 @@ SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -901,6 +937,7 @@ PRODUCT_BUNDLE_IDENTIFIER = io.socket.SocketIOClientSwift; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -1057,6 +1094,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1108,6 +1146,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "io.socket.$(PRODUCT_NAME:rfc1034identifier)"; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1164,6 +1203,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -1210,6 +1250,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "io.socket.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/Socket.IO-Client-Swift.xcodeproj/xcshareddata/xcschemes/SocketIO-Mac.xcscheme b/Socket.IO-Client-Swift.xcodeproj/xcshareddata/xcschemes/SocketIO-Mac.xcscheme index 7fef4ce..ea5febe 100644 --- a/Socket.IO-Client-Swift.xcodeproj/xcshareddata/xcschemes/SocketIO-Mac.xcscheme +++ b/Socket.IO-Client-Swift.xcodeproj/xcshareddata/xcschemes/SocketIO-Mac.xcscheme @@ -1,6 +1,6 @@ (message.startIndex.. [SSLCert] in var certs = certs if let data = NSData(contentsOfFile: path) { - certs.append(SSLCert(data: data)) + certs.append(SSLCert(data: data as Data)) } return certs } @@ -93,10 +93,10 @@ public class SSLSecurity : NSObject { super.init() if self.usePublicKeys { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)) { - let pubKeys = certs.reduce([SecKeyRef]()) { (pubKeys: [SecKeyRef], cert: SSLCert) -> [SecKeyRef] in + DispatchQueue.global(qos: .default).async { + let pubKeys = certs.reduce([SecKey]()) { (pubKeys: [SecKey], cert: SSLCert) -> [SecKey] in var pubKeys = pubKeys - if let data = cert.certData where cert.key == nil { + if let data = cert.certData, cert.key == nil { cert.key = self.extractPublicKey(data) } if let key = cert.key { @@ -109,14 +109,14 @@ public class SSLSecurity : NSObject { self.isReady = true } } else { - let certificates = certs.reduce([NSData]()) { (certificates: [NSData], cert: SSLCert) -> [NSData] in + let certificates = certs.reduce([Data]()) { (certificates: [Data], cert: SSLCert) -> [Data] in var certificates = certificates if let data = cert.certData { certificates.append(data) } return certificates } - self.certificates = certificates + self.certificates = certificates as [NSData] self.isReady = true } } @@ -129,7 +129,7 @@ public class SSLSecurity : NSObject { - returns: if the key was successfully validated */ - public func isValid(trust: SecTrustRef, domain: String?) -> Bool { + public func isValid(_ trust: SecTrust, domain: String?) -> Bool { var tries = 0 while(!self.isReady) { @@ -139,19 +139,19 @@ public class SSLSecurity : NSObject { return false //doesn't appear it is going to ever be ready... } } - var policy: SecPolicyRef + var policy: SecPolicy if self.validatedDN { - policy = SecPolicyCreateSSL(true, domain) + policy = SecPolicyCreateSSL(true, domain as CFString?) } else { policy = SecPolicyCreateBasicX509() } SecTrustSetPolicies(trust,policy) if self.usePublicKeys { if let keys = self.pubKeys { - let serverPubKeys = publicKeyChainForTrust(trust) - for serverKey in serverPubKeys as [AnyObject] { - for key in keys as [AnyObject] { - if serverKey.isEqual(key) { + let serverPubKeys = publicKeyChainForTrust(trust: trust) + for serverKey in serverPubKeys { + for key in keys { + if CFEqual(serverKey, key) { return true } } @@ -163,15 +163,15 @@ public class SSLSecurity : NSObject { for cert in certs { collect.append(SecCertificateCreateWithData(nil,cert)!) } - SecTrustSetAnchorCertificates(trust,collect) - var result: SecTrustResultType = 0 + SecTrustSetAnchorCertificates(trust,collect as CFArray) + var result = SecTrustResultType(rawValue: 0)! SecTrustEvaluate(trust,&result) - let r = Int(result) - if r == kSecTrustResultUnspecified || r == kSecTrustResultProceed { + let r = Int(result.rawValue) + if r == Int(SecTrustResultType.unspecified.rawValue) || r == Int(SecTrustResultType.proceed.rawValue) { var trustedCount = 0 for serverCert in serverCerts { for cert in certs { - if cert == serverCert { + if cert as Data == serverCert { trustedCount += 1 break } @@ -192,8 +192,8 @@ public class SSLSecurity : NSObject { - returns: a public key */ - func extractPublicKey(data: NSData) -> SecKeyRef? { - guard let cert = SecCertificateCreateWithData(nil, data) else { return nil } + func extractPublicKey(_ data: Data) -> SecKey? { + guard let cert = SecCertificateCreateWithData(nil, data as CFData) else { return nil } return extractPublicKeyFromCert(cert, policy: SecPolicyCreateBasicX509()) } @@ -205,13 +205,13 @@ public class SSLSecurity : NSObject { - returns: a public key */ - func extractPublicKeyFromCert(cert: SecCertificate, policy: SecPolicy) -> SecKeyRef? { + func extractPublicKeyFromCert(_ cert: SecCertificate, policy: SecPolicy) -> SecKey? { var possibleTrust: SecTrust? SecTrustCreateWithCertificates(cert, policy, &possibleTrust) guard let trust = possibleTrust else { return nil } - var result: SecTrustResultType = 0 + var result = SecTrustResultType(rawValue: 0)! SecTrustEvaluate(trust, &result) return SecTrustCopyPublicKey(trust) } @@ -223,11 +223,11 @@ public class SSLSecurity : NSObject { - returns: the certificate chain for the trust */ - func certificateChainForTrust(trust: SecTrustRef) -> [NSData] { - let certificates = (0.. [NSData] in + func certificateChainForTrust(_ trust: SecTrust) -> [Data] { + let certificates = (0.. [Data] in var certificates = certificates let cert = SecTrustGetCertificateAtIndex(trust, index) - certificates.append(SecCertificateCopyData(cert!)) + certificates.append(SecCertificateCopyData(cert!) as Data) return certificates } @@ -241,9 +241,9 @@ public class SSLSecurity : NSObject { - returns: the public keys from the certifcate chain for the trust */ - @nonobjc func publicKeyChainForTrust(trust: SecTrustRef) -> [SecKeyRef] { + @nonobjc func publicKeyChainForTrust(trust: SecTrust) -> [SecKey] { let policy = SecPolicyCreateBasicX509() - let keys = (0.. [SecKeyRef] in + let keys = (0.. [SecKey] in var keys = keys let cert = SecTrustGetCertificateAtIndex(trust, index) if let key = extractPublicKeyFromCert(cert!, policy: policy) { @@ -257,4 +257,4 @@ public class SSLSecurity : NSObject { } -} \ No newline at end of file +} diff --git a/Source/SocketAckEmitter.swift b/Source/SocketAckEmitter.swift index edb2522..511d497 100644 --- a/Source/SocketAckEmitter.swift +++ b/Source/SocketAckEmitter.swift @@ -33,15 +33,15 @@ public final class SocketAckEmitter : NSObject { self.ackNum = ackNum } - public func with(items: AnyObject...) { + public func with(_ items: SocketData...) { guard ackNum != -1 else { return } - socket.emitAck(ackNum, withItems: items) + socket.emitAck(ackNum, with: items) } - public func with(items: [AnyObject]) { + public func with(_ items: [Any]) { guard ackNum != -1 else { return } - socket.emitAck(ackNum, withItems: items) + socket.emitAck(ackNum, with: items) } } diff --git a/Source/SocketAckManager.swift b/Source/SocketAckManager.swift index 682b4f8..ae6a6fa 100644 --- a/Source/SocketAckManager.swift +++ b/Source/SocketAckManager.swift @@ -35,7 +35,7 @@ private struct SocketAck : Hashable { self.ack = ack } - init(ack: Int, callback: AckCallback) { + init(ack: Int, callback: @escaping AckCallback) { self.ack = ack self.callback = callback } @@ -52,25 +52,21 @@ private func ==(lhs: SocketAck, rhs: SocketAck) -> Bool { struct SocketAckManager { private var acks = Set(minimumCapacity: 1) - mutating func addAck(ack: Int, callback: AckCallback) { + mutating func addAck(_ ack: Int, callback: @escaping AckCallback) { acks.insert(SocketAck(ack: ack, callback: callback)) } /// Should be called on handle queue - mutating func executeAck(ack: Int, items: [AnyObject], onQueue: dispatch_queue_t) { - let callback = acks.remove(SocketAck(ack: ack)) + mutating func executeAck(_ ack: Int, with items: [Any], onQueue: DispatchQueue) { + let ack = acks.remove(SocketAck(ack: ack)) - dispatch_async(onQueue) { - callback?.callback(items) - } + onQueue.async() { ack?.callback(items) } } /// Should be called on handle queue - mutating func timeoutAck(ack: Int, onQueue: dispatch_queue_t) { - let callback = acks.remove(SocketAck(ack: ack)) + mutating func timeoutAck(_ ack: Int, onQueue: DispatchQueue) { + let ack = acks.remove(SocketAck(ack: ack)) - dispatch_async(onQueue) { - callback?.callback(["NO ACK"]) - } + onQueue.async() { ack?.callback?(["NO ACK"]) } } } diff --git a/Source/SocketAnyEvent.swift b/Source/SocketAnyEvent.swift index 4c26f0e..b3f2833 100644 --- a/Source/SocketAnyEvent.swift +++ b/Source/SocketAnyEvent.swift @@ -26,12 +26,12 @@ import Foundation public final class SocketAnyEvent : NSObject { public let event: String - public let items: NSArray? + public let items: [Any]? override public var description: String { return "SocketAnyEvent: Event: \(event) items: \(items ?? nil)" } - init(event: String, items: NSArray?) { + init(event: String, items: [Any]?) { self.event = event self.items = items } diff --git a/Source/SocketClientManager.swift b/Source/SocketClientManager.swift index ae79179..e230272 100644 --- a/Source/SocketClientManager.swift +++ b/Source/SocketClientManager.swift @@ -43,12 +43,12 @@ import Foundation manager["room1"]?.emit("hello") ``` */ -public final class SocketClientManager : NSObject { - public static let sharedManager = SocketClientManager() +open class SocketClientManager : NSObject { + open static let sharedManager = SocketClientManager() private var sockets = [String: SocketIOClient]() - public subscript(string: String) -> SocketIOClient? { + open subscript(string: String) -> SocketIOClient? { get { return sockets[string] } @@ -58,25 +58,25 @@ public final class SocketClientManager : NSObject { } } - public func addSocket(socket: SocketIOClient, labeledAs label: String) { + open func addSocket(_ socket: SocketIOClient, labeledAs label: String) { sockets[label] = socket } - public func removeSocket(withLabel label: String) -> SocketIOClient? { - return sockets.removeValueForKey(label) + open func removeSocket(withLabel label: String) -> SocketIOClient? { + return sockets.removeValue(forKey: label) } - public func removeSocket(socket: SocketIOClient) -> SocketIOClient? { + open func removeSocket(_ socket: SocketIOClient) -> SocketIOClient? { var returnSocket: SocketIOClient? for (label, dictSocket) in sockets where dictSocket === socket { - returnSocket = sockets.removeValueForKey(label) + returnSocket = sockets.removeValue(forKey: label) } return returnSocket } - public func removeSockets() { + open func removeSockets() { sockets.removeAll() } } diff --git a/Source/SocketEngine.swift b/Source/SocketEngine.swift index 753f750..1919f57 100644 --- a/Source/SocketEngine.swift +++ b/Source/SocketEngine.swift @@ -24,24 +24,24 @@ import Foundation -public final class SocketEngine : NSObject, NSURLSessionDelegate, SocketEnginePollable, SocketEngineWebsocket { - public let emitQueue = dispatch_queue_create("com.socketio.engineEmitQueue", DISPATCH_QUEUE_SERIAL) - public let handleQueue = dispatch_queue_create("com.socketio.engineHandleQueue", DISPATCH_QUEUE_SERIAL) - public let parseQueue = dispatch_queue_create("com.socketio.engineParseQueue", DISPATCH_QUEUE_SERIAL) +public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, SocketEngineWebsocket { + public let emitQueue = DispatchQueue(label: "com.socketio.engineEmitQueue", attributes: []) + public let handleQueue = DispatchQueue(label: "com.socketio.engineHandleQueue", attributes: []) + public let parseQueue = DispatchQueue(label: "com.socketio.engineParseQueue", attributes: []) - public var connectParams: [String: AnyObject]? { + public var connectParams: [String: Any]? { didSet { (urlPolling, urlWebSocket) = createURLs() } } - + public var postWait = [String]() public var waitingForPoll = false public var waitingForPost = false - + public private(set) var closed = false public private(set) var connected = false - public private(set) var cookies: [NSHTTPCookie]? + public private(set) var cookies: [HTTPCookie]? public private(set) var doubleEncodeUTF8 = true public private(set) var extraHeaders: [String: String]? public private(set) var fastUpgrade = false @@ -50,28 +50,28 @@ public final class SocketEngine : NSObject, NSURLSessionDelegate, SocketEnginePo public private(set) var invalidated = false public private(set) var polling = true public private(set) var probing = false - public private(set) var session: NSURLSession? + public private(set) var session: URLSession? public private(set) var sid = "" public private(set) var socketPath = "/engine.io/" - public private(set) var urlPolling = NSURL() - public private(set) var urlWebSocket = NSURL() + public private(set) var urlPolling = URL(string: "http://localhost/")! + public private(set) var urlWebSocket = URL(string: "http://localhost/")! public private(set) var websocket = false public private(set) var ws: WebSocket? public weak var client: SocketEngineClient? - - private weak var sessionDelegate: NSURLSessionDelegate? + + private weak var sessionDelegate: URLSessionDelegate? private let logType = "SocketEngine" - private let url: NSURL - + private let url: URL + private var pingInterval: Double? private var pingTimeout = 0.0 { didSet { pongsMissedMax = Int(pingTimeout / (pingInterval ?? 25)) } } - + private var pongsMissed = 0 private var pongsMissedMax = 0 private var probeWait = ProbeWaitQueue() @@ -80,45 +80,44 @@ public final class SocketEngine : NSObject, NSURLSessionDelegate, SocketEnginePo private var selfSigned = false private var voipEnabled = false - public init(client: SocketEngineClient, url: NSURL, config: SocketIOClientConfiguration) { + public init(client: SocketEngineClient, url: URL, config: SocketIOClientConfiguration) { self.client = client self.url = url - for option in config { switch option { - case let .ConnectParams(params): + case let .connectParams(params): connectParams = params - case let .Cookies(cookies): + case let .cookies(cookies): self.cookies = cookies - case let .DoubleEncodeUTF8(encode): + case let .doubleEncodeUTF8(encode): doubleEncodeUTF8 = encode - case let .ExtraHeaders(headers): + case let .extraHeaders(headers): extraHeaders = headers - case let .SessionDelegate(delegate): + case let .sessionDelegate(delegate): sessionDelegate = delegate - case let .ForcePolling(force): + case let .forcePolling(force): forcePolling = force - case let .ForceWebsockets(force): + case let .forceWebsockets(force): forceWebsockets = force - case let .Path(path): + case let .path(path): socketPath = path - + case let .voipEnabled(enable): if !socketPath.hasSuffix("/") { socketPath += "/" } - case let .VoipEnabled(enable): + voipEnabled = enable - case let .Secure(secure): + case let .secure(secure): self.secure = secure - case let .Security(security): - self.security = security - case let .SelfSigned(selfSigned): + case let .selfSigned(selfSigned): self.selfSigned = selfSigned + case let .security(security): + self.security = security default: continue } } - + super.init() sessionDelegate = sessionDelegate ?? self @@ -126,17 +125,17 @@ public final class SocketEngine : NSObject, NSURLSessionDelegate, SocketEnginePo (urlPolling, urlWebSocket) = createURLs() } - public convenience init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) { + public convenience init(client: SocketEngineClient, url: URL, options: NSDictionary?) { self.init(client: client, url: url, config: options?.toSocketConfiguration() ?? []) } - + deinit { DefaultSocketLogger.Logger.log("Engine is being released", type: logType) closed = true stopPolling() } - - private func checkAndHandleEngineError(msg: String) { + + private func checkAndHandleEngineError(_ msg: String) { do { let dict = try msg.toNSDictionary() guard let error = dict["message"] as? String else { return } @@ -147,76 +146,76 @@ public final class SocketEngine : NSObject, NSURLSessionDelegate, SocketEnginePo 2: Bad handshake request 3: Bad request */ - didError(error) + didError(reason: error) } catch { - client?.engineDidError("Got unknown error from server \(msg)") + client?.engineDidError(reason: "Got unknown error from server \(msg)") } } private func handleBase64(message: String) { // binary in base64 string - let noPrefix = message[message.startIndex.advancedBy(2).. (NSURL, NSURL) { + private func createURLs() -> (URL, URL) { if client == nil { - return (NSURL(), NSURL()) + return (URL(string: "http://localhost/")!, URL(string: "http://localhost/")!) } - let urlPolling = NSURLComponents(string: url.absoluteString)! - let urlWebSocket = NSURLComponents(string: url.absoluteString)! + var urlPolling = URLComponents(string: url.absoluteString)! + var urlWebSocket = URLComponents(string: url.absoluteString)! var queryString = "" - + urlWebSocket.path = socketPath urlPolling.path = socketPath @@ -239,15 +238,15 @@ public final class SocketEngine : NSObject, NSURLSessionDelegate, SocketEnginePo urlWebSocket.percentEncodedQuery = "transport=websocket" + queryString urlPolling.percentEncodedQuery = "transport=polling&b64=1" + queryString - - return (urlPolling.URL!, urlWebSocket.URL!) + + return (urlPolling.url!, urlWebSocket.url!) } private func createWebsocketAndConnect() { - ws = WebSocket(url: urlWebSocketWithSid) - + ws = WebSocket(url: urlWebSocketWithSid as NSURL) + if cookies != nil { - let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) + let headers = HTTPCookie.requestHeaderFields(with: cookies!) for (key, value) in headers { ws?.headers[key] = value } @@ -267,38 +266,38 @@ public final class SocketEngine : NSObject, NSURLSessionDelegate, SocketEnginePo ws?.connect() } - - public func didError(error: String) { - DefaultSocketLogger.Logger.error("%@", type: logType, args: error) - client?.engineDidError(error) - disconnect(error) + + public func didError(reason: String) { + DefaultSocketLogger.Logger.error("%@", type: logType, args: reason) + client?.engineDidError(reason: reason) + disconnect(reason: reason) } - + public func disconnect(reason: String) { - guard connected else { return closeOutEngine(reason) } - + guard connected else { return closeOutEngine(reason: reason) } + DefaultSocketLogger.Logger.log("Engine is being closed.", type: logType) - + if closed { - return closeOutEngine(reason) + return closeOutEngine(reason: reason) } - + if websocket { - sendWebSocketMessage("", withType: .Close, withData: []) - closeOutEngine(reason) + sendWebSocketMessage("", withType: .close, withData: []) + closeOutEngine(reason: reason) } else { - disconnectPolling(reason) + disconnectPolling(reason: reason) } } - + // We need to take special care when we're polling that we send it ASAP // Also make sure we're on the emitQueue since we're touching postWait private func disconnectPolling(reason: String) { - dispatch_sync(emitQueue) { - self.postWait.append(String(SocketEnginePacketType.Close.rawValue)) + emitQueue.sync { + self.postWait.append(String(SocketEnginePacketType.close.rawValue)) let req = self.createRequestForPostWithPostWait() - self.doRequest(req) {_, _, _ in } - self.closeOutEngine(reason) + self.doRequest(for: req) {_, _, _ in } + self.closeOutEngine(reason: reason) } } @@ -308,7 +307,7 @@ public final class SocketEngine : NSObject, NSURLSessionDelegate, SocketEnginePo "we'll probably disconnect soon. You should report this.", type: logType) } - sendWebSocketMessage("", withType: .Upgrade, withData: []) + sendWebSocketMessage("", withType: .upgrade, withData: []) websocket = true polling = false fastUpgrade = false @@ -319,105 +318,108 @@ public final class SocketEngine : NSObject, NSURLSessionDelegate, SocketEnginePo private func flushProbeWait() { DefaultSocketLogger.Logger.log("Flushing probe wait", type: logType) - dispatch_async(emitQueue) { + emitQueue.async { for waiter in self.probeWait { self.write(waiter.msg, withType: waiter.type, withData: waiter.data) } - - self.probeWait.removeAll(keepCapacity: false) - + + self.probeWait.removeAll(keepingCapacity: false) + if self.postWait.count != 0 { self.flushWaitingForPostToWebSocket() } } } - + // We had packets waiting for send when we upgraded // Send them raw public func flushWaitingForPostToWebSocket() { guard let ws = self.ws else { return } - + for msg in postWait { ws.writeString(msg) } - - postWait.removeAll(keepCapacity: true) + + postWait.removeAll(keepingCapacity: false) } - private func handleClose(reason: String) { - client?.engineDidClose(reason) + private func handleClose(_ reason: String) { + client?.engineDidClose(reason: reason) } - private func handleMessage(message: String) { + private func handleMessage(_ message: String) { client?.parseEngineMessage(message) } private func handleNOOP() { doPoll() } - + private func handleOpen(openData: String) { - do { - let json = try openData.toNSDictionary() - guard let sid = json["sid"] as? String else { - client?.engineDidError("Open packet contained no sid") - return - } + guard let json = try? openData.toNSDictionary() else { + didError(reason: "Error parsing open packet") - let upgradeWs: Bool - - self.sid = sid - connected = true - - if let upgrades = json["upgrades"] as? [String] { - upgradeWs = upgrades.contains("websocket") - } else { - upgradeWs = false - } - - if let pingInterval = json["pingInterval"] as? Double, pingTimeout = json["pingTimeout"] as? Double { - self.pingInterval = pingInterval / 1000.0 - self.pingTimeout = pingTimeout / 1000.0 - } - - if !forcePolling && !forceWebsockets && upgradeWs { - createWebsocketAndConnect() - } - - sendPing() - - if !forceWebsockets { - doPoll() - } - - client?.engineDidOpen("Connect") - } catch { - didError("Error parsing open packet") + return } + + guard let sid = json["sid"] as? String else { + didError(reason: "Open packet contained no sid") + + return + } + + let upgradeWs: Bool + + self.sid = sid + connected = true + + if let upgrades = json["upgrades"] as? [String] { + upgradeWs = upgrades.contains("websocket") + } else { + upgradeWs = false + } + + if let pingInterval = json["pingInterval"] as? Double, let pingTimeout = json["pingTimeout"] as? Double { + self.pingInterval = pingInterval / 1000.0 + self.pingTimeout = pingTimeout / 1000.0 + } + + if !forcePolling && !forceWebsockets && upgradeWs { + createWebsocketAndConnect() + } + + sendPing() + + if !forceWebsockets { + doPoll() + } + + client?.engineDidOpen(reason: "Connect") } - private func handlePong(pongMessage: String) { + private func handlePong(with message: String) { pongsMissed = 0 // We should upgrade - if pongMessage == "3probe" { + if message == "3probe" { upgradeTransport() } } - - public func parseEngineData(data: NSData) { + + public func parseEngineData(_ data: Data) { DefaultSocketLogger.Logger.log("Got binary data: %@", type: "SocketEngine", args: data) - client?.parseEngineBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1))) + + client?.parseEngineBinaryData(data.subdata(in: 1.. pongsMissedMax { - client?.engineDidClose("Ping timeout") + client?.engineDidClose(reason: "Ping timeout") + return } + + guard let pingInterval = pingInterval else { return } - if let pingInterval = pingInterval { - pongsMissed += 1 - write("", withType: .Ping, withData: []) - - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(pingInterval * Double(NSEC_PER_SEC))) - dispatch_after(time, dispatch_get_main_queue()) {[weak self] in - self?.sendPing() - } - } + pongsMissed += 1 + write("", withType: .ping, withData: []) + + let time = DispatchTime.now() + Double(Int64(pingInterval * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) + DispatchQueue.main.asyncAfter(deadline: time) {[weak self] in self?.sendPing() } } - + // Moves from long-polling to websockets private func upgradeTransport() { if ws?.isConnected ?? false { DefaultSocketLogger.Logger.log("Upgrading transport to WebSockets", type: logType) fastUpgrade = true - sendPollMessage("", withType: .Noop, withData: []) + sendPollMessage("", withType: .noop, withData: []) // After this point, we should not send anymore polling messages } } /// Write a message, independent of transport. - public func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData]) { - dispatch_async(emitQueue) { + public func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) { + emitQueue.async { guard self.connected else { return } - + if self.websocket { DefaultSocketLogger.Logger.log("Writing ws: %@ has data: %@", type: self.logType, args: msg, data.count != 0) @@ -516,9 +513,9 @@ public final class SocketEngine : NSObject, NSURLSessionDelegate, SocketEnginePo } } } - + // Delegate methods - public func websocketDidConnect(socket: WebSocket) { + public func websocketDidConnect(_ socket: WebSocket) { if !forceWebsockets { probing = true probeWebSocket() @@ -528,23 +525,24 @@ public final class SocketEngine : NSObject, NSURLSessionDelegate, SocketEnginePo polling = false } } - - public func websocketDidDisconnect(socket: WebSocket, error: NSError?) { + + public func websocketDidDisconnect(_ socket: WebSocket, error: NSError?) { probing = false - + if closed { - client?.engineDidClose("Disconnect") + client?.engineDidClose(reason: "Disconnect") + return } - + if websocket { connected = false websocket = false - + if let reason = error?.localizedDescription { - didError(reason) + didError(reason: reason) } else { - client?.engineDidClose("Socket Disconnected") + client?.engineDidClose(reason: "Socket Disconnected") } } else { flushProbeWait() @@ -553,9 +551,9 @@ public final class SocketEngine : NSObject, NSURLSessionDelegate, SocketEnginePo } extension SocketEngine { - public func URLSession(session: NSURLSession, didBecomeInvalidWithError error: NSError?) { + public func URLSession(session: URLSession, didBecomeInvalidWithError error: NSError?) { DefaultSocketLogger.Logger.error("Engine URLSession became invalid", type: "SocketEngine") - didError("Engine URLSession became invalid") + didError(reason: "Engine URLSession became invalid") } } diff --git a/Source/SocketEngineClient.swift b/Source/SocketEngineClient.swift index e3aecc6..49cac7c 100644 --- a/Source/SocketEngineClient.swift +++ b/Source/SocketEngineClient.swift @@ -29,6 +29,6 @@ import Foundation func engineDidError(reason: String) func engineDidClose(reason: String) func engineDidOpen(reason: String) - func parseEngineMessage(msg: String) - func parseEngineBinaryData(data: NSData) + func parseEngineMessage(_ msg: String) + func parseEngineBinaryData(_ data: Data) } diff --git a/Source/SocketEnginePacketType.swift b/Source/SocketEnginePacketType.swift index 592d79b..763335e 100644 --- a/Source/SocketEnginePacketType.swift +++ b/Source/SocketEnginePacketType.swift @@ -26,5 +26,5 @@ import Foundation @objc public enum SocketEnginePacketType : Int { - case Open, Close, Ping, Pong, Message, Upgrade, Noop + case open, close, ping, pong, message, upgrade, noop } \ No newline at end of file diff --git a/Source/SocketEnginePollable.swift b/Source/SocketEnginePollable.swift index b4c5f57..b050975 100644 --- a/Source/SocketEnginePollable.swift +++ b/Source/SocketEnginePollable.swift @@ -30,7 +30,7 @@ public protocol SocketEnginePollable : SocketEngineSpec { /// Holds strings waiting to be sent over polling. /// You shouldn't need to mess with this. var postWait: [String] { get set } - var session: NSURLSession? { get } + var session: URLSession? { get } /// Because socket.io doesn't let you send two polling request at the same time /// we have to keep track if there's an outstanding poll var waitingForPoll: Bool { get set } @@ -39,15 +39,17 @@ public protocol SocketEnginePollable : SocketEngineSpec { var waitingForPost: Bool { get set } func doPoll() - func sendPollMessage(message: String, withType type: SocketEnginePacketType, withData datas: [NSData]) + func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) func stopPolling() } // Default polling methods extension SocketEnginePollable { - private func addHeaders(req: NSMutableURLRequest) { + private func addHeaders(for req: URLRequest) -> URLRequest { + var req = req + if cookies != nil { - let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) + let headers = HTTPCookie.requestHeaderFields(with: cookies!) req.allHTTPHeaderFields = headers } @@ -56,10 +58,12 @@ extension SocketEnginePollable { req.setValue(value, forHTTPHeaderField: headerName) } } + + return req } - func createRequestForPostWithPostWait() -> NSURLRequest { - defer { postWait.removeAll(keepCapacity: true) } + func createRequestForPostWithPostWait() -> URLRequest { + defer { postWait.removeAll(keepingCapacity: true) } var postStr = "" @@ -71,49 +75,52 @@ extension SocketEnginePollable { DefaultSocketLogger.Logger.log("Created POST string: %@", type: "SocketEnginePolling", args: postStr) - let req = NSMutableURLRequest(URL: urlPollingWithSid) - let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! + var req = URLRequest(url: urlPollingWithSid) + let postData = postStr.data(using: .utf8, allowLossyConversion: false)! - addHeaders(req) + req = addHeaders(for: req) - req.HTTPMethod = "POST" + req.httpMethod = "POST" req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") - req.HTTPBody = postData - req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") + + req.httpBody = postData + req.setValue(String(postData.count), forHTTPHeaderField: "Content-Length") - return req + return req as URLRequest } public func doPoll() { - if websocket || waitingForPoll || !connected || closed { return } + if websocket || waitingForPoll || !connected || closed { + return + } waitingForPoll = true - let req = NSMutableURLRequest(URL: urlPollingWithSid) + var req = URLRequest(url: urlPollingWithSid) - addHeaders(req) - doLongPoll(req) + req = addHeaders(for: req) + doLongPoll(for: req ) } - func doRequest(req: NSURLRequest, withCallback callback: (NSData?, NSURLResponse?, NSError?) -> Void) { + func doRequest(for req: URLRequest, callbackWith callback: @escaping (Data?, URLResponse?, Error?) -> Void) { if !polling || closed || invalidated || fastUpgrade { return } DefaultSocketLogger.Logger.log("Doing polling request", type: "SocketEnginePolling") - session?.dataTaskWithRequest(req, completionHandler: callback).resume() + session?.dataTask(with: req, completionHandler: callback).resume() } - func doLongPoll(req: NSURLRequest) { - doRequest(req) {[weak self] data, res, err in - guard let this = self where this.polling else { return } + func doLongPoll(for req: URLRequest) { + doRequest(for: req) {[weak self] data, res, err in + guard let this = self, this.polling else { return } if err != nil || data == nil { DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") if this.polling { - this.didError(err?.localizedDescription ?? "Error") + this.didError(reason: err?.localizedDescription ?? "Error") } return @@ -121,8 +128,8 @@ extension SocketEnginePollable { DefaultSocketLogger.Logger.log("Got polling response", type: "SocketEnginePolling") - if let str = String(data: data!, encoding: NSUTF8StringEncoding) { - dispatch_async(this.parseQueue) { + if let str = String(data: data!, encoding: String.Encoding.utf8) { + this.parseQueue.async { this.parsePollingMessage(str) } } @@ -151,14 +158,14 @@ extension SocketEnginePollable { DefaultSocketLogger.Logger.log("POSTing", type: "SocketEnginePolling") - doRequest(req) {[weak self] data, res, err in + doRequest(for: req) {[weak self] data, res, err in guard let this = self else { return } if err != nil { DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") if this.polling { - this.didError(err?.localizedDescription ?? "Error") + this.didError(reason: err?.localizedDescription ?? "Error") } return @@ -166,7 +173,7 @@ extension SocketEnginePollable { this.waitingForPost = false - dispatch_async(this.emitQueue) { + this.emitQueue.async { if !this.fastUpgrade { this.flushWaitingForPost() this.doPoll() @@ -175,22 +182,18 @@ extension SocketEnginePollable { } } - func parsePollingMessage(str: String) { + func parsePollingMessage(_ str: String) { guard str.characters.count != 1 else { return } var reader = SocketStringReader(message: str) while reader.hasNext { - if let n = Int(reader.readUntilStringOccurence(":")) { - let str = reader.read(n) + if let n = Int(reader.readUntilOccurence(of: ":")) { + let str = reader.read(count: n) - dispatch_async(handleQueue) { - self.parseEngineMessage(str, fromPolling: true) - } + handleQueue.async { self.parseEngineMessage(str, fromPolling: true) } } else { - dispatch_async(handleQueue) { - self.parseEngineMessage(str, fromPolling: true) - } + handleQueue.async { self.parseEngineMessage(str, fromPolling: true) } break } } @@ -198,7 +201,7 @@ extension SocketEnginePollable { /// Send polling message. /// Only call on emitQueue - public func sendPollMessage(message: String, withType type: SocketEnginePacketType, withData datas: [NSData]) { + public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) { DefaultSocketLogger.Logger.log("Sending poll: %@ as type: %@", type: "SocketEnginePolling", args: message, type.rawValue) let fixedMessage: String @@ -211,7 +214,7 @@ extension SocketEnginePollable { postWait.append(String(type.rawValue) + fixedMessage) for data in datas { - if case let .Right(bin) = createBinaryDataForSend(data) { + if case let .right(bin) = createBinaryDataForSend(using: data) { postWait.append(bin) } } diff --git a/Source/SocketEngineSpec.swift b/Source/SocketEngineSpec.swift index 7fdd779..f862889 100644 --- a/Source/SocketEngineSpec.swift +++ b/Source/SocketEngineSpec.swift @@ -29,79 +29,79 @@ import Foundation weak var client: SocketEngineClient? { get set } var closed: Bool { get } var connected: Bool { get } - var connectParams: [String: AnyObject]? { get set } + var connectParams: [String: Any]? { get set } var doubleEncodeUTF8: Bool { get } - var cookies: [NSHTTPCookie]? { get } + var cookies: [HTTPCookie]? { get } var extraHeaders: [String: String]? { get } var fastUpgrade: Bool { get } var forcePolling: Bool { get } var forceWebsockets: Bool { get } - var parseQueue: dispatch_queue_t! { get } + var parseQueue: DispatchQueue { get } var polling: Bool { get } var probing: Bool { get } - var emitQueue: dispatch_queue_t! { get } - var handleQueue: dispatch_queue_t! { get } + var emitQueue: DispatchQueue { get } + var handleQueue: DispatchQueue { get } var sid: String { get } var socketPath: String { get } - var urlPolling: NSURL { get } - var urlWebSocket: NSURL { get } + var urlPolling: URL { get } + var urlWebSocket: URL { get } var websocket: Bool { get } var ws: WebSocket? { get } - init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) + init(client: SocketEngineClient, url: URL, options: NSDictionary?) func connect() - func didError(error: String) + func didError(reason: String) func disconnect(reason: String) func doFastUpgrade() func flushWaitingForPostToWebSocket() - func parseEngineData(data: NSData) - func parseEngineMessage(message: String, fromPolling: Bool) - func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData]) + func parseEngineData(_ data: Data) + func parseEngineMessage(_ message: String, fromPolling: Bool) + func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) } extension SocketEngineSpec { - var urlPollingWithSid: NSURL { - let com = NSURLComponents(URL: urlPolling, resolvingAgainstBaseURL: false)! + var urlPollingWithSid: URL { + var com = URLComponents(url: urlPolling, resolvingAgainstBaseURL: false)! com.percentEncodedQuery = com.percentEncodedQuery! + "&sid=\(sid.urlEncode()!)" - return com.URL! + return com.url! } - var urlWebSocketWithSid: NSURL { - let com = NSURLComponents(URL: urlWebSocket, resolvingAgainstBaseURL: false)! + var urlWebSocketWithSid: URL { + var com = URLComponents(url: urlWebSocket, resolvingAgainstBaseURL: false)! com.percentEncodedQuery = com.percentEncodedQuery! + (sid == "" ? "" : "&sid=\(sid.urlEncode()!)") - return com.URL! + return com.url! } - func createBinaryDataForSend(data: NSData) -> Either { + func createBinaryDataForSend(using data: Data) -> Either { if websocket { - var byteArray = [UInt8](count: 1, repeatedValue: 0x4) + var byteArray = [UInt8](repeating: 0x4, count: 1) let mutData = NSMutableData(bytes: &byteArray, length: 1) - mutData.appendData(data) + mutData.append(data) - return .Left(mutData) + return .left(mutData as Data) } else { - let str = "b4" + data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)) + let str = "b4" + data.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0)) - return .Right(str) + return .right(str) } } - func doubleEncodeUTF8(string: String) -> String { - if let latin1 = string.dataUsingEncoding(NSUTF8StringEncoding), - utf8 = NSString(data: latin1, encoding: NSISOLatin1StringEncoding) { + func doubleEncodeUTF8(_ string: String) -> String { + if let latin1 = string.data(using: String.Encoding.utf8), + let utf8 = NSString(data: latin1, encoding: String.Encoding.isoLatin1.rawValue) { return utf8 as String } else { return string } } - func fixDoubleUTF8(string: String) -> String { - if let utf8 = string.dataUsingEncoding(NSISOLatin1StringEncoding), - latin1 = NSString(data: utf8, encoding: NSUTF8StringEncoding) { + func fixDoubleUTF8(_ string: String) -> String { + if let utf8 = string.data(using: String.Encoding.isoLatin1), + let latin1 = NSString(data: utf8, encoding: String.Encoding.utf8.rawValue) { return latin1 as String } else { return string @@ -109,7 +109,7 @@ extension SocketEngineSpec { } /// Send an engine message (4) - func send(msg: String, withData datas: [NSData]) { - write(msg, withType: .Message, withData: datas) + func send(_ msg: String, withData datas: [Data]) { + write(msg, withType: .message, withData: datas) } } diff --git a/Source/SocketEngineWebsocket.swift b/Source/SocketEngineWebsocket.swift index e1b0ba8..ef7ee4d 100644 --- a/Source/SocketEngineWebsocket.swift +++ b/Source/SocketEngineWebsocket.swift @@ -27,36 +27,36 @@ import Foundation /// Protocol that is used to implement socket.io WebSocket support public protocol SocketEngineWebsocket : SocketEngineSpec, WebSocketDelegate { - func sendWebSocketMessage(str: String, withType type: SocketEnginePacketType, withData datas: [NSData]) + func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) } // WebSocket methods extension SocketEngineWebsocket { func probeWebSocket() { if ws?.isConnected ?? false { - sendWebSocketMessage("probe", withType: .Ping, withData: []) + sendWebSocketMessage("probe", withType: .ping, withData: []) } } /// Send message on WebSockets /// Only call on emitQueue - public func sendWebSocketMessage(str: String, withType type: SocketEnginePacketType, withData datas: [NSData]) { + public func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) { DefaultSocketLogger.Logger.log("Sending ws: %@ as type: %@", type: "SocketEngine", args: str, type.rawValue) ws?.writeString("\(type.rawValue)\(str)") for data in datas { - if case let .Left(bin) = createBinaryDataForSend(data) { + if case let .left(bin) = createBinaryDataForSend(using: data) { ws?.writeData(bin) } } } - public func websocketDidReceiveMessage(socket: WebSocket, text: String) { + public func websocketDidReceiveMessage(_ socket: WebSocket, text: String) { parseEngineMessage(text, fromPolling: false) } - public func websocketDidReceiveData(socket: WebSocket, data: NSData) { + public func websocketDidReceiveData(_ socket: WebSocket, data: Data) { parseEngineData(data) } } diff --git a/Source/SocketEventHandler.swift b/Source/SocketEventHandler.swift index 41774a9..5497f7f 100644 --- a/Source/SocketEventHandler.swift +++ b/Source/SocketEventHandler.swift @@ -26,10 +26,10 @@ import Foundation struct SocketEventHandler { let event: String - let id: NSUUID + let id: UUID let callback: NormalCallback - func executeCallback(items: [AnyObject], withAck ack: Int, withSocket socket: SocketIOClient) { + func executeCallback(with items: [Any], withAck ack: Int, withSocket socket: SocketIOClient) { callback(items, SocketAckEmitter(socket: socket, ackNum: ack)) } } diff --git a/Source/SocketExtensions.swift b/Source/SocketExtensions.swift index 71a8bd8..bf5280a 100644 --- a/Source/SocketExtensions.swift +++ b/Source/SocketExtensions.swift @@ -24,66 +24,66 @@ import Foundation -enum JSONError : ErrorType { +enum JSONError : Error { case notArray case notNSDictionary } -extension Array where Element: AnyObject { - func toJSON() throws -> NSData { - return try NSJSONSerialization.dataWithJSONObject(self as NSArray, options: NSJSONWritingOptions(rawValue: 0)) +extension Array { + func toJSON() throws -> Data { + return try JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions(rawValue: 0)) } } -extension NSCharacterSet { - static var allowedURLCharacterSet: NSCharacterSet { - return NSCharacterSet(charactersInString: "!*'();:@&=+$,/?%#[]\" {}").invertedSet +extension CharacterSet { + static var allowedURLCharacterSet: CharacterSet { + return CharacterSet(charactersIn: "!*'();:@&=+$,/?%#[]\" {}").inverted } } extension NSDictionary { - private static func keyValueToSocketIOClientOption(key: String, value: AnyObject) -> SocketIOClientOption? { + private static func keyValueToSocketIOClientOption(key: String, value: Any) -> SocketIOClientOption? { switch (key, value) { - case let ("connectParams", params as [String: AnyObject]): - return .ConnectParams(params) - case let ("cookies", cookies as [NSHTTPCookie]): - return .Cookies(cookies) + case let ("connectParams", params as [String: Any]): + return .connectParams(params) + case let ("cookies", cookies as [HTTPCookie]): + return .cookies(cookies) case let ("doubleEncodeUTF8", encode as Bool): - return .DoubleEncodeUTF8(encode) + return .doubleEncodeUTF8(encode) case let ("extraHeaders", headers as [String: String]): - return .ExtraHeaders(headers) + return .extraHeaders(headers) case let ("forceNew", force as Bool): - return .ForceNew(force) + return .forceNew(force) case let ("forcePolling", force as Bool): - return .ForcePolling(force) + return .forcePolling(force) case let ("forceWebsockets", force as Bool): - return .ForceWebsockets(force) - case let ("handleQueue", queue as dispatch_queue_t): - return .HandleQueue(queue) + return .forceWebsockets(force) + case let ("handleQueue", queue as DispatchQueue): + return .handleQueue(queue) case let ("log", log as Bool): - return .Log(log) + return .log(log) case let ("logger", logger as SocketLogger): - return .Logger(logger) + return .logger(logger) case let ("nsp", nsp as String): - return .Nsp(nsp) + return .nsp(nsp) case let ("path", path as String): - return .Path(path) + return .path(path) case let ("reconnects", reconnects as Bool): - return .Reconnects(reconnects) + return .reconnects(reconnects) case let ("reconnectAttempts", attempts as Int): - return .ReconnectAttempts(attempts) + return .reconnectAttempts(attempts) case let ("reconnectWait", wait as Int): - return .ReconnectWait(wait) + return .reconnectWait(wait) case let ("secure", secure as Bool): - return .Secure(secure) + return .secure(secure) case let ("security", security as SSLSecurity): - return .Security(security) + return .security(security) case let ("selfSigned", selfSigned as Bool): - return .SelfSigned(selfSigned) - case let ("sessionDelegate", delegate as NSURLSessionDelegate): - return .SessionDelegate(delegate) + return .selfSigned(selfSigned) + case let ("sessionDelegate", delegate as URLSessionDelegate): + return .sessionDelegate(delegate) case let ("voipEnabled", enable as Bool): - return .VoipEnabled(enable) + return .voipEnabled(enable) default: return nil } @@ -93,7 +93,7 @@ extension NSDictionary { var options = [] as SocketIOClientConfiguration for (rawKey, value) in self { - if let key = rawKey as? String, opt = NSDictionary.keyValueToSocketIOClientOption(key, value: value) { + if let key = rawKey as? String, let opt = NSDictionary.keyValueToSocketIOClientOption(key: key, value: value) { options.insert(opt) } } @@ -103,9 +103,9 @@ extension NSDictionary { } extension String { - func toArray() throws -> [AnyObject] { - guard let stringData = dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) else { return [] } - guard let array = try NSJSONSerialization.JSONObjectWithData(stringData, options: .MutableContainers) as? [AnyObject] else { + func toArray() throws -> [Any] { + guard let stringData = data(using: .utf8, allowLossyConversion: false) else { return [] } + guard let array = try JSONSerialization.jsonObject(with: stringData, options: .mutableContainers) as? [Any] else { throw JSONError.notArray } @@ -113,8 +113,8 @@ extension String { } func toNSDictionary() throws -> NSDictionary { - guard let binData = dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) else { return [:] } - guard let json = try NSJSONSerialization.JSONObjectWithData(binData, options: .AllowFragments) as? NSDictionary else { + guard let binData = data(using: .utf8, allowLossyConversion: false) else { return [:] } + guard let json = try JSONSerialization.jsonObject(with: binData, options: .allowFragments) as? NSDictionary else { throw JSONError.notNSDictionary } @@ -122,6 +122,6 @@ extension String { } func urlEncode() -> String? { - return stringByAddingPercentEncodingWithAllowedCharacters(.allowedURLCharacterSet) + return addingPercentEncoding(withAllowedCharacters: .allowedURLCharacterSet) } } diff --git a/Source/SocketIOClient.swift b/Source/SocketIOClient.swift index 98a825b..0a51bd0 100644 --- a/Source/SocketIOClient.swift +++ b/Source/SocketIOClient.swift @@ -25,13 +25,13 @@ import Foundation public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable { - public let socketURL: NSURL + public let socketURL: URL public private(set) var engine: SocketEngineSpec? - public private(set) var status = SocketIOClientStatus.NotConnected { + public private(set) var status = SocketIOClientStatus.notConnected { didSet { switch status { - case .Connected: + case .connected: reconnecting = false currentReconnectAttempt = 0 default: @@ -46,10 +46,10 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable public var reconnects = true public var reconnectWait = 10 - private let ackQueue = dispatch_queue_create("com.socketio.ackQueue", DISPATCH_QUEUE_SERIAL) - private let emitQueue = dispatch_queue_create("com.socketio.emitQueue", DISPATCH_QUEUE_SERIAL) + private let ackQueue = DispatchQueue(label: "com.socketio.ackQueue", attributes: []) + private let emitQueue = DispatchQueue(label: "com.socketio.emitQueue", attributes: []) private let logType = "SocketIOClient" - private let parseQueue = dispatch_queue_create("com.socketio.parseQueue", DISPATCH_QUEUE_SERIAL) + private let parseQueue = DispatchQueue(label: "com.socketio.parseQueue", attributes: []) private var anyHandler: ((SocketAnyEvent) -> Void)? private var currentReconnectAttempt = 0 @@ -58,7 +58,7 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable private var reconnecting = false private(set) var currentAck = -1 - private(set) var handleQueue = dispatch_get_main_queue() + private(set) var handleQueue = DispatchQueue.main private(set) var reconnectAttempts = -1 var waitingPackets = [SocketPacket]() @@ -67,39 +67,39 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable return nsp + "#" + (engine?.sid ?? "") } - /// Type safe way to create a new SocketIOClient. config can be omitted - public init(socketURL: NSURL, config: SocketIOClientConfiguration = []) { + /// Type safe way to create a new SocketIOClient. opts can be omitted + public init(socketURL: URL, config: SocketIOClientConfiguration = []) { self.config = config self.socketURL = socketURL if socketURL.absoluteString.hasPrefix("https://") { - self.config.insert(.Secure(true)) + self.config.insert(.secure(true)) } for option in config { switch option { - case let .Reconnects(reconnects): + case let .reconnects(reconnects): self.reconnects = reconnects - case let .ReconnectAttempts(attempts): + case let .reconnectAttempts(attempts): reconnectAttempts = attempts - case let .ReconnectWait(wait): + case let .reconnectWait(wait): reconnectWait = abs(wait) - case let .Nsp(nsp): + case let .nsp(nsp): self.nsp = nsp - case let .Log(log): + case let .log(log): DefaultSocketLogger.Logger.log = log - case let .Logger(logger): + case let .logger(logger): DefaultSocketLogger.Logger = logger - case let .HandleQueue(queue): + case let .handleQueue(queue): handleQueue = queue - case let .ForceNew(force): + case let .forceNew(force): forceNew = force default: continue } } - - self.config.insert(.Path("/socket.io/"), replacing: false) + + self.config.insert(.path("/socket.io/"), replacing: false) super.init() } @@ -107,16 +107,16 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable /// Not so type safe way to create a SocketIOClient, meant for Objective-C compatiblity. /// If using Swift it's recommended to use `init(socketURL: NSURL, options: Set)` public convenience init(socketURL: NSURL, config: NSDictionary?) { - self.init(socketURL: socketURL, config: config?.toSocketConfiguration() ?? []) + self.init(socketURL: socketURL as URL, config: config?.toSocketConfiguration() ?? []) } deinit { DefaultSocketLogger.Logger.log("Client is being released", type: logType) - engine?.disconnect("Client Deinit") + engine?.disconnect(reason: "Client Deinit") } private func addEngine() -> SocketEngineSpec { - DefaultSocketLogger.Logger.log("Adding engine", type: logType) + DefaultSocketLogger.Logger.log("Adding engine", type: logType, args: "") engine = SocketEngine(client: self, url: socketURL, config: config) @@ -125,19 +125,20 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable /// Connect to the server. public func connect() { - connect(timeoutAfter: 0, withTimeoutHandler: nil) + connect(timeoutAfter: 0, withHandler: nil) } - /// Connect to the server. If we aren't connected after timeoutAfter, call handler - public func connect(timeoutAfter timeoutAfter: Int, withTimeoutHandler handler: (() -> Void)?) { + /// Connect to the server. If we aren't connected after timeoutAfter, call withHandler + /// 0 Never times out + public func connect(timeoutAfter: Int, withHandler handler: (() -> Void)?) { assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)") - guard status != .Connected else { + guard status != .connected else { DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket", type: logType) return } - status = .Connecting + status = .connecting if engine == nil || forceNew { addEngine().connect() @@ -147,25 +148,25 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable guard timeoutAfter != 0 else { return } - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC)) + let time = DispatchTime.now() + Double(Int64(timeoutAfter) * Int64(NSEC_PER_SEC)) / Double(NSEC_PER_SEC) - dispatch_after(time, handleQueue) {[weak self] in - guard let this = self where this.status != .Connected && this.status != .Disconnected else { return } + handleQueue.asyncAfter(deadline: time) {[weak self] in + guard let this = self, this.status != .connected && this.status != .disconnected else { return } - this.status = .Disconnected - this.engine?.disconnect("Connect timeout") + this.status = .disconnected + this.engine?.disconnect(reason: "Connect timeout") handler?() } } - private func createOnAck(items: [AnyObject]) -> OnAckCallback { + private func createOnAck(_ items: [Any]) -> OnAckCallback { currentAck += 1 - + return {[weak self, ack = currentAck] timeout, callback in guard let this = self else { return } - - dispatch_sync(this.ackQueue) { + + this.ackQueue.sync() { this.ackHandlers.addAck(ack, callback: callback) } @@ -173,9 +174,9 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable this._emit(items, ack: ack) if timeout != 0 { - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * NSEC_PER_SEC)) + let time = DispatchTime.now() + Double(Int64(timeout * NSEC_PER_SEC)) / Double(NSEC_PER_SEC) - dispatch_after(time, this.ackQueue) { + this.handleQueue.asyncAfter(deadline: time) { this.ackHandlers.timeoutAck(ack, onQueue: this.handleQueue) } } @@ -184,7 +185,7 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable func didConnect() { DefaultSocketLogger.Logger.log("Socket connected", type: logType) - status = .Connected + status = .connected // Don't handle as internal because something crazy could happen where // we disconnect before it's handled @@ -192,15 +193,15 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable } func didDisconnect(reason: String) { - guard status != .Disconnected else { return } + guard status != .disconnected else { return } DefaultSocketLogger.Logger.log("Disconnected: %@", type: logType, args: reason) reconnecting = false - status = .Disconnected + status = .disconnected // Make sure the engine is actually dead. - engine?.disconnect(reason) + engine?.disconnect(reason: reason) handleEvent("disconnect", data: [reason], isInternalMessage: true) } @@ -208,17 +209,17 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable public func disconnect() { DefaultSocketLogger.Logger.log("Closing socket", type: logType) - didDisconnect("Disconnect") + didDisconnect(reason: "Disconnect") } /// Send a message to the server - public func emit(event: String, _ items: AnyObject...) { - emit(event, withItems: items) + public func emit(_ event: String, _ items: SocketData...) { + emit(event, with: items) } /// Same as emit, but meant for Objective-C - public func emit(event: String, withItems items: [AnyObject]) { - guard status == .Connected else { + public func emit(_ event: String, with items: [Any]) { + guard status == .connected else { handleEvent("error", data: ["Tried emitting \(event) when not connected"], isInternalMessage: true) return } @@ -228,18 +229,18 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable /// Sends a message to the server, requesting an ack. Use the onAck method of SocketAckHandler to add /// an ack. - public func emitWithAck(event: String, _ items: AnyObject...) -> OnAckCallback { - return emitWithAck(event, withItems: items) + public func emitWithAck(_ event: String, _ items: SocketData...) -> OnAckCallback { + return emitWithAck(event, with: items) } /// Same as emitWithAck, but for Objective-C - public func emitWithAck(event: String, withItems items: [AnyObject]) -> OnAckCallback { + public func emitWithAck(_ event: String, with items: [Any]) -> OnAckCallback { return createOnAck([event] + items) } - private func _emit(data: [AnyObject], ack: Int? = nil) { - dispatch_async(emitQueue) { - guard self.status == .Connected else { + private func _emit(_ data: [Any], ack: Int? = nil) { + emitQueue.async { + guard self.status == .connected else { self.handleEvent("error", data: ["Tried emitting when not connected"], isInternalMessage: true) return } @@ -254,31 +255,31 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable } // If the server wants to know that the client received data - func emitAck(ack: Int, withItems items: [AnyObject]) { - dispatch_async(emitQueue) { - if self.status == .Connected { - let packet = SocketPacket.packetFromEmit(items, id: ack ?? -1, nsp: self.nsp, ack: true) - let str = packet.packetString - - DefaultSocketLogger.Logger.log("Emitting Ack: %@", type: self.logType, args: str) - - self.engine?.send(str, withData: packet.binary) - } + func emitAck(_ ack: Int, with items: [Any]) { + emitQueue.async { + guard self.status == .connected else { return } + + let packet = SocketPacket.packetFromEmit(items, id: ack, nsp: self.nsp, ack: true) + let str = packet.packetString + + DefaultSocketLogger.Logger.log("Emitting Ack: %@", type: self.logType, args: str) + + self.engine?.send(str, withData: packet.binary) } } public func engineDidClose(reason: String) { waitingPackets.removeAll() - if status != .Disconnected { - status = .NotConnected + if status != .disconnected { + status = .notConnected } - if status == .Disconnected || !reconnects { - didDisconnect(reason) + if status == .disconnected || !reconnects { + didDisconnect(reason: reason) } else if !reconnecting { reconnecting = true - tryReconnectWithReason(reason) + tryReconnect(reason: reason) } } @@ -294,27 +295,27 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable } // Called when the socket gets an ack for something it sent - func handleAck(ack: Int, data: [AnyObject]) { - guard status == .Connected else { return } + func handleAck(_ ack: Int, data: [Any]) { + guard status == .connected else { return } - DefaultSocketLogger.Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data ?? "") + DefaultSocketLogger.Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data) - dispatch_async(ackQueue) { - self.ackHandlers.executeAck(ack, items: data, onQueue: self.handleQueue) + handleQueue.async() { + self.ackHandlers.executeAck(ack, with: data, onQueue: self.handleQueue) } } /// Causes an event to be handled. Only use if you know what you're doing. - public func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, withAck ack: Int = -1) { - guard status == .Connected || isInternalMessage else { return } + public func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int = -1) { + guard status == .connected || isInternalMessage else { return } - DefaultSocketLogger.Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data ?? "") + DefaultSocketLogger.Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data) - dispatch_async(handleQueue) { + handleQueue.async { self.anyHandler?(SocketAnyEvent(event: event, items: data)) for handler in self.handlers where handler.event == event { - handler.executeCallback(data, withAck: ack, withSocket: self) + handler.executeCallback(with: data, withAck: ack, withSocket: self) } } } @@ -328,7 +329,7 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable } /// Joins namespace - public func joinNamespace(namespace: String) { + public func joinNamespace(_ namespace: String) { nsp = namespace if nsp != "/" { @@ -338,14 +339,14 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable } /// Removes handler(s) based on name - public func off(event: String) { + public func off(_ event: String) { DefaultSocketLogger.Logger.log("Removing handler for event: %@", type: logType, args: event) handlers = handlers.filter({ $0.event != event }) } /// Removes a handler with the specified UUID gotten from an `on` or `once` - public func off(id id: NSUUID) { + public func off(id: UUID) { DefaultSocketLogger.Logger.log("Removing handler with id: %@", type: logType, args: id) handlers = handlers.filter({ $0.id != id }) @@ -353,10 +354,11 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable /// Adds a handler for an event. /// Returns: A unique id for the handler - public func on(event: String, callback: NormalCallback) -> NSUUID { + @discardableResult + public func on(_ event: String, callback: @escaping NormalCallback) -> UUID { DefaultSocketLogger.Logger.log("Adding handler for event: %@", type: logType, args: event) - let handler = SocketEventHandler(event: event, id: NSUUID(), callback: callback) + let handler = SocketEventHandler(event: event, id: UUID(), callback: callback) handlers.append(handler) return handler.id @@ -364,10 +366,11 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable /// Adds a single-use handler for an event. /// Returns: A unique id for the handler - public func once(event: String, callback: NormalCallback) -> NSUUID { + @discardableResult + public func once(_ event: String, callback: @escaping NormalCallback) -> UUID { DefaultSocketLogger.Logger.log("Adding once handler for event: %@", type: logType, args: event) - let id = NSUUID() + let id = UUID() let handler = SocketEventHandler(event: event, id: id) {[weak self] data, ack in guard let this = self else { return } @@ -381,38 +384,34 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable } /// Adds a handler that will be called on every event. - public func onAny(handler: (SocketAnyEvent) -> Void) { + public func onAny(_ handler: @escaping (SocketAnyEvent) -> Void) { anyHandler = handler } - public func parseEngineMessage(msg: String) { + public func parseEngineMessage(_ msg: String) { DefaultSocketLogger.Logger.log("Should parse message: %@", type: "SocketIOClient", args: msg) - dispatch_async(parseQueue) { - self.parseSocketMessage(msg) - } + parseQueue.async { self.parseSocketMessage(msg) } } - public func parseEngineBinaryData(data: NSData) { - dispatch_async(parseQueue) { - self.parseBinaryData(data) - } + public func parseEngineBinaryData(_ data: Data) { + parseQueue.async { self.parseBinaryData(data) } } /// Tries to reconnect to the server. public func reconnect() { guard !reconnecting else { return } - engine?.disconnect("manual reconnect") + engine?.disconnect(reason: "manual reconnect") } /// Removes all handlers. /// Can be used after disconnecting to break any potential remaining retain cycles. public func removeAllHandlers() { - handlers.removeAll(keepCapacity: false) + handlers.removeAll(keepingCapacity: false) } - private func tryReconnectWithReason(reason: String) { + private func tryReconnect(reason: String) { guard reconnecting else { return } DefaultSocketLogger.Logger.log("Starting reconnect", type: logType) @@ -425,37 +424,35 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable guard reconnecting else { return } if reconnectAttempts != -1 && currentReconnectAttempt + 1 > reconnectAttempts || !reconnects { - return didDisconnect("Reconnect Failed") + return didDisconnect(reason: "Reconnect Failed") } DefaultSocketLogger.Logger.log("Trying to reconnect", type: logType) - handleEvent("reconnectAttempt", data: [reconnectAttempts - currentReconnectAttempt], - isInternalMessage: true) + handleEvent("reconnectAttempt", data: [(reconnectAttempts - currentReconnectAttempt)], isInternalMessage: true) currentReconnectAttempt += 1 connect() - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(UInt64(reconnectWait) * NSEC_PER_SEC)) + let deadline = DispatchTime.now() + Double(Int64(UInt64(reconnectWait) * NSEC_PER_SEC)) / Double(NSEC_PER_SEC) - dispatch_after(time, dispatch_get_main_queue(), _tryReconnect) + DispatchQueue.main.asyncAfter(deadline: deadline, execute: _tryReconnect) } -} - -// Test extensions -extension SocketIOClient { + + // Test properties + var testHandlers: [SocketEventHandler] { return handlers } - + func setTestable() { - status = .Connected + status = .connected } - - func setTestEngine(engine: SocketEngineSpec?) { + + func setTestEngine(_ engine: SocketEngineSpec?) { self.engine = engine } - - func emitTest(event: String, _ data: AnyObject...) { + + func emitTest(event: String, _ data: Any...) { _emit([event] + data) } } diff --git a/Source/SocketIOClientConfiguration.swift b/Source/SocketIOClientConfiguration.swift index 4823430..07aba15 100644 --- a/Source/SocketIOClientConfiguration.swift +++ b/Source/SocketIOClientConfiguration.swift @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -public struct SocketIOClientConfiguration : ArrayLiteralConvertible, CollectionType, MutableCollectionType { +public struct SocketIOClientConfiguration : ExpressibleByArrayLiteral, Collection, MutableCollection { public typealias Element = SocketIOClientOption public typealias Index = Array.Index public typealias Generator = Array.Generator @@ -42,7 +42,7 @@ public struct SocketIOClientConfiguration : ArrayLiteralConvertible, CollectionT return backingArray.isEmpty } - public var count: Index.Distance { + public var count: Index.Stride { return backingArray.count } @@ -75,10 +75,14 @@ public struct SocketIOClientConfiguration : ArrayLiteralConvertible, CollectionT } public func generate() -> Generator { - return backingArray.generate() + return backingArray.makeIterator() } - public mutating func insert(element: Element, replacing replace: Bool = true) { + public func index(after i: Index) -> Index { + return backingArray.index(after: i) + } + + public mutating func insert(_ element: Element, replacing replace: Bool = true) { for i in 0.. SubSequence { - return backingArray.prefixUpTo(end) + public func prefix(upTo end: Index) -> SubSequence { + return backingArray.prefix(upTo: end) } - @warn_unused_result - public func prefixThrough(position: Index) -> SubSequence { - return backingArray.prefixThrough(position) + public func prefix(through position: Index) -> SubSequence { + return backingArray.prefix(through: position) } - @warn_unused_result - public func suffixFrom(start: Index) -> SubSequence { - return backingArray.suffixFrom(start) + public func suffix(from start: Index) -> SubSequence { + return backingArray.suffix(from: start) } } diff --git a/Source/SocketIOClientOption.swift b/Source/SocketIOClientOption.swift index 512cd05..dd723f4 100644 --- a/Source/SocketIOClientOption.swift +++ b/Source/SocketIOClientOption.swift @@ -25,123 +25,123 @@ import Foundation protocol ClientOption : CustomStringConvertible, Equatable { - func getSocketIOOptionValue() -> AnyObject + func getSocketIOOptionValue() -> Any } public enum SocketIOClientOption : ClientOption { - case ConnectParams([String: AnyObject]) - case Cookies([NSHTTPCookie]) - case DoubleEncodeUTF8(Bool) - case ExtraHeaders([String: String]) - case ForceNew(Bool) - case ForcePolling(Bool) - case ForceWebsockets(Bool) - case HandleQueue(dispatch_queue_t) - case Log(Bool) - case Logger(SocketLogger) - case Nsp(String) - case Path(String) - case Reconnects(Bool) - case ReconnectAttempts(Int) - case ReconnectWait(Int) - case Secure(Bool) - case Security(SSLSecurity) - case SelfSigned(Bool) - case SessionDelegate(NSURLSessionDelegate) - case VoipEnabled(Bool) + case connectParams([String: Any]) + case cookies([HTTPCookie]) + case doubleEncodeUTF8(Bool) + case extraHeaders([String: String]) + case forceNew(Bool) + case forcePolling(Bool) + case forceWebsockets(Bool) + case handleQueue(DispatchQueue) + case log(Bool) + case logger(SocketLogger) + case nsp(String) + case path(String) + case reconnects(Bool) + case reconnectAttempts(Int) + case reconnectWait(Int) + case secure(Bool) + case security(SSLSecurity) + case selfSigned(Bool) + case sessionDelegate(URLSessionDelegate) + case voipEnabled(Bool) public var description: String { let description: String switch self { - case .ConnectParams: + case .connectParams: description = "connectParams" - case .Cookies: + case .cookies: description = "cookies" - case .DoubleEncodeUTF8: + case .doubleEncodeUTF8: description = "doubleEncodeUTF8" - case .ExtraHeaders: + case .extraHeaders: description = "extraHeaders" - case .ForceNew: + case .forceNew: description = "forceNew" - case .ForcePolling: + case .forcePolling: description = "forcePolling" - case .ForceWebsockets: + case .forceWebsockets: description = "forceWebsockets" - case .HandleQueue: + case .handleQueue: description = "handleQueue" - case .Log: + case .log: description = "log" - case .Logger: + case .logger: description = "logger" - case .Nsp: + case .nsp: description = "nsp" - case .Path: + case .path: description = "path" - case .Reconnects: + case .reconnects: description = "reconnects" - case .ReconnectAttempts: + case .reconnectAttempts: description = "reconnectAttempts" - case .ReconnectWait: + case .reconnectWait: description = "reconnectWait" - case .Secure: + case .secure: description = "secure" - case .Security: - description = "security" - case .SelfSigned: + case .selfSigned: description = "selfSigned" - case .SessionDelegate: + case .security: + description = "security" + case .sessionDelegate: description = "sessionDelegate" - case .VoipEnabled: + case .voipEnabled: description = "voipEnabled" } return description } - func getSocketIOOptionValue() -> AnyObject { - let value: AnyObject + func getSocketIOOptionValue() -> Any { + let value: Any switch self { - case let .ConnectParams(params): + case let .connectParams(params): value = params - case let .Cookies(cookies): + case let .cookies(cookies): value = cookies - case let .DoubleEncodeUTF8(encode): + case let .doubleEncodeUTF8(encode): value = encode - case let .ExtraHeaders(headers): + case let .extraHeaders(headers): value = headers - case let .ForceNew(force): + case let .forceNew(force): value = force - case let .ForcePolling(force): + case let .forcePolling(force): value = force - case let .ForceWebsockets(force): + case let .forceWebsockets(force): value = force - case let .HandleQueue(queue): + case let .handleQueue(queue): value = queue - case let .Log(log): + case let .log(log): value = log - case let .Logger(logger): + case let .logger(logger): value = logger - case let .Nsp(nsp): + case let .nsp(nsp): value = nsp - case let .Path(path): + case let .path(path): value = path - case let .Reconnects(reconnects): + case let .reconnects(reconnects): value = reconnects - case let .ReconnectAttempts(attempts): + case let .reconnectAttempts(attempts): value = attempts - case let .ReconnectWait(wait): + case let .reconnectWait(wait): value = wait - case let .Secure(secure): + case let .secure(secure): value = secure - case let .Security(security): + case let .security(security): value = security - case let .SelfSigned(signed): + case let .selfSigned(signed): value = signed - case let .SessionDelegate(delegate): + case let .sessionDelegate(delegate): value = delegate - case let .VoipEnabled(enabled): + case let .voipEnabled(enabled): value = enabled } diff --git a/Source/SocketIOClientSpec.swift b/Source/SocketIOClientSpec.swift index 8b33cf9..e91c840 100644 --- a/Source/SocketIOClientSpec.swift +++ b/Source/SocketIOClientSpec.swift @@ -29,9 +29,9 @@ protocol SocketIOClientSpec : class { func didConnect() func didDisconnect(reason: String) func didError(reason: String) - func handleAck(ack: Int, data: [AnyObject]) - func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, withAck ack: Int) - func joinNamespace(namespace: String) + func handleAck(_ ack: Int, data: [Any]) + func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int) + func joinNamespace(_ namespace: String) } extension SocketIOClientSpec { diff --git a/Source/SocketIOClientStatus.swift b/Source/SocketIOClientStatus.swift index 0a34c2f..27574e6 100644 --- a/Source/SocketIOClientStatus.swift +++ b/Source/SocketIOClientStatus.swift @@ -28,5 +28,5 @@ import Foundation /// /// **Disconnected**: connected before @objc public enum SocketIOClientStatus : Int { - case NotConnected, Disconnected, Connecting, Connected + case notConnected, disconnected, connecting, connected } diff --git a/Source/SocketLogger.swift b/Source/SocketLogger.swift index bff9d4e..640d344 100644 --- a/Source/SocketLogger.swift +++ b/Source/SocketLogger.swift @@ -29,28 +29,28 @@ public protocol SocketLogger : class { var log: Bool { get set } /// Normal log messages - func log(message: String, type: String, args: AnyObject...) + func log(_ message: String, type: String, args: Any...) /// Error Messages - func error(message: String, type: String, args: AnyObject...) + func error(_ message: String, type: String, args: Any...) } public extension SocketLogger { - func log(message: String, type: String, args: AnyObject...) { + func log(_ message: String, type: String, args: Any...) { abstractLog("LOG", message: message, type: type, args: args) } - func error(message: String, type: String, args: AnyObject...) { + func error(_ message: String, type: String, args: Any...) { abstractLog("ERROR", message: message, type: type, args: args) } - private func abstractLog(logType: String, message: String, type: String, args: [AnyObject]) { + private func abstractLog(_ logType: String, message: String, type: String, args: [Any]) { guard log else { return } - let newArgs = args.map({arg -> CVarArgType in String(arg)}) - let replaced = String(format: message, arguments: newArgs) + let newArgs = args.map({arg -> CVarArg in String(describing: arg)}) + let messageFormat = String(format: message, arguments: newArgs) - NSLog("%@ %@: %@", logType, type, replaced) + NSLog("\(logType) \(type): %@", messageFormat) } } diff --git a/Source/SocketPacket.swift b/Source/SocketPacket.swift index a34691e..95b3218 100644 --- a/Source/SocketPacket.swift +++ b/Source/SocketPacket.swift @@ -27,7 +27,7 @@ import Foundation struct SocketPacket { enum PacketType: Int { - case Connect, Disconnect, Event, Ack, Error, BinaryEvent, BinaryAck + case connect, disconnect, event, ack, error, binaryEvent, binaryAck } private let placeholders: Int @@ -38,11 +38,10 @@ struct SocketPacket { let id: Int let type: PacketType - var binary: [NSData] - var data: [AnyObject] - - var args: [AnyObject] { - if type == .Event || type == .BinaryEvent && data.count != 0 { + var binary: [Data] + var data: [Any] + var args: [Any] { + if type == .event || type == .binaryEvent && data.count != 0 { return Array(data.dropFirst()) } else { return data @@ -51,19 +50,19 @@ struct SocketPacket { var description: String { return "SocketPacket {type: \(String(type.rawValue)); data: " + - "\(String(data)); id: \(id); placeholders: \(placeholders); nsp: \(nsp)}" + "\(String(describing: data)); id: \(id); placeholders: \(placeholders); nsp: \(nsp)}" } var event: String { - return String(data[0]) + return String(describing: data[0]) } var packetString: String { return createPacketString() } - init(type: PacketType, data: [AnyObject] = [AnyObject](), id: Int = -1, - nsp: String, placeholders: Int = 0, binary: [NSData] = [NSData]()) { + init(type: PacketType, data: [Any] = [Any](), id: Int = -1, nsp: String, placeholders: Int = 0, + binary: [Data] = [Data]()) { self.data = data self.id = id self.nsp = nsp @@ -72,7 +71,7 @@ struct SocketPacket { self.binary = binary } - mutating func addData(data: NSData) -> Bool { + mutating func addData(_ data: Data) -> Bool { if placeholders == binary.count { return true } @@ -87,7 +86,7 @@ struct SocketPacket { } } - private func completeMessage(message: String) -> String { + private func completeMessage(_ message: String) -> String { let restOfMessage: String if data.count == 0 { @@ -96,7 +95,7 @@ struct SocketPacket { do { let jsonSend = try data.toJSON() - guard let jsonString = String(data: jsonSend, encoding: NSUTF8StringEncoding) else { return message + "[]" } + guard let jsonString = String(data: jsonSend, encoding: .utf8) else { return message + "[]" } restOfMessage = jsonString } catch { @@ -112,9 +111,9 @@ struct SocketPacket { private func createPacketString() -> String { let typeString = String(type.rawValue) // Binary count? - let binaryCountString = typeString + (type == .BinaryEvent || type == .BinaryAck ? String(binary.count) + "-" : "") + let binaryCountString = typeString + (type == .binaryEvent || type == .binaryAck ? "\(String(binary.count))-" : "") // Namespace? - let nspString = binaryCountString + (nsp != "/" ? nsp + "," : "") + let nspString = binaryCountString + (nsp != "/" ? "\(nsp)," : "") // Ack number? let idString = nspString + (id != -1 ? String(id) : "") @@ -132,18 +131,21 @@ struct SocketPacket { // If object is a collection it will recurse // Returns the object if it is not a placeholder or the corresponding // binary data - private func _fillInPlaceholders(object: AnyObject) -> AnyObject { + private func _fillInPlaceholders(_ object: Any) -> Any { switch object { - case let dict as NSDictionary: + case let dict as [String: Any]: if dict["_placeholder"] as? Bool ?? false { return binary[dict["num"] as! Int] } else { - return dict.reduce(NSMutableDictionary(), combine: {cur, keyValue in - cur[keyValue.0 as! NSCopying] = _fillInPlaceholders(keyValue.1) + return dict.reduce([String: Any](), {cur, keyValue in + var cur = cur + + cur[keyValue.0] = _fillInPlaceholders(keyValue.1) + return cur }) } - case let arr as [AnyObject]: + case let arr as [Any]: return arr.map(_fillInPlaceholders) default: return object @@ -152,22 +154,22 @@ struct SocketPacket { } extension SocketPacket { - private static func findType(binCount: Int, ack: Bool) -> PacketType { + private static func findType(_ binCount: Int, ack: Bool) -> PacketType { switch binCount { case 0 where !ack: - return .Event + return .event case 0 where ack: - return .Ack + return .ack case _ where !ack: - return .BinaryEvent + return .binaryEvent case _ where ack: - return .BinaryAck + return .binaryAck default: - return .Error + return .error } } - static func packetFromEmit(items: [AnyObject], id: Int, nsp: String, ack: Bool) -> SocketPacket { + static func packetFromEmit(_ items: [Any], id: Int, nsp: String, ack: Bool) -> SocketPacket { let (parsedData, binary) = deconstructData(items) let packet = SocketPacket(type: findType(binary.count, ack: ack), data: parsedData, id: id, nsp: nsp, binary: binary) @@ -178,19 +180,23 @@ extension SocketPacket { private extension SocketPacket { // Recursive function that looks for NSData in collections - static func shred(data: AnyObject, inout binary: [NSData]) -> AnyObject { - let placeholder = ["_placeholder": true, "num": binary.count] + static func shred(_ data: Any, binary: inout [Data]) -> Any { + let placeholder = ["_placeholder": true, "num": binary.count] as [String : Any] switch data { - case let bin as NSData: + case let bin as Data: binary.append(bin) + return placeholder - case let arr as [AnyObject]: + case let arr as [Any]: return arr.map({shred($0, binary: &binary)}) - case let dict as NSDictionary: - return dict.reduce(NSMutableDictionary(), combine: {cur, keyValue in - cur[keyValue.0 as! NSCopying] = shred(keyValue.1, binary: &binary) - return cur + case let dict as [String: Any]: + return dict.reduce([String: Any](), {cur, keyValue in + var mutCur = cur + + mutCur[keyValue.0] = shred(keyValue.1, binary: &binary) + + return mutCur }) default: return data @@ -199,8 +205,8 @@ private extension SocketPacket { // Removes binary data from emit data // Returns a type containing the de-binaryed data and the binary - static func deconstructData(data: [AnyObject]) -> ([AnyObject], [NSData]) { - var binary = [NSData]() + static func deconstructData(_ data: [Any]) -> ([Any], [Data]) { + var binary = [Data]() return (data.map({shred($0, binary: &binary)}), binary) } diff --git a/Source/SocketParsable.swift b/Source/SocketParsable.swift index b12ba30..7c9ce21 100644 --- a/Source/SocketParsable.swift +++ b/Source/SocketParsable.swift @@ -23,16 +23,16 @@ import Foundation protocol SocketParsable : SocketIOClientSpec { - func parseBinaryData(data: NSData) - func parseSocketMessage(message: String) + func parseBinaryData(_ data: Data) + func parseSocketMessage(_ message: String) } extension SocketParsable { - private func isCorrectNamespace(nsp: String) -> Bool { + private func isCorrectNamespace(_ nsp: String) -> Bool { return nsp == self.nsp } - private func handleConnect(packetNamespace: String) { + private func handleConnect(_ packetNamespace: String) { if packetNamespace == "/" && nsp != "/" { joinNamespace(nsp) } else { @@ -40,21 +40,21 @@ extension SocketParsable { } } - private func handlePacket(pack: SocketPacket) { + private func handlePacket(_ pack: SocketPacket) { switch pack.type { - case .Event where isCorrectNamespace(pack.nsp): + case .event where isCorrectNamespace(pack.nsp): handleEvent(pack.event, data: pack.args, isInternalMessage: false, withAck: pack.id) - case .Ack where isCorrectNamespace(pack.nsp): + case .ack where isCorrectNamespace(pack.nsp): handleAck(pack.id, data: pack.data) - case .BinaryEvent where isCorrectNamespace(pack.nsp): + case .binaryEvent where isCorrectNamespace(pack.nsp): waitingPackets.append(pack) - case .BinaryAck where isCorrectNamespace(pack.nsp): + case .binaryAck where isCorrectNamespace(pack.nsp): waitingPackets.append(pack) - case .Connect: + case .connect: handleConnect(pack.nsp) - case .Disconnect: - didDisconnect("Got Disconnect") - case .Error: + case .disconnect: + didDisconnect(reason: "Got Disconnect") + case .error: handleEvent("error", data: pack.data, isInternalMessage: true, withAck: pack.id) default: DefaultSocketLogger.Logger.log("Got invalid packet: %@", type: "SocketParser", args: pack.description) @@ -62,93 +62,93 @@ extension SocketParsable { } /// Parses a messsage from the engine. Returning either a string error or a complete SocketPacket - func parseString(message: String) -> Either { + func parseString(_ message: String) -> Either { var reader = SocketStringReader(message: message) - guard let type = SocketPacket.PacketType(rawValue: Int(reader.read(1)) ?? -1) else { - return .Left("Invalid packet type") + guard let type = Int(reader.read(count: 1)).flatMap({ SocketPacket.PacketType(rawValue: $0) }) else { + return .left("Invalid packet type") } if !reader.hasNext { - return .Right(SocketPacket(type: type, nsp: "/")) + return .right(SocketPacket(type: type, nsp: "/")) } var namespace = "/" var placeholders = -1 - if type == .BinaryEvent || type == .BinaryAck { - if let holders = Int(reader.readUntilStringOccurence("-")) { + if type == .binaryEvent || type == .binaryAck { + if let holders = Int(reader.readUntilOccurence(of: "-")) { placeholders = holders } else { - return .Left("Invalid packet") + return .left("Invalid packet") } } if reader.currentCharacter == "/" { - namespace = reader.readUntilStringOccurence(",") ?? reader.readUntilEnd() + namespace = reader.readUntilOccurence(of: ",") } if !reader.hasNext { - return .Right(SocketPacket(type: type, nsp: namespace, placeholders: placeholders)) + return .right(SocketPacket(type: type, nsp: namespace, placeholders: placeholders)) } var idString = "" - if type == .Error { - reader.advanceIndexBy(-1) + if type == .error { + reader.advance(by: -1) } else { while reader.hasNext { - if let int = Int(reader.read(1)) { + if let int = Int(reader.read(count: 1)) { idString += String(int) } else { - reader.advanceIndexBy(-2) + reader.advance(by: -2) break } } } - let d = message[reader.currentIndex.advancedBy(1).. Either { + private func parseData(_ data: String) -> Either { do { - return .Right(try data.toArray()) + return .right(try data.toArray()) } catch { - return .Left("Error parsing data for packet") + return .left("Error parsing data for packet") } } // Parses messages recieved - func parseSocketMessage(message: String) { + func parseSocketMessage(_ message: String) { guard !message.isEmpty else { return } DefaultSocketLogger.Logger.log("Parsing %@", type: "SocketParser", args: message) switch parseString(message) { - case let .Left(err): + case let .left(err): DefaultSocketLogger.Logger.error("\(err): %@", type: "SocketParser", args: message) - case let .Right(pack): + case let .right(pack): DefaultSocketLogger.Logger.log("Decoded packet as: %@", type: "SocketParser", args: pack.description) handlePacket(pack) } } - func parseBinaryData(data: NSData) { + func parseBinaryData(_ data: Data) { guard !waitingPackets.isEmpty else { DefaultSocketLogger.Logger.error("Got data when not remaking packet", type: "SocketParser") return @@ -159,9 +159,8 @@ extension SocketParsable { let packet = waitingPackets.removeLast() - if packet.type != .BinaryAck { - handleEvent(packet.event, data: packet.args ?? [], - isInternalMessage: false, withAck: packet.id) + if packet.type != .binaryAck { + handleEvent(packet.event, data: packet.args, isInternalMessage: false, withAck: packet.id) } else { handleAck(packet.id, data: packet.args) } diff --git a/Source/SocketStringReader.swift b/Source/SocketStringReader.swift index d1e2b59..8bdb4d4 100644 --- a/Source/SocketStringReader.swift +++ b/Source/SocketStringReader.swift @@ -38,31 +38,36 @@ struct SocketStringReader { currentIndex = message.startIndex } - mutating func advanceIndexBy(n: Int) { - currentIndex = currentIndex.advancedBy(n) + @discardableResult + mutating func advance(by: Int) -> String.Index { + currentIndex = message.characters.index(currentIndex, offsetBy: by) + + return currentIndex } - mutating func read(readLength: Int) -> String { - let readString = message[currentIndex.. String { + let readString = message[currentIndex.. String { + mutating func readUntilOccurence(of string: String) -> String { let substring = message[currentIndex.. String { - return read(currentIndex.distanceTo(message.endIndex)) + return read(count: message.characters.distance(from: currentIndex, to: message.endIndex)) } } diff --git a/Source/SocketTypes.swift b/Source/SocketTypes.swift index 7f27c42..42bf329 100644 --- a/Source/SocketTypes.swift +++ b/Source/SocketTypes.swift @@ -24,14 +24,29 @@ import Foundation -public typealias AckCallback = ([AnyObject]) -> Void -public typealias NormalCallback = ([AnyObject], SocketAckEmitter) -> Void -public typealias OnAckCallback = (timeoutAfter: UInt64, callback: AckCallback) -> Void +public protocol SocketData {} -typealias Probe = (msg: String, type: SocketEnginePacketType, data: [NSData]) +extension Array : SocketData {} +extension Bool : SocketData {} +extension Dictionary : SocketData {} +extension Double : SocketData {} +extension Int : SocketData {} +extension NSArray : SocketData {} +extension Data : SocketData {} +extension NSData : SocketData {} +extension NSDictionary : SocketData {} +extension NSString : SocketData {} +extension NSNull : SocketData {} +extension String : SocketData {} + +public typealias AckCallback = ([Any]) -> Void +public typealias NormalCallback = ([Any], SocketAckEmitter) -> Void +public typealias OnAckCallback = (_ timeoutAfter: UInt64, _ callback: @escaping AckCallback) -> Void + +typealias Probe = (msg: String, type: SocketEnginePacketType, data: [Data]) typealias ProbeWaitQueue = [Probe] enum Either { - case Left(E) - case Right(V) + case left(E) + case right(V) } diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index ce3c023..8b1b022 100644 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -28,51 +28,51 @@ public let WebsocketDidDisconnectNotification = "WebsocketDidDisconnectNotificat public let WebsocketDisconnectionErrorKeyName = "WebsocketDisconnectionErrorKeyName" public protocol WebSocketDelegate: class { - func websocketDidConnect(socket: WebSocket) - func websocketDidDisconnect(socket: WebSocket, error: NSError?) - func websocketDidReceiveMessage(socket: WebSocket, text: String) - func websocketDidReceiveData(socket: WebSocket, data: NSData) + func websocketDidConnect(_ socket: WebSocket) + func websocketDidDisconnect(_ socket: WebSocket, error: NSError?) + func websocketDidReceiveMessage(_ socket: WebSocket, text: String) + func websocketDidReceiveData(_ socket: WebSocket, data: Data) } public protocol WebSocketPongDelegate: class { - func websocketDidReceivePong(socket: WebSocket) + func websocketDidReceivePong(_ socket: WebSocket) } -public class WebSocket: NSObject, NSStreamDelegate { +public class WebSocket : NSObject, StreamDelegate { - enum OpCode: UInt8 { - case ContinueFrame = 0x0 - case TextFrame = 0x1 - case BinaryFrame = 0x2 + enum OpCode : UInt8 { + case continueFrame = 0x0 + case textFrame = 0x1 + case binaryFrame = 0x2 // 3-7 are reserved. - case ConnectionClose = 0x8 - case Ping = 0x9 - case Pong = 0xA + case connectionClose = 0x8 + case ping = 0x9 + case pong = 0xA // B-F reserved. } - public enum CloseCode: UInt16 { - case Normal = 1000 - case GoingAway = 1001 - case ProtocolError = 1002 - case ProtocolUnhandledType = 1003 + public enum CloseCode : UInt16 { + case normal = 1000 + case goingAway = 1001 + case protocolError = 1002 + case protocolUnhandledType = 1003 // 1004 reserved. - case NoStatusReceived = 1005 + case noStatusReceived = 1005 // 1006 reserved. - case Encoding = 1007 - case PolicyViolated = 1008 - case MessageTooBig = 1009 + case encoding = 1007 + case policyViolated = 1008 + case messageTooBig = 1009 } public static let ErrorDomain = "WebSocket" - enum InternalErrorCode: UInt16 { + enum InternalErrorCode : UInt16 { // 0-999 WebSocket status codes not used - case OutputStreamWriteError = 1 + case outputStreamWriteError = 1 } /// Where the callback is executed. It defaults to the main UI thread queue. - public var callbackQueue = dispatch_get_main_queue() + public var callbackQueue = DispatchQueue.main var optionalProtocols : [String]? @@ -101,7 +101,7 @@ public class WebSocket: NSObject, NSStreamDelegate { class WSResponse { var isFin = false - var code: OpCode = .ContinueFrame + var code: OpCode = .continueFrame var bytesLeft = 0 var frameCount = 0 var buffer: NSMutableData? @@ -122,7 +122,7 @@ public class WebSocket: NSObject, NSStreamDelegate { public var onConnect: ((Void) -> Void)? public var onDisconnect: ((NSError?) -> Void)? public var onText: ((String) -> Void)? - public var onData: ((NSData) -> Void)? + public var onData: ((Data) -> Void)? public var onPong: ((Void) -> Void)? public var headers = [String: String]() @@ -135,24 +135,25 @@ public class WebSocket: NSObject, NSStreamDelegate { public var isConnected :Bool { return connected } + public var currentURL: NSURL { return url } // MARK: - Private private var url: NSURL - private var inputStream: NSInputStream? - private var outputStream: NSOutputStream? + private var inputStream: InputStream? + private var outputStream: OutputStream? private var connected = false private var isConnecting = false - private var writeQueue = NSOperationQueue() + private var writeQueue = OperationQueue() private var readStack = [WSResponse]() - private var inputQueue = [NSData]() - private var fragBuffer: NSData? + private var inputQueue = [Data]() + private var fragBuffer: Data? private var certValidated = false private var didDisconnect = false private var readyToWrite = false private let mutex = NSLock() - private let notificationCenter = NSNotificationCenter.defaultCenter() + private let notificationCenter = NotificationCenter.default private var canDispatch: Bool { mutex.lock() let canWork = readyToWrite @@ -161,7 +162,7 @@ public class WebSocket: NSObject, NSStreamDelegate { } /// The shared processing queue used for all WebSocket. - private static let sharedWorkQueue = dispatch_queue_create("com.vluxe.starscream.websocket", DISPATCH_QUEUE_SERIAL) + private static let sharedWorkQueue = DispatchQueue(label: "com.vluxe.starscream.websocket") /// Used for setting protocols. public init(url: NSURL, protocols: [String]? = nil) { @@ -189,18 +190,18 @@ public class WebSocket: NSObject, NSStreamDelegate { - Parameter forceTimeout: Maximum time to wait for the server to close the socket. */ - public func disconnect(forceTimeout forceTimeout: NSTimeInterval? = nil) { + public func disconnect(forceTimeout: TimeInterval? = nil) { switch forceTimeout { - case .Some(let seconds) where seconds > 0: - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC))), callbackQueue) { [weak self] in - self?.disconnectStream(nil) + case .some(let seconds) where seconds > 0: + callbackQueue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(seconds * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { [weak self] in + self?.disconnectStream(error: nil) } fallthrough - case .None: - writeError(CloseCode.Normal.rawValue) + case .none: + writeError(code: CloseCode.normal.rawValue) default: - disconnectStream(nil) + disconnectStream(error: nil) break } } @@ -213,9 +214,9 @@ public class WebSocket: NSObject, NSStreamDelegate { - parameter str: The string to write. - parameter completion: The (optional) completion handler. */ - public func writeString(str: String, completion: (() -> ())? = nil) { + public func writeString(_ str: String, completion: (() -> ())? = nil) { guard isConnected else { return } - dequeueWrite(str.dataUsingEncoding(NSUTF8StringEncoding)!, code: .TextFrame, writeCompletion: completion) + dequeueWrite(data: str.data(using: String.Encoding.utf8)! as NSData, code: .textFrame, writeCompletion: completion) } /** @@ -226,54 +227,54 @@ public class WebSocket: NSObject, NSStreamDelegate { - parameter data: The data to write. - parameter completion: The (optional) completion handler. */ - public func writeData(data: NSData, completion: (() -> ())? = nil) { + public func writeData(_ data: Data, completion: (() -> ())? = nil) { guard isConnected else { return } - dequeueWrite(data, code: .BinaryFrame, writeCompletion: completion) + dequeueWrite(data: data as NSData, code: .binaryFrame, writeCompletion: completion) } // Write a ping to the websocket. This sends it as a control frame. // Yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s public func writePing(data: NSData, completion: (() -> ())? = nil) { guard isConnected else { return } - dequeueWrite(data, code: .Ping, writeCompletion: completion) + dequeueWrite(data: data, code: .ping, writeCompletion: completion) } /// Private method that starts the connection. private func createHTTPRequest() { - let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET", + let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET" as CFString, url, kCFHTTPVersion1_1).takeRetainedValue() var port = url.port if port == nil { - if supportedSSLSchemes.contains(url.scheme) { + if supportedSSLSchemes.contains(url.scheme!) { port = 443 } else { port = 80 } } - addHeader(urlRequest, key: headerWSUpgradeName, val: headerWSUpgradeValue) - addHeader(urlRequest, key: headerWSConnectionName, val: headerWSConnectionValue) + addHeader(urlRequest, key: headerWSUpgradeName as NSString, val: headerWSUpgradeValue as NSString) + addHeader(urlRequest, key: headerWSConnectionName as NSString, val: headerWSConnectionValue as NSString) if let protocols = optionalProtocols { - addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joinWithSeparator(",")) + addHeader(urlRequest, key: headerWSProtocolName as NSString, val: protocols.joined(separator: ",") as NSString) } - addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue) - addHeader(urlRequest, key: headerWSKeyName, val: generateWebSocketKey()) + addHeader(urlRequest, key: headerWSVersionName as NSString, val: headerWSVersionValue as NSString) + addHeader(urlRequest, key: headerWSKeyName as NSString, val: generateWebSocketKey() as NSString) if let origin = origin { - addHeader(urlRequest, key: headerOriginName, val: origin) + addHeader(urlRequest, key: headerOriginName as NSString, val: origin as NSString) } - addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)") + addHeader(urlRequest, key: headerWSHostName as NSString, val: "\(url.host!):\(port!)" as NSString) for (key,value) in headers { - addHeader(urlRequest, key: key, val: value) + addHeader(urlRequest, key: key as NSString, val: value as NSString) } if let cfHTTPMessage = CFHTTPMessageCopySerializedMessage(urlRequest) { let serializedRequest = cfHTTPMessage.takeRetainedValue() - initStreamsWithData(serializedRequest, Int(port!)) + initStreamsWithData(data: serializedRequest as NSData, Int(port!)) } } /// Add a header to the CFHTTPMessage by using the NSString bridges to CFString. - private func addHeader(urlRequest: CFHTTPMessage, key: NSString, val: NSString) { + private func addHeader(_ urlRequest: CFHTTPMessage, key: NSString, val: NSString) { CFHTTPMessageSetHeaderFieldValue(urlRequest, key, val) } @@ -283,10 +284,10 @@ public class WebSocket: NSObject, NSStreamDelegate { let seed = 16 for _ in 0..? var writeStream: Unmanaged? - let h: NSString = url.host! - CFStreamCreatePairWithSocketToHost(nil, h, UInt32(port), &readStream, &writeStream) + let h = url.host! + CFStreamCreatePairWithSocketToHost(nil, h as NSString, UInt32(port), &readStream, &writeStream) inputStream = readStream!.takeRetainedValue() outputStream = writeStream!.takeRetainedValue() guard let inStream = inputStream, let outStream = outputStream else { return } inStream.delegate = self outStream.delegate = self - if supportedSSLSchemes.contains(url.scheme) { - inStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey) - outStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey) + if supportedSSLSchemes.contains(url.scheme!) { + inStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey) + outStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey) } else { certValidated = true //not a https session, so no need to check SSL pinning } if voipEnabled { - inStream.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType) - outStream.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType) + inStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType) + outStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType) } if selfSignedSSL { - let settings: [NSObject: NSObject] = [kCFStreamSSLValidatesCertificateChain: NSNumber(bool: false), kCFStreamSSLPeerName: kCFNull] - inStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) - outStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) + let settings: [NSObject: NSObject] = [kCFStreamSSLValidatesCertificateChain: NSNumber(value: false), kCFStreamSSLPeerName: kCFNull] + inStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey) + outStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey) } if let cipherSuites = self.enabledSSLCipherSuites { - if let sslContextIn = CFReadStreamCopyProperty(inputStream, kCFStreamPropertySSLContext) as! SSLContextRef?, - sslContextOut = CFWriteStreamCopyProperty(outputStream, kCFStreamPropertySSLContext) as! SSLContextRef? { + if let sslContextIn = CFReadStreamCopyProperty(inputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext?, + let sslContextOut = CFWriteStreamCopyProperty(outputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext? { let resIn = SSLSetEnabledCiphers(sslContextIn, cipherSuites, cipherSuites.count) let resOut = SSLSetEnabledCiphers(sslContextOut, cipherSuites, cipherSuites.count) if resIn != errSecSuccess { let error = self.errorWithDetail("Error setting ingoing cypher suites", code: UInt16(resIn)) - disconnectStream(error) + disconnectStream(error: error) return } if resOut != errSecSuccess { let error = self.errorWithDetail("Error setting outgoing cypher suites", code: UInt16(resOut)) - disconnectStream(error) + disconnectStream(error: error) return } } @@ -345,15 +346,15 @@ public class WebSocket: NSObject, NSStreamDelegate { self.readyToWrite = true self.mutex.unlock() - let bytes = UnsafePointer(data.bytes) + let bytes = UnsafeRawPointer(data.bytes).assumingMemoryBound(to: UInt8.self) var out = timeout * 1000000 // wait 5 seconds before giving up - writeQueue.addOperationWithBlock { [weak self] in + writeQueue.addOperation { [weak self] in while !outStream.hasSpaceAvailable { usleep(100) // wait until the socket is ready out -= 100 if out < 0 { self?.cleanupStream() - self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2)) + self?.doDisconnect(error: self?.errorWithDetail("write wait timed out", code: 2)) return } else if outStream.streamError != nil { return // disconnectStream will be called. @@ -364,29 +365,26 @@ public class WebSocket: NSObject, NSStreamDelegate { } // Delegate for the stream methods. Processes incoming bytes. - public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) { - - if let sec = security where !certValidated && [.HasBytesAvailable, .HasSpaceAvailable].contains(eventCode) { - let possibleTrust: AnyObject? = aStream.propertyForKey(kCFStreamPropertySSLPeerTrust as String) - if let trust: AnyObject = possibleTrust { - let domain: AnyObject? = aStream.propertyForKey(kCFStreamSSLPeerName as String) - if sec.isValid(trust as! SecTrustRef, domain: domain as! String?) { - certValidated = true - } else { - let error = errorWithDetail("Invalid SSL certificate", code: 1) - disconnectStream(error) - return - } + public func stream(_ aStream: Stream, handle eventCode: Stream.Event) { + if let sec = security , !certValidated && [.hasBytesAvailable, .hasSpaceAvailable].contains(eventCode) { + let possibleTrust = aStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) + let domain = aStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String + if sec.isValid(possibleTrust as! SecTrust, domain: domain) { + certValidated = true + } else { + let error = errorWithDetail("Invalid SSL certificate", code: 1) + disconnectStream(error: error) + return } } - if eventCode == .HasBytesAvailable { + if eventCode == .hasBytesAvailable { if aStream == inputStream { processInputStream() } - } else if eventCode == .ErrorOccurred { - disconnectStream(aStream.streamError) - } else if eventCode == .EndEncountered { - disconnectStream(nil) + } else if eventCode == .errorOccurred { + disconnectStream(error: aStream.streamError as NSError?) + } else if eventCode == .endEncountered { + disconnectStream(error: nil) } } @@ -398,7 +396,7 @@ public class WebSocket: NSObject, NSStreamDelegate { writeQueue.cancelAllOperations() } cleanupStream() - doDisconnect(error) + doDisconnect(error: error) } private func cleanupStream() { @@ -419,7 +417,7 @@ public class WebSocket: NSObject, NSStreamDelegate { /// Handles the incoming bytes and sending them to the proper processing method. private func processInputStream() { let buf = NSMutableData(capacity: BUFFER_MAX) - let buffer = UnsafeMutablePointer(buf!.bytes) + let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self) let length = inputStream!.read(buffer, maxLength: BUFFER_MAX) guard length > 0 else { return } @@ -427,7 +425,7 @@ public class WebSocket: NSObject, NSStreamDelegate { if inputQueue.count == 0 { process = true } - inputQueue.append(NSData(bytes: buffer, length: length)) + inputQueue.append(Data(bytes: UnsafeRawPointer(buffer).assumingMemoryBound(to: UInt8.self), count: length)) if process { dequeueInput() } @@ -440,14 +438,14 @@ public class WebSocket: NSObject, NSStreamDelegate { var work = data if let fragBuffer = fragBuffer { let combine = NSMutableData(data: fragBuffer) - combine.appendData(data) - work = combine + combine.append(data) + work = combine as Data self.fragBuffer = nil } - let buffer = UnsafePointer(work.bytes) - let length = work.length + let buffer = UnsafeRawPointer((work as NSData).bytes).assumingMemoryBound(to: UInt8.self) + let length = work.count if !connected { - processTCPHandshake(buffer, bufferLen: length) + processTCPHandshake(buffer: buffer, bufferLen: length) } else { processRawMessagesInBuffer(buffer, bufferLen: length) } @@ -457,22 +455,22 @@ public class WebSocket: NSObject, NSStreamDelegate { // Handle checking the initial connection status. private func processTCPHandshake(buffer: UnsafePointer, bufferLen: Int) { - let code = processHTTP(buffer, bufferLen: bufferLen) + let code = processHTTP(buffer: buffer, bufferLen: bufferLen) switch code { case 0: connected = true guard canDispatch else {return} - dispatch_async(callbackQueue) { [weak self] in + callbackQueue.async { [weak self] in guard let s = self else { return } s.onConnect?() s.delegate?.websocketDidConnect(s) - s.notificationCenter.postNotificationName(WebsocketDidConnectNotification, object: self) + s.notificationCenter.post(name: NSNotification.Name(WebsocketDidConnectNotification), object: self) } case -1: - fragBuffer = NSData(bytes: buffer, length: bufferLen) + fragBuffer = NSData(bytes: buffer, length: bufferLen) as Data break // do nothing, we are going to collect more data default: - doDisconnect(errorWithDetail("Invalid HTTP upgrade", code: UInt16(code))) + doDisconnect(error: errorWithDetail("Invalid HTTP upgrade", code: UInt16(code))) } } @@ -493,7 +491,7 @@ public class WebSocket: NSObject, NSStreamDelegate { } } if totalSize > 0 { - let code = validateResponse(buffer, bufferLen: totalSize) + let code = validateResponse(buffer: buffer, bufferLen: totalSize) if code != 0 { return code } @@ -517,7 +515,7 @@ public class WebSocket: NSObject, NSStreamDelegate { } if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) { let headers = cfHeaders.takeRetainedValue() as NSDictionary - if let acceptKey = headers[headerWSAcceptName] as? NSString { + if let acceptKey = headers[headerWSAcceptName as NSString] as? NSString { if acceptKey.length > 0 { return 0 } @@ -554,16 +552,16 @@ public class WebSocket: NSObject, NSStreamDelegate { } /// Process one message at the start of `buffer`. Return another buffer (sharing storage) that contains the leftover contents of `buffer` that I didn't process. - @warn_unused_result + private func processOneRawMessage(inBuffer buffer: UnsafeBufferPointer) -> UnsafeBufferPointer { let response = readStack.last - let baseAddress = buffer.baseAddress + guard let baseAddress = buffer.baseAddress else { fatalError("") } let bufferLen = buffer.count if response != nil && bufferLen < 2 { - fragBuffer = NSData(buffer: buffer) + fragBuffer = Data(buffer: buffer) return emptyBuffer } - if let response = response where response.bytesLeft > 0 { + if let response = response, response.bytesLeft > 0 { var len = response.bytesLeft var extra = bufferLen - response.bytesLeft if response.bytesLeft > bufferLen { @@ -571,8 +569,8 @@ public class WebSocket: NSObject, NSStreamDelegate { extra = 0 } response.bytesLeft -= len - response.buffer?.appendData(NSData(bytes: baseAddress, length: len)) - processResponse(response) + response.buffer?.append(Data(bytes: baseAddress, count: len)) + _ = processResponse(response) return buffer.fromOffset(bufferLen - extra) } else { let isFin = (FinMask & baseAddress[0]) @@ -580,34 +578,34 @@ public class WebSocket: NSObject, NSStreamDelegate { let isMasked = (MaskMask & baseAddress[1]) let payloadLen = (PayloadLenMask & baseAddress[1]) var offset = 2 - if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .Pong { - let errCode = CloseCode.ProtocolError.rawValue - doDisconnect(errorWithDetail("masked and rsv data is not currently supported", code: errCode)) - writeError(errCode) + if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .pong { + let errCode = CloseCode.protocolError.rawValue + doDisconnect(error: errorWithDetail("masked and rsv data is not currently supported", code: errCode)) + writeError(code: errCode) return emptyBuffer } - let isControlFrame = (receivedOpcode == .ConnectionClose || receivedOpcode == .Ping) - if !isControlFrame && (receivedOpcode != .BinaryFrame && receivedOpcode != .ContinueFrame && - receivedOpcode != .TextFrame && receivedOpcode != .Pong) { - let errCode = CloseCode.ProtocolError.rawValue - doDisconnect(errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode)) - writeError(errCode) + let isControlFrame = (receivedOpcode == .connectionClose || receivedOpcode == .ping) + if !isControlFrame && (receivedOpcode != .binaryFrame && receivedOpcode != .continueFrame && + receivedOpcode != .textFrame && receivedOpcode != .pong) { + let errCode = CloseCode.protocolError.rawValue + doDisconnect(error: errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode)) + writeError(code: errCode) return emptyBuffer } if isControlFrame && isFin == 0 { - let errCode = CloseCode.ProtocolError.rawValue - doDisconnect(errorWithDetail("control frames can't be fragmented", code: errCode)) - writeError(errCode) + let errCode = CloseCode.protocolError.rawValue + doDisconnect(error: errorWithDetail("control frames can't be fragmented", code: errCode)) + writeError(code: errCode) return emptyBuffer } - if receivedOpcode == .ConnectionClose { - var code = CloseCode.Normal.rawValue + if receivedOpcode == .connectionClose { + var code = CloseCode.normal.rawValue if payloadLen == 1 { - code = CloseCode.ProtocolError.rawValue + code = CloseCode.protocolError.rawValue } else if payloadLen > 1 { - code = WebSocket.readUint16(baseAddress, offset: offset) + code = WebSocket.readUint16(buffer: baseAddress, offset: offset) if code < 1000 || (code > 1003 && code < 1007) || (code > 1011 && code < 3000) { - code = CloseCode.ProtocolError.rawValue + code = CloseCode.protocolError.rawValue } offset += 2 } @@ -616,47 +614,47 @@ public class WebSocket: NSObject, NSStreamDelegate { let len = Int(payloadLen - 2) if len > 0 { let bytes = baseAddress + offset - if let customCloseReason = String(data: NSData(bytes: bytes, length: len), encoding: NSUTF8StringEncoding) { + if let customCloseReason = String(data: NSData(bytes: bytes, length: len) as Data, encoding: String.Encoding.utf8) { closeReason = customCloseReason } else { - code = CloseCode.ProtocolError.rawValue + code = CloseCode.protocolError.rawValue } } } - doDisconnect(errorWithDetail(closeReason, code: code)) - writeError(code) + doDisconnect(error: errorWithDetail(closeReason, code: code)) + writeError(code: code) return emptyBuffer } if isControlFrame && payloadLen > 125 { - writeError(CloseCode.ProtocolError.rawValue) + writeError(code: CloseCode.protocolError.rawValue) return emptyBuffer } var dataLength = UInt64(payloadLen) if dataLength == 127 { - dataLength = WebSocket.readUint64(baseAddress, offset: offset) - offset += sizeof(UInt64) + dataLength = WebSocket.readUint64(buffer: baseAddress, offset: offset) + offset += MemoryLayout.size } else if dataLength == 126 { - dataLength = UInt64(WebSocket.readUint16(baseAddress, offset: offset)) - offset += sizeof(UInt16) + dataLength = UInt64(WebSocket.readUint16(buffer: baseAddress, offset: offset)) + offset += MemoryLayout.size } if bufferLen < offset || UInt64(bufferLen - offset) < dataLength { - fragBuffer = NSData(bytes: baseAddress, length: bufferLen) + fragBuffer = Data(bytes: baseAddress, count: bufferLen) return emptyBuffer } var len = dataLength if dataLength > UInt64(bufferLen) { len = UInt64(bufferLen-offset) } - let data: NSData + let data: Data if len < 0 { len = 0 - data = NSData() + data = Data() } else { - data = NSData(bytes: baseAddress+offset, length: Int(len)) + data = Data(bytes: baseAddress+offset, count: Int(len)) } - if receivedOpcode == .Pong { + if receivedOpcode == .pong { if canDispatch { - dispatch_async(callbackQueue) { [weak self] in + callbackQueue.async { [weak self] in guard let s = self else { return } s.onPong?() s.pongDelegate?.websocketDidReceivePong(s) @@ -668,19 +666,19 @@ public class WebSocket: NSObject, NSStreamDelegate { if isControlFrame { response = nil // Don't append pings. } - if isFin == 0 && receivedOpcode == .ContinueFrame && response == nil { - let errCode = CloseCode.ProtocolError.rawValue - doDisconnect(errorWithDetail("continue frame before a binary or text frame", code: errCode)) - writeError(errCode) + if isFin == 0 && receivedOpcode == .continueFrame && response == nil { + let errCode = CloseCode.protocolError.rawValue + doDisconnect(error: errorWithDetail("continue frame before a binary or text frame", code: errCode)) + writeError(code: errCode) return emptyBuffer } var isNew = false if response == nil { - if receivedOpcode == .ContinueFrame { - let errCode = CloseCode.ProtocolError.rawValue - doDisconnect(errorWithDetail("first frame can't be a continue frame", - code: errCode)) - writeError(errCode) + if receivedOpcode == .continueFrame { + let errCode = CloseCode.protocolError.rawValue + doDisconnect(error: errorWithDetail("first frame can't be a continue frame", + code: errCode)) + writeError(code: errCode) return emptyBuffer } isNew = true @@ -689,16 +687,16 @@ public class WebSocket: NSObject, NSStreamDelegate { response!.bytesLeft = Int(dataLength) response!.buffer = NSMutableData(data: data) } else { - if receivedOpcode == .ContinueFrame { + if receivedOpcode == .continueFrame { response!.bytesLeft = Int(dataLength) } else { - let errCode = CloseCode.ProtocolError.rawValue - doDisconnect(errorWithDetail("second and beyond of fragment message must be a continue frame", - code: errCode)) - writeError(errCode) + let errCode = CloseCode.protocolError.rawValue + doDisconnect(error: errorWithDetail("second and beyond of fragment message must be a continue frame", + code: errCode)) + writeError(code: errCode) return emptyBuffer } - response!.buffer!.appendData(data) + response!.buffer!.append(data) } if let response = response { response.bytesLeft -= Int(len) @@ -707,7 +705,7 @@ public class WebSocket: NSObject, NSStreamDelegate { if isNew { readStack.append(response) } - processResponse(response) + _ = processResponse(response) } let step = Int(offset + numericCast(len)) @@ -716,42 +714,42 @@ public class WebSocket: NSObject, NSStreamDelegate { } /// Process all messages in the buffer if possible. - private func processRawMessagesInBuffer(pointer: UnsafePointer, bufferLen: Int) { + private func processRawMessagesInBuffer(_ pointer: UnsafePointer, bufferLen: Int) { var buffer = UnsafeBufferPointer(start: pointer, count: bufferLen) repeat { buffer = processOneRawMessage(inBuffer: buffer) } while buffer.count >= 2 if buffer.count > 0 { - fragBuffer = NSData(buffer: buffer) + fragBuffer = Data(buffer: buffer) } } /// Process the finished response of a buffer. - private func processResponse(response: WSResponse) -> Bool { + private func processResponse(_ response: WSResponse) -> Bool { if response.isFin && response.bytesLeft <= 0 { - if response.code == .Ping { + if response.code == .ping { let data = response.buffer! // local copy so it's not perverse for writing - dequeueWrite(data, code: OpCode.Pong) - } else if response.code == .TextFrame { - let str: NSString? = NSString(data: response.buffer!, encoding: NSUTF8StringEncoding) + dequeueWrite(data: data, code: OpCode.pong) + } else if response.code == .textFrame { + let str: NSString? = NSString(data: response.buffer! as Data, encoding: String.Encoding.utf8.rawValue) if str == nil { - writeError(CloseCode.Encoding.rawValue) + writeError(code: CloseCode.encoding.rawValue) return false } if canDispatch { - dispatch_async(callbackQueue) { [weak self] in + callbackQueue.async { [weak self] in guard let s = self else { return } s.onText?(str! as String) s.delegate?.websocketDidReceiveMessage(s, text: str! as String) } } - } else if response.code == .BinaryFrame { + } else if response.code == .binaryFrame { if canDispatch { let data = response.buffer! //local copy so it's not perverse for writing - dispatch_async(callbackQueue) { [weak self] in + callbackQueue.async { [weak self] in guard let s = self else { return } - s.onData?(data) - s.delegate?.websocketDidReceiveData(s, data: data) + s.onData?(data as Data) + s.delegate?.websocketDidReceiveData(s, data: data as Data) } } } @@ -762,7 +760,7 @@ public class WebSocket: NSObject, NSStreamDelegate { } /// Create an error. - private func errorWithDetail(detail: String, code: UInt16) -> NSError { + private func errorWithDetail(_ detail: String, code: UInt16) -> NSError { var details = [String: String]() details[NSLocalizedDescriptionKey] = detail return NSError(domain: WebSocket.ErrorDomain, code: Int(code), userInfo: details) @@ -770,64 +768,64 @@ public class WebSocket: NSObject, NSStreamDelegate { /// Write a an error to the socket. private func writeError(code: UInt16) { - let buf = NSMutableData(capacity: sizeof(UInt16)) - let buffer = UnsafeMutablePointer(buf!.bytes) - WebSocket.writeUint16(buffer, offset: 0, value: code) - dequeueWrite(NSData(bytes: buffer, length: sizeof(UInt16)), code: .ConnectionClose) + let buf = NSMutableData(capacity: MemoryLayout.size) + let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self) + WebSocket.writeUint16(buffer: buffer, offset: 0, value: code) + dequeueWrite(data: Data(bytes: buffer, count: MemoryLayout.size) as NSData, code: .connectionClose) } /// Used to write things to the stream. private func dequeueWrite(data: NSData, code: OpCode, writeCompletion: (() -> ())? = nil) { - writeQueue.addOperationWithBlock { [weak self] in + writeQueue.addOperation { [weak self] in //stream isn't ready, let's wait guard let s = self else { return } var offset = 2 - let bytes = UnsafeMutablePointer(data.bytes) + let bytes = UnsafeMutableRawPointer(mutating: (data as NSData).bytes).assumingMemoryBound(to: UInt8.self) let dataLength = data.length let frame = NSMutableData(capacity: dataLength + s.MaxFrameSize) - let buffer = UnsafeMutablePointer(frame!.mutableBytes) + let buffer = UnsafeMutableRawPointer(frame!.mutableBytes).assumingMemoryBound(to: UInt8.self) buffer[0] = s.FinMask | code.rawValue if dataLength < 126 { buffer[1] = CUnsignedChar(dataLength) } else if dataLength <= Int(UInt16.max) { buffer[1] = 126 - WebSocket.writeUint16(buffer, offset: offset, value: UInt16(dataLength)) - offset += sizeof(UInt16) + WebSocket.writeUint16(buffer: buffer, offset: offset, value: UInt16(dataLength)) + offset += MemoryLayout.size } else { buffer[1] = 127 - WebSocket.writeUint64(buffer, offset: offset, value: UInt64(dataLength)) - offset += sizeof(UInt64) + WebSocket.writeUint64(buffer: buffer, offset: offset, value: UInt64(dataLength)) + offset += MemoryLayout.size } buffer[1] |= s.MaskMask let maskKey = UnsafeMutablePointer(buffer + offset) - SecRandomCopyBytes(kSecRandomDefault, Int(sizeof(UInt32)), maskKey) - offset += sizeof(UInt32) + _ = SecRandomCopyBytes(kSecRandomDefault, Int(MemoryLayout.size), maskKey) + offset += MemoryLayout.size for i in 0...size] offset += 1 } var total = 0 while true { guard let outStream = s.outputStream else { break } - let writeBuffer = UnsafePointer(frame!.bytes+total) + let writeBuffer = UnsafeRawPointer(frame!.bytes+total).assumingMemoryBound(to: UInt8.self) let len = outStream.write(writeBuffer, maxLength: offset-total) if len < 0 { var error: NSError? if let streamError = outStream.streamError { - error = streamError + error = streamError as NSError } else { - let errCode = InternalErrorCode.OutputStreamWriteError.rawValue + let errCode = InternalErrorCode.outputStreamWriteError.rawValue error = s.errorWithDetail("output stream error during write", code: errCode) } - s.doDisconnect(error) + s.doDisconnect(error: error) break } else { total += len } if total >= offset { - if let callbackQueue = self?.callbackQueue, callback = writeCompletion { - dispatch_async(callbackQueue) { + if let callbackQueue = self?.callbackQueue, let callback = writeCompletion { + callbackQueue.async { callback() } } @@ -845,12 +843,12 @@ public class WebSocket: NSObject, NSStreamDelegate { didDisconnect = true connected = false guard canDispatch else {return} - dispatch_async(callbackQueue) { [weak self] in + callbackQueue.async { [weak self] in guard let s = self else { return } s.onDisconnect?(error) s.delegate?.websocketDidDisconnect(s, error: error) let userInfo = error.map{ [WebsocketDisconnectionErrorKeyName: $0] } - s.notificationCenter.postNotificationName(WebsocketDidDisconnectNotification, object: self, userInfo: userInfo) + s.notificationCenter.post(name: NSNotification.Name(rawValue: WebsocketDidDisconnectNotification), object: self, userInfo: userInfo) } } @@ -865,18 +863,18 @@ public class WebSocket: NSObject, NSStreamDelegate { } -private extension NSData { +private extension Data { - convenience init(buffer: UnsafeBufferPointer) { - self.init(bytes: buffer.baseAddress, length: buffer.count) + init(buffer: UnsafeBufferPointer) { + self.init(bytes: buffer.baseAddress!, count: buffer.count) } } private extension UnsafeBufferPointer { - func fromOffset(offset: Int) -> UnsafeBufferPointer { - return UnsafeBufferPointer(start: baseAddress.advancedBy(offset), count: count - offset) + func fromOffset(_ offset: Int) -> UnsafeBufferPointer { + return UnsafeBufferPointer(start: baseAddress!.advanced(by: offset), count: count - offset) } }