commit 1101d251b936b3d1c21e5e55a895db5662b7570c Author: Erik Date: Sun Nov 23 17:01:27 2014 -0500 initial commit diff --git a/SocketIO-Swift-Bridging-Header.h b/SocketIO-Swift-Bridging-Header.h new file mode 100644 index 0000000..a51452e --- /dev/null +++ b/SocketIO-Swift-Bridging-Header.h @@ -0,0 +1,8 @@ +// +// SocketIO-Swift-Bridging-Header.h +// Socket.IO-Swift +// +// Created by Erik Little on 11/23/14. +// + +#import "SRWebSocket.h" \ No newline at end of file diff --git a/SocketIOClient.swift b/SocketIOClient.swift new file mode 100644 index 0000000..e8e010f --- /dev/null +++ b/SocketIOClient.swift @@ -0,0 +1,226 @@ +// +// SocketIOClient.swift +// Socket.IO-Swift +// +// Created by Erik Little on 11/23/14. +// + +import Foundation + +private class EventHandler: NSObject { + let event:String! + let callback:((data:Any?) -> Void)! + + init(event:String, callback:((data:Any?) -> Void)?) { + self.event = event + self.callback = callback + } + + func executeCallback(args:Any?) { + if (args != nil) { + callback(data: args!) + } else { + callback(data: nil) + } + } +} + +private struct socketMessage { + var event:String! + var args:AnyObject! + + init(event:String, args:AnyObject?) { + self.event = event + self.args = args? + } + + func createMessage() -> String { + var array = "42[" + array += "\"" + event + "\"" + if (args? != nil) { + if (args is NSDictionary) { + array += "," + var jsonSendError:NSError? + var jsonSend = NSJSONSerialization.dataWithJSONObject(args, + options: NSJSONWritingOptions(0), error: &jsonSendError) + var jsonString = NSString(data: jsonSend!, encoding: NSUTF8StringEncoding) + return array + jsonString! + "]" + } else { + array += ",\"\(args!)\"" + return array + "]" + } + } else { + return array + "]" + } + } +} + +class SocketIOClient: NSObject, SRWebSocketDelegate { + let session:NSURLSession? + let socketURL:String! + var connected = false + var connecting = false + private var handlers = [EventHandler]() + var io:SRWebSocket? + var pingTimer:NSTimer! + var secure = false + + init(socketURL:String, secure:Bool = false) { + let sessionConfig:NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration() + sessionConfig.allowsCellularAccess = true + sessionConfig.HTTPAdditionalHeaders = ["Content-Type": "application/json"] + sessionConfig.timeoutIntervalForRequest = 30 + sessionConfig.timeoutIntervalForResource = 60 + sessionConfig.HTTPMaximumConnectionsPerHost = 1 + + self.session = NSURLSession(configuration: sessionConfig) + var mutURL = RegexMutable(socketURL) + mutURL = mutURL["http://"] ~= "" + mutURL = mutURL["https://"] ~= "" + self.socketURL = mutURL + self.secure = secure + } + + // Closes the socket + func close() { + self.pingTimer?.invalidate() + self.connecting = false + self.connected = false + self.io?.close() + } + + // Connects to the server + func connect() { + self.connecting = true + var endpoint:String! + if (self.secure) { + endpoint = "wss://\(self.socketURL)/socket.io/?EIO=2&transport=websocket" + } else { + endpoint = "ws://\(self.socketURL)/socket.io/?EIO=2&transport=websocket" + } + self.io = SRWebSocket(URL: NSURL(string: endpoint)) + self.io?.delegate = self + self.io?.open() + } + + // Sends a message + func emit(event:String, args:AnyObject? = nil) { + if (!self.connected) { + return + } + + let frame = socketMessage(event: event, args: args) + let str = frame.createMessage() + + println("Sending: \(str)") + self.io?.send(str) + } + + // Handles events + func handleEvent(#event:String, data:Any?) { + println("Should do event: \(event) with data: \(data)") + + for handler in self.handlers { + if (handler.event == event) { + if (data != nil) { + handler.executeCallback(data) + } else { + handler.executeCallback(nil) + } + } + } + + } + + // Adds handlers to the socket + func on(name:String, callback:((data:Any?) -> Void)?) { + let handler = EventHandler(event: name, callback: callback) + self.handlers.append(handler) + } + + // Opens the connection to the socket + func open() { + self.connect() + } + + // Parses messages recieved + private func parseSocketMessage(#message:AnyObject?) { + if (message == nil) { + // TODO handle nil + return + } + + if let stringMessage = message as? String { + /** + Begin check for socket info frame + **/ + var mutMessage = RegexMutable(stringMessage) + var setup:String! + let messageData = mutMessage["(\\d*)(\\{.*\\})?"].groups() + if (messageData != nil && messageData[1] == "0") { + setup = messageData[2] + let data = setup.dataUsingEncoding(NSUTF8StringEncoding)! + var jsonError:NSError? + if let json:AnyObject? = NSJSONSerialization.JSONObjectWithData(data, + options: nil, error: &jsonError) { + self.startPingTimer(interval: (json!["pingInterval"] as Int) / 1000) + return + } + } + /** + End check for socket info frame + **/ + + let messageGroups = mutMessage["(\\d*)(\\[.*\\])?"].groups() + if (messageGroups.count == 3 && messageGroups[1] == "42") { + let messagePart = messageGroups[2] + let messageInternals = RegexMutable(messagePart)["\\[\"(.*?)\",?(.*?)?(,.*)?\\]"].groups() + if (messageInternals != nil && messageInternals.count > 2) { + let event = messageInternals[1] + var data:Any! + if (messageInternals[2] == "") { + data = nil + } else { + data = messageInternals[2] + + } + self.handleEvent(event: event, data: data) + } + } + } + } + + // Sends ping + func sendPing() { + if (!self.connected) { + return + } + self.io?.send("2") + } + + // Starts the ping timer + func startPingTimer(#interval:Int) { + self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(interval), target: self, + selector: Selector("sendPing"), userInfo: nil, repeats: true) + } + + // Called when a message is recieved + func webSocket(webSocket: SRWebSocket!, didReceiveMessage message:AnyObject?) { + // println(message) + self.parseSocketMessage(message: message) + } + + // Called when the socket is opened + func webSocketDidOpen(webSocket: SRWebSocket!) { + self.connecting = false + self.connected = true + self.handleEvent(event: "connect", data: nil) + } + + // Called when the socket is closed + func webSocket(webSocket: SRWebSocket!, didCloseWithCode code: Int, reason: String!, wasClean: Bool) { + self.connected = false + self.connecting = false + self.handleEvent(event: "disconnect", data: reason) + } +} \ No newline at end of file diff --git a/SwiftRegex.swift b/SwiftRegex.swift new file mode 100644 index 0000000..96c03cf --- /dev/null +++ b/SwiftRegex.swift @@ -0,0 +1,324 @@ +// +// SwiftRegex.swift +// SwiftRegex +// +// Created by John Holdsworth on 26/06/2014. +// Copyright (c) 2014 John Holdsworth. +// +// $Id: //depot/SwiftRegex/SwiftRegex.swift#37 $ +// +// This code is in the public domain from: +// https://github.com/johnno1962/SwiftRegex +// + +import Foundation + +var swiftRegexCache = Dictionary() + +public class SwiftRegex: NSObject, BooleanType { + + var target: NSString + var regex: NSRegularExpression + + init(target:NSString, pattern:String, options:NSRegularExpressionOptions = nil) { + self.target = target + if let regex = swiftRegexCache[pattern] { + self.regex = regex + } else { + var error: NSError? + if let regex = NSRegularExpression(pattern: pattern, options:options, error:&error) { + swiftRegexCache[pattern] = regex + self.regex = regex + } + else { + SwiftRegex.failure("Error in pattern: \(pattern) - \(error)") + self.regex = NSRegularExpression() + } + } + super.init() + } + + class func failure(message: String) { + println("SwiftRegex: "+message) + //assert(false,"SwiftRegex: failed") + } + + final var targetRange: NSRange { + return NSRange(location: 0,length: target.length) + } + + final func substring(range: NSRange) -> NSString! { + if ( range.location != NSNotFound ) { + return target.substringWithRange(range) + } else { + return nil + } + } + + public func doesMatch(options: NSMatchingOptions = nil) -> Bool { + return range(options: options).location != NSNotFound + } + + public func range(options: NSMatchingOptions = nil) -> NSRange { + return regex.rangeOfFirstMatchInString(target, options: nil, range: targetRange) + } + + public func match(options: NSMatchingOptions = nil) -> String! { + return substring(range(options: options)) + } + + public func groups(options: NSMatchingOptions = nil) -> [String]! { + return groupsForMatch( regex.firstMatchInString(target, options: options, range: targetRange) ) + } + + func groupsForMatch(match: NSTextCheckingResult!) -> [String]! { + if match != nil { + var groups = [String]() + for groupno in 0...regex.numberOfCaptureGroups { + if let group = substring(match.rangeAtIndex(groupno)) as String! { + groups += [group] + } else { + groups += ["_"] // avoids bridging problems + } + } + return groups + } else { + return nil + } + } + + public subscript(groupno: Int) -> String! { + get { + return groups()[groupno] + } + set(newValue) { + if let mutableTarget = target as? NSMutableString { + for match in matchResults().reverse() { + let replacement = regex.replacementStringForResult( match, + inString: target, offset: 0, template: newValue ) + mutableTarget.replaceCharactersInRange(match.rangeAtIndex(groupno), withString: replacement) + } + } else { + SwiftRegex.failure("Group modify on non-mutable") + } + } + } + + func matchResults(options: NSMatchingOptions = nil) -> [NSTextCheckingResult] { + return regex.matchesInString(target, options: options, range: targetRange) as [NSTextCheckingResult] + } + + public func ranges(options: NSMatchingOptions = nil) -> [NSRange] { + return matchResults(options: options).map { $0.range } + } + + public func matches(options: NSMatchingOptions = nil) -> [String] { + return matchResults(options: options).map { self.substring($0.range) } + } + + public func allGroups(options: NSMatchingOptions = nil) -> [[String]] { + return matchResults(options: options).map { self.groupsForMatch($0) } + } + + public func dictionary(options: NSMatchingOptions = nil) -> Dictionary { + var out = Dictionary() + for match in matchResults(options: options) { + out[substring(match.rangeAtIndex(1))] = + substring(match.rangeAtIndex(2)) + } + return out + } + + func substituteMatches(substitution: (NSTextCheckingResult, UnsafeMutablePointer) -> String, + options:NSMatchingOptions = nil) -> NSMutableString { + let out = NSMutableString() + var pos = 0 + + regex.enumerateMatchesInString(target, options: options, range: targetRange ) { + (match: NSTextCheckingResult!, flags: NSMatchingFlags, stop: UnsafeMutablePointer) in + + let matchRange = match.range + out.appendString( self.substring( NSRange(location:pos, length:matchRange.location-pos) ) ) + out.appendString( substitution(match, stop) ) + pos = matchRange.location + matchRange.length + } + + out.appendString( substring( NSRange(location:pos, length:targetRange.length-pos) ) ) + + if let mutableTarget = target as? NSMutableString { + mutableTarget.setString(out) + return mutableTarget + } else { + SwiftRegex.failure("Modify on non-mutable") + return out + } + } + /* removed Beta6 + public func __conversion() -> Bool { + return doesMatch() + } + + public func __conversion() -> NSRange { + return range() + } + + public func __conversion() -> String { + return match() + } + + public func __conversion() -> [String] { + return matches() + } + + public func __conversion() -> [[String]] { + return allGroups() + } + + public func __conversion() -> [String:String] { + return dictionary() + } + */ + public var boolValue: Bool { + return doesMatch() + } +} + +extension NSString { + public subscript(pattern: String, options: NSRegularExpressionOptions) -> SwiftRegex { + return SwiftRegex(target: self, pattern: pattern, options: options) + } +} + +extension NSString { + public subscript(pattern: String) -> SwiftRegex { + return SwiftRegex(target: self, pattern: pattern) + } +} + +extension String { + public subscript(pattern: String, options: NSRegularExpressionOptions) -> SwiftRegex { + return SwiftRegex(target: self, pattern: pattern, options: options) + } +} + +extension String { + public subscript(pattern: String) -> SwiftRegex { + return SwiftRegex(target: self, pattern: pattern) + } +} + +public func RegexMutable(string: NSString) -> NSMutableString { + return NSMutableString(string:string) +} + +public func ~= (left: SwiftRegex, right: String) -> NSMutableString { + return left.substituteMatches { + (match: NSTextCheckingResult, stop: UnsafeMutablePointer) in + return left.regex.replacementStringForResult( match, + inString: left.target, offset: 0, template: right ) + } +} + +public func ~= (left: SwiftRegex, right: [String]) -> NSMutableString { + var matchNumber = 0 + return left.substituteMatches { + (match: NSTextCheckingResult, stop: UnsafeMutablePointer) in + + if ++matchNumber == right.count { + stop.memory = true + } + + return left.regex.replacementStringForResult( match, + inString: left.target, offset: 0, template: right[matchNumber-1] ) + } +} + +public func ~= (left: SwiftRegex, right: (String) -> String) -> NSMutableString { + return left.substituteMatches { + (match: NSTextCheckingResult, stop: UnsafeMutablePointer) in + return right(left.substring(match.range)) + } +} + +public func ~= (left: SwiftRegex, right: ([String]) -> String) -> NSMutableString { + return left.substituteMatches { + (match: NSTextCheckingResult, stop: UnsafeMutablePointer) in + return right(left.groupsForMatch(match)) + } +} + +// my take on custom threading operators from +// http://ijoshsmith.com/2014/07/05/custom-threading-operator-in-swift/ + +private let _queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) + +public func | (left: () -> Void, right: () -> Void) { + dispatch_async(_queue) { + left() + dispatch_async(dispatch_get_main_queue(), right) + } +} + +public func | (left: () -> R, right: (result:R) -> Void) { + dispatch_async(_queue) { + let result = left() + dispatch_async(dispatch_get_main_queue(), { + right(result:result) + }) + } +} + +// dispatch groups { block } & { block } | { completion } +public func & (left: () -> Void, right: () -> Void) -> [() -> Void] { + return [left, right]; +} + +public func & (left: [() -> Void], right: () -> Void) -> [() -> Void] { + var out = left + out.append( right ) + return out +} + +public func | (left: [() -> Void], right: () -> Void) { + let group = dispatch_group_create() + + for block in left { + dispatch_group_async(group, _queue, block) + } + + dispatch_group_notify(group, dispatch_get_main_queue(), right) +} + +// parallel blocks with returns +public func & (left: () -> R, right: () -> R) -> [() -> R] { + return [left, right] +} + +public func & (left: [() -> R], right: () -> R) -> [() -> R] { + var out = left + out.append( right ) + return out +} + +public func | (left: [() -> R], right: (results:[R!]) -> Void) { + let group = dispatch_group_create() + + var results = Array() + for t in 0..