From e7b54d7bc9cf836a99aff918e7d430c5da046948 Mon Sep 17 00:00:00 2001 From: Marko Tadic Date: Mon, 1 Aug 2016 13:50:43 +0200 Subject: [PATCH 01/17] Swift Migrator - Xcode 8 Beta 3 --- .../project.pbxproj | 80 ++++++++++- Example/MissionControlDemo/AppDelegate.swift | 8 +- .../MissionControlDemo/BaseLaunchView.swift | 126 +++++++++--------- Example/MissionControlDemo/LaunchBrain.swift | 26 ++-- Example/MissionControlDemo/LaunchView.swift | 48 +++---- MissionControl.xcodeproj/project.pbxproj | 11 ++ Sources/MissionControl.swift | 104 +++++++-------- Tests/MissionControlTests.swift | 52 ++++---- 8 files changed, 272 insertions(+), 183 deletions(-) diff --git a/Example/MissionControlDemo.xcodeproj/project.pbxproj b/Example/MissionControlDemo.xcodeproj/project.pbxproj index 136f5ca..8a00f1f 100644 --- a/Example/MissionControlDemo.xcodeproj/project.pbxproj +++ b/Example/MissionControlDemo.xcodeproj/project.pbxproj @@ -42,6 +42,41 @@ remoteGlobalIDString = 8B63137A1CE5F9A10029DC98; remoteInfo = "MissionControl iOS"; }; + 8BA444261D4F6EAA00E99FAE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8B2A29C61CEA27DF00FAE67F /* MissionControl.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8B03C1E01CF5E10500B09B48; + remoteInfo = "MissionControl watchOS"; + }; + 8BA444281D4F6EAA00E99FAE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8B2A29C61CEA27DF00FAE67F /* MissionControl.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8B03C1EF1CF5E1DD00B09B48; + remoteInfo = "MissionControl tvOS"; + }; + 8BA4442A1D4F6EAA00E99FAE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8B2A29C61CEA27DF00FAE67F /* MissionControl.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8B03C1F81CF5E1DD00B09B48; + remoteInfo = "MissionControl tvOS Tests"; + }; + 8BA4442C1D4F6EAA00E99FAE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8B2A29C61CEA27DF00FAE67F /* MissionControl.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8B03C20E1CF5E28C00B09B48; + remoteInfo = "MissionControl OSX"; + }; + 8BA4442E1D4F6EAA00E99FAE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8B2A29C61CEA27DF00FAE67F /* MissionControl.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8B03C2171CF5E28C00B09B48; + remoteInfo = "MissionControl OSX Tests"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -90,6 +125,11 @@ children = ( 8B2A29CC1CEA27DF00FAE67F /* MissionControl.framework */, 8B2A29CE1CEA27DF00FAE67F /* MissionControlTests.xctest */, + 8BA444271D4F6EAA00E99FAE /* MissionControl.framework */, + 8BA444291D4F6EAA00E99FAE /* MissionControl.framework */, + 8BA4442B1D4F6EAA00E99FAE /* MissionControl tvOS Tests.xctest */, + 8BA4442D1D4F6EAA00E99FAE /* MissionControl.framework */, + 8BA4442F1D4F6EAA00E99FAE /* MissionControl OSX Tests.xctest */, ); name = Products; sourceTree = ""; @@ -194,6 +234,7 @@ TargetAttributes = { 8B464DCB1CE3742F00BAE834 = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0800; }; }; }; @@ -232,10 +273,45 @@ 8B2A29CE1CEA27DF00FAE67F /* MissionControlTests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; - path = MissionControlTests.xctest; + path = "MissionControl iOS Tests.xctest"; remoteRef = 8B2A29CD1CEA27DF00FAE67F /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 8BA444271D4F6EAA00E99FAE /* MissionControl.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MissionControl.framework; + remoteRef = 8BA444261D4F6EAA00E99FAE /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8BA444291D4F6EAA00E99FAE /* MissionControl.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MissionControl.framework; + remoteRef = 8BA444281D4F6EAA00E99FAE /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8BA4442B1D4F6EAA00E99FAE /* MissionControl tvOS Tests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = "MissionControl tvOS Tests.xctest"; + remoteRef = 8BA4442A1D4F6EAA00E99FAE /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8BA4442D1D4F6EAA00E99FAE /* MissionControl.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MissionControl.framework; + remoteRef = 8BA4442C1D4F6EAA00E99FAE /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8BA4442F1D4F6EAA00E99FAE /* MissionControl OSX Tests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = "MissionControl OSX Tests.xctest"; + remoteRef = 8BA4442E1D4F6EAA00E99FAE /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ @@ -389,6 +465,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appculture.MissionControlDemo; PRODUCT_NAME = MissionControlDemo; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -401,6 +478,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appculture.MissionControlDemo; PRODUCT_NAME = MissionControlDemo; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/Example/MissionControlDemo/AppDelegate.swift b/Example/MissionControlDemo/AppDelegate.swift index 497b91e..223f82f 100644 --- a/Example/MissionControlDemo/AppDelegate.swift +++ b/Example/MissionControlDemo/AppDelegate.swift @@ -31,19 +31,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - let url = NSURL(string: "http://private-83024-missioncontrol5.apiary-mock.com/mission-control/launch-config")! + let url = URL(string: "http://private-83024-missioncontrol5.apiary-mock.com/mission-control/launch-config")! MissionControl.launch(remoteConfigURL: url) return true } - func applicationWillEnterForeground(application: UIApplication) { + func applicationWillEnterForeground(_ application: UIApplication) { MissionControl.refresh() } - func applicationDidBecomeActive(application: UIApplication) { + func applicationDidBecomeActive(_ application: UIApplication) { MissionControl.refresh() } diff --git a/Example/MissionControlDemo/BaseLaunchView.swift b/Example/MissionControlDemo/BaseLaunchView.swift index 627c750..4c80b0f 100644 --- a/Example/MissionControlDemo/BaseLaunchView.swift +++ b/Example/MissionControlDemo/BaseLaunchView.swift @@ -48,31 +48,31 @@ class BaseLaunchView: UIView { var padding: CGFloat = 24.0 - var buttonHighlightColor = UIColor.lightGrayColor() - var buttonColor = UIColor.whiteColor() { + var buttonHighlightColor = UIColor.lightGray() + var buttonColor = UIColor.white() { didSet { button.backgroundColor = buttonColor } } - var buttonTitleColor = UIColor.darkGrayColor() { + var buttonTitleColor = UIColor.darkGray() { didSet { buttonTitle.textColor = buttonTitleColor } } - var statusLightColor = UIColor.darkGrayColor() { + var statusLightColor = UIColor.darkGray() { didSet { statusLight.backgroundColor = statusLightColor } } - var statusTitleColor = UIColor.whiteColor() { + var statusTitleColor = UIColor.white() { didSet { statusTitle.textColor = statusTitleColor - statusLight.layer.borderColor = statusTitleColor.CGColor + statusLight.layer.borderColor = statusTitleColor.cgColor } } - var countdownColor = UIColor.whiteColor() { + var countdownColor = UIColor.white() { didSet { countdown.textColor = countdownColor } @@ -91,7 +91,7 @@ class BaseLaunchView: UIView { } init() { - super.init(frame: CGRectZero) + super.init(frame: CGRect.zero) commonInit() } @@ -103,29 +103,29 @@ class BaseLaunchView: UIView { // MARK: - Override - override func layoutSublayersOfLayer(layer: CALayer) { + override func layoutSublayers(of layer: CALayer) { super.layoutSublayersOfLayer(layer) gradientLayer.frame = gradient.bounds } - override func touchesBegan(touches: Set, withEvent event: UIEvent?) { - super.touchesBegan(touches, withEvent: event) + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) if touchesInsideView(touches, view: button) { highlightButton() } } - override func touchesMoved(touches: Set, withEvent event: UIEvent?) { - super.touchesMoved(touches, withEvent: event) + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + super.touchesMoved(touches, with: event) if !touchesInsideView(touches, view: button) { restoreButton() } } - override func touchesEnded(touches: Set, withEvent event: UIEvent?) { - super.touchesEnded(touches, withEvent: event) + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) if touchesInsideView(touches, view: button) { restoreButton() @@ -135,24 +135,24 @@ class BaseLaunchView: UIView { } } - private func touchesInsideView(touches: Set, view: UIView) -> Bool { + private func touchesInsideView(_ touches: Set, view: UIView) -> Bool { guard let touch = touches.first else { return false } - let location = touch.locationInView(view) - let insideView = CGRectContainsPoint(view.bounds, location) + let location = touch.location(in: view) + let insideView = view.bounds.contains(location) return insideView } private func highlightButton() { - UIView.animateWithDuration(0.2, animations: { [unowned self] in + UIView.animate(withDuration: 0.2, animations: { [unowned self] in self.button.backgroundColor = self.buttonHighlightColor - self.buttonImage.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2)) + self.buttonImage.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2)) }) } private func restoreButton() { - UIView.animateWithDuration(0.2, animations: { [unowned self] in + UIView.animate(withDuration: 0.2, animations: { [unowned self] in self.button.backgroundColor = self.buttonColor - self.buttonImage.transform = CGAffineTransformIdentity + self.buttonImage.transform = CGAffineTransform.identity }) } @@ -167,10 +167,10 @@ class BaseLaunchView: UIView { private func configureGradient() { gradient.translatesAutoresizingMaskIntoConstraints = false - gradient.layer.insertSublayer(gradientLayer, atIndex: 0) + gradient.layer.insertSublayer(gradientLayer, at: 0) - gradientLayer.colors = [UIColor.orangeColor().CGColor, UIColor.blueColor().CGColor] - gradientLayer.contentsScale = UIScreen.mainScreen().scale + gradientLayer.colors = [UIColor.orange().cgColor, UIColor.blue().cgColor] + gradientLayer.contentsScale = UIScreen.main().scale gradientLayer.drawsAsynchronously = true gradientLayer.needsDisplayOnBoundsChange = true gradientLayer.setNeedsDisplay() @@ -179,33 +179,33 @@ class BaseLaunchView: UIView { private func configureButton() { button.translatesAutoresizingMaskIntoConstraints = false button.backgroundColor = buttonColor - button.layer.borderColor = statusLightColor.CGColor + button.layer.borderColor = statusLightColor.cgColor button.layer.borderWidth = 10.0 button.layer.cornerRadius = 10.0 button.clipsToBounds = true buttonImage.translatesAutoresizingMaskIntoConstraints = false - buttonImage.contentMode = .ScaleAspectFill + buttonImage.contentMode = .scaleAspectFill buttonImage.image = UIImage(named: "appculture") buttonTitle.translatesAutoresizingMaskIntoConstraints = false buttonTitle.adjustsFontSizeToFitWidth = true - buttonTitle.textAlignment = .Center + buttonTitle.textAlignment = .center buttonTitle.textColor = buttonTitleColor buttonTitle.text = "BUTTON" } private func configureStatus() { statusTitle.translatesAutoresizingMaskIntoConstraints = false - statusTitle.setContentHuggingPriority(251.0, forAxis: .Vertical) + statusTitle.setContentHuggingPriority(251.0, for: .vertical) statusTitle.adjustsFontSizeToFitWidth = true - statusTitle.textAlignment = .Center + statusTitle.textAlignment = .center statusTitle.textColor = statusTitleColor statusTitle.text = "STATUS" statusLight.translatesAutoresizingMaskIntoConstraints = false statusLight.backgroundColor = statusLightColor - statusLight.layer.borderColor = statusTitleColor.CGColor + statusLight.layer.borderColor = statusTitleColor.cgColor statusLight.layer.borderWidth = 2.0 statusLight.layer.cornerRadius = 16.0 } @@ -213,7 +213,7 @@ class BaseLaunchView: UIView { private func configureCountdown() { countdown.translatesAutoresizingMaskIntoConstraints = false countdown.adjustsFontSizeToFitWidth = true - countdown.textAlignment = .Center + countdown.textAlignment = .center countdown.textColor = countdownColor countdown.text = "00" } @@ -249,64 +249,64 @@ class BaseLaunchView: UIView { } private var gradientConstraints: [NSLayoutConstraint] { - let leading = gradient.leadingAnchor.constraintEqualToAnchor(leadingAnchor) - let trailing = gradient.trailingAnchor.constraintEqualToAnchor(trailingAnchor) - let top = gradient.topAnchor.constraintEqualToAnchor(topAnchor) - let bottom = gradient.bottomAnchor.constraintEqualToAnchor(bottomAnchor) + let leading = gradient.leadingAnchor.constraint(equalTo: leadingAnchor) + let trailing = gradient.trailingAnchor.constraint(equalTo: trailingAnchor) + let top = gradient.topAnchor.constraint(equalTo: topAnchor) + let bottom = gradient.bottomAnchor.constraint(equalTo: bottomAnchor) return [leading, trailing, top, bottom] } private var buttonConstraints: [NSLayoutConstraint] { - let leading = button.leadingAnchor.constraintEqualToAnchor(leadingAnchor, constant: padding) - let trailing = button.trailingAnchor.constraintEqualToAnchor(trailingAnchor, constant: -padding) - let bottom = button.bottomAnchor.constraintEqualToAnchor(bottomAnchor, constant: -padding) - let height = button.heightAnchor.constraintEqualToConstant(90.0) + let leading = button.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding) + let trailing = button.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding) + let bottom = button.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding) + let height = button.heightAnchor.constraint(equalToConstant: 90.0) return [leading, trailing, bottom, height] } private var buttonImageConstraints: [NSLayoutConstraint] { - let leading = buttonImage.leadingAnchor.constraintEqualToAnchor(button.leadingAnchor, constant: 20.0) - let top = buttonImage.topAnchor.constraintEqualToAnchor(button.topAnchor, constant: 22.0) - let bottom = buttonImage.bottomAnchor.constraintEqualToAnchor(button.bottomAnchor, constant: -22.0) - let width = buttonImage.widthAnchor.constraintEqualToAnchor(buttonImage.heightAnchor) + let leading = buttonImage.leadingAnchor.constraint(equalTo: button.leadingAnchor, constant: 20.0) + let top = buttonImage.topAnchor.constraint(equalTo: button.topAnchor, constant: 22.0) + let bottom = buttonImage.bottomAnchor.constraint(equalTo: button.bottomAnchor, constant: -22.0) + let width = buttonImage.widthAnchor.constraint(equalTo: buttonImage.heightAnchor) return [leading, top, bottom, width] } private var buttonTitleConstraints: [NSLayoutConstraint] { - let leading = buttonTitle.leadingAnchor.constraintEqualToAnchor(buttonImage.trailingAnchor, constant: 12.0) - let trailing = buttonTitle.trailingAnchor.constraintEqualToAnchor(button.trailingAnchor, constant: -22.0) - let centerY = buttonTitle.centerYAnchor.constraintEqualToAnchor(button.centerYAnchor) + let leading = buttonTitle.leadingAnchor.constraint(equalTo: buttonImage.trailingAnchor, constant: 12.0) + let trailing = buttonTitle.trailingAnchor.constraint(equalTo: button.trailingAnchor, constant: -22.0) + let centerY = buttonTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor) return [leading, trailing, centerY] } private var statusTitleConstraints: [NSLayoutConstraint] { - let leading = statusTitle.leadingAnchor.constraintEqualToAnchor(button.leadingAnchor) - let trailing = statusTitle.trailingAnchor.constraintEqualToAnchor(button.trailingAnchor) - let bottom = statusTitle.bottomAnchor.constraintEqualToAnchor(button.topAnchor, constant: -padding) + let leading = statusTitle.leadingAnchor.constraint(equalTo: button.leadingAnchor) + let trailing = statusTitle.trailingAnchor.constraint(equalTo: button.trailingAnchor) + let bottom = statusTitle.bottomAnchor.constraint(equalTo: button.topAnchor, constant: -padding) return [leading, trailing, bottom] } private var statusLightConstraints: [NSLayoutConstraint] { - let centerX = statusLight.centerXAnchor.constraintEqualToAnchor(centerXAnchor) - let bottom = statusLight.bottomAnchor.constraintEqualToAnchor(statusTitle.topAnchor, constant: -padding) - let width = statusLight.widthAnchor.constraintEqualToConstant(32.0) - let height = statusLight.heightAnchor.constraintEqualToConstant(32.0) + let centerX = statusLight.centerXAnchor.constraint(equalTo: centerXAnchor) + let bottom = statusLight.bottomAnchor.constraint(equalTo: statusTitle.topAnchor, constant: -padding) + let width = statusLight.widthAnchor.constraint(equalToConstant: 32.0) + let height = statusLight.heightAnchor.constraint(equalToConstant: 32.0) return [centerX, bottom, width, height] } private var countdownConstraints: [NSLayoutConstraint] { - let leading = countdown.leadingAnchor.constraintEqualToAnchor(leadingAnchor) - let trailing = countdown.trailingAnchor.constraintEqualToAnchor(trailingAnchor) - let top = countdown.topAnchor.constraintEqualToAnchor(topAnchor) - let bottom = countdown.bottomAnchor.constraintEqualToAnchor(statusLight.topAnchor) + let leading = countdown.leadingAnchor.constraint(equalTo: leadingAnchor) + let trailing = countdown.trailingAnchor.constraint(equalTo: trailingAnchor) + let top = countdown.topAnchor.constraint(equalTo: topAnchor) + let bottom = countdown.bottomAnchor.constraint(equalTo: statusLight.topAnchor) return [leading, trailing, top, bottom] } // MARK: - Interface Builder override func prepareForInterfaceBuilder() { - let bundle = NSBundle(forClass: self.dynamicType) - let image = UIImage(named: "appculture", inBundle: bundle, compatibleWithTraitCollection: traitCollection) + let bundle = Bundle(for: self.dynamicType) + let image = UIImage(named: "appculture", in: bundle, compatibleWith: traitCollection) buttonImage.image = image } @@ -319,12 +319,12 @@ extension UIColor { convenience init (hex: String) { var colorString: String = hex if (hex.hasPrefix("#")) { - let index = hex.startIndex.advancedBy(1) - colorString = colorString.substringFromIndex(index) + let index = hex.characters.index(hex.startIndex, offsetBy: 1) + colorString = colorString.substring(from: index) } var rgbValue:UInt32 = 0 - NSScanner(string: colorString).scanHexInt(&rgbValue) + Scanner(string: colorString).scanHexInt32(&rgbValue) self.init( red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, diff --git a/Example/MissionControlDemo/LaunchBrain.swift b/Example/MissionControlDemo/LaunchBrain.swift index 7c1b5f5..1468632 100644 --- a/Example/MissionControlDemo/LaunchBrain.swift +++ b/Example/MissionControlDemo/LaunchBrain.swift @@ -58,7 +58,7 @@ class LaunchBrain: MissionControlDelegate { } } - var timer: NSTimer? + var timer: Timer? private var launchForce: Double { return 1.0 - ConfigDouble("LaunchForce", fallback: 0.5) @@ -81,12 +81,12 @@ class LaunchBrain: MissionControlDelegate { // MARK: - MissionControlDelegate - func missionControlDidRefreshConfig(old old: [String : AnyObject]?, new: [String : AnyObject]) { + func missionControlDidRefreshConfig(old: [String : AnyObject]?, new: [String : AnyObject]) { print("missionControlDidRefreshConfig") updateUIForState(state) } - func missionControlDidFailRefreshingConfig(error error: ErrorType) { + func missionControlDidFailRefreshingConfig(error: ErrorProtocol) { print("missionControlDidFailRefreshingConfig") stopCountdown() @@ -101,7 +101,7 @@ class LaunchBrain: MissionControlDelegate { // MARK: - Actions - func didTapButton(sender: AnyObject) { + func didTapButton(_ sender: AnyObject) { switch state { case .Offline: ConfigBoolForce("Ready", fallback: false, completion: { (forced) in @@ -126,7 +126,7 @@ class LaunchBrain: MissionControlDelegate { updateUIForState(state) } - private func updateUIForState(state: LaunchState) { + private func updateUIForState(_ state: LaunchState) { updateUIForAnyState(state) switch state { @@ -147,16 +147,16 @@ class LaunchBrain: MissionControlDelegate { } } - private func updateUIForAnyState(state: LaunchState) { + private func updateUIForAnyState(_ state: LaunchState) { let color1 = UIColor(hex: ConfigString("TopColor", fallback: "#000000")) let color2 = UIColor(hex: ConfigString("BottomColor", fallback: "#4A90E2")) view.gradientLayer.colors = [color1.CGColor, color2.CGColor] - view.button.layer.borderColor = colorForState(state).CGColor + view.button.layer.borderColor = colorForState(state).cgColor view.buttonTitle.text = commandForState(state) view.stopBlinkingStatusLight() - view.statusTitle.text = "STATUS: \(state.rawValue.capitalizedString)" + view.statusTitle.text = "STATUS: \(state.rawValue.capitalized)" view.statusLightOnColor = colorForState(state) view.statusLightOn = true @@ -167,7 +167,7 @@ class LaunchBrain: MissionControlDelegate { view.stopAnimatingGradient() view.stopRotatingButtonImage() - view.button.layer.borderColor = view.statusLightOffColor.CGColor + view.button.layer.borderColor = view.statusLightOffColor.cgColor view.countdown.alpha = 0.1 seconds = 0 view.startBlinkingStatusLight(timeInterval: 0.5) @@ -204,7 +204,7 @@ class LaunchBrain: MissionControlDelegate { view.startBlinkingStatusLight(timeInterval: 0.25) } - private func commandForState(state: LaunchState) -> String { + private func commandForState(_ state: LaunchState) -> String { switch state { case .Offline: return "CONNECT" @@ -217,7 +217,7 @@ class LaunchBrain: MissionControlDelegate { } } - private func colorForState(state: LaunchState) -> UIColor { + private func colorForState(_ state: LaunchState) -> UIColor { switch state { case .Offline: return UIColor(hex: ConfigString("OfflineColor", fallback: "#F8E71C")) @@ -238,7 +238,7 @@ class LaunchBrain: MissionControlDelegate { private func startCountdown() { if timer == nil { - timer = NSTimer.scheduledTimerWithTimeInterval(1.0, + timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerTick(_:)), userInfo: nil, repeats: true) @@ -250,7 +250,7 @@ class LaunchBrain: MissionControlDelegate { timer = nil } - @objc func timerTick(sender: NSTimer) { + @objc func timerTick(_ sender: Timer) { ConfigBoolForce("Abort", fallback: true) { (forced) in if forced { self.stopCountdown() diff --git a/Example/MissionControlDemo/LaunchView.swift b/Example/MissionControlDemo/LaunchView.swift index 9a8de04..8646e9e 100644 --- a/Example/MissionControlDemo/LaunchView.swift +++ b/Example/MissionControlDemo/LaunchView.swift @@ -29,17 +29,17 @@ class LaunchView: BaseLaunchView { // MARK: - Properties - var blinkTimer: NSTimer? + var blinkTimer: Timer? var statusLightOffColor = UIColor(hex: "#4A4A4A") - var statusLightOnColor = UIColor.whiteColor() + var statusLightOnColor = UIColor.white() var statusLightOn = false { didSet { if statusLightOn { - UIView.animateWithDuration(0.2) { + UIView.animate(withDuration: 0.2) { self.turnStatusLightOn() } } else { - UIView.animateWithDuration(0.2) { + UIView.animate(withDuration: 0.2) { self.turnStatusLightOff() } } @@ -59,13 +59,13 @@ class LaunchView: BaseLaunchView { private func configureDefaultUI() { padding = 24.0 - gradientLayer.colors = [UIColor(hex: "#000000").CGColor, UIColor(hex: "#4A90E2").CGColor] + gradientLayer.colors = [UIColor(hex: "#000000").cgColor, UIColor(hex: "#4A90E2").cgColor] gradientLayer.locations = [0.0, 1.0] - buttonColor = UIColor.whiteColor() + buttonColor = UIColor.white() buttonHighlightColor = UIColor(hex: "#E4F6F6") - statusTitleColor = UIColor.whiteColor() - countdownColor = UIColor.whiteColor() + statusTitleColor = UIColor.white() + countdownColor = UIColor.white() buttonTitle.font = UIFont(name: "AvenirNext-Heavy", size: 36.0) statusTitle.font = UIFont(name: "Nasa-Display", size: 40.0) @@ -74,8 +74,8 @@ class LaunchView: BaseLaunchView { // MARK: - Blink - func startBlinkingStatusLight(timeInterval timeInterval: NSTimeInterval) { - blinkTimer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, + func startBlinkingStatusLight(timeInterval: TimeInterval) { + blinkTimer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(blinkStatusLight), userInfo: nil, repeats: true) @@ -93,7 +93,7 @@ class LaunchView: BaseLaunchView { func turnStatusLightOn() { statusLightColor = statusLightOnColor - statusLight.layer.shadowColor = statusLightOnColor.CGColor + statusLight.layer.shadowColor = statusLightOnColor.cgColor statusLight.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) statusLight.layer.shadowOpacity = 1.0 statusLight.layer.shadowRadius = 5.0 @@ -106,7 +106,7 @@ class LaunchView: BaseLaunchView { // MARK: - Button Image Rotation - func rotateButtonImageWithDuration(duration: Double) { + func rotateButtonImageWithDuration(_ duration: Double) { buttonImage.rotate(withDuration: duration) } @@ -116,7 +116,7 @@ class LaunchView: BaseLaunchView { // MARK: - Gradient Animation - func animateGradientWithDuration(duration: Double) { + func animateGradientWithDuration(_ duration: Double) { animateGradientLayer(gradientLayer, withDuration: duration) } @@ -131,7 +131,7 @@ private extension UIView { @nonobjc static let rotationKey = "AERotation" func rotate(withDuration duration: Double = 1.0) { - if layer.animationForKey(UIView.rotationKey) == nil { + if layer.animation(forKey: UIView.rotationKey) == nil { let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation") rotationAnimation.fromValue = 0.0 @@ -139,18 +139,18 @@ private extension UIView { rotationAnimation.duration = duration rotationAnimation.repeatCount = Float.infinity - layer.addAnimation(rotationAnimation, forKey: UIView.rotationKey) + layer.add(rotationAnimation, forKey: UIView.rotationKey) } } func stopRotation() { - layer.removeAnimationForKey(UIView.rotationKey) + layer.removeAnimation(forKey: UIView.rotationKey) } @nonobjc static let gradientKey = "AEGradientAnimation" - func animateGradientLayer(gradientLayer: CAGradientLayer, withDuration duration: Double = 2.0) { - if gradientLayer.animationForKey(UIView.gradientKey) == nil { + func animateGradientLayer(_ gradientLayer: CAGradientLayer, withDuration duration: Double = 2.0) { + if gradientLayer.animation(forKey: UIView.gradientKey) == nil { let sequenceDuration = duration / 4.0 let currentLocations = [0.0, 1.0] @@ -171,9 +171,9 @@ private extension UIView { let colorAnimation1 = CABasicAnimation(keyPath: "colors") colorAnimation1.fromValue = [color1, color1] - colorAnimation1.toValue = gradientLayer.colors?.reverse() + colorAnimation1.toValue = gradientLayer.colors?.reversed() colorAnimation1.duration = sequenceDuration - colorAnimation1.removedOnCompletion = false + colorAnimation1.isRemovedOnCompletion = false colorAnimation1.fillMode = kCAFillModeForwards colorAnimation1.beginTime = sequenceDuration @@ -191,7 +191,7 @@ private extension UIView { colorAnimation2.fromValue = [color2, color2] colorAnimation2.toValue = gradientLayer.colors colorAnimation2.duration = sequenceDuration - colorAnimation2.removedOnCompletion = false + colorAnimation2.isRemovedOnCompletion = false colorAnimation2.fillMode = kCAFillModeForwards colorAnimation2.beginTime = 3 * sequenceDuration @@ -202,12 +202,12 @@ private extension UIView { group.animations = [locationAnimation1, colorAnimation1, locationAnimation2, colorAnimation2] group.repeatCount = Float.infinity - gradientLayer.addAnimation(group, forKey: UIView.gradientKey) + gradientLayer.add(group, forKey: UIView.gradientKey) } } - func stopGradientAnimation(gradientLayer: CAGradientLayer) { - gradientLayer.removeAnimationForKey(UIView.gradientKey) + func stopGradientAnimation(_ gradientLayer: CAGradientLayer) { + gradientLayer.removeAnimation(forKey: UIView.gradientKey) } } diff --git a/MissionControl.xcodeproj/project.pbxproj b/MissionControl.xcodeproj/project.pbxproj index 7204d49..3254c72 100644 --- a/MissionControl.xcodeproj/project.pbxproj +++ b/MissionControl.xcodeproj/project.pbxproj @@ -385,9 +385,11 @@ }; 8B63137A1CE5F9A10029DC98 = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0800; }; 8B6313841CE5F9A10029DC98 = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0800; }; }; }; @@ -827,6 +829,7 @@ PRODUCT_NAME = MissionControl; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -845,6 +848,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.appculture.MissionControl-iOSTests"; PRODUCT_NAME = MissionControl; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -855,6 +859,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appculture.MissionControlTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -865,6 +870,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appculture.MissionControlTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -878,6 +884,7 @@ 8B03C1E61CF5E10500B09B48 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 8B03C2001CF5E1DD00B09B48 /* Build configuration list for PBXNativeTarget "MissionControl tvOS" */ = { isa = XCConfigurationList; @@ -886,6 +893,7 @@ 8B03C2021CF5E1DD00B09B48 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 8B03C2031CF5E1DD00B09B48 /* Build configuration list for PBXNativeTarget "MissionControl tvOS Tests" */ = { isa = XCConfigurationList; @@ -894,6 +902,7 @@ 8B03C2051CF5E1DD00B09B48 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 8B03C21F1CF5E28C00B09B48 /* Build configuration list for PBXNativeTarget "MissionControl OSX" */ = { isa = XCConfigurationList; @@ -902,6 +911,7 @@ 8B03C2211CF5E28C00B09B48 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 8B03C2221CF5E28C00B09B48 /* Build configuration list for PBXNativeTarget "MissionControl OSX Tests" */ = { isa = XCConfigurationList; @@ -910,6 +920,7 @@ 8B03C2241CF5E28C00B09B48 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 8B6313751CE5F9A10029DC98 /* Build configuration list for PBXProject "MissionControl" */ = { isa = XCConfigurationList; diff --git a/Sources/MissionControl.swift b/Sources/MissionControl.swift index 09f943a..8db849d 100644 --- a/Sources/MissionControl.swift +++ b/Sources/MissionControl.swift @@ -32,13 +32,13 @@ public class MissionControl { // MARK: Types /// Errors types which can be throwed when refreshing local config from remote. - public enum Error: ErrorType { + public enum Error: ErrorProtocol { /// Property `remoteConfigURL` is not set on launch. - case NoRemoteURL + case noRemoteURL /// Server returned response code other then 200 OK. - case BadResponseCode + case badResponseCode /// Server returned data with invalid format. - case InvalidData + case invalidData } /// Constants for keys of sent NSNotification objects. @@ -76,12 +76,12 @@ public class MissionControl { } /// Date of last successful refresh from remote. - public class var refreshDate: NSDate? { + public class var refreshDate: Date? { return ACMissionControl.sharedInstance.refreshDate } /// Date of last cached remote config. - public class var cacheDate: NSDate? { + public class var cacheDate: Date? { return ACMissionControl.sharedInstance.cacheDate } @@ -95,7 +95,7 @@ public class MissionControl { - parameter localConfig: Default local config which can be used until remote config is fetched. - parameter remoteConfigURL: If this parameter is set then `refresh` will be called, otherwise not. */ - public class func launch(localConfig localConfig: [String : AnyObject]? = nil, remoteConfigURL url: NSURL? = nil) { + public class func launch(localConfig: [String : AnyObject]? = nil, remoteConfigURL url: URL? = nil) { ACMissionControl.sharedInstance.localConfig = localConfig ACMissionControl.sharedInstance.remoteURL = url } @@ -107,7 +107,7 @@ public class MissionControl { - parameter completion: Completion handler (SEE: `ThrowWithInnerBlock`). */ - public class func refresh(completion: ThrowWithInnerBlock? = nil) { + public class func refresh(_ completion: ThrowWithInnerBlock? = nil) { ACMissionControl.sharedInstance.refresh(completion) } @@ -127,14 +127,14 @@ public protocol MissionControlDelegate: class { - parameter old: Previous config (nil if it's the first refresh) - parameter new: Current config */ - func missionControlDidRefreshConfig(old old: [String : AnyObject]?, new: [String : AnyObject]) + func missionControlDidRefreshConfig(old: [String : AnyObject]?, new: [String : AnyObject]) /** Called when refreshing config from remote fails. - parameter error: Error which happened during config refresh from remote. */ - func missionControlDidFailRefreshingConfig(error error: ErrorType) + func missionControlDidFailRefreshingConfig(error: ErrorProtocol) } // MARK: - Custom Types @@ -161,7 +161,7 @@ public typealias ThrowJSONWithInnerBlock = (block: () throws -> [String : AnyObj - returns: Resolved setting of generic type `T` for given key. */ -public func ConfigGeneric(key: String, fallback: T) -> T { +public func ConfigGeneric(_ key: String, fallback: T) -> T { if let remoteValue = ACMissionControl.sharedInstance.remoteConfig?[key] as? T { return remoteValue } else if let cachedValue = ACMissionControl.sharedInstance.cachedConfig?[key] as? T { @@ -186,7 +186,7 @@ public func ConfigGeneric(key: String, fallback: T) -> T { - parameter key: Key for the setting. - parameter fallback: Fallback value of generic type `T` if refresh is not successful. */ -public func ConfigGenericForce(key: String, fallback: T, completion: ((forced: T) -> Void)) { +public func ConfigGenericForce(_ key: String, fallback: T, completion: ((forced: T) -> Void)) { MissionControl.refresh({ (innerBlock) in do { let _ = try innerBlock() @@ -206,7 +206,7 @@ public func ConfigGenericForce(key: String, fallback: T, completion: ((forced - returns: Resolved setting of type `Bool` for given key. */ -public func ConfigBool(key: String, fallback: Bool = Bool()) -> Bool { +public func ConfigBool(_ key: String, fallback: Bool = Bool()) -> Bool { return ConfigGeneric(key, fallback: fallback) } @@ -217,7 +217,7 @@ public func ConfigBool(key: String, fallback: Bool = Bool()) -> Bool { - parameter key: Key for the setting. - parameter fallback: Fallback value if refresh was not successful. */ -public func ConfigBoolForce(key: String, fallback: Bool, completion: ((forced: Bool) -> Void)) { +public func ConfigBoolForce(_ key: String, fallback: Bool, completion: ((forced: Bool) -> Void)) { ConfigGenericForce(key, fallback: fallback, completion: completion) } @@ -230,7 +230,7 @@ public func ConfigBoolForce(key: String, fallback: Bool, completion: ((forced: B - returns: Resolved setting of type `Int` for given key. */ -public func ConfigInt(key: String, fallback: Int = Int()) -> Int { +public func ConfigInt(_ key: String, fallback: Int = Int()) -> Int { return ConfigGeneric(key, fallback: fallback) } @@ -241,7 +241,7 @@ public func ConfigInt(key: String, fallback: Int = Int()) -> Int { - parameter key: Key for the setting. - parameter fallback: Fallback value if refresh was not successful. */ -public func ConfigIntForce(key: String, fallback: Int, completion: ((forced: Int) -> Void)) { +public func ConfigIntForce(_ key: String, fallback: Int, completion: ((forced: Int) -> Void)) { ConfigGenericForce(key, fallback: fallback, completion: completion) } @@ -254,7 +254,7 @@ public func ConfigIntForce(key: String, fallback: Int, completion: ((forced: Int - returns: Resolved setting of type `Double` for given key. */ -public func ConfigDouble(key: String, fallback: Double = Double()) -> Double { +public func ConfigDouble(_ key: String, fallback: Double = Double()) -> Double { return ConfigGeneric(key, fallback: fallback) } @@ -265,7 +265,7 @@ public func ConfigDouble(key: String, fallback: Double = Double()) -> Double { - parameter key: Key for the setting. - parameter fallback: Fallback value if refresh was not successful. */ -public func ConfigDoubleForce(key: String, fallback: Double, completion: ((forced: Double) -> Void)) { +public func ConfigDoubleForce(_ key: String, fallback: Double, completion: ((forced: Double) -> Void)) { ConfigGenericForce(key, fallback: fallback, completion: completion) } @@ -278,7 +278,7 @@ public func ConfigDoubleForce(key: String, fallback: Double, completion: ((force - returns: Resolved setting of type `String` for given key. */ -public func ConfigString(key: String, fallback: String = String()) -> String { +public func ConfigString(_ key: String, fallback: String = String()) -> String { return ConfigGeneric(key, fallback: fallback) } @@ -289,7 +289,7 @@ public func ConfigString(key: String, fallback: String = String()) -> String { - parameter key: Key for the setting. - parameter fallback: Fallback value if refresh was not successful. */ -public func ConfigStringForce(key: String, fallback: String, completion: ((forced: String) -> Void)) { +public func ConfigStringForce(_ key: String, fallback: String, completion: ((forced: String) -> Void)) { ConfigGenericForce(key, fallback: fallback, completion: completion) } @@ -307,7 +307,7 @@ class ACMissionControl { var localConfig: [String : AnyObject]? - var remoteURL: NSURL? { + var remoteURL: URL? { didSet { if let _ = remoteURL { refresh({ (block) in @@ -324,7 +324,7 @@ class ACMissionControl { var remoteConfig: [String : AnyObject]? { didSet { if let newConfig = remoteConfig { - refreshDate = NSDate() + refreshDate = Date() cachedConfig = newConfig cacheDate = refreshDate @@ -334,13 +334,13 @@ class ACMissionControl { } } - private func informListeners(oldConfig oldConfig: [String : AnyObject]?, newConfig: [String : AnyObject]) { + private func informListeners(oldConfig: [String : AnyObject]?, newConfig: [String : AnyObject]) { let userInfo = userInfoWithConfig(old: oldConfig, new: newConfig) delegate?.missionControlDidRefreshConfig(old: oldConfig, new: newConfig) sendNotification(MissionControl.Notification.DidRefreshConfig, userInfo: userInfo) } - var refreshDate: NSDate? + var refreshDate: Date? private struct Cache { static let Config = "ACMissionControl.CachedConfig" @@ -349,35 +349,35 @@ class ACMissionControl { var cachedConfig: [String : AnyObject]? { get { - let userDefaults = NSUserDefaults.standardUserDefaults() - let config = userDefaults.objectForKey(Cache.Config) as? [String : AnyObject] + let userDefaults = UserDefaults.standard + let config = userDefaults.object(forKey: Cache.Config) as? [String : AnyObject] return config } set { - let userDefaults = NSUserDefaults.standardUserDefaults() - userDefaults.setObject(newValue, forKey: Cache.Config) + let userDefaults = UserDefaults.standard + userDefaults.set(newValue, forKey: Cache.Config) userDefaults.synchronize() } } - var cacheDate: NSDate? { + var cacheDate: Date? { get { - let userDefaults = NSUserDefaults.standardUserDefaults() - let config = userDefaults.objectForKey(Cache.Date) as? NSDate + let userDefaults = UserDefaults.standard + let config = userDefaults.object(forKey: Cache.Date) as? Date return config } set { - let userDefaults = NSUserDefaults.standardUserDefaults() - userDefaults.setObject(newValue, forKey: Cache.Date) + let userDefaults = UserDefaults.standard + userDefaults.set(newValue, forKey: Cache.Date) userDefaults.synchronize() } } // MARK: API - func refresh(completion: ThrowWithInnerBlock? = nil) { + func refresh(_ completion: ThrowWithInnerBlock? = nil) { getRemoteConfig { [unowned self] (block) in - dispatch_async(dispatch_get_main_queue()) { [unowned self] in + DispatchQueue.main.async { [unowned self] in do { let remoteConfig = try block() self.remoteConfig = remoteConfig @@ -390,7 +390,7 @@ class ACMissionControl { } } - private func informListeners(error: ErrorType) { + private func informListeners(_ error: ErrorProtocol) { delegate?.missionControlDidFailRefreshingConfig(error: error) let userInfo = ["Error" : "\(error)"] sendNotification(MissionControl.Notification.DidFailRefreshingConfig, userInfo: userInfo) @@ -412,7 +412,7 @@ class ACMissionControl { refreshDate = nil } - private func userInfoWithConfig(old old: [String : AnyObject]?, new: [String : AnyObject]?) -> [NSObject : AnyObject]? { + private func userInfoWithConfig(old: [String : AnyObject]?, new: [String : AnyObject]?) -> [NSObject : AnyObject]? { if old == nil && new == nil { return nil } else { @@ -427,38 +427,38 @@ class ACMissionControl { } } - private func sendNotification(name: String, userInfo: [NSObject : AnyObject]? = nil) { - let center = NSNotificationCenter.defaultCenter() - center.postNotificationName(name, object: self, userInfo: userInfo) + private func sendNotification(_ name: String, userInfo: [NSObject : AnyObject]? = nil) { + let center = NotificationCenter.default + center.post(name: Notification.Name(rawValue: name), object: self, userInfo: userInfo) } - private func getRemoteConfig(completion: ThrowJSONWithInnerBlock) { + private func getRemoteConfig(_ completion: ThrowJSONWithInnerBlock) { guard let url = remoteURL - else { completion(block: { throw MissionControl.Error.NoRemoteURL }); return } + else { completion(block: { throw MissionControl.Error.noRemoteURL }); return } - let request = NSURLRequest(URL: url) - let session = NSURLSession.sharedSession() + let request = URLRequest(url: url) + let session = URLSession.shared - let task = session.dataTaskWithRequest(request) { [unowned self] (data, response, error) in - guard let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode == 200 - else { completion(block: { throw MissionControl.Error.BadResponseCode }); return } + let task = session.dataTask(with: request) { [unowned self] (data, response, error) in + guard let httpResponse = response as? HTTPURLResponse where httpResponse.statusCode == 200 + else { completion(block: { throw MissionControl.Error.badResponseCode }); return } self.parseRemoteConfigFromData(data, completion: completion) } task.resume() } - private func parseRemoteConfigFromData(data: NSData?, completion: ThrowJSONWithInnerBlock) { + private func parseRemoteConfigFromData(_ data: Data?, completion: ThrowJSONWithInnerBlock) { guard let configData = data - else { completion(block: { throw MissionControl.Error.InvalidData }); return } + else { completion(block: { throw MissionControl.Error.invalidData }); return } do { - let json = try NSJSONSerialization.JSONObjectWithData(configData, options: .AllowFragments) + let json = try JSONSerialization.jsonObject(with: configData, options: .allowFragments) guard let config = json as? [String : AnyObject] - else { completion(block: { throw MissionControl.Error.InvalidData }); return } + else { completion(block: { throw MissionControl.Error.invalidData }); return } completion(block: { return config }) } catch { - completion(block: { throw MissionControl.Error.InvalidData }) + completion(block: { throw MissionControl.Error.invalidData }) } } diff --git a/Tests/MissionControlTests.swift b/Tests/MissionControlTests.swift index e8db178..5439065 100644 --- a/Tests/MissionControlTests.swift +++ b/Tests/MissionControlTests.swift @@ -44,10 +44,10 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { // MARK: - Helper Properties struct URL { - static let BadResponseConfig = NSURL(string: "http://appculture.com/mission-control/not-existing-config.json")! - static let EmptyDataConfig = NSURL(string: "http://private-83024-missioncontrol5.apiary-mock.com/mission-control/empty-config")! - static let InvalidDataConfig = NSURL(string: "http://private-83024-missioncontrol5.apiary-mock.com/mission-control/invalid-config")! - static let RemoteTestConfig = NSURL(string: "http://private-83024-missioncontrol5.apiary-mock.com/mission-control/test-config")! + static let BadResponseConfig = Foundation.URL(string: "http://appculture.com/mission-control/not-existing-config.json")! + static let EmptyDataConfig = Foundation.URL(string: "http://private-83024-missioncontrol5.apiary-mock.com/mission-control/empty-config")! + static let InvalidDataConfig = Foundation.URL(string: "http://private-83024-missioncontrol5.apiary-mock.com/mission-control/invalid-config")! + static let RemoteTestConfig = Foundation.URL(string: "http://private-83024-missioncontrol5.apiary-mock.com/mission-control/test-config")! } struct Key { @@ -83,11 +83,11 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { // MARK: - MissionControlDelegate - func missionControlDidRefreshConfig(old old: [String : AnyObject]?, new: [String : AnyObject]) { + func missionControlDidRefreshConfig(old: [String : AnyObject]?, new: [String : AnyObject]) { didRefreshConfigExpectation?.fulfill() } - func missionControlDidFailRefreshingConfig(error error: ErrorType) { + func missionControlDidFailRefreshingConfig(error: ErrorProtocol) { didFailRefreshingConfigExpectation?.fulfill() } @@ -238,19 +238,19 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { // MARK: - Test Remote Accessors - func confirmRemoteConfigStateAfterNotification(notification: String) { + func confirmRemoteConfigStateAfterNotification(_ notification: String) { confirmDidRefreshConfigDelegateCallback() - let _ = expectationForNotification(notification, object: nil) { (notification) -> Bool in + let _ = expectation(forNotification: notification, object: nil) { (notification) -> Bool in self.confirmRemoteConfigState() return true } - waitForExpectationsWithTimeout(5, handler: nil) + waitForExpectations(timeout: 5, handler: nil) } func confirmDidRefreshConfigDelegateCallback() { MissionControl.delegate = self - didRefreshConfigExpectation = expectationWithDescription("Should call MissionControlDelegate.") + didRefreshConfigExpectation = expectation(description: "Should call MissionControlDelegate.") } func confirmRemoteConfigState() { @@ -305,10 +305,10 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { func testForceRemoteAccessors() { MissionControl.launch(remoteConfigURL: URL.RemoteTestConfig) - let boolExpectation = expectationWithDescription("ConfigBoolForce") - let intExpectation = expectationWithDescription("ConfigIntForce") - let doubleExpectation = expectationWithDescription("ConfigDoubleForce") - let stringExpectation = expectationWithDescription("ConfigStringForce") + let boolExpectation = expectation(description: "ConfigBoolForce") + let intExpectation = expectation(description: "ConfigIntForce") + let doubleExpectation = expectation(description: "ConfigDoubleForce") + let stringExpectation = expectation(description: "ConfigStringForce") let fallbackBool = fallbackTestConfig[Key.Bool] as! Bool let fallbackInt = fallbackTestConfig[Key.Int] as! Int @@ -336,16 +336,16 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { stringExpectation.fulfill() } - waitForExpectationsWithTimeout(10.0, handler: nil) + waitForExpectations(timeout: 10.0, handler: nil) } func testForceRemoteAccessorsFallback() { MissionControl.launch(remoteConfigURL: URL.BadResponseConfig) - let boolExpectation = expectationWithDescription("ConfigBoolForceFallback") - let intExpectation = expectationWithDescription("ConfigIntForceFallback") - let doubleExpectation = expectationWithDescription("ConfigDoubleForceFallback") - let stringExpectation = expectationWithDescription("ConfigStringForceFallback") + let boolExpectation = expectation(description: "ConfigBoolForceFallback") + let intExpectation = expectation(description: "ConfigIntForceFallback") + let doubleExpectation = expectation(description: "ConfigDoubleForceFallback") + let stringExpectation = expectation(description: "ConfigStringForceFallback") let fallbackBool = fallbackTestConfig[Key.Bool] as! Bool let fallbackInt = fallbackTestConfig[Key.Int] as! Int @@ -369,7 +369,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { stringExpectation.fulfill() } - waitForExpectationsWithTimeout(10.0, handler: nil) + waitForExpectations(timeout: 10.0, handler: nil) } // MARK: - Test Cache @@ -383,7 +383,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { self.confirmCachedConfigState() return true } - waitForExpectationsWithTimeout(5, handler: nil) + waitForExpectations(timeout: 5, handler: nil) } func confirmCachedConfigState() { @@ -403,7 +403,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { MissionControl.launch() /// - NOTE: refresh is NOT called automatically during launch (remote URL missing) - let asyncExpectation = expectationWithDescription("ManualRefreshWithoutURL") + let asyncExpectation = expectation(description: "ManualRefreshWithoutURL") MissionControl.refresh { (block) in do { let _ = try block() @@ -415,7 +415,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { asyncExpectation.fulfill() } } - waitForExpectationsWithTimeout(5, handler: nil) + waitForExpectations(timeout: 5, handler: nil) } func testRefreshErrorBadResponseCode() { @@ -442,7 +442,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { confirmConfigRefreshFailedNotification(MissionControl.Error.InvalidData, message: message) } - func confirmConfigRefreshFailedNotification(error: MissionControl.Error, message: String) { + func confirmConfigRefreshFailedNotification(_ error: MissionControl.Error, message: String) { confirmDidFailRefreshingConfigDelegateCallback() let notification = MissionControl.Notification.DidFailRefreshingConfig @@ -452,12 +452,12 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { self.confirmInitialState() return true } - waitForExpectationsWithTimeout(5, handler: nil) + waitForExpectations(timeout: 5, handler: nil) } func confirmDidFailRefreshingConfigDelegateCallback() { MissionControl.delegate = self - didFailRefreshingConfigExpectation = expectationWithDescription("Should call MissionControlDelegate.") + didFailRefreshingConfigExpectation = expectation(description: "Should call MissionControlDelegate.") } } From 3e0546fd87a148a01e3414aba056522afccbafc6 Mon Sep 17 00:00:00 2001 From: Marko Tadic Date: Mon, 1 Aug 2016 13:51:27 +0200 Subject: [PATCH 02/17] Project update to recommended settings - Xcode 8 Beta 3 --- Example/MissionControlDemo.xcodeproj/project.pbxproj | 11 ++++++----- .../xcschemes/MissionControlDemo.xcscheme | 2 +- MissionControl.xcodeproj/project.pbxproj | 9 ++++++++- .../xcschemes/MissionControl OSX.xcscheme | 2 +- .../xcschemes/MissionControl iOS.xcscheme | 2 +- .../xcschemes/MissionControl tvOS.xcscheme | 2 +- .../xcschemes/MissionControl watchOS.xcscheme | 2 +- 7 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Example/MissionControlDemo.xcodeproj/project.pbxproj b/Example/MissionControlDemo.xcodeproj/project.pbxproj index 8a00f1f..dbc2b27 100644 --- a/Example/MissionControlDemo.xcodeproj/project.pbxproj +++ b/Example/MissionControlDemo.xcodeproj/project.pbxproj @@ -124,7 +124,7 @@ isa = PBXGroup; children = ( 8B2A29CC1CEA27DF00FAE67F /* MissionControl.framework */, - 8B2A29CE1CEA27DF00FAE67F /* MissionControlTests.xctest */, + 8B2A29CE1CEA27DF00FAE67F /* MissionControl iOS Tests.xctest */, 8BA444271D4F6EAA00E99FAE /* MissionControl.framework */, 8BA444291D4F6EAA00E99FAE /* MissionControl.framework */, 8BA4442B1D4F6EAA00E99FAE /* MissionControl tvOS Tests.xctest */, @@ -229,7 +229,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0730; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = appculture; TargetAttributes = { 8B464DCB1CE3742F00BAE834 = { @@ -270,7 +270,7 @@ remoteRef = 8B2A29CB1CEA27DF00FAE67F /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 8B2A29CE1CEA27DF00FAE67F /* MissionControlTests.xctest */ = { + 8B2A29CE1CEA27DF00FAE67F /* MissionControl iOS Tests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = "MissionControl iOS Tests.xctest"; @@ -459,8 +459,8 @@ 8B464DDF1CE3742F00BAE834 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; INFOPLIST_FILE = MissionControlDemo/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appculture.MissionControlDemo; @@ -472,12 +472,13 @@ 8B464DE01CE3742F00BAE834 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; INFOPLIST_FILE = MissionControlDemo/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appculture.MissionControlDemo; PRODUCT_NAME = MissionControlDemo; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 3.0; }; name = Release; diff --git a/Example/MissionControlDemo.xcodeproj/xcshareddata/xcschemes/MissionControlDemo.xcscheme b/Example/MissionControlDemo.xcodeproj/xcshareddata/xcschemes/MissionControlDemo.xcscheme index c1a3774..98def11 100644 --- a/Example/MissionControlDemo.xcodeproj/xcshareddata/xcschemes/MissionControlDemo.xcscheme +++ b/Example/MissionControlDemo.xcodeproj/xcshareddata/xcschemes/MissionControlDemo.xcscheme @@ -1,6 +1,6 @@ Date: Mon, 1 Aug 2016 13:54:47 +0200 Subject: [PATCH 03/17] Manual fixes of build warnings and errors in framework project --- Sources/MissionControl.swift | 2 +- Tests/MissionControlTests.swift | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/MissionControl.swift b/Sources/MissionControl.swift index 8db849d..21d7f84 100644 --- a/Sources/MissionControl.swift +++ b/Sources/MissionControl.swift @@ -440,7 +440,7 @@ class ACMissionControl { let session = URLSession.shared let task = session.dataTask(with: request) { [unowned self] (data, response, error) in - guard let httpResponse = response as? HTTPURLResponse where httpResponse.statusCode == 200 + guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { completion(block: { throw MissionControl.Error.badResponseCode }); return } self.parseRemoteConfigFromData(data, completion: completion) } diff --git a/Tests/MissionControlTests.swift b/Tests/MissionControlTests.swift index 5439065..86b318f 100644 --- a/Tests/MissionControlTests.swift +++ b/Tests/MissionControlTests.swift @@ -378,7 +378,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { MissionControl.launch(remoteConfigURL: URL.RemoteTestConfig) let notification = MissionControl.Notification.DidRefreshConfig - let _ = expectationForNotification(notification, object: nil) { (notification) -> Bool in + let _ = expectation(forNotification: notification, object: nil) { (notification) -> Bool in ACMissionControl.sharedInstance.resetRemote() self.confirmCachedConfigState() return true @@ -411,7 +411,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { asyncExpectation.fulfill() } catch { let message = "Should return NoRemoteURL error whene remoteURL is not set." - XCTAssertEqual("\(error)", "\(MissionControl.Error.NoRemoteURL)", message) + XCTAssertEqual("\(error)", "\(MissionControl.Error.noRemoteURL)", message) asyncExpectation.fulfill() } } @@ -423,7 +423,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { /// - NOTE: refresh is called automatically during launch let message = "Should return BadResponseCode error when response is not 200 OK." - confirmConfigRefreshFailedNotification(MissionControl.Error.BadResponseCode, message: message) + confirmConfigRefreshFailedNotification(MissionControl.Error.badResponseCode, message: message) } func testRefreshErrorInvalidDataEmpty() { @@ -431,7 +431,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { /// - NOTE: refresh is called automatically during launch let message = "Should return InvalidData error when response data is empty." - confirmConfigRefreshFailedNotification(MissionControl.Error.InvalidData, message: message) + confirmConfigRefreshFailedNotification(MissionControl.Error.invalidData, message: message) } func testRefreshErrorInvalidData() { @@ -439,14 +439,14 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { /// - NOTE: refresh is called automatically during launch let message = "Should return InvalidData error when response data is not valid JSON." - confirmConfigRefreshFailedNotification(MissionControl.Error.InvalidData, message: message) + confirmConfigRefreshFailedNotification(MissionControl.Error.invalidData, message: message) } func confirmConfigRefreshFailedNotification(_ error: MissionControl.Error, message: String) { confirmDidFailRefreshingConfigDelegateCallback() let notification = MissionControl.Notification.DidFailRefreshingConfig - let _ = expectationForNotification(notification, object: nil) { (notification) -> Bool in + let _ = expectation(forNotification: notification, object: nil) { (notification) -> Bool in guard let errorInfo = notification.userInfo?["Error"] as? String else { return false } XCTAssertEqual("\(errorInfo)", "\(error)", message) self.confirmInitialState() From f513dc538b62f64525193c5c1963652bf2dad611 Mon Sep 17 00:00:00 2001 From: Marko Tadic Date: Mon, 1 Aug 2016 13:57:37 +0200 Subject: [PATCH 04/17] Manual fixes of build warnings and errors in demo project --- Example/MissionControlDemo/BaseLaunchView.swift | 2 +- Example/MissionControlDemo/LaunchBrain.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/MissionControlDemo/BaseLaunchView.swift b/Example/MissionControlDemo/BaseLaunchView.swift index 4c80b0f..830dab3 100644 --- a/Example/MissionControlDemo/BaseLaunchView.swift +++ b/Example/MissionControlDemo/BaseLaunchView.swift @@ -104,7 +104,7 @@ class BaseLaunchView: UIView { // MARK: - Override override func layoutSublayers(of layer: CALayer) { - super.layoutSublayersOfLayer(layer) + super.layoutSublayers(of: layer) gradientLayer.frame = gradient.bounds } diff --git a/Example/MissionControlDemo/LaunchBrain.swift b/Example/MissionControlDemo/LaunchBrain.swift index 1468632..d64d02b 100644 --- a/Example/MissionControlDemo/LaunchBrain.swift +++ b/Example/MissionControlDemo/LaunchBrain.swift @@ -150,7 +150,7 @@ class LaunchBrain: MissionControlDelegate { private func updateUIForAnyState(_ state: LaunchState) { let color1 = UIColor(hex: ConfigString("TopColor", fallback: "#000000")) let color2 = UIColor(hex: ConfigString("BottomColor", fallback: "#4A90E2")) - view.gradientLayer.colors = [color1.CGColor, color2.CGColor] + view.gradientLayer.colors = [color1.cgColor, color2.cgColor] view.button.layer.borderColor = colorForState(state).cgColor view.buttonTitle.text = commandForState(state) From d2709a65e9fb82ef11332a716cd4bc2efcef7b8d Mon Sep 17 00:00:00 2001 From: Marko Tadic Date: Mon, 1 Aug 2016 14:27:28 +0200 Subject: [PATCH 05/17] Fixed project settings for all platforms --- MissionControl.xcodeproj/project.pbxproj | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/MissionControl.xcodeproj/project.pbxproj b/MissionControl.xcodeproj/project.pbxproj index 306e007..213b017 100644 --- a/MissionControl.xcodeproj/project.pbxproj +++ b/MissionControl.xcodeproj/project.pbxproj @@ -370,18 +370,23 @@ TargetAttributes = { 8B03C1DF1CF5E10500B09B48 = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0800; }; 8B03C1EE1CF5E1DD00B09B48 = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0800; }; 8B03C1F71CF5E1DD00B09B48 = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0800; }; 8B03C20D1CF5E28C00B09B48 = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0800; }; 8B03C2161CF5E28C00B09B48 = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0800; }; 8B63137A1CE5F9A10029DC98 = { CreatedOnToolsVersion = 7.3.1; @@ -561,6 +566,7 @@ PRODUCT_NAME = MissionControl; SDKROOT = watchos; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = 4; }; name = Debug; @@ -581,6 +587,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = 4; }; name = Release; @@ -600,6 +607,7 @@ PRODUCT_NAME = MissionControl; SDKROOT = appletvos; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = 3; }; name = Debug; @@ -620,6 +628,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = 3; }; name = Release; @@ -632,6 +641,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.appculture.MissionControl-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; + SWIFT_VERSION = 3.0; TVOS_DEPLOYMENT_TARGET = 9.2; }; name = Debug; @@ -645,6 +655,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; TVOS_DEPLOYMENT_TARGET = 9.2; }; name = Release; @@ -668,6 +679,7 @@ PRODUCT_NAME = MissionControl; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -691,6 +703,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -705,6 +718,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.appculture.MissionControl-OSXTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -720,6 +734,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; }; name = Release; }; From d330708de8dff22d79dae7dddcd4a64d0acfb206 Mon Sep 17 00:00:00 2001 From: Marko Tadic Date: Wed, 3 Aug 2016 14:59:57 +0200 Subject: [PATCH 06/17] Fixed build warnings and errors for Xcode 8 Beta 4 --- Example/MissionControlDemo/BaseLaunchView.swift | 16 ++++++++-------- Example/MissionControlDemo/LaunchBrain.swift | 2 +- Example/MissionControlDemo/LaunchView.swift | 8 ++++---- .../LaunchViewController.swift | 4 ++-- Sources/MissionControl.swift | 16 ++++++++-------- Tests/MissionControlTests.swift | 16 ++++++++-------- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Example/MissionControlDemo/BaseLaunchView.swift b/Example/MissionControlDemo/BaseLaunchView.swift index 830dab3..b455d19 100644 --- a/Example/MissionControlDemo/BaseLaunchView.swift +++ b/Example/MissionControlDemo/BaseLaunchView.swift @@ -48,31 +48,31 @@ class BaseLaunchView: UIView { var padding: CGFloat = 24.0 - var buttonHighlightColor = UIColor.lightGray() - var buttonColor = UIColor.white() { + var buttonHighlightColor = UIColor.lightGray + var buttonColor = UIColor.white { didSet { button.backgroundColor = buttonColor } } - var buttonTitleColor = UIColor.darkGray() { + var buttonTitleColor = UIColor.darkGray { didSet { buttonTitle.textColor = buttonTitleColor } } - var statusLightColor = UIColor.darkGray() { + var statusLightColor = UIColor.darkGray { didSet { statusLight.backgroundColor = statusLightColor } } - var statusTitleColor = UIColor.white() { + var statusTitleColor = UIColor.white { didSet { statusTitle.textColor = statusTitleColor statusLight.layer.borderColor = statusTitleColor.cgColor } } - var countdownColor = UIColor.white() { + var countdownColor = UIColor.white { didSet { countdown.textColor = countdownColor } @@ -169,8 +169,8 @@ class BaseLaunchView: UIView { gradient.translatesAutoresizingMaskIntoConstraints = false gradient.layer.insertSublayer(gradientLayer, at: 0) - gradientLayer.colors = [UIColor.orange().cgColor, UIColor.blue().cgColor] - gradientLayer.contentsScale = UIScreen.main().scale + gradientLayer.colors = [UIColor.orange.cgColor, UIColor.blue.cgColor] + gradientLayer.contentsScale = UIScreen.main.scale gradientLayer.drawsAsynchronously = true gradientLayer.needsDisplayOnBoundsChange = true gradientLayer.setNeedsDisplay() diff --git a/Example/MissionControlDemo/LaunchBrain.swift b/Example/MissionControlDemo/LaunchBrain.swift index d64d02b..bf9b310 100644 --- a/Example/MissionControlDemo/LaunchBrain.swift +++ b/Example/MissionControlDemo/LaunchBrain.swift @@ -86,7 +86,7 @@ class LaunchBrain: MissionControlDelegate { updateUIForState(state) } - func missionControlDidFailRefreshingConfig(error: ErrorProtocol) { + func missionControlDidFailRefreshingConfig(error: Error) { print("missionControlDidFailRefreshingConfig") stopCountdown() diff --git a/Example/MissionControlDemo/LaunchView.swift b/Example/MissionControlDemo/LaunchView.swift index 8646e9e..93cb596 100644 --- a/Example/MissionControlDemo/LaunchView.swift +++ b/Example/MissionControlDemo/LaunchView.swift @@ -31,7 +31,7 @@ class LaunchView: BaseLaunchView { var blinkTimer: Timer? var statusLightOffColor = UIColor(hex: "#4A4A4A") - var statusLightOnColor = UIColor.white() + var statusLightOnColor = UIColor.white var statusLightOn = false { didSet { if statusLightOn { @@ -62,10 +62,10 @@ class LaunchView: BaseLaunchView { gradientLayer.colors = [UIColor(hex: "#000000").cgColor, UIColor(hex: "#4A90E2").cgColor] gradientLayer.locations = [0.0, 1.0] - buttonColor = UIColor.white() + buttonColor = UIColor.white buttonHighlightColor = UIColor(hex: "#E4F6F6") - statusTitleColor = UIColor.white() - countdownColor = UIColor.white() + statusTitleColor = UIColor.white + countdownColor = UIColor.white buttonTitle.font = UIFont(name: "AvenirNext-Heavy", size: 36.0) statusTitle.font = UIFont(name: "Nasa-Display", size: 40.0) diff --git a/Example/MissionControlDemo/LaunchViewController.swift b/Example/MissionControlDemo/LaunchViewController.swift index 9883266..e17910d 100644 --- a/Example/MissionControlDemo/LaunchViewController.swift +++ b/Example/MissionControlDemo/LaunchViewController.swift @@ -39,8 +39,8 @@ class LaunchViewController: UIViewController, LaunchDelegate { launch = LaunchBrain(view: launchView, delegate: self) } - - override func prefersStatusBarHidden() -> Bool { + + override var prefersStatusBarHidden: Bool { return true } diff --git a/Sources/MissionControl.swift b/Sources/MissionControl.swift index 21d7f84..892c9d6 100644 --- a/Sources/MissionControl.swift +++ b/Sources/MissionControl.swift @@ -32,7 +32,7 @@ public class MissionControl { // MARK: Types /// Errors types which can be throwed when refreshing local config from remote. - public enum Error: ErrorProtocol { + public enum ServerError: Error { /// Property `remoteConfigURL` is not set on launch. case noRemoteURL /// Server returned response code other then 200 OK. @@ -134,7 +134,7 @@ public protocol MissionControlDelegate: class { - parameter error: Error which happened during config refresh from remote. */ - func missionControlDidFailRefreshingConfig(error: ErrorProtocol) + func missionControlDidFailRefreshingConfig(error: Error) } // MARK: - Custom Types @@ -390,7 +390,7 @@ class ACMissionControl { } } - private func informListeners(_ error: ErrorProtocol) { + private func informListeners(_ error: Error) { delegate?.missionControlDidFailRefreshingConfig(error: error) let userInfo = ["Error" : "\(error)"] sendNotification(MissionControl.Notification.DidFailRefreshingConfig, userInfo: userInfo) @@ -434,14 +434,14 @@ class ACMissionControl { private func getRemoteConfig(_ completion: ThrowJSONWithInnerBlock) { guard let url = remoteURL - else { completion(block: { throw MissionControl.Error.noRemoteURL }); return } + else { completion(block: { throw MissionControl.ServerError.noRemoteURL }); return } let request = URLRequest(url: url) let session = URLSession.shared let task = session.dataTask(with: request) { [unowned self] (data, response, error) in guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 - else { completion(block: { throw MissionControl.Error.badResponseCode }); return } + else { completion(block: { throw MissionControl.ServerError.badResponseCode }); return } self.parseRemoteConfigFromData(data, completion: completion) } @@ -450,15 +450,15 @@ class ACMissionControl { private func parseRemoteConfigFromData(_ data: Data?, completion: ThrowJSONWithInnerBlock) { guard let configData = data - else { completion(block: { throw MissionControl.Error.invalidData }); return } + else { completion(block: { throw MissionControl.ServerError.invalidData }); return } do { let json = try JSONSerialization.jsonObject(with: configData, options: .allowFragments) guard let config = json as? [String : AnyObject] - else { completion(block: { throw MissionControl.Error.invalidData }); return } + else { completion(block: { throw MissionControl.ServerError.invalidData }); return } completion(block: { return config }) } catch { - completion(block: { throw MissionControl.Error.invalidData }) + completion(block: { throw MissionControl.ServerError.invalidData }) } } diff --git a/Tests/MissionControlTests.swift b/Tests/MissionControlTests.swift index 86b318f..5b76a06 100644 --- a/Tests/MissionControlTests.swift +++ b/Tests/MissionControlTests.swift @@ -80,14 +80,14 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { var didRefreshConfigExpectation: XCTestExpectation? var didFailRefreshingConfigExpectation: XCTestExpectation? - + // MARK: - MissionControlDelegate func missionControlDidRefreshConfig(old: [String : AnyObject]?, new: [String : AnyObject]) { didRefreshConfigExpectation?.fulfill() } - func missionControlDidFailRefreshingConfig(error: ErrorProtocol) { + func missionControlDidFailRefreshingConfig(error: Error) { didFailRefreshingConfigExpectation?.fulfill() } @@ -411,7 +411,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { asyncExpectation.fulfill() } catch { let message = "Should return NoRemoteURL error whene remoteURL is not set." - XCTAssertEqual("\(error)", "\(MissionControl.Error.noRemoteURL)", message) + XCTAssertEqual("\(error)", "\(MissionControl.ServerError.noRemoteURL)", message) asyncExpectation.fulfill() } } @@ -423,7 +423,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { /// - NOTE: refresh is called automatically during launch let message = "Should return BadResponseCode error when response is not 200 OK." - confirmConfigRefreshFailedNotification(MissionControl.Error.badResponseCode, message: message) + confirmConfigRefreshFailedNotification(MissionControl.ServerError.badResponseCode, message: message) } func testRefreshErrorInvalidDataEmpty() { @@ -431,7 +431,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { /// - NOTE: refresh is called automatically during launch let message = "Should return InvalidData error when response data is empty." - confirmConfigRefreshFailedNotification(MissionControl.Error.invalidData, message: message) + confirmConfigRefreshFailedNotification(MissionControl.ServerError.invalidData, message: message) } func testRefreshErrorInvalidData() { @@ -439,10 +439,10 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { /// - NOTE: refresh is called automatically during launch let message = "Should return InvalidData error when response data is not valid JSON." - confirmConfigRefreshFailedNotification(MissionControl.Error.invalidData, message: message) + confirmConfigRefreshFailedNotification(MissionControl.ServerError.invalidData, message: message) } - func confirmConfigRefreshFailedNotification(_ error: MissionControl.Error, message: String) { + func confirmConfigRefreshFailedNotification(_ error: Error, message: String) { confirmDidFailRefreshingConfigDelegateCallback() let notification = MissionControl.Notification.DidFailRefreshingConfig @@ -459,5 +459,5 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { MissionControl.delegate = self didFailRefreshingConfigExpectation = expectation(description: "Should call MissionControlDelegate.") } - + } From 1a81ee134b7e5ee4f2f344f8cc544e09b9d0e24d Mon Sep 17 00:00:00 2001 From: Lex Tang Date: Wed, 14 Sep 2016 16:19:01 +0800 Subject: [PATCH 07/17] Update project settings. --- MissionControl.xcodeproj/project.pbxproj | 28 +++++++++-- .../UserInterfaceState.xcuserstate | Bin 0 -> 19621 bytes .../xcschemes/MissionControl OSX.xcscheme | 2 +- .../xcschemes/MissionControl iOS.xcscheme | 2 +- .../xcschemes/MissionControl tvOS.xcscheme | 2 +- .../xcschemes/MissionControl watchOS.xcscheme | 2 +- .../xcschemes/xcschememanagement.plist | 44 ++++++++++++++++++ 7 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 MissionControl.xcodeproj/project.xcworkspace/xcuserdata/lex.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 MissionControl.xcodeproj/xcuserdata/lex.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/MissionControl.xcodeproj/project.pbxproj b/MissionControl.xcodeproj/project.pbxproj index 7204d49..c7ea4eb 100644 --- a/MissionControl.xcodeproj/project.pbxproj +++ b/MissionControl.xcodeproj/project.pbxproj @@ -365,7 +365,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0730; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = appculture; TargetAttributes = { 8B03C1DF1CF5E10500B09B48 = { @@ -385,9 +385,11 @@ }; 8B63137A1CE5F9A10029DC98 = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0800; }; 8B6313841CE5F9A10029DC98 = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0800; }; }; }; @@ -548,6 +550,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -567,6 +570,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -586,6 +590,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -605,6 +610,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -648,7 +654,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -670,7 +676,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -730,8 +736,10 @@ 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -781,8 +789,10 @@ 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -802,6 +812,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; TVOS_DEPLOYMENT_TARGET = 9.0; VALIDATE_PRODUCT = YES; @@ -816,6 +827,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -827,6 +839,7 @@ PRODUCT_NAME = MissionControl; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 2.3; }; name = Debug; }; @@ -835,6 +848,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -845,6 +859,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.appculture.MissionControl-iOSTests"; PRODUCT_NAME = MissionControl; SKIP_INSTALL = YES; + SWIFT_VERSION = 2.3; }; name = Release; }; @@ -855,6 +870,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appculture.MissionControlTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 2.3; }; name = Debug; }; @@ -865,6 +881,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appculture.MissionControlTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 2.3; }; name = Release; }; @@ -878,6 +895,7 @@ 8B03C1E61CF5E10500B09B48 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 8B03C2001CF5E1DD00B09B48 /* Build configuration list for PBXNativeTarget "MissionControl tvOS" */ = { isa = XCConfigurationList; @@ -886,6 +904,7 @@ 8B03C2021CF5E1DD00B09B48 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 8B03C2031CF5E1DD00B09B48 /* Build configuration list for PBXNativeTarget "MissionControl tvOS Tests" */ = { isa = XCConfigurationList; @@ -894,6 +913,7 @@ 8B03C2051CF5E1DD00B09B48 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 8B03C21F1CF5E28C00B09B48 /* Build configuration list for PBXNativeTarget "MissionControl OSX" */ = { isa = XCConfigurationList; @@ -902,6 +922,7 @@ 8B03C2211CF5E28C00B09B48 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 8B03C2221CF5E28C00B09B48 /* Build configuration list for PBXNativeTarget "MissionControl OSX Tests" */ = { isa = XCConfigurationList; @@ -910,6 +931,7 @@ 8B03C2241CF5E28C00B09B48 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 8B6313751CE5F9A10029DC98 /* Build configuration list for PBXProject "MissionControl" */ = { isa = XCConfigurationList; diff --git a/MissionControl.xcodeproj/project.xcworkspace/xcuserdata/lex.xcuserdatad/UserInterfaceState.xcuserstate b/MissionControl.xcodeproj/project.xcworkspace/xcuserdata/lex.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..ed436ceea8b4cb8ff46b43f7ebfbac09dada6586 GIT binary patch literal 19621 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 literal 0 HcmV?d00001 diff --git a/MissionControl.xcodeproj/xcshareddata/xcschemes/MissionControl OSX.xcscheme b/MissionControl.xcodeproj/xcshareddata/xcschemes/MissionControl OSX.xcscheme index 1a31318..f421293 100644 --- a/MissionControl.xcodeproj/xcshareddata/xcschemes/MissionControl OSX.xcscheme +++ b/MissionControl.xcodeproj/xcshareddata/xcschemes/MissionControl OSX.xcscheme @@ -1,6 +1,6 @@ + + + + SuppressBuildableAutocreation + + 8B03C1DF1CF5E10500B09B48 + + primary + + + 8B03C1EE1CF5E1DD00B09B48 + + primary + + + 8B03C1F71CF5E1DD00B09B48 + + primary + + + 8B03C20D1CF5E28C00B09B48 + + primary + + + 8B03C2161CF5E28C00B09B48 + + primary + + + 8B63137A1CE5F9A10029DC98 + + primary + + + 8B6313841CE5F9A10029DC98 + + primary + + + + + From 895e689c198dac2ec591307d34dfd5580e387c96 Mon Sep 17 00:00:00 2001 From: Lex Tang Date: Sun, 18 Sep 2016 09:44:16 +0800 Subject: [PATCH 08/17] Update README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f6ab8bb..5a0e0be 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Mission Control **Super powerful remote config utility written in Swift (iOS, watchOS, tvOS, OSX)** -[![Language Swift 2.2](https://img.shields.io/badge/Language-Swift%202.2-orange.svg?style=flat)](https://swift.org) +[![Language Swift 2.3](https://img.shields.io/badge/Language-Swift%202.3-orange.svg?style=flat)](https://swift.org) [![Platforms iOS | watchOS | tvOS | OSX](https://img.shields.io/badge/Platforms-iOS%20%7C%20watchOS%20%7C%20tvOS%20%7C%20OS%20X-lightgray.svg?style=flat)](http://www.apple.com) [![License MIT](https://img.shields.io/badge/License-MIT-lightgrey.svg?style=flat)](https://github.com/appculture/MissionControl-iOS/blob/master/LICENSE) @@ -193,7 +193,7 @@ Just remember to pass your URL to **MissionControl** `launch:` method. ### So, are you ready for the "Real Time" apps?! [We are](http://appculture.com). ## Requirements -- Xcode 7.3+ +- Xcode 8.0+ - iOS 8.0+ ## Installation From 0121f578e3d723f1159eda896dac23d009b16f7e Mon Sep 17 00:00:00 2001 From: Marko Tadic Date: Tue, 18 Oct 2016 16:20:01 +0200 Subject: [PATCH 09/17] Update project settings to recommended with Xcode 8.1 Beta (8T47) --- MissionControl.xcodeproj/project.pbxproj | 16 +++++++++++++--- .../xcschemes/MissionControl OSX.xcscheme | 2 +- .../xcschemes/MissionControl iOS.xcscheme | 2 +- .../xcschemes/MissionControl tvOS.xcscheme | 2 +- .../xcschemes/MissionControl watchOS.xcscheme | 2 +- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/MissionControl.xcodeproj/project.pbxproj b/MissionControl.xcodeproj/project.pbxproj index 213b017..ef386f1 100644 --- a/MissionControl.xcodeproj/project.pbxproj +++ b/MissionControl.xcodeproj/project.pbxproj @@ -365,7 +365,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 0810; ORGANIZATIONNAME = appculture; TargetAttributes = { 8B03C1DF1CF5E10500B09B48 = { @@ -555,6 +555,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -575,6 +576,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -596,6 +598,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -616,6 +619,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -664,7 +668,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -687,7 +691,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -752,8 +756,10 @@ 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -803,8 +809,10 @@ 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -838,6 +846,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -858,6 +867,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; diff --git a/MissionControl.xcodeproj/xcshareddata/xcschemes/MissionControl OSX.xcscheme b/MissionControl.xcodeproj/xcshareddata/xcschemes/MissionControl OSX.xcscheme index f421293..2ddd393 100644 --- a/MissionControl.xcodeproj/xcshareddata/xcschemes/MissionControl OSX.xcscheme +++ b/MissionControl.xcodeproj/xcshareddata/xcschemes/MissionControl OSX.xcscheme @@ -1,6 +1,6 @@ Date: Tue, 18 Oct 2016 16:20:30 +0200 Subject: [PATCH 10/17] Fixed all build errors and warnings in Xcode 8.1 Beta (8T47) --- Sources/MissionControl.swift | 54 ++++++++++++++++----------------- Tests/MissionControlTests.swift | 8 ++--- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Sources/MissionControl.swift b/Sources/MissionControl.swift index 892c9d6..398a6ca 100644 --- a/Sources/MissionControl.swift +++ b/Sources/MissionControl.swift @@ -66,11 +66,11 @@ public class MissionControl { } /// The latest version of config dictionary, directly accessible, if needed. - public class var config: [String : AnyObject] { + public class var config: [String : Any] { let remoteConfig = ACMissionControl.sharedInstance.remoteConfig let cachedConfig = ACMissionControl.sharedInstance.cachedConfig let localConfig = ACMissionControl.sharedInstance.localConfig - let emptyConfig = [String : AnyObject]() + let emptyConfig = [String : Any]() let resolvedConfig = remoteConfig ?? cachedConfig ?? localConfig ?? emptyConfig return resolvedConfig } @@ -95,7 +95,7 @@ public class MissionControl { - parameter localConfig: Default local config which can be used until remote config is fetched. - parameter remoteConfigURL: If this parameter is set then `refresh` will be called, otherwise not. */ - public class func launch(localConfig: [String : AnyObject]? = nil, remoteConfigURL url: URL? = nil) { + public class func launch(localConfig: [String : Any]? = nil, remoteConfigURL url: URL? = nil) { ACMissionControl.sharedInstance.localConfig = localConfig ACMissionControl.sharedInstance.remoteURL = url } @@ -127,7 +127,7 @@ public protocol MissionControlDelegate: class { - parameter old: Previous config (nil if it's the first refresh) - parameter new: Current config */ - func missionControlDidRefreshConfig(old: [String : AnyObject]?, new: [String : AnyObject]) + func missionControlDidRefreshConfig(old: [String : Any]?, new: [String : Any]) /** Called when refreshing config from remote fails. @@ -143,7 +143,7 @@ public protocol MissionControlDelegate: class { public typealias ThrowWithInnerBlock = (() throws -> Void) -> Void /// Block which throws dictionary via inner block. -public typealias ThrowJSONWithInnerBlock = (block: () throws -> [String : AnyObject]) -> Void +public typealias ThrowJSONWithInnerBlock = (_ block: @escaping () throws -> [String : AnyObject]) -> Void // MARK: - Accessors @@ -186,13 +186,13 @@ public func ConfigGeneric(_ key: String, fallback: T) -> T { - parameter key: Key for the setting. - parameter fallback: Fallback value of generic type `T` if refresh is not successful. */ -public func ConfigGenericForce(_ key: String, fallback: T, completion: ((forced: T) -> Void)) { +public func ConfigGenericForce(_ key: String, fallback: T, completion: @escaping ((_ forced: T) -> Void)) { MissionControl.refresh({ (innerBlock) in do { let _ = try innerBlock() - completion(forced: ConfigGeneric(key, fallback: fallback)) + completion(ConfigGeneric(key, fallback: fallback)) } catch { - completion(forced: fallback) + completion(fallback) } }) } @@ -217,7 +217,7 @@ public func ConfigBool(_ key: String, fallback: Bool = Bool()) -> Bool { - parameter key: Key for the setting. - parameter fallback: Fallback value if refresh was not successful. */ -public func ConfigBoolForce(_ key: String, fallback: Bool, completion: ((forced: Bool) -> Void)) { +public func ConfigBoolForce(_ key: String, fallback: Bool, completion: @escaping ((_ forced: Bool) -> Void)) { ConfigGenericForce(key, fallback: fallback, completion: completion) } @@ -241,7 +241,7 @@ public func ConfigInt(_ key: String, fallback: Int = Int()) -> Int { - parameter key: Key for the setting. - parameter fallback: Fallback value if refresh was not successful. */ -public func ConfigIntForce(_ key: String, fallback: Int, completion: ((forced: Int) -> Void)) { +public func ConfigIntForce(_ key: String, fallback: Int, completion: @escaping ((_ forced: Int) -> Void)) { ConfigGenericForce(key, fallback: fallback, completion: completion) } @@ -265,7 +265,7 @@ public func ConfigDouble(_ key: String, fallback: Double = Double()) -> Double { - parameter key: Key for the setting. - parameter fallback: Fallback value if refresh was not successful. */ -public func ConfigDoubleForce(_ key: String, fallback: Double, completion: ((forced: Double) -> Void)) { +public func ConfigDoubleForce(_ key: String, fallback: Double, completion: @escaping ((_ forced: Double) -> Void)) { ConfigGenericForce(key, fallback: fallback, completion: completion) } @@ -289,7 +289,7 @@ public func ConfigString(_ key: String, fallback: String = String()) -> String { - parameter key: Key for the setting. - parameter fallback: Fallback value if refresh was not successful. */ -public func ConfigStringForce(_ key: String, fallback: String, completion: ((forced: String) -> Void)) { +public func ConfigStringForce(_ key: String, fallback: String, completion: @escaping ((_ forced: String) -> Void)) { ConfigGenericForce(key, fallback: fallback, completion: completion) } @@ -305,7 +305,7 @@ class ACMissionControl { weak var delegate: MissionControlDelegate? - var localConfig: [String : AnyObject]? + var localConfig: [String : Any]? var remoteURL: URL? { didSet { @@ -321,7 +321,7 @@ class ACMissionControl { } } - var remoteConfig: [String : AnyObject]? { + var remoteConfig: [String : Any]? { didSet { if let newConfig = remoteConfig { refreshDate = Date() @@ -334,7 +334,7 @@ class ACMissionControl { } } - private func informListeners(oldConfig: [String : AnyObject]?, newConfig: [String : AnyObject]) { + private func informListeners(oldConfig: [String : Any]?, newConfig: [String : Any]) { let userInfo = userInfoWithConfig(old: oldConfig, new: newConfig) delegate?.missionControlDidRefreshConfig(old: oldConfig, new: newConfig) sendNotification(MissionControl.Notification.DidRefreshConfig, userInfo: userInfo) @@ -347,7 +347,7 @@ class ACMissionControl { static let Date = "ACMissionControl.CacheDate" } - var cachedConfig: [String : AnyObject]? { + var cachedConfig: [String : Any]? { get { let userDefaults = UserDefaults.standard let config = userDefaults.object(forKey: Cache.Config) as? [String : AnyObject] @@ -392,7 +392,7 @@ class ACMissionControl { private func informListeners(_ error: Error) { delegate?.missionControlDidFailRefreshingConfig(error: error) - let userInfo = ["Error" : "\(error)"] + let userInfo: [AnyHashable : Any] = ["Error" : "\(error)"] sendNotification(MissionControl.Notification.DidFailRefreshingConfig, userInfo: userInfo) } @@ -412,11 +412,11 @@ class ACMissionControl { refreshDate = nil } - private func userInfoWithConfig(old: [String : AnyObject]?, new: [String : AnyObject]?) -> [NSObject : AnyObject]? { + private func userInfoWithConfig(old: [AnyHashable : Any]?, new: [AnyHashable : Any]?) -> [AnyHashable : Any]? { if old == nil && new == nil { return nil } else { - var userInfo = [NSObject : AnyObject]() + var userInfo = [AnyHashable : Any]() if let oldConfig = old { userInfo[MissionControl.Notification.UserInfo.OldConfigKey] = oldConfig } @@ -427,21 +427,21 @@ class ACMissionControl { } } - private func sendNotification(_ name: String, userInfo: [NSObject : AnyObject]? = nil) { + private func sendNotification(_ name: String, userInfo: [AnyHashable : Any]? = nil) { let center = NotificationCenter.default center.post(name: Notification.Name(rawValue: name), object: self, userInfo: userInfo) } - private func getRemoteConfig(_ completion: ThrowJSONWithInnerBlock) { + private func getRemoteConfig(_ completion: @escaping ThrowJSONWithInnerBlock) { guard let url = remoteURL - else { completion(block: { throw MissionControl.ServerError.noRemoteURL }); return } + else { completion({ throw MissionControl.ServerError.noRemoteURL }); return } let request = URLRequest(url: url) let session = URLSession.shared let task = session.dataTask(with: request) { [unowned self] (data, response, error) in guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 - else { completion(block: { throw MissionControl.ServerError.badResponseCode }); return } + else { completion({ throw MissionControl.ServerError.badResponseCode }); return } self.parseRemoteConfigFromData(data, completion: completion) } @@ -450,15 +450,15 @@ class ACMissionControl { private func parseRemoteConfigFromData(_ data: Data?, completion: ThrowJSONWithInnerBlock) { guard let configData = data - else { completion(block: { throw MissionControl.ServerError.invalidData }); return } + else { completion({ throw MissionControl.ServerError.invalidData }); return } do { let json = try JSONSerialization.jsonObject(with: configData, options: .allowFragments) guard let config = json as? [String : AnyObject] - else { completion(block: { throw MissionControl.ServerError.invalidData }); return } - completion(block: { return config }) + else { completion({ throw MissionControl.ServerError.invalidData }); return } + completion({ return config }) } catch { - completion(block: { throw MissionControl.ServerError.invalidData }) + completion({ throw MissionControl.ServerError.invalidData }) } } diff --git a/Tests/MissionControlTests.swift b/Tests/MissionControlTests.swift index 5b76a06..22302bf 100644 --- a/Tests/MissionControlTests.swift +++ b/Tests/MissionControlTests.swift @@ -57,21 +57,21 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { static let String = "StringSetting" } - let localTestConfig: [String : AnyObject] = [ + let localTestConfig: [String : Any] = [ Key.Bool : false, Key.Int : 21, Key.Double : 0.8, Key.String : "Local" ] - let remoteTestConfig: [String : AnyObject] = [ + let remoteTestConfig: [String : Any] = [ Key.Bool : true, Key.Int : 8, Key.Double : 2.1, Key.String : "Remote" ] - let fallbackTestConfig: [String : AnyObject] = [ + let fallbackTestConfig: [String : Any] = [ Key.Bool : false, Key.Int : 1984, Key.Double : 21.08, @@ -83,7 +83,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { // MARK: - MissionControlDelegate - func missionControlDidRefreshConfig(old: [String : AnyObject]?, new: [String : AnyObject]) { + func missionControlDidRefreshConfig(old: [String : Any]?, new: [String : Any]) { didRefreshConfigExpectation?.fulfill() } From 9dffff05fd6649c4c6857cf938ecfeb0bb87f786 Mon Sep 17 00:00:00 2001 From: Marko Tadic Date: Tue, 18 Oct 2016 16:21:22 +0200 Subject: [PATCH 11/17] Update demo project settings to recommended with Xcode 8.1 Beta (8T47) --- Example/MissionControlDemo.xcodeproj/project.pbxproj | 6 +++++- .../xcshareddata/xcschemes/MissionControlDemo.xcscheme | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Example/MissionControlDemo.xcodeproj/project.pbxproj b/Example/MissionControlDemo.xcodeproj/project.pbxproj index dbc2b27..27e4a2a 100644 --- a/Example/MissionControlDemo.xcodeproj/project.pbxproj +++ b/Example/MissionControlDemo.xcodeproj/project.pbxproj @@ -229,7 +229,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 0810; ORGANIZATIONNAME = appculture; TargetAttributes = { 8B464DCB1CE3742F00BAE834 = { @@ -385,8 +385,10 @@ 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -431,8 +433,10 @@ 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; diff --git a/Example/MissionControlDemo.xcodeproj/xcshareddata/xcschemes/MissionControlDemo.xcscheme b/Example/MissionControlDemo.xcodeproj/xcshareddata/xcschemes/MissionControlDemo.xcscheme index 98def11..6c3d035 100644 --- a/Example/MissionControlDemo.xcodeproj/xcshareddata/xcschemes/MissionControlDemo.xcscheme +++ b/Example/MissionControlDemo.xcodeproj/xcshareddata/xcschemes/MissionControlDemo.xcscheme @@ -1,6 +1,6 @@ Date: Tue, 18 Oct 2016 16:27:42 +0200 Subject: [PATCH 12/17] Fixed all build errors and warnings in demo project with Xcode 8.1 Beta (8T47) --- Example/MissionControlDemo/AppDelegate.swift | 6 +++--- Example/MissionControlDemo/BaseLaunchView.swift | 6 +++--- Example/MissionControlDemo/LaunchBrain.swift | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Example/MissionControlDemo/AppDelegate.swift b/Example/MissionControlDemo/AppDelegate.swift index 223f82f..3d0875d 100644 --- a/Example/MissionControlDemo/AppDelegate.swift +++ b/Example/MissionControlDemo/AppDelegate.swift @@ -30,9 +30,9 @@ import MissionControl class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - + + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { let url = URL(string: "http://private-83024-missioncontrol5.apiary-mock.com/mission-control/launch-config")! MissionControl.launch(remoteConfigURL: url) diff --git a/Example/MissionControlDemo/BaseLaunchView.swift b/Example/MissionControlDemo/BaseLaunchView.swift index b455d19..b25320b 100644 --- a/Example/MissionControlDemo/BaseLaunchView.swift +++ b/Example/MissionControlDemo/BaseLaunchView.swift @@ -44,7 +44,7 @@ class BaseLaunchView: UIView { // MARK: - Properties - var didTapButtonAction: ((sender: AnyObject) -> Void)? + var didTapButtonAction: ((_ sender: AnyObject) -> Void)? var padding: CGFloat = 24.0 @@ -130,7 +130,7 @@ class BaseLaunchView: UIView { if touchesInsideView(touches, view: button) { restoreButton() if let action = didTapButtonAction { - action(sender: button) + action(button) } } } @@ -305,7 +305,7 @@ class BaseLaunchView: UIView { // MARK: - Interface Builder override func prepareForInterfaceBuilder() { - let bundle = Bundle(for: self.dynamicType) + let bundle = Bundle(for: type(of: self)) let image = UIImage(named: "appculture", in: bundle, compatibleWith: traitCollection) buttonImage.image = image } diff --git a/Example/MissionControlDemo/LaunchBrain.swift b/Example/MissionControlDemo/LaunchBrain.swift index bf9b310..64e53be 100644 --- a/Example/MissionControlDemo/LaunchBrain.swift +++ b/Example/MissionControlDemo/LaunchBrain.swift @@ -81,7 +81,7 @@ class LaunchBrain: MissionControlDelegate { // MARK: - MissionControlDelegate - func missionControlDidRefreshConfig(old: [String : AnyObject]?, new: [String : AnyObject]) { + func missionControlDidRefreshConfig(old: [String : Any]?, new: [String : Any]) { print("missionControlDidRefreshConfig") updateUIForState(state) } From e43f65c8fdb6d829df58ee213ac87c63cb2845ef Mon Sep 17 00:00:00 2001 From: Marko Tadic Date: Tue, 18 Oct 2016 16:44:22 +0200 Subject: [PATCH 13/17] Refactoring and swiftyfying --- Sources/MissionControl.swift | 28 ++++++++++++++-------------- Tests/MissionControlTests.swift | 4 ++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Sources/MissionControl.swift b/Sources/MissionControl.swift index 398a6ca..89b4bbc 100644 --- a/Sources/MissionControl.swift +++ b/Sources/MissionControl.swift @@ -61,15 +61,15 @@ public class MissionControl { /// Delegate for Mission Control. public class var delegate: MissionControlDelegate? { - get { return ACMissionControl.sharedInstance.delegate } - set { ACMissionControl.sharedInstance.delegate = newValue } + get { return ACMissionControl.shared.delegate } + set { ACMissionControl.shared.delegate = newValue } } /// The latest version of config dictionary, directly accessible, if needed. public class var config: [String : Any] { - let remoteConfig = ACMissionControl.sharedInstance.remoteConfig - let cachedConfig = ACMissionControl.sharedInstance.cachedConfig - let localConfig = ACMissionControl.sharedInstance.localConfig + let remoteConfig = ACMissionControl.shared.remoteConfig + let cachedConfig = ACMissionControl.shared.cachedConfig + let localConfig = ACMissionControl.shared.localConfig let emptyConfig = [String : Any]() let resolvedConfig = remoteConfig ?? cachedConfig ?? localConfig ?? emptyConfig return resolvedConfig @@ -77,12 +77,12 @@ public class MissionControl { /// Date of last successful refresh from remote. public class var refreshDate: Date? { - return ACMissionControl.sharedInstance.refreshDate + return ACMissionControl.shared.refreshDate } /// Date of last cached remote config. public class var cacheDate: Date? { - return ACMissionControl.sharedInstance.cacheDate + return ACMissionControl.shared.cacheDate } // MARK: API @@ -96,8 +96,8 @@ public class MissionControl { - parameter remoteConfigURL: If this parameter is set then `refresh` will be called, otherwise not. */ public class func launch(localConfig: [String : Any]? = nil, remoteConfigURL url: URL? = nil) { - ACMissionControl.sharedInstance.localConfig = localConfig - ACMissionControl.sharedInstance.remoteURL = url + ACMissionControl.shared.localConfig = localConfig + ACMissionControl.shared.remoteURL = url } /** @@ -108,7 +108,7 @@ public class MissionControl { - parameter completion: Completion handler (SEE: `ThrowWithInnerBlock`). */ public class func refresh(_ completion: ThrowWithInnerBlock? = nil) { - ACMissionControl.sharedInstance.refresh(completion) + ACMissionControl.shared.refresh(completion) } } @@ -162,11 +162,11 @@ public typealias ThrowJSONWithInnerBlock = (_ block: @escaping () throws -> [Str - returns: Resolved setting of generic type `T` for given key. */ public func ConfigGeneric(_ key: String, fallback: T) -> T { - if let remoteValue = ACMissionControl.sharedInstance.remoteConfig?[key] as? T { + if let remoteValue = ACMissionControl.shared.remoteConfig?[key] as? T { return remoteValue - } else if let cachedValue = ACMissionControl.sharedInstance.cachedConfig?[key] as? T { + } else if let cachedValue = ACMissionControl.shared.cachedConfig?[key] as? T { return cachedValue - } else if let localValue = ACMissionControl.sharedInstance.localConfig?[key] as? T { + } else if let localValue = ACMissionControl.shared.localConfig?[key] as? T { return localValue } else { return fallback @@ -299,7 +299,7 @@ class ACMissionControl { // MARK: Singleton - static let sharedInstance = ACMissionControl() + static let shared = ACMissionControl() // MARK: Properties diff --git a/Tests/MissionControlTests.swift b/Tests/MissionControlTests.swift index 22302bf..f9d551a 100644 --- a/Tests/MissionControlTests.swift +++ b/Tests/MissionControlTests.swift @@ -36,7 +36,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. - ACMissionControl.sharedInstance.resetAll() + ACMissionControl.shared.resetAll() super.tearDown() } @@ -379,7 +379,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { let notification = MissionControl.Notification.DidRefreshConfig let _ = expectation(forNotification: notification, object: nil) { (notification) -> Bool in - ACMissionControl.sharedInstance.resetRemote() + ACMissionControl.shared.resetRemote() self.confirmCachedConfigState() return true } From a8c343a2a666601f730b7ff78f42d4d7b15901af Mon Sep 17 00:00:00 2001 From: Marko Tadic Date: Tue, 18 Oct 2016 16:56:19 +0200 Subject: [PATCH 14/17] Reorganizing sources into multiple files --- MissionControl.xcodeproj/project.pbxproj | 16 ++ Sources/Accessors.swift | 171 ++++++++++++ Sources/Brain.swift | 200 ++++++++++++++ Sources/MissionControl.swift | 337 +---------------------- 4 files changed, 390 insertions(+), 334 deletions(-) create mode 100644 Sources/Accessors.swift create mode 100644 Sources/Brain.swift diff --git a/MissionControl.xcodeproj/project.pbxproj b/MissionControl.xcodeproj/project.pbxproj index ef386f1..bece823 100644 --- a/MissionControl.xcodeproj/project.pbxproj +++ b/MissionControl.xcodeproj/project.pbxproj @@ -21,6 +21,12 @@ 8B6313861CE5F9A10029DC98 /* MissionControl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B63137B1CE5F9A10029DC98 /* MissionControl.framework */; }; 8B63138B1CE5F9A10029DC98 /* MissionControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B63138A1CE5F9A10029DC98 /* MissionControlTests.swift */; }; 8B6313961CE5FA2B0029DC98 /* MissionControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B6313951CE5FA2B0029DC98 /* MissionControl.swift */; }; + 8BFA21AE1DB66DBB00CAB201 /* Brain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BFA21AD1DB66DBB00CAB201 /* Brain.swift */; }; + 8BFA21AF1DB66DBB00CAB201 /* Brain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BFA21AD1DB66DBB00CAB201 /* Brain.swift */; }; + 8BFA21B01DB66DBB00CAB201 /* Brain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BFA21AD1DB66DBB00CAB201 /* Brain.swift */; }; + 8BFA21B21DB66DC700CAB201 /* Accessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BFA21B11DB66DC700CAB201 /* Accessors.swift */; }; + 8BFA21B31DB66DC700CAB201 /* Accessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BFA21B11DB66DC700CAB201 /* Accessors.swift */; }; + 8BFA21B41DB66DC700CAB201 /* Accessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BFA21B11DB66DC700CAB201 /* Accessors.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -65,6 +71,8 @@ 8B63138A1CE5F9A10029DC98 /* MissionControlTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MissionControlTests.swift; sourceTree = ""; }; 8B63138C1CE5F9A10029DC98 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8B6313951CE5FA2B0029DC98 /* MissionControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MissionControl.swift; sourceTree = ""; }; + 8BFA21AD1DB66DBB00CAB201 /* Brain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Brain.swift; sourceTree = ""; }; + 8BFA21B11DB66DC700CAB201 /* Accessors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Accessors.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -163,6 +171,8 @@ isa = PBXGroup; children = ( 8B6313951CE5FA2B0029DC98 /* MissionControl.swift */, + 8BFA21B11DB66DC700CAB201 /* Accessors.swift */, + 8BFA21AD1DB66DBB00CAB201 /* Brain.swift */, 8B6313971CE5FA3C0029DC98 /* Supporting Files */, ); path = Sources; @@ -478,6 +488,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8BFA21B31DB66DC700CAB201 /* Accessors.swift in Sources */, + 8BFA21AF1DB66DBB00CAB201 /* Brain.swift in Sources */, 8B03C1E81CF5E17F00B09B48 /* MissionControl.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -486,6 +498,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8BFA21B41DB66DC700CAB201 /* Accessors.swift in Sources */, + 8BFA21B01DB66DBB00CAB201 /* Brain.swift in Sources */, 8B03C2061CF5E22E00B09B48 /* MissionControl.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -518,6 +532,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8BFA21B21DB66DC700CAB201 /* Accessors.swift in Sources */, + 8BFA21AE1DB66DBB00CAB201 /* Brain.swift in Sources */, 8B6313961CE5FA2B0029DC98 /* MissionControl.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/Accessors.swift b/Sources/Accessors.swift new file mode 100644 index 0000000..7f6c577 --- /dev/null +++ b/Sources/Accessors.swift @@ -0,0 +1,171 @@ +// +// Accessors.swift +// +// Copyright (c) 2016 appculture http://appculture.com +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + +/** + Accessor for retreiving setting of generic type `T` for given key. + + This method will resolve to proper setting by following this priority order: + 1. Remote setting from memory (received in the last refresh). + 2. Remote setting from disk cache (if never refreshed in current app session (ex. offline)). + 3. Local setting from disk (defaults provided in `localConfig` on MissionControl `launch`). + 4. Provided fallback value (if provided) + + - parameter key: Key for the setting. + - parameter fallback: Fallback value if setting is not available in any config. + + - returns: Resolved setting of generic type `T` for given key. +*/ +public func ConfigGeneric(_ key: String, fallback: T) -> T { + if let remoteValue = ACMissionControl.shared.remoteConfig?[key] as? T { + return remoteValue + } else if let cachedValue = ACMissionControl.shared.cachedConfig?[key] as? T { + return cachedValue + } else if let localValue = ACMissionControl.shared.localConfig?[key] as? T { + return localValue + } else { + return fallback + } +} + +/** + Async "Force Remote" Accessor for retreiving the latest setting of generic type `T` for given key. + + This method will first call `refresh` method after which it will evaluate its success. + + If `refresh` was successful, it will call normal accessor of generic type `T` for given key, + which will by its priority order resolve to the latest remote value as a parameter inside `completion` handler. + + If `refresh` fails, it will return provided `fallback` value as a parameter inside `completion` block. + + - parameter key: Key for the setting. + - parameter fallback: Fallback value of generic type `T` if refresh is not successful. +*/ +public func ConfigGenericForce(_ key: String, fallback: T, completion: @escaping ((_ forced: T) -> Void)) { + MissionControl.refresh({ (innerBlock) in + do { + let _ = try innerBlock() + completion(ConfigGeneric(key, fallback: fallback)) + } catch { + completion(fallback) + } + }) +} + +/** + Accessor helper for retreiving setting of type `Bool` for given key. + It will call `ConfigGeneric` with `Bool` type. + + - parameter key: Key for the setting. + - parameter fallback: Fallback value if setting not available in any config. Defaults to `Bool()`. + + - returns: Resolved setting of type `Bool` for given key. +*/ +public func ConfigBool(_ key: String, fallback: Bool = Bool()) -> Bool { + return ConfigGeneric(key, fallback: fallback) +} + +/** + Async "Force Remote" Accessor helper for retreiving the latest setting of type `Bool` for given key. + It will call `ConfigGenericForce` with `Bool` type. + + - parameter key: Key for the setting. + - parameter fallback: Fallback value if refresh was not successful. +*/ +public func ConfigBoolForce(_ key: String, fallback: Bool, completion: @escaping ((_ forced: Bool) -> Void)) { + ConfigGenericForce(key, fallback: fallback, completion: completion) +} + +/** + Accessor helper for retreiving setting of type `Int` for given key. + It will call `ConfigGeneric` with `Int` type. + + - parameter key: Key for the setting. + - parameter fallback: Fallback value if setting not available in any config. Defaults to `Int()`. + + - returns: Resolved setting of type `Int` for given key. +*/ +public func ConfigInt(_ key: String, fallback: Int = Int()) -> Int { + return ConfigGeneric(key, fallback: fallback) +} + +/** + Async "Force Remote" Accessor helper for retreiving the latest setting of type `Int` for given key. + It will call `ConfigGenericForce` with `Int` type. + + - parameter key: Key for the setting. + - parameter fallback: Fallback value if refresh was not successful. +*/ +public func ConfigIntForce(_ key: String, fallback: Int, completion: @escaping ((_ forced: Int) -> Void)) { + ConfigGenericForce(key, fallback: fallback, completion: completion) +} + +/** + Accessor helper for retreiving setting of type `Double` for given key. + It will call `ConfigGeneric` with `Double` type. + + - parameter key: Key for the setting. + - parameter fallback: Fallback value if setting not available in any config. Defaults to `Double()`. + + - returns: Resolved setting of type `Double` for given key. +*/ +public func ConfigDouble(_ key: String, fallback: Double = Double()) -> Double { + return ConfigGeneric(key, fallback: fallback) +} + +/** + Async "Force Remote" Accessor helper for retreiving the latest setting of type `Double` for given key. + It will call `ConfigGenericForce` with `Double` type. + + - parameter key: Key for the setting. + - parameter fallback: Fallback value if refresh was not successful. +*/ +public func ConfigDoubleForce(_ key: String, fallback: Double, completion: @escaping ((_ forced: Double) -> Void)) { + ConfigGenericForce(key, fallback: fallback, completion: completion) +} + +/** + Accessor helper for retreiving setting of type `String` for given key. + It will call `ConfigGeneric` with `String` type. + + - parameter key: Key for the setting. + - parameter fallback: Fallback value if setting not available in any config. Defaults to `String()`. + + - returns: Resolved setting of type `String` for given key. +*/ +public func ConfigString(_ key: String, fallback: String = String()) -> String { + return ConfigGeneric(key, fallback: fallback) +} + +/** + Async "Force Remote" Accessor helper for retreiving the latest setting of type `String` for given key. + It will call `ConfigGenericForce` with `String` type. + + - parameter key: Key for the setting. + - parameter fallback: Fallback value if refresh was not successful. +*/ +public func ConfigStringForce(_ key: String, fallback: String, completion: @escaping ((_ forced: String) -> Void)) { + ConfigGenericForce(key, fallback: fallback, completion: completion) +} diff --git a/Sources/Brain.swift b/Sources/Brain.swift new file mode 100644 index 0000000..849c619 --- /dev/null +++ b/Sources/Brain.swift @@ -0,0 +1,200 @@ +// +// Brain.swift +// +// Copyright (c) 2016 appculture http://appculture.com +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + +/// Block which throws via inner block. +public typealias ThrowWithInnerBlock = (() throws -> Void) -> Void + +/// Block which throws dictionary via inner block. +public typealias ThrowJSONWithInnerBlock = (_ block: @escaping () throws -> [String : AnyObject]) -> Void + +class ACMissionControl { + + // MARK: - Singleton + + static let shared = ACMissionControl() + + // MARK: - Properties + + weak var delegate: MissionControlDelegate? + + var localConfig: [String : Any]? + + var remoteURL: URL? { + didSet { + if let _ = remoteURL { + refresh({ (block) in + do { + _ = try block() + } catch { + print(error) + } + }) + } + } + } + + var remoteConfig: [String : Any]? { + didSet { + if let newConfig = remoteConfig { + refreshDate = Date() + + cachedConfig = newConfig + cacheDate = refreshDate + + informListeners(oldConfig: oldValue, newConfig: newConfig) + } + } + } + + private func informListeners(oldConfig: [String : Any]?, newConfig: [String : Any]) { + let userInfo = userInfoWithConfig(old: oldConfig, new: newConfig) + delegate?.missionControlDidRefreshConfig(old: oldConfig, new: newConfig) + sendNotification(MissionControl.Notification.DidRefreshConfig, userInfo: userInfo) + } + + var refreshDate: Date? + + private struct Cache { + static let Config = "ACMissionControl.CachedConfig" + static let Date = "ACMissionControl.CacheDate" + } + + var cachedConfig: [String : Any]? { + get { + let userDefaults = UserDefaults.standard + let config = userDefaults.object(forKey: Cache.Config) as? [String : AnyObject] + return config + } + set { + let userDefaults = UserDefaults.standard + userDefaults.set(newValue, forKey: Cache.Config) + userDefaults.synchronize() + } + } + + var cacheDate: Date? { + get { + let userDefaults = UserDefaults.standard + let config = userDefaults.object(forKey: Cache.Date) as? Date + return config + } + set { + let userDefaults = UserDefaults.standard + userDefaults.set(newValue, forKey: Cache.Date) + userDefaults.synchronize() + } + } + + // MARK: - API + + func refresh(_ completion: ThrowWithInnerBlock? = nil) { + getRemoteConfig { [unowned self] (block) in + DispatchQueue.main.async { [unowned self] in + do { + let remoteConfig = try block() + self.remoteConfig = remoteConfig + completion?({ }) + } catch { + self.informListeners(error) + completion?({ throw error }) + } + } + } + } + + private func informListeners(_ error: Error) { + delegate?.missionControlDidFailRefreshingConfig(error: error) + let userInfo: [AnyHashable : Any] = ["Error" : "\(error)"] + sendNotification(MissionControl.Notification.DidFailRefreshingConfig, userInfo: userInfo) + } + + // MARK: - Helpers + + func resetAll() { + localConfig = nil + cachedConfig = nil + remoteConfig = nil + refreshDate = nil + remoteURL = nil + delegate = nil + } + + func resetRemote() { + remoteConfig = nil + refreshDate = nil + } + + private func userInfoWithConfig(old: [AnyHashable : Any]?, new: [AnyHashable : Any]?) -> [AnyHashable : Any]? { + if old == nil && new == nil { + return nil + } else { + var userInfo = [AnyHashable : Any]() + if let oldConfig = old { + userInfo[MissionControl.Notification.UserInfo.OldConfigKey] = oldConfig + } + if let newConfig = new { + userInfo[MissionControl.Notification.UserInfo.NewConfigKey] = newConfig + } + return userInfo + } + } + + private func sendNotification(_ name: String, userInfo: [AnyHashable : Any]? = nil) { + let center = NotificationCenter.default + center.post(name: Notification.Name(rawValue: name), object: self, userInfo: userInfo) + } + + private func getRemoteConfig(_ completion: @escaping ThrowJSONWithInnerBlock) { + guard let url = remoteURL + else { completion({ throw MissionControl.ServerError.noRemoteURL }); return } + + let request = URLRequest(url: url) + let session = URLSession.shared + + let task = session.dataTask(with: request) { [unowned self] (data, response, error) in + guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 + else { completion({ throw MissionControl.ServerError.badResponseCode }); return } + self.parseRemoteConfigFromData(data, completion: completion) + } + + task.resume() + } + + private func parseRemoteConfigFromData(_ data: Data?, completion: ThrowJSONWithInnerBlock) { + guard let configData = data + else { completion({ throw MissionControl.ServerError.invalidData }); return } + + do { + let json = try JSONSerialization.jsonObject(with: configData, options: .allowFragments) + guard let config = json as? [String : AnyObject] + else { completion({ throw MissionControl.ServerError.invalidData }); return } + completion({ return config }) + } catch { + completion({ throw MissionControl.ServerError.invalidData }) + } + } + +} diff --git a/Sources/MissionControl.swift b/Sources/MissionControl.swift index 89b4bbc..651996e 100644 --- a/Sources/MissionControl.swift +++ b/Sources/MissionControl.swift @@ -24,12 +24,10 @@ import Foundation -// MARK: - MissionControl - /// Facade class for using MissionControl. public class MissionControl { - // MARK: Types + // MARK: - Types /// Errors types which can be throwed when refreshing local config from remote. public enum ServerError: Error { @@ -57,7 +55,7 @@ public class MissionControl { } } - // MARK: Properties + // MARK: - Properties /// Delegate for Mission Control. public class var delegate: MissionControlDelegate? { @@ -85,7 +83,7 @@ public class MissionControl { return ACMissionControl.shared.cacheDate } - // MARK: API + // MARK: - API /** This should be called on your app start to initialize and/or refresh remote config. @@ -113,8 +111,6 @@ public class MissionControl { } -// MARK: - MissionControlDelegate - /** Delegate for Mission Control. @@ -136,330 +132,3 @@ public protocol MissionControlDelegate: class { */ func missionControlDidFailRefreshingConfig(error: Error) } - -// MARK: - Custom Types - -/// Block which throws via inner block. -public typealias ThrowWithInnerBlock = (() throws -> Void) -> Void - -/// Block which throws dictionary via inner block. -public typealias ThrowJSONWithInnerBlock = (_ block: @escaping () throws -> [String : AnyObject]) -> Void - -// MARK: - Accessors - -/** - Accessor for retreiving setting of generic type `T` for given key. - - This method will resolve to proper setting by following this priority order: - 1. Remote setting from memory (received in the last refresh). - 2. Remote setting from disk cache (if never refreshed in current app session (ex. offline)). - 3. Local setting from disk (defaults provided in `localConfig` on MissionControl `launch`). - 4. Provided fallback value (if provided) - - - parameter key: Key for the setting. - - parameter fallback: Fallback value if setting is not available in any config. - - - returns: Resolved setting of generic type `T` for given key. -*/ -public func ConfigGeneric(_ key: String, fallback: T) -> T { - if let remoteValue = ACMissionControl.shared.remoteConfig?[key] as? T { - return remoteValue - } else if let cachedValue = ACMissionControl.shared.cachedConfig?[key] as? T { - return cachedValue - } else if let localValue = ACMissionControl.shared.localConfig?[key] as? T { - return localValue - } else { - return fallback - } -} - -/** - Async "Force Remote" Accessor for retreiving the latest setting of generic type `T` for given key. - - This method will first call `refresh` method after which it will evaluate its success. - - If `refresh` was successful, it will call normal accessor of generic type `T` for given key, - which will by its priority order resolve to the latest remote value as a parameter inside `completion` handler. - - If `refresh` fails, it will return provided `fallback` value as a parameter inside `completion` block. - - - parameter key: Key for the setting. - - parameter fallback: Fallback value of generic type `T` if refresh is not successful. -*/ -public func ConfigGenericForce(_ key: String, fallback: T, completion: @escaping ((_ forced: T) -> Void)) { - MissionControl.refresh({ (innerBlock) in - do { - let _ = try innerBlock() - completion(ConfigGeneric(key, fallback: fallback)) - } catch { - completion(fallback) - } - }) -} - -/** - Accessor helper for retreiving setting of type `Bool` for given key. - It will call `ConfigGeneric` with `Bool` type. - - - parameter key: Key for the setting. - - parameter fallback: Fallback value if setting not available in any config. Defaults to `Bool()`. - - - returns: Resolved setting of type `Bool` for given key. -*/ -public func ConfigBool(_ key: String, fallback: Bool = Bool()) -> Bool { - return ConfigGeneric(key, fallback: fallback) -} - -/** - Async "Force Remote" Accessor helper for retreiving the latest setting of type `Bool` for given key. - It will call `ConfigGenericForce` with `Bool` type. - - - parameter key: Key for the setting. - - parameter fallback: Fallback value if refresh was not successful. -*/ -public func ConfigBoolForce(_ key: String, fallback: Bool, completion: @escaping ((_ forced: Bool) -> Void)) { - ConfigGenericForce(key, fallback: fallback, completion: completion) -} - -/** - Accessor helper for retreiving setting of type `Int` for given key. - It will call `ConfigGeneric` with `Int` type. - - - parameter key: Key for the setting. - - parameter fallback: Fallback value if setting not available in any config. Defaults to `Int()`. - - - returns: Resolved setting of type `Int` for given key. -*/ -public func ConfigInt(_ key: String, fallback: Int = Int()) -> Int { - return ConfigGeneric(key, fallback: fallback) -} - -/** - Async "Force Remote" Accessor helper for retreiving the latest setting of type `Int` for given key. - It will call `ConfigGenericForce` with `Int` type. - - - parameter key: Key for the setting. - - parameter fallback: Fallback value if refresh was not successful. -*/ -public func ConfigIntForce(_ key: String, fallback: Int, completion: @escaping ((_ forced: Int) -> Void)) { - ConfigGenericForce(key, fallback: fallback, completion: completion) -} - -/** - Accessor helper for retreiving setting of type `Double` for given key. - It will call `ConfigGeneric` with `Double` type. - - - parameter key: Key for the setting. - - parameter fallback: Fallback value if setting not available in any config. Defaults to `Double()`. - - - returns: Resolved setting of type `Double` for given key. -*/ -public func ConfigDouble(_ key: String, fallback: Double = Double()) -> Double { - return ConfigGeneric(key, fallback: fallback) -} - -/** - Async "Force Remote" Accessor helper for retreiving the latest setting of type `Double` for given key. - It will call `ConfigGenericForce` with `Double` type. - - - parameter key: Key for the setting. - - parameter fallback: Fallback value if refresh was not successful. -*/ -public func ConfigDoubleForce(_ key: String, fallback: Double, completion: @escaping ((_ forced: Double) -> Void)) { - ConfigGenericForce(key, fallback: fallback, completion: completion) -} - -/** - Accessor helper for retreiving setting of type `String` for given key. - It will call `ConfigGeneric` with `String` type. - - - parameter key: Key for the setting. - - parameter fallback: Fallback value if setting not available in any config. Defaults to `String()`. - - - returns: Resolved setting of type `String` for given key. -*/ -public func ConfigString(_ key: String, fallback: String = String()) -> String { - return ConfigGeneric(key, fallback: fallback) -} - -/** - Async "Force Remote" Accessor helper for retreiving the latest setting of type `String` for given key. - It will call `ConfigGenericForce` with `String` type. - - - parameter key: Key for the setting. - - parameter fallback: Fallback value if refresh was not successful. -*/ -public func ConfigStringForce(_ key: String, fallback: String, completion: @escaping ((_ forced: String) -> Void)) { - ConfigGenericForce(key, fallback: fallback, completion: completion) -} - -// MARK: - ACMissionControl - -class ACMissionControl { - - // MARK: Singleton - - static let shared = ACMissionControl() - - // MARK: Properties - - weak var delegate: MissionControlDelegate? - - var localConfig: [String : Any]? - - var remoteURL: URL? { - didSet { - if let _ = remoteURL { - refresh({ (block) in - do { - _ = try block() - } catch { - print(error) - } - }) - } - } - } - - var remoteConfig: [String : Any]? { - didSet { - if let newConfig = remoteConfig { - refreshDate = Date() - - cachedConfig = newConfig - cacheDate = refreshDate - - informListeners(oldConfig: oldValue, newConfig: newConfig) - } - } - } - - private func informListeners(oldConfig: [String : Any]?, newConfig: [String : Any]) { - let userInfo = userInfoWithConfig(old: oldConfig, new: newConfig) - delegate?.missionControlDidRefreshConfig(old: oldConfig, new: newConfig) - sendNotification(MissionControl.Notification.DidRefreshConfig, userInfo: userInfo) - } - - var refreshDate: Date? - - private struct Cache { - static let Config = "ACMissionControl.CachedConfig" - static let Date = "ACMissionControl.CacheDate" - } - - var cachedConfig: [String : Any]? { - get { - let userDefaults = UserDefaults.standard - let config = userDefaults.object(forKey: Cache.Config) as? [String : AnyObject] - return config - } - set { - let userDefaults = UserDefaults.standard - userDefaults.set(newValue, forKey: Cache.Config) - userDefaults.synchronize() - } - } - - var cacheDate: Date? { - get { - let userDefaults = UserDefaults.standard - let config = userDefaults.object(forKey: Cache.Date) as? Date - return config - } - set { - let userDefaults = UserDefaults.standard - userDefaults.set(newValue, forKey: Cache.Date) - userDefaults.synchronize() - } - } - - // MARK: API - - func refresh(_ completion: ThrowWithInnerBlock? = nil) { - getRemoteConfig { [unowned self] (block) in - DispatchQueue.main.async { [unowned self] in - do { - let remoteConfig = try block() - self.remoteConfig = remoteConfig - completion?({ }) - } catch { - self.informListeners(error) - completion?({ throw error }) - } - } - } - } - - private func informListeners(_ error: Error) { - delegate?.missionControlDidFailRefreshingConfig(error: error) - let userInfo: [AnyHashable : Any] = ["Error" : "\(error)"] - sendNotification(MissionControl.Notification.DidFailRefreshingConfig, userInfo: userInfo) - } - - // MARK: Helpers - - func resetAll() { - localConfig = nil - cachedConfig = nil - remoteConfig = nil - refreshDate = nil - remoteURL = nil - delegate = nil - } - - func resetRemote() { - remoteConfig = nil - refreshDate = nil - } - - private func userInfoWithConfig(old: [AnyHashable : Any]?, new: [AnyHashable : Any]?) -> [AnyHashable : Any]? { - if old == nil && new == nil { - return nil - } else { - var userInfo = [AnyHashable : Any]() - if let oldConfig = old { - userInfo[MissionControl.Notification.UserInfo.OldConfigKey] = oldConfig - } - if let newConfig = new { - userInfo[MissionControl.Notification.UserInfo.NewConfigKey] = newConfig - } - return userInfo - } - } - - private func sendNotification(_ name: String, userInfo: [AnyHashable : Any]? = nil) { - let center = NotificationCenter.default - center.post(name: Notification.Name(rawValue: name), object: self, userInfo: userInfo) - } - - private func getRemoteConfig(_ completion: @escaping ThrowJSONWithInnerBlock) { - guard let url = remoteURL - else { completion({ throw MissionControl.ServerError.noRemoteURL }); return } - - let request = URLRequest(url: url) - let session = URLSession.shared - - let task = session.dataTask(with: request) { [unowned self] (data, response, error) in - guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 - else { completion({ throw MissionControl.ServerError.badResponseCode }); return } - self.parseRemoteConfigFromData(data, completion: completion) - } - - task.resume() - } - - private func parseRemoteConfigFromData(_ data: Data?, completion: ThrowJSONWithInnerBlock) { - guard let configData = data - else { completion({ throw MissionControl.ServerError.invalidData }); return } - - do { - let json = try JSONSerialization.jsonObject(with: configData, options: .allowFragments) - guard let config = json as? [String : AnyObject] - else { completion({ throw MissionControl.ServerError.invalidData }); return } - completion({ return config }) - } catch { - completion({ throw MissionControl.ServerError.invalidData }) - } - } - -} From cad205960084d6b0f0904d658d5771deb2aa12bb Mon Sep 17 00:00:00 2001 From: Bennett Yuan Date: Mon, 15 Apr 2019 14:19:43 +0800 Subject: [PATCH 15/17] Create .gitignore --- .gitignore | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b20294 --- /dev/null +++ b/.gitignore @@ -0,0 +1,81 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. + +Carthage/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +# macOS + +*.DS_Store From b6ef6bfa49fe6086a9a429023d2fdfc8106a0016 Mon Sep 17 00:00:00 2001 From: Bennett Yuan Date: Mon, 15 Apr 2019 14:36:10 +0800 Subject: [PATCH 16/17] IDEDidComputeMac32BitWarning --- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 MissionControl.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/MissionControl.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/MissionControl.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/MissionControl.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + From 0d9a90cb896f4a180262d0bfeb55d5de7aa657d3 Mon Sep 17 00:00:00 2001 From: Bennett Yuan Date: Mon, 15 Apr 2019 14:36:55 +0800 Subject: [PATCH 17/17] Upgrade to Swift 5 --- MissionControl.xcodeproj/project.pbxproj | 13 +++++++------ Tests/MissionControlTests.swift | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/MissionControl.xcodeproj/project.pbxproj b/MissionControl.xcodeproj/project.pbxproj index 3472389..f94af5c 100644 --- a/MissionControl.xcodeproj/project.pbxproj +++ b/MissionControl.xcodeproj/project.pbxproj @@ -400,11 +400,11 @@ }; 8B63137A1CE5F9A10029DC98 = { CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; }; 8B6313841CE5F9A10029DC98 = { CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; }; }; }; @@ -413,6 +413,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 8B6313711CE5F9A10029DC98; @@ -875,7 +876,7 @@ PRODUCT_NAME = MissionControl; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -896,7 +897,7 @@ PRODUCT_NAME = MissionControl; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -907,7 +908,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.appculture.MissionControlTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -919,7 +920,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.appculture.MissionControlTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/Tests/MissionControlTests.swift b/Tests/MissionControlTests.swift index f9d551a..0d83bbb 100644 --- a/Tests/MissionControlTests.swift +++ b/Tests/MissionControlTests.swift @@ -241,7 +241,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { func confirmRemoteConfigStateAfterNotification(_ notification: String) { confirmDidRefreshConfigDelegateCallback() - let _ = expectation(forNotification: notification, object: nil) { (notification) -> Bool in + let _ = expectation(forNotification: NSNotification.Name(rawValue: notification), object: nil) { (notification) -> Bool in self.confirmRemoteConfigState() return true } @@ -378,7 +378,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { MissionControl.launch(remoteConfigURL: URL.RemoteTestConfig) let notification = MissionControl.Notification.DidRefreshConfig - let _ = expectation(forNotification: notification, object: nil) { (notification) -> Bool in + let _ = expectation(forNotification: NSNotification.Name(rawValue: notification), object: nil) { (notification) -> Bool in ACMissionControl.shared.resetRemote() self.confirmCachedConfigState() return true @@ -446,7 +446,7 @@ class MissionControlTests: XCTestCase, MissionControlDelegate { confirmDidFailRefreshingConfigDelegateCallback() let notification = MissionControl.Notification.DidFailRefreshingConfig - let _ = expectation(forNotification: notification, object: nil) { (notification) -> Bool in + let _ = expectation(forNotification: NSNotification.Name(rawValue: notification), object: nil) { (notification) -> Bool in guard let errorInfo = notification.userInfo?["Error"] as? String else { return false } XCTAssertEqual("\(errorInfo)", "\(error)", message) self.confirmInitialState()