diff --git a/README.md b/README.md index f810761..6b40249 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ SwiftWebSocket passes all 521 of the Autobahn's fuzzing tests, including strict - Strict UTF-8 processing. - `binaryType` property to choose between `[UInt8]` or `NSData` messages. - Zero asserts. All networking, stream, and protocol errors are routed through the `error` event. +- Objective-C compatibility. ## Example diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index 5eddc0b..064a046 100644 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -386,18 +386,18 @@ private struct z_stream { var next_in : UnsafePointer = nil var avail_in : CUnsignedInt = 0 var total_in : CUnsignedLong = 0 - + var next_out : UnsafeMutablePointer = nil var avail_out : CUnsignedInt = 0 var total_out : CUnsignedLong = 0 - + var msg : UnsafePointer = nil var state : COpaquePointer = nil - + var zalloc : COpaquePointer = nil var zfree : COpaquePointer = nil var opaque : COpaquePointer = nil - + var data_type : CInt = 0 var adler : CUnsignedLong = 0 var reserved : CUnsignedLong = 0 @@ -491,6 +491,24 @@ private class Deflater { } } +/// WebSocketDelegate is an Objective-C alternative to WebSocketEvents and is used to delegate the events for the WebSocket connection. +@objc public protocol WebSocketDelegate { + /// A function to be called when the WebSocket connection's readyState changes to .Open; this indicates that the connection is ready to send and receive data. + func webSocketOpen() + /// A function to be called when the WebSocket connection's readyState changes to .Closed. + func webSocketClose(code: Int, reason: String, wasClean: Bool) + /// A function to be called when an error occurs. + func webSocketError(error: NSError) + /// A function to be called when a message (string) is received from the server. + optional func webSocketMessageText(text: String) + /// A function to be called when a message (binary) is received from the server. + optional func webSocketMessageData(data: NSData) + /// A function to be called when a pong is received from the server. + optional func webSocketPong() + /// A function to be called when the WebSocket process has ended; this event is guarenteed to be called once and can be used as an alternative to the "close" or "error" events. + optional func webSocketEnd(code: Int, reason: String, wasClean: Bool, error: NSError?) +} + /// WebSocket objects are bidirectional network streams that communicate over HTTP. RFC 6455. private class InnerWebSocket: Hashable { var id : Int @@ -517,10 +535,11 @@ private class InnerWebSocket: Hashable { var _allowSelfSignedSSL = false var _services = WebSocketService.None var _event = WebSocketEvents() + var _eventDelegate: WebSocketDelegate? var _binaryType = WebSocketBinaryType.UInt8Array var _readyState = WebSocketReadyState.Connecting var _networkTimeout = NSTimeInterval(-1) - + var url : String { return request.URL!.description } @@ -547,6 +566,10 @@ private class InnerWebSocket: Hashable { get { lock(); defer { unlock() }; return _event } set { lock(); defer { unlock() }; _event = newValue } } + var eventDelegate : WebSocketDelegate? { + get { lock(); defer { unlock() }; return _eventDelegate } + set { lock(); defer { unlock() }; _eventDelegate = newValue } + } var eventQueue : dispatch_queue_t? { get { lock(); defer { unlock() }; return _eventQueue; } set { lock(); defer { unlock() }; _eventQueue = newValue } @@ -562,7 +585,7 @@ private class InnerWebSocket: Hashable { get { lock(); defer { unlock() }; return _readyState } set { lock(); defer { unlock() }; _readyState = newValue } } - + func copyOpen(request: NSURLRequest, subProtocols : [String] = []) -> InnerWebSocket{ let ws = InnerWebSocket(request: request, subProtocols: subProtocols, stub: false) ws.compression = compression @@ -573,9 +596,9 @@ private class InnerWebSocket: Hashable { ws.binaryType = binaryType return ws } - + var hashValue: Int { return id } - + init(request: NSURLRequest, subProtocols : [String] = [], stub : Bool = false){ pthread_mutex_init(&mutex, nil) self.id = manager.nextId() @@ -611,7 +634,7 @@ private class InnerWebSocket: Hashable { @inline(__always) private func unlock(){ pthread_mutex_unlock(&mutex) } - + private var dirty : Bool { lock() defer { unlock() } @@ -674,6 +697,7 @@ private class InnerWebSocket: Hashable { privateReadyState = .Open fire { self.event.open() + self.eventDelegate?.webSocketOpen() } stage = .HandleFrames case .HandleFrames: @@ -688,13 +712,19 @@ private class InnerWebSocket: Hashable { case .Text: fire { self.event.message(data: frame.utf8.text) + self.eventDelegate?.webSocketMessageText?(frame.utf8.text) } case .Binary: fire { switch self.binaryType { - case .UInt8Array: self.event.message(data: frame.payload.array) - case .NSData: self.event.message(data: frame.payload.nsdata) - case .UInt8UnsafeBufferPointer: self.event.message(data: frame.payload.buffer) + case .UInt8Array: + self.event.message(data: frame.payload.array) + case .NSData: + self.event.message(data: frame.payload.nsdata) + // The WebSocketDelegate is necessary to add Objective-C compability and it is only possible to send binary data with NSData. + self.eventDelegate?.webSocketMessageData?(frame.payload.nsdata) + case .UInt8UnsafeBufferPointer: + self.event.message(data: frame.payload.buffer) } } case .Ping: @@ -706,10 +736,14 @@ private class InnerWebSocket: Hashable { case .Pong: fire { switch self.binaryType { - case .UInt8Array: self.event.pong(data: frame.payload.array) - case .NSData: self.event.pong(data: frame.payload.nsdata) - case .UInt8UnsafeBufferPointer: self.event.pong(data: frame.payload.buffer) + case .UInt8Array: + self.event.pong(data: frame.payload.array) + case .NSData: + self.event.pong(data: frame.payload.nsdata) + case .UInt8UnsafeBufferPointer: + self.event.pong(data: frame.payload.buffer) } + self.eventDelegate?.webSocketPong?() } case .Close: lock() @@ -721,24 +755,27 @@ private class InnerWebSocket: Hashable { case .CloseConn: if let error = finalError { self.event.error(error: error) + self.eventDelegate?.webSocketError(error as NSError) } privateReadyState = .Closed if rd != nil { closeConn() fire { self.event.close(code: Int(self.closeCode), reason: self.closeReason, wasClean: self.closeFinal) + self.eventDelegate?.webSocketClose(Int(self.closeCode), reason: self.closeReason, wasClean: self.closeFinal) } } stage = .End case .End: fire { self.event.end(code: Int(self.closeCode), reason: self.closeReason, wasClean: self.closeClean, error: self.finalError) + self.eventDelegate?.webSocketEnd?(Int(self.closeCode), reason: self.closeReason, wasClean: self.closeClean, error: self.finalError as? NSError) } exit = true manager.remove(self) } } catch WebSocketError.NeedMoreInput { - + } catch { if finalError != nil { return @@ -865,7 +902,7 @@ private class InnerWebSocket: Hashable { block() } } - + var readStateSaved = false var readStateFrame : Frame? var readStateFinished = false @@ -920,7 +957,7 @@ private class InnerWebSocket: Hashable { readStateFinished = false return frame } - + func closeConn() { rd.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode) wr.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode) @@ -929,14 +966,14 @@ private class InnerWebSocket: Hashable { rd.close() wr.close() } - + func openConn() throws { let req = request.mutableCopy() as! NSMutableURLRequest req.setValue("websocket", forHTTPHeaderField: "Upgrade") req.setValue("Upgrade", forHTTPHeaderField: "Connection") req.setValue("SwiftWebSocket", forHTTPHeaderField: "User-Agent") req.setValue("13", forHTTPHeaderField: "Sec-WebSocket-Version") - + if req.URL == nil || req.URL!.host == nil{ throw WebSocketError.InvalidAddress } @@ -1058,7 +1095,7 @@ private class InnerWebSocket: Hashable { wr.open() try write(header, length: header.count) } - + func write(bytes: UnsafePointer, length: Int) throws { if outputBytesStart+outputBytesLength+length > outputBytesSize { var size = outputBytesSize @@ -1075,7 +1112,7 @@ private class InnerWebSocket: Hashable { memcpy(outputBytes+outputBytesStart+outputBytesLength, bytes, length) outputBytesLength += length } - + func readResponse() throws { let end : [UInt8] = [ 0x0D, 0x0A, 0x0D, 0x0A ] let ptr = UnsafeMutablePointer(memmem(inputBytes+inputBytesStart, inputBytesLength, end, 4)) @@ -1156,7 +1193,7 @@ private class InnerWebSocket: Hashable { inputBytesStart += bufferCount+4 } } - + class ByteReader { var start : UnsafePointer var end : UnsafePointer @@ -1186,7 +1223,7 @@ private class InnerWebSocket: Hashable { } } } - + var fragStateSaved = false var fragStatePosition = 0 var fragStateInflate = false @@ -1210,7 +1247,7 @@ private class InnerWebSocket: Hashable { var payload : Payload var statusCode : UInt16 var headerLen : Int - + let reader = ByteReader(bytes: inputBytes+inputBytesStart, length: inputBytesLength) if fragStateSaved { // load state @@ -1312,7 +1349,7 @@ private class InnerWebSocket: Hashable { } headerLen = reader.position } - + let rlen : Int let rfin : Bool let chopped : Bool @@ -1333,13 +1370,13 @@ private class InnerWebSocket: Hashable { (bytes, bytesLen) = (UnsafeMutablePointer(reader.bytes), rlen) } reader.bytes += rlen - + if leaderCode == .Text || leaderCode == .Close { try utf8.append(bytes, length: bytesLen) } else { payload.append(bytes, length: bytesLen) } - + if chopped { // save state fragStateHeaderLen = headerLen @@ -1355,19 +1392,19 @@ private class InnerWebSocket: Hashable { fragStateSaved = true throw WebSocketError.NeedMoreInput } - + inputBytesLength -= reader.position if inputBytesLength == 0 { inputBytesStart = 0 } else { inputBytesStart += reader.position } - + let f = Frame() (f.code, f.payload, f.utf8, f.statusCode, f.inflate, f.finished) = (code, payload, utf8, statusCode, inflate, fin) return f } - + var head = [UInt8](count: 0xFF, repeatedValue: 0) func writeFrame(f : Frame) throws { if !f.finished{ @@ -1392,7 +1429,7 @@ private class InnerWebSocket: Hashable { } payloadLen += payloadBytes.count if deflate { - + } var usingStatusCode = false if f.statusCode != 0 && payloadLen != 0 { @@ -1586,15 +1623,19 @@ private class Manager { private let manager = Manager() /// WebSocket objects are bidirectional network streams that communicate over HTTP. RFC 6455. -public class WebSocket: Hashable { - private var ws : InnerWebSocket +public class WebSocket: NSObject { + private var ws: InnerWebSocket private var id = manager.nextId() - private var opened : Bool - public var hashValue: Int { return id } + private var opened: Bool + public override var hashValue: Int { return id } /// Create a WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. public convenience init(_ url: String){ self.init(request: NSURLRequest(URL: NSURL(string: url)!), subProtocols: []) } + /// Create a WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. + public convenience init(url: NSURL){ + self.init(request: NSURLRequest(URL: url), subProtocols: []) + } /// Create a WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. Also include a list of protocols. public convenience init(_ url: String, subProtocols : [String]){ self.init(request: NSURLRequest(URL: NSURL(string: url)!), subProtocols: subProtocols) @@ -1609,9 +1650,10 @@ public class WebSocket: Hashable { ws = InnerWebSocket(request: request, subProtocols: subProtocols, stub: false) } /// Create a WebSocket object with a deferred connection; the connection is not opened until the .open() method is called. - public init(){ + public override init(){ opened = false ws = InnerWebSocket(request: NSURLRequest(), subProtocols: [], stub: true) + super.init() } /// The URL as resolved by the constructor. This is always an absolute URL. Read only. public var url : String{ return ws.url } @@ -1652,15 +1694,19 @@ public class WebSocket: Hashable { return ws.readyState } /// Opens a deferred or closed WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. - public func open(url: String){ + public func open(url url: String){ open(NSURLRequest(URL: NSURL(string: url)!), subProtocols: []) } + /// Opens a deferred or closed WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. + public func open(nsurl url: NSURL){ + open(NSURLRequest(URL: url), subProtocols: []) + } /// Opens a deferred or closed WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. Also include a list of protocols. - public func open(url: String, subProtocols : [String]){ + public func open(url url: String, subProtocols : [String]){ open(NSURLRequest(URL: NSURL(string: url)!), subProtocols: subProtocols) } /// Opens a deferred or closed WebSocket connection to a URL; this should be the URL to which the WebSocket server will respond. Also include a protocol. - public func open(url: String, subProtocol : String){ + public func open(url url: String, subProtocol : String){ open(NSURLRequest(URL: NSURL(string: url)!), subProtocols: [subProtocol]) } /// Opens a deferred or closed WebSocket connection from an NSURLRequest; Also include a list of protocols. @@ -1677,7 +1723,7 @@ public class WebSocket: Hashable { } /** Closes the WebSocket connection or connection attempt, if any. If the connection is already closed or in the state of closing, this method does nothing. - + :param: code An integer indicating the status code explaining why the connection is being closed. If this parameter is not specified, a default value of 1000 (indicating a normal closure) is assumed. :param: reason A human-readable string explaining why the connection is closing. This string must be no longer than 123 bytes of UTF-8 text (not characters). */ @@ -1690,8 +1736,8 @@ public class WebSocket: Hashable { } /** Transmits message to the server over the WebSocket connection. - - :param: message The data to be sent to the server. + + :param: message The message to be sent to the server. */ public func send(message : Any){ if !opened{ @@ -1701,7 +1747,7 @@ public class WebSocket: Hashable { } /** Transmits a ping to the server over the WebSocket connection. - + :param: optional message The data to be sent to the server. */ public func ping(message : Any){ @@ -1725,3 +1771,28 @@ public func ==(lhs: WebSocket, rhs: WebSocket) -> Bool { return lhs.id == rhs.id } +extension WebSocket { + /// The events of the WebSocket using a delegate. + public var delegate : WebSocketDelegate? { + get { return ws.eventDelegate } + set { ws.eventDelegate = newValue } + } + /** + Transmits message to the server over the WebSocket connection. + + :param: text The message (string) to be sent to the server. + */ + @objc + public func send(text text: String){ + send(text) + } + /** + Transmits message to the server over the WebSocket connection. + + :param: data The message (binary) to be sent to the server. + */ + @objc + public func send(data data: NSData){ + send(data) + } +} diff --git a/SwiftWebSocket.xcodeproj/project.pbxproj b/SwiftWebSocket.xcodeproj/project.pbxproj index 21380c2..ed873ba 100755 --- a/SwiftWebSocket.xcodeproj/project.pbxproj +++ b/SwiftWebSocket.xcodeproj/project.pbxproj @@ -17,6 +17,9 @@ D71948F51B35E5670015C529 /* SwiftWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = D71948F11B35E5670015C529 /* SwiftWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; D719491A1B35E6640015C529 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D71948FA1B35E59D0015C529 /* libz.dylib */; }; D719491B1B35E7510015C529 /* SwiftWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = D71948F11B35E5670015C529 /* SwiftWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D734D9EC1C232E6A00437555 /* Connection.m in Sources */ = {isa = PBXBuildFile; fileRef = D734D9EB1C232E6A00437555 /* Connection.m */; }; + D78C40331C232C3800EB72AA /* Test_ObjectiveC.m in Sources */ = {isa = PBXBuildFile; fileRef = D78C40321C232C3800EB72AA /* Test_ObjectiveC.m */; }; + D78C40351C232C3800EB72AA /* SwiftWebSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03D1E7031B20897100AC49AC /* SwiftWebSocket.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -34,6 +37,13 @@ remoteGlobalIDString = 03D1E7021B20897100AC49AC; remoteInfo = "SwiftWebSocket-iOS"; }; + D78C40361C232C3800EB72AA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 03D1E6FA1B20897100AC49AC /* Project object */; + proxyType = 1; + remoteGlobalIDString = 03D1E7021B20897100AC49AC; + remoteInfo = "SwiftWebSocket-iOS"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -48,6 +58,11 @@ D71948F11B35E5670015C529 /* SwiftWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwiftWebSocket.h; sourceTree = ""; }; D71948FA1B35E59D0015C529 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/lib/libz.dylib; sourceTree = DEVELOPER_DIR; }; D71949011B35E6130015C529 /* SwiftWebSocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftWebSocket.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D734D9EA1C232E6A00437555 /* Connection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Connection.h; sourceTree = ""; }; + D734D9EB1C232E6A00437555 /* Connection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Connection.m; sourceTree = ""; }; + D78C40301C232C3800EB72AA /* Test-ObjectiveC.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Test-ObjectiveC.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + D78C40321C232C3800EB72AA /* Test_ObjectiveC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Test_ObjectiveC.m; sourceTree = ""; }; + D78C40341C232C3800EB72AA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -83,6 +98,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D78C402D1C232C3800EB72AA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D78C40351C232C3800EB72AA /* SwiftWebSocket.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -93,6 +116,7 @@ 03D1E7211B208A5C00AC49AC /* libz.dylib */, D71948F01B35E5670015C529 /* Source */, 03D70CE71BDAC70100D245C3 /* Test */, + D78C40311C232C3800EB72AA /* Test-ObjectiveC */, 03D1E7041B20897100AC49AC /* Products */, ); sourceTree = ""; @@ -104,6 +128,7 @@ D71949011B35E6130015C529 /* SwiftWebSocket.framework */, 03D70CCC1BDAC5EC00D245C3 /* Test-OSX.xctest */, 03D70CDB1BDAC63600D245C3 /* Test-iOS.xctest */, + D78C40301C232C3800EB72AA /* Test-ObjectiveC.xctest */, ); name = Products; sourceTree = ""; @@ -127,6 +152,17 @@ path = Source; sourceTree = ""; }; + D78C40311C232C3800EB72AA /* Test-ObjectiveC */ = { + isa = PBXGroup; + children = ( + D78C40341C232C3800EB72AA /* Info.plist */, + D734D9EA1C232E6A00437555 /* Connection.h */, + D734D9EB1C232E6A00437555 /* Connection.m */, + D78C40321C232C3800EB72AA /* Test_ObjectiveC.m */, + ); + path = "Test-ObjectiveC"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -221,6 +257,24 @@ productReference = D71949011B35E6130015C529 /* SwiftWebSocket.framework */; productType = "com.apple.product-type.framework"; }; + D78C402F1C232C3800EB72AA /* Test-ObjectiveC */ = { + isa = PBXNativeTarget; + buildConfigurationList = D78C403A1C232C3800EB72AA /* Build configuration list for PBXNativeTarget "Test-ObjectiveC" */; + buildPhases = ( + D78C402C1C232C3800EB72AA /* Sources */, + D78C402D1C232C3800EB72AA /* Frameworks */, + D78C402E1C232C3800EB72AA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D78C40371C232C3800EB72AA /* PBXTargetDependency */, + ); + name = "Test-ObjectiveC"; + productName = "Test-ObjectiveC"; + productReference = D78C40301C232C3800EB72AA /* Test-ObjectiveC.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -243,6 +297,9 @@ D71949001B35E6130015C529 = { CreatedOnToolsVersion = 6.3.2; }; + D78C402F1C232C3800EB72AA = { + CreatedOnToolsVersion = 7.1.1; + }; }; }; buildConfigurationList = 03D1E6FD1B20897100AC49AC /* Build configuration list for PBXProject "SwiftWebSocket" */; @@ -261,6 +318,7 @@ D71949001B35E6130015C529 /* SwiftWebSocket-OSX */, 03D70CCB1BDAC5EC00D245C3 /* Test-OSX */, 03D70CDA1BDAC63600D245C3 /* Test-iOS */, + D78C402F1C232C3800EB72AA /* Test-ObjectiveC */, ); }; /* End PBXProject section */ @@ -294,6 +352,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D78C402E1C232C3800EB72AA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -329,6 +394,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D78C402C1C232C3800EB72AA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D78C40331C232C3800EB72AA /* Test_ObjectiveC.m in Sources */, + D734D9EC1C232E6A00437555 /* Connection.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -342,6 +416,11 @@ target = 03D1E7021B20897100AC49AC /* SwiftWebSocket-iOS */; targetProxy = 03D70CE11BDAC63600D245C3 /* PBXContainerItemProxy */; }; + D78C40371C232C3800EB72AA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 03D1E7021B20897100AC49AC /* SwiftWebSocket-iOS */; + targetProxy = D78C40361C232C3800EB72AA /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -572,6 +651,31 @@ }; name = Release; }; + D78C40381C232C3800EB72AA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEBUG_INFORMATION_FORMAT = dwarf; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + INFOPLIST_FILE = "Test-ObjectiveC/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.oncast.Test-ObjectiveC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + D78C40391C232C3800EB72AA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + INFOPLIST_FILE = "Test-ObjectiveC/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.oncast.Test-ObjectiveC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -600,6 +704,7 @@ 03D70CD51BDAC5EC00D245C3 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 03D70CE31BDAC63600D245C3 /* Build configuration list for PBXNativeTarget "Test-iOS" */ = { isa = XCConfigurationList; @@ -608,6 +713,7 @@ 03D70CE51BDAC63600D245C3 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; D71949141B35E6160015C529 /* Build configuration list for PBXNativeTarget "SwiftWebSocket-OSX" */ = { isa = XCConfigurationList; @@ -618,6 +724,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D78C403A1C232C3800EB72AA /* Build configuration list for PBXNativeTarget "Test-ObjectiveC" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D78C40381C232C3800EB72AA /* Debug */, + D78C40391C232C3800EB72AA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 03D1E6FA1B20897100AC49AC /* Project object */; diff --git a/SwiftWebSocket.xcodeproj/xcshareddata/xcschemes/SwiftWebSocket-iOS.xcscheme b/SwiftWebSocket.xcodeproj/xcshareddata/xcschemes/SwiftWebSocket-iOS.xcscheme index 2c9a4dc..0f08b79 100755 --- a/SwiftWebSocket.xcodeproj/xcshareddata/xcschemes/SwiftWebSocket-iOS.xcscheme +++ b/SwiftWebSocket.xcodeproj/xcshareddata/xcschemes/SwiftWebSocket-iOS.xcscheme @@ -28,7 +28,26 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + + + diff --git a/Test-ObjectiveC/Connection.h b/Test-ObjectiveC/Connection.h new file mode 100644 index 0000000..2644f97 --- /dev/null +++ b/Test-ObjectiveC/Connection.h @@ -0,0 +1,15 @@ +// +// Connection.h +// SwiftWebSocket +// +// Created by Ricardo Pereira on 17/12/15. +// Copyright © 2015 ONcast, LLC. All rights reserved. +// + +#import + +@interface Connection : NSObject + +- (void)open; + +@end diff --git a/Test-ObjectiveC/Connection.m b/Test-ObjectiveC/Connection.m new file mode 100644 index 0000000..139a01c --- /dev/null +++ b/Test-ObjectiveC/Connection.m @@ -0,0 +1,64 @@ +// +// Connection.m +// SwiftWebSocket +// +// Created by Ricardo Pereira on 17/12/15. +// Copyright © 2015 ONcast, LLC. All rights reserved. +// + +#import "Connection.h" +#import + +@interface Connection () + +@end + +@implementation Connection { + WebSocket *_webSocket; +} + +- (instancetype)init { + if (self = [super init]) { + _webSocket = nil; + } + return self; +} + +- (void)open { + _webSocket = [[WebSocket alloc] init:@"ws://localhost:9000"]; + _webSocket.delegate = self; + [_webSocket open]; +} + +- (void)webSocketOpen { + NSLog(@"Open"); + [_webSocket sendWithText:@"test"]; + [_webSocket sendWithData:[@"test" dataUsingEncoding:NSUTF8StringEncoding]]; + [_webSocket close:0 reason:@""]; +} + +- (void)webSocketClose:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { + NSLog(@"Close: %@", reason); +} + +- (void)webSocketMessageText:(NSString *)text { + NSLog(@"Message: %@", text); +} + +- (void)webSocketMessageData:(NSData *)data { + NSLog(@"Message: %@", data); +} + +- (void)webSocketPong { + NSLog(@"Pong"); +} + +- (void)webSocketError:(NSError *)error { + NSLog(@"Error: %@", error); +} + +- (void)webSocketEnd:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean error:(NSError *)error { + NSLog(@"End: %@", error); +} + +@end diff --git a/Test-ObjectiveC/Info.plist b/Test-ObjectiveC/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/Test-ObjectiveC/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Test-ObjectiveC/Test_ObjectiveC.m b/Test-ObjectiveC/Test_ObjectiveC.m new file mode 100644 index 0000000..902e365 --- /dev/null +++ b/Test-ObjectiveC/Test_ObjectiveC.m @@ -0,0 +1,32 @@ +// +// Test_ObjectiveC.m +// Test-ObjectiveC +// +// Created by Ricardo Pereira on 17/12/15. +// Copyright © 2015 ONcast, LLC. All rights reserved. +// + +#import +#import "Connection.h" + +@interface Test_ObjectiveC : XCTestCase + +@end + +@implementation Test_ObjectiveC + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testObjectiveC { + [[[Connection alloc] init] open]; +} + +@end diff --git a/tools/run-objc-test.sh b/tools/run-objc-test.sh new file mode 100755 index 0000000..f09dac6 --- /dev/null +++ b/tools/run-objc-test.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +set -o pipefail + +: ${BUILDTOOL:=xcodebuild} #Default + +# Xcode Build Command Line +# https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/xcodebuild.1.html +: ${PROJECT:="SwiftWebSocket.xcodeproj"} +: ${SCHEME:="SwiftWebSocket-iOS"} +: ${TARGET:="Test-ObjectiveC"} +: ${SDK:="iphonesimulator"} + +echo "Started: $(date)" + +init() { + # Launch the simulator before running the tests + # Avoid "iPhoneSimulator: Timed out waiting" + open -b com.apple.iphonesimulator +} + +COMMAND="-project \"${PROJECT}\" -scheme \"${SCHEME}\" -sdk \"${SDK}\"" + +case "${BUILDTOOL}" in + xctool) echo "Selected build tool: xctool" + init + # Tests (Swift & Objective-C) + case "${CLASS}" in + "") echo "Testing all classes" + COMMAND="xctool clean test "${COMMAND} + ;; + *) echo "Testing ${CLASS}" + COMMAND="xctool clean test -only ${CLASS} "${COMMAND} + ;; + esac + ;; + xcodebuild-travis) echo "Selected tool: xcodebuild + xcpretty (format: travisci)" + init + # Use xcpretty together with tee to store the raw log in a file, and get the pretty output in the terminal + COMMAND="xcodebuild clean test "${COMMAND}" | tee xcodebuild.log | xcpretty -f `xcpretty-travis-formatter`" + ;; + xcodebuild-pretty) echo "Selected tool: xcodebuild + xcpretty" + init + COMMAND="xcodebuild clean test "${COMMAND}" | xcpretty --test" + ;; + xcodebuild) echo "Selected tool: xcodebuild" + init + COMMAND="xcodebuild clean test "${COMMAND} + ;; + *) echo "No build tool especified" && exit 2 +esac + +set -x +eval "${COMMAND}"