diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..28f05ea --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @naimaudio/apps diff --git a/Playgrounds/Scratch.playground/Contents.swift b/Playgrounds/Scratch.playground/Contents.swift index 49ae50a..6640e67 100644 --- a/Playgrounds/Scratch.playground/Contents.swift +++ b/Playgrounds/Scratch.playground/Contents.swift @@ -4,25 +4,25 @@ import UIKit func extractValue(scanner: NSScanner) -> (String?, String?) { - var field: NSString? - scanner.scanUpToString(":", intoString: &field) - scanner.scanString(":", intoString: nil) + var field: NSString? + scanner.scanUpToString(":", intoString: &field) + scanner.scanString(":", intoString: nil) - var value: NSString? - scanner.scanUpToString("\n", intoString: &value) + var value: NSString? + scanner.scanUpToString("\n", intoString: &value) - return (field?.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()), value?.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())) + return (field?.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()), value?.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())) } func extractValue(forField field: String, scanner: NSScanner) -> String? { - scanner.scanUpToString(field, intoString: nil) - scanner.scanString(field, intoString: nil) + scanner.scanUpToString(field, intoString: nil) + scanner.scanString(field, intoString: nil) - var value: NSString? - scanner.scanUpToString("\n", intoString: &value) + var value: NSString? + scanner.scanUpToString("\n", intoString: &value) - return value as? String + return value as? String } var str = "id: 88e92600-e220-11e5-8f19-6b8864780aab\n\rdata: I'm busy" @@ -30,25 +30,25 @@ var str2 = "id: 8ab35eb0-e220-11e5-8f19-6b8864780aab\n\revent: user-connected\n\ for event in [str, str2] { - let scanner = NSScanner(string: event as String) - scanner.charactersToBeSkipped = NSCharacterSet.whitespaceCharacterSet() + let scanner = NSScanner(string: event as String) + scanner.charactersToBeSkipped = NSCharacterSet.whitespaceCharacterSet() // let identifier = extractValue(forField: "id:", scanner: scanner) // let event = extractValue(forField: "event:", scanner: scanner) // let data = extractValue(forField: "data:", scanner: scanner) - var entity: (String?, String?) + var entity: (String?, String?) - repeat { + repeat { - entity = extractValue(scanner) + entity = extractValue(scanner) - if entity.1 != nil { - print("\(entity.0!):\t\(entity.1!)") - } + if entity.1 != nil { + print("\(entity.0!):\t\(entity.1!)") + } - } while(entity.0 != nil && entity.1 != nil) + } while(entity.0 != nil && entity.1 != nil) // let identifier = extractValue(scanner) @@ -67,5 +67,5 @@ for event in [str, str2] { // print("DATA:\t\(data.1!)") // } - print("---------------------------------------------------------------") + print("---------------------------------------------------------------") } \ No newline at end of file diff --git a/Projects/MacSSE/MacSSE/AppDelegate.swift b/Projects/MacSSE/MacSSE/AppDelegate.swift index fc8a069..478c0a4 100644 --- a/Projects/MacSSE/MacSSE/AppDelegate.swift +++ b/Projects/MacSSE/MacSSE/AppDelegate.swift @@ -13,13 +13,13 @@ class AppDelegate: NSObject, NSApplicationDelegate { - func applicationDidFinishLaunching(aNotification: NSNotification) { - // Insert code here to initialize your application - } + func applicationDidFinishLaunching(aNotification: NSNotification) { + // Insert code here to initialize your application + } - func applicationWillTerminate(aNotification: NSNotification) { - // Insert code here to tear down your application - } + func applicationWillTerminate(aNotification: NSNotification) { + // Insert code here to tear down your application + } } diff --git a/Projects/MacSSE/MacSSE/ViewController.swift b/Projects/MacSSE/MacSSE/ViewController.swift index 3d920a1..00fad4b 100644 --- a/Projects/MacSSE/MacSSE/ViewController.swift +++ b/Projects/MacSSE/MacSSE/ViewController.swift @@ -10,24 +10,24 @@ import Cocoa class ViewController: NSViewController { - @IBOutlet weak var logviewer: NSScrollView! - @IBOutlet weak var ipAddress: NSTextField! + @IBOutlet weak var logviewer: NSScrollView! + @IBOutlet weak var ipAddress: NSTextField! - override func viewDidLoad() { - super.viewDidLoad() + override func viewDidLoad() { + super.viewDidLoad() - // Do any additional setup after loading the view. - } + // Do any additional setup after loading the view. + } - override var representedObject: AnyObject? { - didSet { - // Update the view, if already loaded. - } - } + override var representedObject: AnyObject? { + didSet { + // Update the view, if already loaded. + } + } - @IBAction func onConnect(sender: NSButton) { + @IBAction func onConnect(sender: NSButton) { - } + } } diff --git a/Projects/SSEKit/SSEKit.xcodeproj/project.pbxproj b/Projects/SSEKit/SSEKit.xcodeproj/project.pbxproj index 4e1a9c0..61687c7 100644 --- a/Projects/SSEKit/SSEKit.xcodeproj/project.pbxproj +++ b/Projects/SSEKit/SSEKit.xcodeproj/project.pbxproj @@ -3,20 +3,17 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ + 011CBDF32762597A00712220 /* NaimKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 011CBDF22762597A00712220 /* NaimKit.xcframework */; }; 2A18FBC41C7CB8A80052DFCE /* EventSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A18FBC31C7CB8A80052DFCE /* EventSource.swift */; }; 2A18FBC61C7CB8C40052DFCE /* EventSourceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A18FBC51C7CB8C40052DFCE /* EventSourceConfiguration.swift */; }; 2A18FBC81C7CB8EF0052DFCE /* SSEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A18FBC71C7CB8EF0052DFCE /* SSEManager.swift */; }; 2AA0D2341C7C9A3B006E83D0 /* SSEKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 2AA0D2331C7C9A3B006E83D0 /* SSEKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2AA0D23B1C7C9A3B006E83D0 /* SSEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AA0D2301C7C9A3B006E83D0 /* SSEKit.framework */; }; 2AA0D2401C7C9A3B006E83D0 /* SSEKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA0D23F1C7C9A3B006E83D0 /* SSEKitTests.swift */; }; - 2AB7AD181D53743600A5F64A /* SSEKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 2AA0D2331C7C9A3B006E83D0 /* SSEKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 2AB7AD761D53835E00A5F64A /* SSEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A18FBC71C7CB8EF0052DFCE /* SSEManager.swift */; }; - 2AB7AD771D53835E00A5F64A /* EventSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A18FBC31C7CB8A80052DFCE /* EventSource.swift */; }; - 2AB7AD781D53835E00A5F64A /* EventSourceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A18FBC51C7CB8C40052DFCE /* EventSourceConfiguration.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -30,6 +27,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 011CBDF22762597A00712220 /* NaimKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = NaimKit.xcframework; path = ../../../../Build/NaimKit.xcframework; sourceTree = ""; }; + 12EC047D2731990700FA5116 /* NaimKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = NaimKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2A18FBC31C7CB8A80052DFCE /* EventSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventSource.swift; sourceTree = ""; }; 2A18FBC51C7CB8C40052DFCE /* EventSourceConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventSourceConfiguration.swift; sourceTree = ""; }; 2A18FBC71C7CB8EF0052DFCE /* SSEManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSEManager.swift; sourceTree = ""; }; @@ -39,7 +38,6 @@ 2AA0D23A1C7C9A3B006E83D0 /* SSEKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SSEKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 2AA0D23F1C7C9A3B006E83D0 /* SSEKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSEKitTests.swift; sourceTree = ""; }; 2AA0D2411C7C9A3B006E83D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 2AB7AD101D53740E00A5F64A /* SSEKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SSEKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2AB7AD191D53744400A5F64A /* Info-macOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-macOS.plist"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -48,6 +46,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 011CBDF32762597A00712220 /* NaimKit.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -59,22 +58,25 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 2AB7AD0C1D53740E00A5F64A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 12EC047C2731990700FA5116 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 011CBDF22762597A00712220 /* NaimKit.xcframework */, + 12EC047D2731990700FA5116 /* NaimKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 2AA0D2261C7C9A3B006E83D0 = { isa = PBXGroup; children = ( 2AA0D2321C7C9A3B006E83D0 /* SSEKit */, 2AA0D23E1C7C9A3B006E83D0 /* SSEKitTests */, 2AA0D2311C7C9A3B006E83D0 /* Products */, + 12EC047C2731990700FA5116 /* Frameworks */, ); sourceTree = ""; }; @@ -83,7 +85,6 @@ children = ( 2AA0D2301C7C9A3B006E83D0 /* SSEKit.framework */, 2AA0D23A1C7C9A3B006E83D0 /* SSEKitTests.xctest */, - 2AB7AD101D53740E00A5F64A /* SSEKit.framework */, ); name = Products; sourceTree = ""; @@ -121,14 +122,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 2AB7AD0D1D53740E00A5F64A /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 2AB7AD181D53743600A5F64A /* SSEKit.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ @@ -168,24 +161,6 @@ productReference = 2AA0D23A1C7C9A3B006E83D0 /* SSEKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 2AB7AD0F1D53740E00A5F64A /* SSEKit macOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 2AB7AD151D53740E00A5F64A /* Build configuration list for PBXNativeTarget "SSEKit macOS" */; - buildPhases = ( - 2AB7AD0B1D53740E00A5F64A /* Sources */, - 2AB7AD0C1D53740E00A5F64A /* Frameworks */, - 2AB7AD0D1D53740E00A5F64A /* Headers */, - 2AB7AD0E1D53740E00A5F64A /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "SSEKit macOS"; - productName = "SSEKit macOS"; - productReference = 2AB7AD101D53740E00A5F64A /* SSEKit.framework */; - productType = "com.apple.product-type.framework"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -193,29 +168,27 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1220; ORGANIZATIONNAME = "Naim Audio Ltd"; TargetAttributes = { 2AA0D22F1C7C9A3B006E83D0 = { CreatedOnToolsVersion = 7.3; DevelopmentTeam = 99935HA2E3; - LastSwiftMigration = 0900; + LastSwiftMigration = 1020; }; 2AA0D2391C7C9A3B006E83D0 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 0800; }; - 2AB7AD0F1D53740E00A5F64A = { - CreatedOnToolsVersion = 7.3.1; - }; }; }; buildConfigurationList = 2AA0D22A1C7C9A3B006E83D0 /* Build configuration list for PBXProject "SSEKit" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = 2AA0D2261C7C9A3B006E83D0; productRefGroup = 2AA0D2311C7C9A3B006E83D0 /* Products */; @@ -223,7 +196,6 @@ projectRoot = ""; targets = ( 2AA0D22F1C7C9A3B006E83D0 /* SSEKit iOS */, - 2AB7AD0F1D53740E00A5F64A /* SSEKit macOS */, 2AA0D2391C7C9A3B006E83D0 /* SSEKitTests */, ); }; @@ -244,13 +216,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 2AB7AD0E1D53740E00A5F64A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -272,16 +237,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 2AB7AD0B1D53740E00A5F64A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 2AB7AD761D53835E00A5F64A /* SSEManager.swift in Sources */, - 2AB7AD771D53835E00A5F64A /* EventSource.swift in Sources */, - 2AB7AD781D53835E00A5F64A /* EventSourceConfiguration.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -297,6 +252,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -306,14 +262,17 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -337,7 +296,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = SSEKit; @@ -353,6 +312,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -362,14 +322,17 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -387,11 +350,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = SSEKit; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -410,13 +374,16 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "SSEKit/Info-iOS.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.naimaudio.SSEKit; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -430,12 +397,15 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "SSEKit/Info-iOS.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.naimaudio.SSEKit; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -443,11 +413,15 @@ isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = SSEKitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.naimaudio.SSEKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -455,55 +429,15 @@ isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = SSEKitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.naimaudio.SSEKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 2.3; - }; - name = Release; - }; - 2AB7AD161D53740E00A5F64A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INFOPLIST_FILE = "SSEKit/Info-macOS.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = "com.empiricalmagic.SSEKit-macOS"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; - }; - name = Debug; - }; - 2AB7AD171D53740E00A5F64A /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = ""; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INFOPLIST_FILE = "SSEKit/Info-macOS.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = "com.empiricalmagic.SSEKit-macOS"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -537,15 +471,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 2AB7AD151D53740E00A5F64A /* Build configuration list for PBXNativeTarget "SSEKit macOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 2AB7AD161D53740E00A5F64A /* Debug */, - 2AB7AD171D53740E00A5F64A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = 2AA0D2271C7C9A3B006E83D0 /* Project object */; diff --git a/Projects/SSEKit/SSEKit.xcodeproj/xcshareddata/xcschemes/SSEKit iOS.xcscheme b/Projects/SSEKit/SSEKit.xcodeproj/xcshareddata/xcschemes/SSEKit iOS.xcscheme index 0a7496c..695a324 100644 --- a/Projects/SSEKit/SSEKit.xcodeproj/xcshareddata/xcschemes/SSEKit iOS.xcscheme +++ b/Projects/SSEKit/SSEKit.xcodeproj/xcshareddata/xcschemes/SSEKit iOS.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> @@ -47,6 +47,15 @@ + + + + @@ -59,23 +68,11 @@ - - - - - - - - - - - - () = {}) { - // don't do anything - virtual? + deinit { + } } public struct Event: CustomDebugStringConvertible { - struct Metadata { + struct Metadata { - let timestamp: Date - let hostUri: String - } + let timestamp: Date + let hostUri: String + } - let metadata: Metadata - let configuration: EventSourceConfiguration + let metadata: Metadata - let identifier: String? - let event: String? - let data: Data? + let identifier: String? + let event: String? + let data: Data? let jsonData: Dictionary? - init?(withEventSource eventSource: EventSource, identifier: String?, event: String?, data: Data?) { + init?(withEventSource eventSource: EventSource, identifier: String?, event: String?, data: Data?) { - guard identifier != nil else { + guard identifier != nil else { - return nil - } - - configuration = eventSource.configuration - self.metadata = Metadata(timestamp: Date(), hostUri: configuration.uri) + return nil + } + + self.metadata = Metadata(timestamp: Date(), hostUri: eventSource.manager!.connectionURL!.absoluteString) - self.identifier = identifier - self.event = event - self.data = data + self.identifier = identifier + self.event = event + self.data = data if (data != nil) { let jsonData = try? JSONSerialization.jsonObject(with: data!, options:[]) self.jsonData = jsonData as? Dictionary @@ -91,326 +90,10 @@ public struct Event: CustomDebugStringConvertible { else { self.jsonData = nil; } - } - - public var debugDescription: String { - - return "Event {\(self.identifier != nil ? self.identifier! : "nil"), \(self.event != nil ? self.event! : "nil"), Data length: \(self.data != nil ? self.data!.count : 0)}" - } -} - -@objc -public final class PrimaryEventSource: EventSource { - - fileprivate var task: URLSessionDataTask? - fileprivate var children = Set() - fileprivate var retries = 0 - fileprivate let maxRetries = 3 - var session:URLSession? - - internal func add(child: ChildEventSource) { - - _ = self.queue.async { - self.children.insert(child) - } - } - - internal func remove(child: ChildEventSource) { - - _ = self.queue.async { - self.children.remove(child) - } - } - - public override func connect() { - _ = self.queue.async { - self.readyState = .connecting - - let sessionConfig = URLSessionConfiguration.default - sessionConfig.requestCachePolicy = .reloadIgnoringLocalCacheData - sessionConfig.timeoutIntervalForRequest = TimeInterval(5) - sessionConfig.timeoutIntervalForResource = TimeInterval(INT_MAX) - sessionConfig.httpAdditionalHeaders = ["Accept" : "text/event-stream", "Cache-Control" : "no-cache"] - - if self.session != nil { - self.session!.invalidateAndCancel() - } - - let session = Foundation.URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil) //This requires self be marked as @objc - - self.session = session - - var urlComponents = URLComponents() - urlComponents.host = self.configuration.hostAddress - urlComponents.path = self.configuration.endpoint - urlComponents.port = self.configuration.port - urlComponents.scheme = "http" //FIXME: This should be settable in config - - if let url = urlComponents.url { - - //print("URL: \(url)") - - self.task = session.dataTask(with: url) - self.task?.resume() - } - else { - //error - } - } - } - - public override func disconnect(allowRetry:Bool = true, completion:@escaping ()->() = {}) { - _ = self.queue.async { - self.session?.invalidateAndCancel() - self.session = nil - - guard let t = self.task, t.state != .canceling else { - completion() - return - } - - self.task?.cancel() - self.task = nil - self.readyState = .closed - - if allowRetry && self.retries < self.maxRetries { - self.retries = self.retries + 1 - self.connect() - } else { - self.delegate?.eventSourceWillDisconnect(self) - - self.delegate?.eventSourceDidDisconnect(self) - - for child in self.children { - child.delegate?.eventSourceWillDisconnect(child) - - child.delegate?.eventSourceDidDisconnect(child) - } - - completion() - } - } } -} - -extension PrimaryEventSource: URLSessionDataDelegate { - public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - - disconnect() - } - - public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { - - guard self.readyState != .closed else { - - //Discard any data from here on in - return - } - - guard let response = dataTask.response as? HTTPURLResponse else { - - return //not connected yet - } + public var debugDescription: String { - if self.readyState == .connecting { - - switch response.statusCode { - - case 200...299: - fallthrough - case 300...399: - self.delegate?.eventSourceDidConnect(self) - self.readyState = .open - self.retries = 0 - break - - case 400...499: - fallthrough - default: - self.delegate?.eventSource(self, didEncounterError: .sourceNotFound(response.statusCode)) - disconnect() - return - } - } - - inline_URLSession(session, dataTask: dataTask, didReceiveData: data) - } - - public func inline_URLSession(_ session: Foundation.URLSession, dataTask: URLSessionDataTask, didReceiveData data: Data) { - - func scan(_ scanner: Scanner, field:String) -> (String?) { - - let originalLocation = scanner.scanLocation - - scanner.scanUpTo("\(field):", into: nil) - - guard scanner.scanString("\(field):", into:nil) else { // not found, reset and return nil - scanner.scanLocation = originalLocation - return nil - } - - var value: NSString? - scanner.scanUpTo("\n", into: &value) - - // get rid of any newlines - scanner.scanCharacters(from: CharacterSet.whitespacesAndNewlines, into: nil) - - return value as String?; - } - - if let eventString = String(data: data, encoding: .utf8) { //NSString(bytes: data.withUnsafeBytes length: data.count, encoding: 4) { - - - let scanner = Scanner(string: eventString as String) - scanner.charactersToBeSkipped = CharacterSet.whitespaces - - repeat { - - var eventId: String?, eventName: String?, eventData: String? - - eventId = scan(scanner, field:"id") - - guard eventId != nil else { // finished - NSLog("SSEKit SSE - No id!") - return - } - - let loc = scanner.scanLocation - eventName = scan(scanner, field:"event") - scanner.scanLocation = loc // reset, as this is optional... - - // is this actually optional? -// guard eventName != nil else { // finished -// NSLog("SSEKit SSE - No event name!") -// return -// } - - eventData = scan(scanner, field:"data") - - guard eventData != nil else { // finished - NSLog("SSEKit SSE - No event data!") - return - } - - - - // Send all events to children - for child in self.children { - self.queue.async { - - if let event = Event(withEventSource: child, identifier: eventId, event: eventName, data: eventData?.data(using: String.Encoding.utf8)) { - child.eventSource(self, didReceiveEvent: event) - } - } - } - - // Don't create events if nobody is listerning - if let evnArray = self.configuration.events, let evn = eventName, evnArray.contains(evn) { - - self.queue.async { - - if let event = Event(withEventSource: self, identifier: eventId, event: evn, data: eventData?.data(using: String.Encoding.utf8)) { - self.delegate?.eventSource(self, didReceiveEvent: event) - } - } - } - else if self.configuration.events == nil { - - self.queue.async { - - if let event = Event(withEventSource: self, identifier: eventId, event: eventName, data: eventData?.data(using: String.Encoding.utf8)) { - self.delegate?.eventSource(self, didReceiveEvent: event) - } - } - } - - } while(!scanner.isAtEnd) - } - } -} - -@objc -public final class ChildEventSource: EventSource { - - weak public var primaryEventSource: PrimaryEventSource? - public required init(configuration: EventSourceConfiguration, delegate: EventSourceDelegate, queue:DispatchQueue) { - - super.init(configuration: configuration, delegate: delegate, queue:queue) - } - - internal convenience init(withConfiguration config: EventSourceConfiguration, primaryEventSource: PrimaryEventSource, delegate: EventSourceDelegate, queue:DispatchQueue) { - self.init(configuration: config, delegate: delegate, queue:queue) - self.primaryEventSource = primaryEventSource - } - - public required init(configuration: EventSourceConfiguration, delegate: EventSourceDelegate) { - fatalError("init(configuration:delegate:) has not been implemented") + return "Event {\(self.identifier != nil ? self.identifier! : "nil"), \(self.event != nil ? self.event! : "nil"), Data length: \(self.data != nil ? self.data!.count : 0)}" } - - public override func connect() { - - // TODO: Return an error if there is a probelm with `primaryEventSource` - - //print("CHILD CONNECTED") - self.primaryEventSource?.add(child: self) - self.delegate?.eventSourceDidConnect(self) - } - - public override func disconnect(allowRetry:Bool = true, completion:@escaping ()->() = {}) { - - delegate?.eventSourceWillDisconnect(self) - self.readyState = .closed - delegate?.eventSourceDidDisconnect(self) - completion() - } -} - -extension ChildEventSource: EventSourceDelegate { - - public func eventSource(_ eventSource: EventSource, didChangeState state: ReadyState) { /* Ignore */ } - - public func eventSourceDidConnect(_ eventSource: EventSource) { /* Ignore */ } - - public func eventSourceWillDisconnect(_ eventSource: EventSource) { /* Ignore */ } - - public func eventSourceDidDisconnect(_ eventSource: EventSource) { - self.disconnect() - } - - public func eventSource(_ eventSource: EventSource, didReceiveEvent event: Event) { - - if let evnArray = self.configuration.events, let evn = event.event, evnArray.contains(evn) { - - self.queue.async { - - if let event = Event(withEventSource: self, identifier: event.identifier, event: event.event, data: event.data) { - self.delegate?.eventSource(self, didReceiveEvent: event) - } - } - } - else if self.configuration.events == nil { - - self.queue.async { - - if let event = Event(withEventSource: self, identifier: event.identifier, event: event.event, data: event.data) { - self.delegate?.eventSource(self, didReceiveEvent: event) - } - } - } - } - - public func eventSource(_ eventSource: EventSource, didEncounterError error: EventSourceError) { /* Ignore */ } -} - -public protocol EventSourceDelegate: class { - - func eventSource(_ eventSource: EventSource, didChangeState state: ReadyState) - - func eventSourceDidConnect(_ eventSource: EventSource) - func eventSourceWillDisconnect(_ eventSource: EventSource) - func eventSourceDidDisconnect(_ eventSource: EventSource) - - func eventSource(_ eventSource: EventSource, didReceiveEvent event: Event) - func eventSource(_ eventSource: EventSource, didEncounterError error: EventSourceError) } diff --git a/Projects/SSEKit/SSEKit/EventSourceConfiguration.swift b/Projects/SSEKit/SSEKit/EventSourceConfiguration.swift index d04d9f2..81f705a 100644 --- a/Projects/SSEKit/SSEKit/EventSourceConfiguration.swift +++ b/Projects/SSEKit/SSEKit/EventSourceConfiguration.swift @@ -3,40 +3,43 @@ // SSEKit // // Created by Richard Stelling on 23/02/2016. -// Copyright © 2016 Richard Stelling All rights reserved. +// Copyright © 2016 Naim Audio All rights reserved. // import Foundation +// TODO: delete this file! +// leaving in for now in case we want to patch 5.13 + public struct EventSourceConfiguration { - internal let name: String? + internal let name: String? - internal let hostAddress: String - internal let port: Int - internal let endpoint: String + internal let hostAddress: String + internal let port: Int + internal let endpoint: String - internal let timeout: TimeInterval + internal let timeout: TimeInterval - internal let events: [String]? + internal let events: [String]? - internal var uri: String { - return "\(self.hostAddress):\(self.port)\(self.endpoint)" - } + internal var uri: String { + return "http:\(self.hostAddress):\(self.port)\(self.endpoint)" + } - //options? + //options? - public init(withHost host: String, port: Int = 80, endpoint: String, timeout: TimeInterval = 5, events: [String]? = nil, name: String? = nil) { + public init(withHost host: String, port: Int = 80, endpoint: String, timeout: TimeInterval = 5, events: [String]? = nil, name: String? = nil) { - precondition(endpoint.characters.first == "/", "Endpoint does not begin with a /") + precondition(endpoint.first == "/", "Endpoint does not begin with a /") - self.hostAddress = host - self.port = port - self.endpoint = endpoint - self.timeout = timeout + self.hostAddress = host + self.port = port + self.endpoint = endpoint + self.timeout = timeout - self.events = events + self.events = events - self.name = name - } + self.name = name + } } diff --git a/Projects/SSEKit/SSEKit/SSEManager.swift b/Projects/SSEKit/SSEKit/SSEManager.swift index 02de4ab..6f7ecf0 100644 --- a/Projects/SSEKit/SSEKit/SSEManager.swift +++ b/Projects/SSEKit/SSEKit/SSEManager.swift @@ -3,217 +3,395 @@ // SSEKit // // Created by Richard Stelling on 23/02/2016. -// Copyright © 2016 Richard Stelling All rights reserved. +// Copyright © 2016 Naim Audio All rights reserved. // import Foundation +import NaimKit // MARK: Notifications public extension SSEManager { - public enum Notification: String { + enum Notification: String { - case Connected - case Event - case Disconnected + case Connected + case Event + case Disconnected - public enum Key: String { + public enum Key: String { - // Event - case Source - case Identifier - case Name - case Data + // Event + case Source + case Identifier + case Name + case Data case JSONData - case Timestamp - } - } + case Timestamp + } + } } // MARK: - SSEManager -open class SSEManager { - private static var instanceCount = 0 // debug! +open class SSEManager : NSObject, URLSessionDelegate { + + public typealias CompletionClosure = (_ error: NSError?) -> (); + + public enum ConnectionState: Int { + case idle = 0 + case connecting = 1 + case connected = 2 + case disconnecting = 3 + } - fileprivate var primaryEventSource: PrimaryEventSource? // TODO: remove - 1 connection per manager - adds too much complexity (and certainly the cause of a few bugs - as order of add/remove source matters) - fileprivate var eventSources = Set() + public internal(set) var connectionState:ConnectionState = ConnectionState.idle - fileprivate var queue = DispatchQueue(label: "com.naim.ssekit") + internal var session:URLSession? = nil + internal var sessionTask: URLSessionDataTask? = nil + internal var connectionURL: URL? = nil + + private static var instanceCount = 0 // debug! + fileprivate var eventSources = Set() + + internal var queue = DispatchQueue(label: "com.naim.ssekit") + internal var connectionRetries = 0 + public var maxConnectionRetries = 3 + public var logEvents: Set? = nil // log all events on empty set, no events on nil + + fileprivate var shouldDisconnectOnLastSource = false // TODO: just used for old EventSourceConfiguration usage + + private var connectCompletionClosure: CompletionClosure? = nil - public init(sources: [EventSourceConfiguration]) { - - for config in sources { - _ = addEventSource(config) - } + public override init() { SSEManager.instanceCount = SSEManager.instanceCount + 1 - NSLog("SSEManager init - instances \(SSEManager.instanceCount)") - } + } + + @available(*, deprecated, message: "EventSourceConfiguration to be removed") + public convenience init(sources: [EventSourceConfiguration]) { + self.init() + for eventSourceConfig in sources { + _ = self.addEventSource(eventSourceConfig) + } + } deinit { - self.primaryEventSource = nil SSEManager.instanceCount = SSEManager.instanceCount - 1 - NSLog("SSEManager dealloc - deinit \(SSEManager.instanceCount)") } - - /** - Add an EventSource to the manager. - */ - open func addEventSource(_ eventSourceConfig: EventSourceConfiguration) -> EventSource { - - var eventSource: EventSource! + + /// connect to the given SSE endpoint + /// + /// - Parameter url: url of the product probably http://:15081/notify + open func connect(toURL url: URL, completion: CompletionClosure? = nil ) { + self.queue.async { + + guard self.connectionState == .idle else { + completion?(NSError(domain:"com.naim.ssekit", code:1, userInfo:[NSLocalizedDescriptionKey: "Already connected/connecting"])) + return + } + + self.connectCompletionClosure = completion + + self.connectionState = .connecting + + let sessionConfig = URLSessionConfiguration.default + sessionConfig.requestCachePolicy = .reloadIgnoringLocalCacheData + sessionConfig.timeoutIntervalForRequest = TimeInterval(5) // configuration + sessionConfig.timeoutIntervalForResource = TimeInterval(INT_MAX) + sessionConfig.httpAdditionalHeaders = ["Accept" : "text/event-stream", "Cache-Control" : "no-cache"] + + if self.session != nil { + self.session!.invalidateAndCancel() + } + + let session = Foundation.URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil) //This requires self be marked as @objc + + self.session = session + + self.connectionURL = url + self.sessionTask = session.dataTask(with: url) + + self.sessionTask?.resume() + + } + } + + /// disconnect the SSE connection socket + /// + /// - Parameter completion: completion closure + open func disconnect(completion:@escaping ()->() = {}) { - _ = self.queue.sync { // must be sync, need to check there is a primary source - - if self.primaryEventSource == nil { - eventSource = PrimaryEventSource(configuration: eventSourceConfig, delegate: self, queue: self.queue) - self.primaryEventSource = eventSource as? PrimaryEventSource - precondition(self.eventSources.count == 0, "primary event source created AFTER other sources...bugs will arise...") - } - else { - eventSource = ChildEventSource(withConfiguration: eventSourceConfig, primaryEventSource: self.primaryEventSource!, delegate: self, queue:self.queue) - } - } + self.queue.async { + guard self.connectionState != .disconnecting && self.connectionState != .idle else { + completion() + return + } + + self.connectionState = .disconnecting + self.session?.invalidateAndCancel() + self.session = nil + + self.sessionTask?.cancel() + self.sessionTask = nil + self.connectionState = .idle + + DispatchQueue.main.sync { + self.eventSources.forEach({ (eventSource) in + NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: Notification.Disconnected.rawValue), object: eventSource, userInfo: [ Notification.Key.Source.rawValue : self.connectionURL!.absoluteString ]) + }) + + NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: Notification.Disconnected.rawValue), object: self, userInfo: [ Notification.Key.Source.rawValue : self.connectionURL!.absoluteString ]) + } + + completion() + } + } + + + /// create a new event source given the config. + /// + /// - Parameter eventSourceConfig: a populated EventSourceConfig instance + /// - Returns: the new EventSource instance + @available(*, deprecated, message: "EventSourceConfiguration to be removed, use connect and the addEventSource that takes strings") + open func addEventSource(_ eventSourceConfig: EventSourceConfiguration) -> EventSource { - precondition(eventSource != nil, "Cannot be nil.") - - _ = self.queue.async { - self.eventSources.insert(eventSource) - } + self.queue.async { + if (self.connectionState == .idle) { + self.shouldDisconnectOnLastSource = true + self.connect(toURL: URL(string: eventSourceConfig.uri)!) + } + } + return self.addEventSource(forEvents: eventSourceConfig.events ?? []) + } + + /// create a new event source that listens to the provided events + /// + /// - Parameter events: array of event name strings, if empty array [] listen to everything + /// - Returns: the EventSource instance + open func addEventSource(forEvents events:[String]) -> EventSource { + var eventSource: EventSource! + eventSource = EventSource(withManager: self, events: events) - eventSource?.connect() - - return eventSource - } - - public func reconnect() { self.queue.async { - if (self.eventSources.count > 0 && self.primaryEventSource?.readyState != .open && self.primaryEventSource?.readyState != .connecting) { - precondition(self.primaryEventSource != nil, "no primary event source!Dump other event sources: \(self.eventSources)") - - self.primaryEventSource?.connect() - for eventSource in self.eventSources { - if (eventSource != self.primaryEventSource) { - eventSource.connect() - } - } - } + + precondition(eventSource != nil, "Cannot be nil.") + + self.eventSources.insert(eventSource) } + + return eventSource } - /** - Disconnect and remove EventSource from manager. - */ - open func removeEventSource(_ eventSource: EventSource, _ completion:@escaping ()->() = {}) { - - eventSource.disconnect(allowRetry: false, completion: { - self.queue.async { + /// remove the event source from listening to events + /// + /// - Parameter eventSource: the EventSource instance to remove + /// - Parameter completion: completion closure + open func removeEventSource(_ eventSource: EventSource, _ completion:@escaping ()->() = {}) { + + self.queue.async { + + self.eventSources.remove(eventSource) - if (eventSource == self.primaryEventSource) { - NSLog("destroy primary event source") - self.primaryEventSource = nil - + if self.shouldDisconnectOnLastSource && self.eventSources.count == 0 { + self.disconnect() { + DispatchQueue.main.async { + completion() + } } - - self.eventSources.remove(eventSource) - + } + else { DispatchQueue.main.async { completion() } } - }) - } + + } + } - open func removeAllEventSources(_ completion:@escaping ()->()) { + /// remove all the listening event sources + /// + /// - Parameter completion: completion closure + open func removeAllEventSources(_ completion:@escaping ()->() = {}) { self.queue.async { - var count = self.eventSources.count - guard count != 0 else { + self.eventSources.removeAll() + + if self.shouldDisconnectOnLastSource { + self.disconnect() { + DispatchQueue.main.async { + completion() + } + } + } + else { DispatchQueue.main.async { completion() } - return } + } + } +} + + +// MARK: - URLSessionDataDelegate +extension SSEManager: URLSessionDataDelegate { + + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + self.queue.async { + if let url = self.connectionURL, session == self.session { + guard self.connectionState != .disconnecting else { + self.connectionState = .idle + return + } - for eventSource in self.eventSources { - eventSource.disconnect(allowRetry: false) { - count = count - 1 - if count == 0 { - self.primaryEventSource = nil - self.eventSources.removeAll() - DispatchQueue.main.async { - completion() - } + // session ended... + self.connectionRetries = self.connectionRetries + 1 + if (self.connectionRetries < self.maxConnectionRetries) { + self.connectionState = .idle + self.connect(toURL: url, completion: self.connectCompletionClosure) + } + else { + DispatchQueue.main.sync { + self.connectCompletionClosure?(error as NSError?) + self.connectCompletionClosure = nil + } + self.disconnect() { } } } } } -} - -// MARK: - EventSourceDelegate -extension SSEManager: EventSourceDelegate { - public func eventSource(_ eventSource: EventSource, didChangeState state: ReadyState) { - - //TODO: Logging - print("State -> \(eventSource) -> \(state)") - } - - public func eventSourceDidConnect(_ eventSource: EventSource) { - DispatchQueue.main.async { - NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: Notification.Connected.rawValue), object: eventSource, userInfo: [ Notification.Key.Source.rawValue : eventSource.configuration.uri ]) + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + + guard self.connectionState != .idle, + self.connectionState != .disconnecting, + session == self.session else { + + //Discard any data from here on in + return } - } - - public func eventSourceWillDisconnect(_ eventSource: EventSource) {} - - public func eventSourceDidDisconnect(_ eventSource: EventSource) { - - //Remove disconnected EventSource objects from the array -// self.queue.async { - //TODO -// if let esIndex = self.eventSources.indexOf(eventSource) { -// self.eventSources.removeAtIndex(esIndex) -// } -// } - DispatchQueue.main.async { - NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: Notification.Disconnected.rawValue), object: eventSource, userInfo: [ Notification.Key.Source.rawValue : eventSource.configuration.uri ]) + + guard let response = dataTask.response as? HTTPURLResponse else { + + return //not connected yet } - } - - public func eventSource(_ eventSource: EventSource, didReceiveEvent event: Event) { - - //print("[ES#: \(eventSources.count)] \(eventSource) -> \(event)") - - var userInfo: [String: AnyObject] = [Notification.Key.Source.rawValue : eventSource.configuration.uri as AnyObject, Notification.Key.Timestamp.rawValue : event.metadata.timestamp as AnyObject] - - if let identifier = event.identifier { - userInfo[Notification.Key.Identifier.rawValue] = identifier as AnyObject - } - - if let name = event.event { - userInfo[Notification.Key.Name.rawValue] = name as AnyObject - } - - if let data = event.data { - userInfo[Notification.Key.Data.rawValue] = data as AnyObject - } - if let jsonData = event.jsonData { - userInfo[Notification.Key.JSONData.rawValue] = jsonData as AnyObject + self.queue.async { + + if self.connectionState == .connecting { + + switch response.statusCode { + + case 200...299: + fallthrough + case 300...399: + self.connectionState = .connected + self.connectionRetries = 0 + DispatchQueue.main.sync { + self.eventSources.forEach({ (eventSource) in + NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: Notification.Connected.rawValue), object: eventSource, userInfo: [ Notification.Key.Source.rawValue : self.connectionURL!.absoluteString ]) + }) + self.connectCompletionClosure?(nil) + self.connectCompletionClosure = nil + NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: Notification.Connected.rawValue), object: self, userInfo: [ Notification.Key.Source.rawValue : self.connectionURL!.absoluteString ]) + } + break + + case 400...499: + fallthrough + default: + self.disconnect { + } + return + } + } + + self.parseEventData(data) + } + } + + /// parse the event data, create Event instances, and tell all EventSources about them + /// + /// - Parameter data: the event data received + /// + /// TODO - this should be fileprivate - just enabling for test, put back when mocked URLSession properly + internal func parseEventData(_ data: Data) { + + func scan(_ scanner: Scanner, field:String) -> (String?) { + + let originalLocation = scanner.scanLocation + + scanner.scanUpTo("\(field):", into: nil) + + guard scanner.scanString("\(field):", into:nil) else { // not found, reset and return nil + scanner.scanLocation = originalLocation + return nil + } + + var value: NSString? + scanner.scanUpTo("\n", into: &value) + + // get rid of any newlines + scanner.scanCharacters(from: CharacterSet.whitespacesAndNewlines, into: nil) + + return value as String?; } - DispatchQueue.main.async { - NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: Notification.Event.rawValue), object: eventSource, userInfo: userInfo) + if let eventString = String(data: data, encoding: .utf8) { //NSString(bytes: data.withUnsafeBytes length: data.count, encoding: 4) { + + + let scanner = Scanner(string: eventString as String) + scanner.charactersToBeSkipped = CharacterSet.whitespaces + + repeat { + + var eventId: String?, eventName: String?, eventData: String? + + eventId = scan(scanner, field:"id") + var logger = Logger(subsystem: "SSEKit", category: "SSEManager", ipAddress: self.connectionURL?.host) + + guard let unwrappedEventId = eventId else { // finished + logger.error("No id!") + return + } + + logger = logger.with(properties: ["eventId" : unwrappedEventId]) + + let loc = scanner.scanLocation + eventName = scan(scanner, field:"event") + logger = logger.with(properties: ["eventName" : eventName ?? "UNKNOWN EVENT NAME"]) + + scanner.scanLocation = loc // reset, as this is optional... + + eventData = scan(scanner, field:"data") + + guard let unwrappedEventData = eventData else { // finished + logger.error("No event data!") + return + } + + logger = logger.with(properties: ["eventData" : unwrappedEventData]) + + // SSE EVENT LOGGING + // On if empty set, or specific event matches name + if let logEvents = self.logEvents, logEvents.count == 0 || (eventName != nil && logEvents.contains(eventName!)) { + logger.info("EVENT: \(eventName ?? "nil")") + } + + // Send events to all event sources + for eventSource in self.eventSources { + self.queue.async { + + if let event = Event(withEventSource: eventSource, identifier: unwrappedEventId, event: eventName ?? "", data: unwrappedEventData.data(using: String.Encoding.utf8)) { + eventSource.handleEvent(event) + } + } + } + + } while(!scanner.isAtEnd) } - } - - public func eventSource(_ eventSource: EventSource, didEncounterError error: EventSourceError) { - - //TODO: Send error notification - //print("Error -> \(eventSource) -> \(error)") - } + } } diff --git a/Projects/SSEKit/SSEKitTests/SSEKitTests.swift b/Projects/SSEKit/SSEKitTests/SSEKitTests.swift index cc0ac93..f6b24de 100644 --- a/Projects/SSEKit/SSEKitTests/SSEKitTests.swift +++ b/Projects/SSEKit/SSEKitTests/SSEKitTests.swift @@ -3,34 +3,186 @@ // SSEKitTests // // Created by Richard Stelling on 23/02/2016. -// Copyright © 2016 Richard Stelling All rights reserved. +// Copyright © 2016 Naim Audio All rights reserved. // import XCTest @testable import SSEKit +let DUT_SSE_Endpoint = URL(string: "http://192.168.0.20:15081/notify")! + +// TODO: mock/stub URLSession & Data task + + class SSEKitTests: XCTestCase { - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } + func testConnectSendDisconnectOK() { + + let connectExpectation = self.expectation(description: "should connect ok") + let connectEventExpectation = self.expectation(description: "should get connect event") + let disconnectEventExpectation = self.expectation(description: "has sent disconnection event") + let npEventExpectation = self.expectation(description: "has sent now playing event") + let disconnectExpectation = self.expectation(description: "should disconnect ok") + + let manager = SSEManager() + manager.connect(toURL: DUT_SSE_Endpoint) { (error) in + XCTAssert(error == nil) + XCTAssert(Thread.isMainThread, "closure should be on main thread") + connectExpectation.fulfill() + } + + let eventSource = manager.addEventSource(forEvents: []) // listen to all events + let cheeseSource = manager.addEventSource(forEvents: ["cheese"]) // listen for cheese + + let observer = NotificationCenter.default.addObserver(forName: Notification.Name(SSEManager.Notification.Connected.rawValue), object:eventSource, queue: nil) { (notification) in + + XCTAssert(Thread.isMainThread, "Notifications should be on main thread") + XCTAssert(notification.object as! EventSource == eventSource, "should be eventSource") + + // simulate cheese event + manager.parseEventData(Data("id:11713\nevent:cheese\ndata:{\"version\":\"1\",\"ussi\":\"cheese\",\"id\":\"1\",\"parameters\":{\"transportPosition\":\"8872\"}}".utf8)) + + connectEventExpectation.fulfill() + } + + + let npObserver = NotificationCenter.default.addObserver(forName: Notification.Name(SSEManager.Notification.Event.rawValue), object:cheeseSource, queue: nil) { (notification) in + + XCTAssert(Thread.isMainThread, "Notifications should be on main thread") + XCTAssert(notification.object as! EventSource == cheeseSource, "should be cheeseSource") + + // we are done, lets diconnect + manager.disconnect() { + XCTAssert(manager.connectionState == .idle, "should be in idle state at the end") + disconnectExpectation.fulfill() + } + + npEventExpectation.fulfill() + } + + + let disconnectObserver = NotificationCenter.default.addObserver(forName: Notification.Name(SSEManager.Notification.Disconnected.rawValue), object:eventSource, queue: nil) { (notification) in + XCTAssert(Thread.isMainThread, "Notifications should be on main thread") + disconnectEventExpectation.fulfill() + } + + self.waitForExpectations(timeout: 15) { (error) in + XCTAssertNil(error) + + NotificationCenter.default.removeObserver(observer) + NotificationCenter.default.removeObserver(disconnectObserver) + NotificationCenter.default.removeObserver(npObserver) + } + } + + func testConnectSendOK_depricated() { // use the old way + + let connectExpectation = self.expectation(description: "should connect ok") + let cheeseEventExpectation = self.expectation(description: "has sent cheese event") + let disconnectExpectation = self.expectation(description: "should disconnect ok") + + let manager = SSEManager(sources: []) + + let eventSource = manager.addEventSource(EventSourceConfiguration(withHost: DUT_SSE_Endpoint.host!, port: DUT_SSE_Endpoint.port!, endpoint: DUT_SSE_Endpoint.path, timeout: 5, events: nil, name: "everything!")) + let cheeseSource = manager.addEventSource(EventSourceConfiguration(withHost: DUT_SSE_Endpoint.host!, port: DUT_SSE_Endpoint.port!, endpoint: DUT_SSE_Endpoint.path, timeout: 5, events: ["cheese"], name: "cheese!")) + + let observer = NotificationCenter.default.addObserver(forName: Notification.Name(SSEManager.Notification.Connected.rawValue), object:eventSource, queue: nil) { (notification) in + + XCTAssert(Thread.isMainThread, "Notifications should be on main thread") + + connectExpectation.fulfill() + + // simulate cheese event + manager.parseEventData(Data("id:11713\nevent:cheese\ndata:{\"version\":\"1\",\"ussi\":\"cheese\",\"id\":\"1\",\"parameters\":{\"transportPosition\":\"8872\"}}".utf8)) + + } + + let cheeseObserver = NotificationCenter.default.addObserver(forName: Notification.Name(SSEManager.Notification.Event.rawValue), object:cheeseSource, queue: nil) { (notification) in + + XCTAssert(Thread.isMainThread, "Notifications should be on main thread") + XCTAssert(notification.object as! EventSource == cheeseSource, "should be cheeseSource") + + manager.removeAllEventSources { + XCTAssert(manager.connectionState == .idle, "should be in idle state at the end") + disconnectExpectation.fulfill() + } + + cheeseEventExpectation.fulfill() + } + + self.waitForExpectations(timeout: 5) { (error) in + XCTAssertNil(error) + + NotificationCenter.default.removeObserver(observer) + NotificationCenter.default.removeObserver(cheeseObserver) + } + } + + func testConnectNOK() { + let connectResponseExpectation = self.expectation(description: "should call connect closure") + + + let manager = SSEManager() + + manager.maxConnectionRetries = 2 // or test takes too long! + + // connect to an invalid ip (get a request timed out) + manager.connect(toURL: URL(string: "http://999.999.999.999:15081/notify")!, completion: { (error) in + XCTAssert(error != nil) + XCTAssert(Thread.isMainThread, "closure should be on main thread") + connectResponseExpectation.fulfill() + }) + + self.waitForExpectations(timeout: 15) { (error) in + XCTAssertNil(error) + } + } + + + func testConnectOKThenFail() { + let connectResponseExpectation = self.expectation(description: "should call connect closure") + let disconnectEventExpectation = self.expectation(description: "should disconnect") + + let manager = SSEManager() + + manager.maxConnectionRetries = 0 // stop retries + + manager.connect(toURL: DUT_SSE_Endpoint, completion: { (error) in + XCTAssert(error == nil) + XCTAssert(Thread.isMainThread, "closure should be on main thread") + connectResponseExpectation.fulfill() + + // force an error + manager.urlSession(manager.session!, task: manager.sessionTask!, didCompleteWithError: NSError(domain: "com.naim.ssekittest", code: 1, userInfo: [:]) as Error) + }) + + let disconnectObserver = NotificationCenter.default.addObserver(forName: Notification.Name(SSEManager.Notification.Disconnected.rawValue), object:manager, queue: nil) { (notification) in + XCTAssert(Thread.isMainThread, "Notifications should be on main thread") + disconnectEventExpectation.fulfill() + } + + self.waitForExpectations(timeout: 15) { (error) in + XCTAssertNil(error) + XCTAssert(manager.connectionState == SSEManager.ConnectionState.idle, "should be idle") + NotificationCenter.default.removeObserver(disconnectObserver) + } + } - func testPerformanceExample() { - // This is an example of a performance test case. - self.measureBlock { - // Put the code you want to measure the time of here. - } - } + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } } diff --git a/Projects/SSEKitExample/SSEKitExample/AppDelegate.swift b/Projects/SSEKitExample/SSEKitExample/AppDelegate.swift index b80976d..52ddfdd 100644 --- a/Projects/SSEKitExample/SSEKitExample/AppDelegate.swift +++ b/Projects/SSEKitExample/SSEKitExample/AppDelegate.swift @@ -11,35 +11,35 @@ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? + var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. + } - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. + } - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } } diff --git a/Projects/SSEKitExample/SSEKitExample/ViewController.swift b/Projects/SSEKitExample/SSEKitExample/ViewController.swift index ccf2e02..d544155 100644 --- a/Projects/SSEKitExample/SSEKitExample/ViewController.swift +++ b/Projects/SSEKitExample/SSEKitExample/ViewController.swift @@ -11,41 +11,41 @@ import SSEKit class ViewController: UIViewController { - var manager: SSEManager? + var manager: SSEManager? - @IBOutlet weak internal var logViewer: UITextView! + @IBOutlet weak internal var logViewer: UITextView! - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. - NotificationCenter.default.addObserver(forName: nil, object: nil, queue: nil) { + NotificationCenter.default.addObserver(forName: nil, object: nil, queue: nil) { - //SSEManager.Notification.Key.Name.rawValue + //SSEManager.Notification.Key.Name.rawValue - guard let name = $0.userInfo?[SSEManager.Notification.Key.Name.rawValue], - let data = $0.userInfo?[SSEManager.Notification.Key.Data.rawValue] as? Data, - let identifier = $0.userInfo?[SSEManager.Notification.Key.Identifier.rawValue] as? String, - let timestamp = $0.userInfo?[SSEManager.Notification.Key.Timestamp.rawValue] as? Date - else { + guard let name = $0.userInfo?[SSEManager.Notification.Key.Name.rawValue], + let data = $0.userInfo?[SSEManager.Notification.Key.Data.rawValue] as? Data, + let identifier = $0.userInfo?[SSEManager.Notification.Key.Identifier.rawValue] as? String, + let timestamp = $0.userInfo?[SSEManager.Notification.Key.Timestamp.rawValue] as? Date + else { - return - } + return + } - let dataStr = String(data: data, encoding: String.Encoding(rawValue: 4))! + let dataStr = String(data: data, encoding: String.Encoding(rawValue: 4))! - print("\(timestamp): [\(identifier)] \(name) -> \(dataStr)") - } + print("\(timestamp): [\(identifier)] \(name) -> \(dataStr)") + } - //let config = EventSourceConfiguration(withHost: "192.168.37.123", port: 15081, endpoint: "/notify", timeout: 10, events: nil) - //let config2 = EventSourceConfiguration(withHost: "192.168.37.123", port: 15081, endpoint: "/notify", events: ["nowplaying"]) - //let config4 = EventSourceConfiguration(withHost: "192.168.37.76", port: 8080, endpoint: "/sse", events: ["bad-event"]) + //let config = EventSourceConfiguration(withHost: "192.168.37.123", port: 15081, endpoint: "/notify", timeout: 10, events: nil) + //let config2 = EventSourceConfiguration(withHost: "192.168.37.123", port: 15081, endpoint: "/notify", events: ["nowplaying"]) + //let config4 = EventSourceConfiguration(withHost: "192.168.37.76", port: 8080, endpoint: "/sse", events: ["bad-event"]) - //let config2 = EventSourceConfiguration(withHost: "localhost", port: 8080, endpoint: "/sse2") - //let config3 = EventSourceConfiguration(withHost: "localhost", port: 8081, endpoint: "/sse") + //let config2 = EventSourceConfiguration(withHost: "localhost", port: 8080, endpoint: "/sse2") + //let config3 = EventSourceConfiguration(withHost: "localhost", port: 8081, endpoint: "/sse") - manager = SSEManager(sources: []) + manager = SSEManager(sources: []) /* for i in 0...10 { @@ -78,62 +78,62 @@ class ViewController: UIViewController { // let es2 = manager?.addEventSource(config2) // // NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(onEvent2(_:)), name: SSEManager.Notification.Event.rawValue, object: es2) - } + } - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } - @objc - func onEvent(_ note: Notification) { + @objc + func onEvent(_ note: Notification) { - if let identifier = note.userInfo?["Identifier"], - let name = note.userInfo?["Name"], - let source = note.userInfo?["Source"], - let timestamp = note.userInfo?["Timestamp"] { + if let identifier = note.userInfo?["Identifier"], + let name = note.userInfo?["Name"], + let source = note.userInfo?["Source"], + let timestamp = note.userInfo?["Timestamp"] { - guard let eventSource = note.object as? EventSource else { - return - } + guard let eventSource = note.object as? EventSource else { + return + } - let event = "\(eventSource.name!) [\(identifier)]\t\(name)\t\(source)\t\(timestamp)\n" + let event = "\(eventSource.name!) [\(identifier)]\t\(name)\t\(source)\t\(timestamp)\n" - DispatchQueue.main.async { - self.logViewer.text = (event + self.logViewer.text) - } - } - } + DispatchQueue.main.async { + self.logViewer.text = (event + self.logViewer.text) + } + } + } - func onEvent2(_ note: Notification) { + func onEvent2(_ note: Notification) { - if let identifier = note.userInfo?["Identifier"], - let name = note.userInfo?["Name"] { + if let identifier = note.userInfo?["Identifier"], + let name = note.userInfo?["Name"] { - let event = "2️⃣[\(identifier)]\t\(name)\n" + let event = "2️⃣[\(identifier)]\t\(name)\n" - DispatchQueue.main.async { - self.logViewer.text = (event + self.logViewer.text) - } + DispatchQueue.main.async { + self.logViewer.text = (event + self.logViewer.text) + } - } - } + } + } - func onConnected(_ note: Notification) { + func onConnected(_ note: Notification) { - DispatchQueue.main.async { - self.logViewer.text = (" **** CONNECTED ****" + self.logViewer.text) - } - } + DispatchQueue.main.async { + self.logViewer.text = (" **** CONNECTED ****" + self.logViewer.text) + } + } - func onDisconnected(_ note: Notification) { + func onDisconnected(_ note: Notification) { - DispatchQueue.main.async { - self.logViewer.text = (" ==== DISCONNECTED ====" + self.logViewer.text) - } - } + DispatchQueue.main.async { + self.logViewer.text = (" ==== DISCONNECTED ====" + self.logViewer.text) + } + } - override var prefersStatusBarHidden : Bool { - return true - } + override var prefersStatusBarHidden : Bool { + return true + } } diff --git a/Projects/SSEKitExample/SSEKitExampleTests/SSEKitExampleTests.swift b/Projects/SSEKitExample/SSEKitExampleTests/SSEKitExampleTests.swift index 68789b0..bc6aa2c 100644 --- a/Projects/SSEKitExample/SSEKitExampleTests/SSEKitExampleTests.swift +++ b/Projects/SSEKitExample/SSEKitExampleTests/SSEKitExampleTests.swift @@ -11,26 +11,26 @@ import XCTest class SSEKitExampleTests: XCTestCase { - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } } diff --git a/Prototypes/MyPlayground.playground/Contents.swift b/Prototypes/MyPlayground.playground/Contents.swift index 2d9e29d..cff89b5 100644 --- a/Prototypes/MyPlayground.playground/Contents.swift +++ b/Prototypes/MyPlayground.playground/Contents.swift @@ -16,31 +16,31 @@ private let underlyingQueue: dispatch_queue_t! = dispatch_queue_create("com.naim class hold { - init(queue: dispatch_queue_t!) { - print("Git queue: \(queue)") - } + init(queue: dispatch_queue_t!) { + print("Git queue: \(queue)") + } } class holdNumber { - init(holdNumber: Int) { - print("Got number: \(holdNumber)") - } + init(holdNumber: Int) { + print("Got number: \(holdNumber)") + } } public class test { - lazy var underlyingQueue: dispatch_queue_t! = dispatch_queue_create("com.naim.DuleQueue", DISPATCH_QUEUE_SERIAL) - internal var number: Int = 2 + lazy var underlyingQueue: dispatch_queue_t! = dispatch_queue_create("com.naim.DuleQueue", DISPATCH_QUEUE_SERIAL) + internal var number: Int = 2 - //let myHold = hold(queue: underlyingQueue) - private lazy var myNumber : holdNumber = holdNumber(holdNumber: self.number) + //let myHold = hold(queue: underlyingQueue) + private lazy var myNumber : holdNumber = holdNumber(holdNumber: self.number) - func out() { - print("Test: \(underlyingQueue)") - } + func out() { + print("Test: \(underlyingQueue)") + } } var myTest = test() diff --git a/Prototypes/Operation Queue.playground/Contents.swift b/Prototypes/Operation Queue.playground/Contents.swift index fba3e22..10434e9 100644 --- a/Prototypes/Operation Queue.playground/Contents.swift +++ b/Prototypes/Operation Queue.playground/Contents.swift @@ -31,37 +31,37 @@ XCPSetExecutionShouldContinueIndefinitely(true) class DuleQueue { - //dispatch_queue_attr_make_with_qos_class - private let underlyingQueue: dispatch_queue_t! // = dispatch_queue_create("com.naim.DuleQueue", DISPATCH_QUEUE_SERIAL) + //dispatch_queue_attr_make_with_qos_class + private let underlyingQueue: dispatch_queue_t! // = dispatch_queue_create("com.naim.DuleQueue", DISPATCH_QUEUE_SERIAL) - //private let testQueue: NSOperationQueue = NSOperationQueue( + //private let testQueue: NSOperationQueue = NSOperationQueue( - private let firstQueue = NSOperationQueue(qualityOfService: .Utility) + private let firstQueue = NSOperationQueue(qualityOfService: .Utility) - private let secondQueue = NSOperationQueue(qualityOfService: .Utility) + private let secondQueue = NSOperationQueue(qualityOfService: .Utility) - init() { + init() { - let attrs = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0) - underlyingQueue = dispatch_queue_create("com.naim.DuleQueue", attrs) + let attrs = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0) + underlyingQueue = dispatch_queue_create("com.naim.DuleQueue", attrs) - firstQueue.underlyingQueue = underlyingQueue - secondQueue.underlyingQueue = underlyingQueue - } + firstQueue.underlyingQueue = underlyingQueue + secondQueue.underlyingQueue = underlyingQueue + } - func dispachOnFirstQueue(block: dispatch_block_t) { + func dispachOnFirstQueue(block: dispatch_block_t) { - let operation = NSBlockOperation(block: block) + let operation = NSBlockOperation(block: block) - firstQueue.addOperations([operation], waitUntilFinished: true) - } + firstQueue.addOperations([operation], waitUntilFinished: true) + } - func dispachOnSecondQueue(block: dispatch_block_t) { + func dispachOnSecondQueue(block: dispatch_block_t) { - let operation = NSBlockOperation(block: block) + let operation = NSBlockOperation(block: block) - secondQueue.addOperations([operation], waitUntilFinished: true) - } + secondQueue.addOperations([operation], waitUntilFinished: true) + } } let queue = DuleQueue() @@ -71,19 +71,19 @@ var data = [Int]() for i in 0..<10 { - queue.dispachOnFirstQueue { - data.append(1) - sleep(3) - } + queue.dispachOnFirstQueue { + data.append(1) + sleep(3) + } - queue.dispachOnSecondQueue { - data.append(8) - sleep(1) - } + queue.dispachOnSecondQueue { + data.append(8) + sleep(1) + } } queue.dispachOnSecondQueue { - print(" --> \(data)") + print(" --> \(data)") } //for i in 0..<10 { diff --git a/Prototypes/Operation Queue.playground/Sources/OperationQueue.swift b/Prototypes/Operation Queue.playground/Sources/OperationQueue.swift index 2a3d09f..9357696 100644 --- a/Prototypes/Operation Queue.playground/Sources/OperationQueue.swift +++ b/Prototypes/Operation Queue.playground/Sources/OperationQueue.swift @@ -10,25 +10,25 @@ import Foundation extension NSOperationQueue { - //private let underlyingQueue: dispatch_queue_t! + //private let underlyingQueue: dispatch_queue_t! - convenience init(qualityOfService: NSQualityOfService, maxConcurrentOperationCount: Int = 1, underlyingQueue: dispatch_queue_t?) { + convenience init(qualityOfService: NSQualityOfService, maxConcurrentOperationCount: Int = 1, underlyingQueue: dispatch_queue_t?) { - self.init() + self.init() - self.qualityOfService = qualityOfService - self.maxConcurrentOperationCount = maxConcurrentOperationCount + self.qualityOfService = qualityOfService + self.maxConcurrentOperationCount = maxConcurrentOperationCount - if let queue = underlyingQueue { - self.underlyingQueue = queue - } + if let queue = underlyingQueue { + self.underlyingQueue = queue + } // else { // let attrs = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0) // underlyingQueue = dispatch_queue_create("com.naim.DuleQueue", attrs) // // self.underlyingQueue = underlyingQueue // } - } + } // convenience init(qualityOfService: NSQualityOfService, maxConcurrentOperationCount: Int = 1) { // self.init() diff --git a/Prototypes/ProducerConsumer.playground/Contents.swift b/Prototypes/ProducerConsumer.playground/Contents.swift index 904b051..0b0bf8d 100644 --- a/Prototypes/ProducerConsumer.playground/Contents.swift +++ b/Prototypes/ProducerConsumer.playground/Contents.swift @@ -8,54 +8,54 @@ XCPSetExecutionShouldContinueIndefinitely(true) extension NSOperationQueue { - convenience init(qualityOfService: NSQualityOfService, maxConcurrentOperationCount: Int = 1) { - self.init() + convenience init(qualityOfService: NSQualityOfService, maxConcurrentOperationCount: Int = 1) { + self.init() - self.qualityOfService = qualityOfService - self.maxConcurrentOperationCount = maxConcurrentOperationCount - } + self.qualityOfService = qualityOfService + self.maxConcurrentOperationCount = maxConcurrentOperationCount + } } class Producer { - //private unowned let dataWrite: NSMutableData - private var dataWriteString: DataContainer + //private unowned let dataWrite: NSMutableData + private var dataWriteString: DataContainer - //private let dataToWrite = "abcdefghijklmnopqrstuvwxyz+0123456789+ABCDEFGHIJKLMNOPQRSTUVWXYZ+" - private let dataToWrite = "abcd123+efgh456+abcd123+efgh456+abcd123+efgh456+abcd123+efgh456+abcd123+efgh456+a+b+c+d+f+g+h+i+j+k+l+aa+bb+cc+dd+aaa+bbb+ccc+efgh456abcd123efgh456efgh456abcd123efgh456+" + //private let dataToWrite = "abcdefghijklmnopqrstuvwxyz+0123456789+ABCDEFGHIJKLMNOPQRSTUVWXYZ+" + private let dataToWrite = "abcd123+efgh456+abcd123+efgh456+abcd123+efgh456+abcd123+efgh456+abcd123+efgh456+a+b+c+d+f+g+h+i+j+k+l+aa+bb+cc+dd+aaa+bbb+ccc+efgh456abcd123efgh456efgh456abcd123efgh456+" - var clock: NSTimer! - //lazy var clock: NSTimer! = NSTimer.scheduledTimerWithTimeInterval(4, target: self, selector: "produceSomeData", userInfo: nil, repeats: true) + var clock: NSTimer! + //lazy var clock: NSTimer! = NSTimer.scheduledTimerWithTimeInterval(4, target: self, selector: "produceSomeData", userInfo: nil, repeats: true) - var count = 0 + var count = 0 - private unowned let consumer: Consumer + private unowned let consumer: Consumer - init(data: DataContainer, consumer: Consumer) { - dataWriteString = data - self.consumer = consumer + init(data: DataContainer, consumer: Consumer) { + dataWriteString = data + self.consumer = consumer - clock = NSTimer.scheduledTimerWithTimeInterval(0.0125, target: self, selector: "produceSomeData:", userInfo: nil, repeats: true) - clock.fire() + clock = NSTimer.scheduledTimerWithTimeInterval(0.0125, target: self, selector: "produceSomeData:", userInfo: nil, repeats: true) + clock.fire() - print("Created Producer...") - } + print("Created Producer...") + } - @objc func produceSomeData(timer: NSTimer) { - //print("- tick") + @objc func produceSomeData(timer: NSTimer) { + //print("- tick") - let length = dataToWrite.characters.count - let randomInt = Int(arc4random_uniform(UInt32(length))) + 1 //no zeros - let index = dataToWrite.startIndex.advancedBy(randomInt) - let string = dataToWrite.substringToIndex(index) + let length = dataToWrite.characters.count + let randomInt = Int(arc4random_uniform(UInt32(length))) + 1 //no zeros + let index = dataToWrite.startIndex.advancedBy(randomInt) + let string = dataToWrite.substringToIndex(index) - //print("Adding string \"\(string)\" to data container") + //print("Adding string \"\(string)\" to data container") - //let data: NSData! = string.dataUsingEncoding(NSUTF8StringEncoding) + //let data: NSData! = string.dataUsingEncoding(NSUTF8StringEncoding) - //print("Adding string \"\(string)\" to data container") - //print("Adding data \"\(data)\" to data container") + //print("Adding string \"\(string)\" to data container") + //print("Adding data \"\(data)\" to data container") // if(data != nil) { // dataWrite.appendData(data) @@ -74,35 +74,35 @@ class Producer { if (count % 50) == 0 { //(Int(arc4random_uniform(200))) + 10 clock.fireDate = NSDate().dateByAddingTimeInterval(Double(arc4random_uniform(5))) } - } + } } class Consumer { - //private unowned let dataRead: NSMutableData - private var dataReadString: DataContainer + //private unowned let dataRead: NSMutableData + private var dataReadString: DataContainer - var clock: NSTimer! + var clock: NSTimer! - var location: String.Index! - var distance: String.Index.Distance = 0 - var foundChuncks = 0 + var location: String.Index! + var distance: String.Index.Distance = 0 + var foundChuncks = 0 - var progressArray: [Float] = [] + var progressArray: [Float] = [] - let opsQueue = NSOperationQueue(qualityOfService: .Background) + let opsQueue = NSOperationQueue(qualityOfService: .Background) - init(data: DataContainer) { - dataReadString = data - location = dataReadString.startIndex(); + init(data: DataContainer) { + dataReadString = data + location = dataReadString.startIndex(); - clock = NSTimer.scheduledTimerWithTimeInterval(0.25, target: self, selector: "consumeSomeData:", userInfo: nil, repeats: true) + clock = NSTimer.scheduledTimerWithTimeInterval(0.25, target: self, selector: "consumeSomeData:", userInfo: nil, repeats: true) - print("Created Consumer...") - } + print("Created Consumer...") + } - func scheduleDrain() { + func scheduleDrain() { // let dataDelta = dataReadString.characters.count - location // XCPCaptureValue("Delta", value: dataDelta) @@ -113,93 +113,93 @@ class Consumer { } } - } + } - var count: Int = 0 + var count: Int = 0 - func drain() { + func drain() { - count++ + count++ - //location in NSData is not the same as location in String - //DATA->String->string buffer->consumer - //let nextRange = Range(start: location, end: dataReadString.endIndex) + //location in NSData is not the same as location in String + //DATA->String->string buffer->consumer + //let nextRange = Range(start: location, end: dataReadString.endIndex) - //let nextRange: Range = NSMakeRange(location, (dataReadString.characters.count - location)) //range from location to end of data container - //let subData = dataRead.subdataWithRange(nextRange) - let newLocation = dataReadString.startIndex(); //This gets the Index for the current string size - let newNewLoc = newLocation.advancedBy(distance) + //let nextRange: Range = NSMakeRange(location, (dataReadString.characters.count - location)) //range from location to end of data container + //let subData = dataRead.subdataWithRange(nextRange) + let newLocation = dataReadString.startIndex(); //This gets the Index for the current string size + let newNewLoc = newLocation.advancedBy(distance) - //let range = Range(start: newNewLoc, end: dataReadString.endIndex()) - // print("distance: \(distance) range: \(range)") + //let range = Range(start: newNewLoc, end: dataReadString.endIndex()) + // print("distance: \(distance) range: \(range)") - let string = dataReadString.chunk(fromIndex: newNewLoc) + let string = dataReadString.chunk(fromIndex: newNewLoc) - //print("Data: \(subData)") + //print("Data: \(subData)") - //let string: String! = String(data: subData, encoding: NSUTF8StringEncoding) + //let string: String! = String(data: subData, encoding: NSUTF8StringEncoding) - if (string.characters.count > 0) { + if (string.characters.count > 0) { - let scanner = NSScanner(string: string) + let scanner = NSScanner(string: string) - while(!scanner.atEnd) { + while(!scanner.atEnd) { - var foundStr: NSString? = nil + var foundStr: NSString? = nil - if scanner.scanUpToString("+", intoString: &foundStr) { //capture string in nil + if scanner.scanUpToString("+", intoString: &foundStr) { //capture string in nil - if(scanner.scanString("+", intoString: nil)) { //scan past +) + if(scanner.scanString("+", intoString: nil)) { //scan past +) - //GET NEW LOCATION EVERY TIME + //GET NEW LOCATION EVERY TIME - //location.advancedBy(foundStr!.length) + //location.advancedBy(foundStr!.length) - //print("Length: NS->\(foundStr!.length), S->\(foundString.characters.count)") - //let dis: String.Index.Distance = foundString.characters.count as String.Index.Distance - // print("DISTANCE: \(dis)") - //let newLocation = location.advancedBy(foundString.characters.count) - // print("NEW LOCATION: \(newLocation)") + //print("Length: NS->\(foundStr!.length), S->\(foundString.characters.count)") + //let dis: String.Index.Distance = foundString.characters.count as String.Index.Distance + // print("DISTANCE: \(dis)") + //let newLocation = location.advancedBy(foundString.characters.count) + // print("NEW LOCATION: \(newLocation)") - let foundString: String = foundStr as! String + let foundString: String = foundStr as! String - let distanceToAdvance = 1 + (foundString.characters.count as String.Index.Distance) //+1 for the '+' - distance += distanceToAdvance + let distanceToAdvance = 1 + (foundString.characters.count as String.Index.Distance) //+1 for the '+' + distance += distanceToAdvance - //print("location: \(location)") + //print("location: \(location)") - foundChuncks++ - } - } - } - - if (dataReadString.length >= 512) || ((foundChuncks % 64) == 0) { - //TODO: only add one at a time - let op = NSBlockOperation { - let index = self.dataReadString.startIndex().advancedBy(self.distance) - self.dataReadString.trim(fromIndex: index) - self.distance = 0 - } + foundChuncks++ + } + } + } + + if (dataReadString.length >= 512) || ((foundChuncks % 64) == 0) { + //TODO: only add one at a time + let op = NSBlockOperation { + let index = self.dataReadString.startIndex().advancedBy(self.distance) + self.dataReadString.trim(fromIndex: index) + self.distance = 0 + } - op.queuePriority = .VeryHigh + op.queuePriority = .VeryHigh - opsQueue.addOperation(op) - } - } - else { - print("data string: \(dataReadString)") - print("Bad data: \(string)") - } - } + opsQueue.addOperation(op) + } + } + else { + print("data string: \(dataReadString)") + print("Bad data: \(string)") + } + } - @objc func consumeSomeData(timer: NSTimer) { + @objc func consumeSomeData(timer: NSTimer) { - //print("Consumer storage length: \(dataReadString.length)") + //print("Consumer storage length: \(dataReadString.length)") - _ = dataReadString.length - //XCPCaptureValue("Length", value: storageLength) + _ = dataReadString.length + //XCPCaptureValue("Length", value: storageLength) - // print("Location index: \(location)") + // print("Location index: \(location)") // let progress = 1.0 - (Float(location) / Float(dataRead.length)) @@ -207,24 +207,24 @@ class Consumer { // // let queueLength = opsQueue.operationCount // XCPCaptureValue("Queue Length", value: queueLength) - } + } } class DataContainer { - init() { + init() { - } + } - //location? + //location? - var length: Int { + var length: Int { - get { - return internalStringStorage.characters.count //TODO - } - } + get { + return internalStringStorage.characters.count //TODO + } + } // func distanceToEnd(fromIndex index: Range) { // @@ -232,41 +232,41 @@ class DataContainer { // // } - private var internalStringStorage = String() + private var internalStringStorage = String() - func append(data: String) { + func append(data: String) { - internalStringStorage += data //TODO: Thread safe? + internalStringStorage += data //TODO: Thread safe? - } + } - func chunk(range: Range) -> String { + func chunk(range: Range) -> String { - return internalStringStorage.substringWithRange(range) - } + return internalStringStorage.substringWithRange(range) + } - func chunk(fromIndex index: String.Index) -> String { + func chunk(fromIndex index: String.Index) -> String { - let range = Range(start: index, end: internalStringStorage.endIndex) + let range = Range(start: index, end: internalStringStorage.endIndex) - //print("\tchunk :: \(range) -> internalStringStorage") + //print("\tchunk :: \(range) -> internalStringStorage") - return internalStringStorage.substringWithRange(range) - } + return internalStringStorage.substringWithRange(range) + } - func startIndex() -> String.Index { - return internalStringStorage.startIndex - } + func startIndex() -> String.Index { + return internalStringStorage.startIndex + } - func endIndex() -> String.Index { - return internalStringStorage.endIndex - } + func endIndex() -> String.Index { + return internalStringStorage.endIndex + } - func trim(fromIndex index: String.Index) { + func trim(fromIndex index: String.Index) { - let trimmedString = chunk(fromIndex: index) - internalStringStorage = trimmedString - } + let trimmedString = chunk(fromIndex: index) + internalStringStorage = trimmedString + } } //let dataContainer: NSMutableData! = NSMutableData(capacity: 512) diff --git a/Prototypes/SSEKitExample/SSEKitExample/AppDelegate.swift b/Prototypes/SSEKitExample/SSEKitExample/AppDelegate.swift index 4fd15af..4349b7e 100644 --- a/Prototypes/SSEKitExample/SSEKitExample/AppDelegate.swift +++ b/Prototypes/SSEKitExample/SSEKitExample/AppDelegate.swift @@ -11,35 +11,35 @@ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? + var window: UIWindow? - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - // Override point for customization after application launch. - return true - } + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + // Override point for customization after application launch. + return true + } - func applicationWillResignActive(application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } + func applicationWillResignActive(application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. + } - func applicationDidEnterBackground(application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } + func applicationDidEnterBackground(application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } - func applicationWillEnterForeground(application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } + func applicationWillEnterForeground(application: UIApplication) { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. + } - func applicationDidBecomeActive(application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } + func applicationDidBecomeActive(application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } - func applicationWillTerminate(application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } + func applicationWillTerminate(application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } } diff --git a/Prototypes/SSEKitExample/SSEKitExample/ViewController.swift b/Prototypes/SSEKitExample/SSEKitExample/ViewController.swift index b1828e9..2fdbeb6 100644 --- a/Prototypes/SSEKitExample/SSEKitExample/ViewController.swift +++ b/Prototypes/SSEKitExample/SSEKitExample/ViewController.swift @@ -11,58 +11,58 @@ import SSEKit class ViewController: UIViewController { - let sse = EventSource(host: "lamp.private", path: "/sse.php") + let sse = EventSource(host: "lamp.private", path: "/sse.php") - override func viewDidLoad() { - super.viewDidLoad() + override func viewDidLoad() { + super.viewDidLoad() - print("SSEKit version: \(sse.versionString)") + print("SSEKit version: \(sse.versionString)") - } + } - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } } /* class ViewController: UIViewController, StateDelegate { - enum ExampleState { - case Initial - } + enum ExampleState { + case Initial + } - typealias StateType = ExampleState + typealias StateType = ExampleState - override func viewDidLoad() { - super.viewDidLoad() + override func viewDidLoad() { + super.viewDidLoad() - let stateMachine = State(initialState:.Initial, delegate:self) + let stateMachine = State(initialState:.Initial, delegate:self) - print("State: \(stateMachine.state)") - print("State Machine version: \(stateMachine.version)") + print("State: \(stateMachine.state)") + print("State Machine version: \(stateMachine.version)") - } + } - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } - // MARK: StateDelegate + // MARK: StateDelegate - func shouldTransitionFrom(from:StateType, to:StateType) -> Bool { - return true - } + func shouldTransitionFrom(from:StateType, to:StateType) -> Bool { + return true + } - func didTransitionFrom(from:StateType, to:StateType) { + func didTransitionFrom(from:StateType, to:StateType) { - } + } - func failedTransitionFrom(from:StateType, to:StateType) { + func failedTransitionFrom(from:StateType, to:StateType) { - } + } } */ \ No newline at end of file diff --git a/SSEKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SSEKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/SSEKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + +