├── Avenue.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── Avenue.xcscheme └── project.pbxproj ├── Demo ├── Demo.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── project.pbxproj ├── DemoWatch Extension │ ├── Assets.xcassets │ │ └── Complication.complicationset │ │ │ ├── Graphic Bezel.imageset │ │ │ └── Contents.json │ │ │ ├── Graphic Corner.imageset │ │ │ └── Contents.json │ │ │ ├── Graphic Circular.imageset │ │ │ └── Contents.json │ │ │ ├── Graphic Large Rectangular.imageset │ │ │ └── Contents.json │ │ │ ├── Modular.imageset │ │ │ └── Contents.json │ │ │ ├── Circular.imageset │ │ │ └── Contents.json │ │ │ ├── Extra Large.imageset │ │ │ └── Contents.json │ │ │ ├── Utilitarian.imageset │ │ │ └── Contents.json │ │ │ └── Contents.json │ ├── InterfaceController.swift │ ├── Info.plist │ └── ExtensionDelegate.swift ├── ExampleWrappers │ ├── IvkoServiceError.swift │ ├── AssetManager.swift │ └── IvkoService.swift ├── Demo │ ├── AppDelegate.swift │ ├── ViewController.swift │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ └── Info.plist ├── DemoWatch │ ├── Base.lproj │ │ └── Interface.storyboard │ ├── Info.plist │ └── Assets.xcassets │ │ └── AppIcon.appiconset │ │ └── Contents.json └── Vendor │ └── Bundle-Extensions.swift ├── Framework ├── Avenue.h └── Info.plist ├── LICENSE ├── ServerTrust ├── ServerTrust-Extensions.swift ├── NetworkSession-ServerTrust.swift └── ServerTrustPolicy.swift ├── Alley ├── NetworkError-Retries.swift ├── NetworkError.swift ├── NetworkError-Localized.swift └── URLSession-extensions.swift ├── Avenue.podspec ├── .gitignore ├── Avenue ├── URLSessionTask-Extensions.swift ├── NetworkPayload.swift ├── NetworkSession.swift └── NetworkOperation.swift ├── Essentials ├── Atomic.swift └── AsyncOperation.swift └── README.md /Avenue.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/DemoWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Demo/DemoWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Demo/DemoWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Demo/DemoWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Avenue.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Latest 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/ExampleWrappers/IvkoServiceError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IvkoServiceError.swift 3 | // CoordinatorExample 4 | // 5 | // Created by Aleksandar Vacić on 20.8.17.. 6 | // Copyright © 2017. Radiant Tap. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Avenue 11 | 12 | 13 | enum IvkoServiceError: Error { 14 | case network(NetworkError?) 15 | case unexpectedResponse(HTTPURLResponse, String?) 16 | } 17 | -------------------------------------------------------------------------------- /Framework/Avenue.h: -------------------------------------------------------------------------------- 1 | // 2 | // Avenue.h 3 | // Avenue 4 | // 5 | // Created by Aleksandar Vacić on 2/17/19. 6 | // Copyright © 2019 Radiant Tap. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Avenue. 12 | FOUNDATION_EXPORT double AvenueVersionNumber; 13 | 14 | //! Project version string for Avenue. 15 | FOUNDATION_EXPORT const unsigned char AvenueVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Demo/DemoWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /Demo/DemoWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /Demo/DemoWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /Demo/DemoWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /Framework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.1.1 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Demo/Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Demo 4 | // 5 | // Created by Aleksandar Vacić on 19.8.17.. 6 | // Copyright © 2017. Radiant Tap. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Avenue 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 18 | // Example: disable HTTPS server-trust checks for development 19 | ServerTrustPolicy.defaultPolicy = .disableEvaluation 20 | 21 | return true 22 | } 23 | 24 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 25 | return true 26 | } 27 | 28 | } 29 | 30 | -------------------------------------------------------------------------------- /Demo/DemoWatch Extension/InterfaceController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InterfaceController.swift 3 | // DemoWatch Extension 4 | // 5 | // Created by Aleksandar Vacić on 27.9.17.. 6 | // Copyright © 2017. Radiant Tap. All rights reserved. 7 | // 8 | 9 | import WatchKit 10 | import Foundation 11 | 12 | 13 | class InterfaceController: WKInterfaceController { 14 | 15 | override func awake(withContext context: Any?) { 16 | super.awake(withContext: context) 17 | 18 | // Configure interface objects here. 19 | } 20 | 21 | override func willActivate() { 22 | // This method is called when watch view controller is about to be visible to user 23 | super.willActivate() 24 | } 25 | 26 | override func didDeactivate() { 27 | // This method is called when watch view controller is no longer visible 28 | super.didDeactivate() 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Demo/DemoWatch/Base.lproj/Interface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Demo/Vendor/Bundle-Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle-Extensions.swift 3 | // Radiant Tap Essentials 4 | // 5 | // Copyright © 2016 Radiant Tap 6 | // MIT License · http://choosealicense.com/licenses/mit/ 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Bundle { 12 | static var appName: String { 13 | guard let str = main.object(forInfoDictionaryKey: "CFBundleName") as? String else { return "" } 14 | return str 15 | } 16 | 17 | static var appVersion: String { 18 | guard let str = main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else { return "" } 19 | return str 20 | } 21 | 22 | static var appBuild: String { 23 | guard let str = main.object(forInfoDictionaryKey: "CFBundleVersion") as? String else { return "" } 24 | return str 25 | } 26 | 27 | static var identifier: String { 28 | guard let str = main.object(forInfoDictionaryKey: "CFBundleIdentifier") as? String else { return "" } 29 | return str 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Aleksandar Vacić 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ServerTrust/ServerTrust-Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Network-Extensions.swift 3 | // Avenue 4 | // 5 | // Copyright © 2017 Radiant Tap 6 | // MIT License · http://choosealicense.com/licenses/mit/ 7 | // 8 | 9 | import Foundation 10 | #if os(iOS) 11 | import WebKit 12 | #endif 13 | 14 | 15 | 16 | extension ServerTrustPolicy { 17 | /// Default value to use throughout the app, aids consistency. 18 | /// URLSession and WKWebView‘s `serverTrustPolicy` should use this value. 19 | /// 20 | /// ATTENTION: 21 | /// Override this setting in some configuration .swift file, per target. 22 | /// So you can have diff. setting for development, testing, production build etc. 23 | public static var defaultPolicy: ServerTrustPolicy = .performDefaultEvaluation(validateHost: true) 24 | } 25 | 26 | 27 | 28 | // These below will simply follow what the setting above is 29 | 30 | extension URLSession { 31 | public var serverTrustPolicy : ServerTrustPolicy { 32 | return ServerTrustPolicy.defaultPolicy 33 | } 34 | } 35 | 36 | #if os(iOS) 37 | extension WKWebView { 38 | public var serverTrustPolicy : ServerTrustPolicy { 39 | return ServerTrustPolicy.defaultPolicy 40 | } 41 | } 42 | #endif 43 | 44 | 45 | -------------------------------------------------------------------------------- /Demo/DemoWatch/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Avenue 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | UISupportedInterfaceOrientations 24 | 25 | UIInterfaceOrientationPortrait 26 | UIInterfaceOrientationPortraitUpsideDown 27 | 28 | WKCompanionAppBundleIdentifier 29 | com.radianttap.AvenueDemo 30 | WKWatchKitApp 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Demo/Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Demo 4 | // 5 | // Created by Aleksandar Vacić on 19.8.17.. 6 | // Copyright © 2017. Radiant Tap. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ViewController: UIViewController { 12 | // UI 13 | 14 | @IBOutlet private weak var textView: UITextView! 15 | 16 | // Dependencies 17 | 18 | private lazy var service: IvkoService = { 19 | return IvkoService.shared 20 | }() 21 | 22 | private lazy var assetManager: AssetManager = { 23 | return AssetManager.shared 24 | }() 25 | 26 | // View Lifecycle 27 | 28 | override func viewWillAppear(_ animated: Bool) { 29 | super.viewWillAppear(animated) 30 | 31 | let path = IvkoService.Path.promotions 32 | service.call(path: path) { 33 | [weak self] json, serviceError in 34 | 35 | DispatchQueue.main.async { 36 | guard let `self` = self else { return } 37 | 38 | if let serviceError = serviceError { 39 | self.textView.text = serviceError.localizedDescription 40 | return 41 | } 42 | 43 | self.textView.text = "Completed network call to IvkoService.Path.\( path )\n(See Xcode console for details)" 44 | } 45 | } 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /Demo/DemoWatch Extension/Assets.xcassets/Complication.complicationset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "idiom" : "watch", 5 | "filename" : "Circular.imageset", 6 | "role" : "circular" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "filename" : "Extra Large.imageset", 11 | "role" : "extra-large" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "filename" : "Graphic Bezel.imageset", 16 | "role" : "graphic-bezel" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "filename" : "Graphic Circular.imageset", 21 | "role" : "graphic-circular" 22 | }, 23 | { 24 | "idiom" : "watch", 25 | "filename" : "Graphic Corner.imageset", 26 | "role" : "graphic-corner" 27 | }, 28 | { 29 | "idiom" : "watch", 30 | "filename" : "Graphic Large Rectangular.imageset", 31 | "role" : "graphic-large-rectangular" 32 | }, 33 | { 34 | "idiom" : "watch", 35 | "filename" : "Modular.imageset", 36 | "role" : "modular" 37 | }, 38 | { 39 | "idiom" : "watch", 40 | "filename" : "Utilitarian.imageset", 41 | "role" : "utilitarian" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Demo/DemoWatch Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | DemoWatch Extension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSExtension 24 | 25 | NSExtensionAttributes 26 | 27 | WKAppBundleIdentifier 28 | com.radianttap.AvenueDemo.watchkitapp 29 | 30 | NSExtensionPointIdentifier 31 | com.apple.watchkit 32 | 33 | WKExtensionDelegateClassName 34 | $(PRODUCT_MODULE_NAME).ExtensionDelegate 35 | 36 | 37 | -------------------------------------------------------------------------------- /Alley/NetworkError-Retries.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkError-Retries.swift 3 | // Alley 4 | // 5 | // Copyright © 2019 Radiant Tap 6 | // MIT License · http://choosealicense.com/licenses/mit/ 7 | // 8 | 9 | import Foundation 10 | 11 | public extension NetworkError { 12 | /// Returns `true` if URLRequest should be retried for the given `NetworkError` instance. 13 | /// 14 | /// At the lowest network levels, it makes sense to retry for cases of (possible) temporary outage. Things like timeouts, can't connect to host, network connection lost. 15 | /// In mobile context, this can happen as you move through the building or traffic and may not represent serious or more permanent connection issues. 16 | /// 17 | /// Upper layers of the app architecture may build on this to add more specific cases when the request should be retried. 18 | var shouldRetry: Bool { 19 | 20 | switch self { 21 | case .urlError(let urlError): 22 | // if temporary network issues, retry 23 | switch urlError.code { 24 | case URLError.timedOut, 25 | URLError.cannotFindHost, 26 | URLError.cannotConnectToHost, 27 | URLError.networkConnectionLost, 28 | URLError.dnsLookupFailed: 29 | return true 30 | default: 31 | break 32 | } 33 | 34 | default: 35 | break 36 | } 37 | 38 | return false 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Demo/DemoWatch/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "24x24", 5 | "idiom" : "watch", 6 | "scale" : "2x", 7 | "role" : "notificationCenter", 8 | "subtype" : "38mm" 9 | }, 10 | { 11 | "size" : "27.5x27.5", 12 | "idiom" : "watch", 13 | "scale" : "2x", 14 | "role" : "notificationCenter", 15 | "subtype" : "42mm" 16 | }, 17 | { 18 | "size" : "29x29", 19 | "idiom" : "watch", 20 | "role" : "companionSettings", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "size" : "29x29", 25 | "idiom" : "watch", 26 | "role" : "companionSettings", 27 | "scale" : "3x" 28 | }, 29 | { 30 | "size" : "40x40", 31 | "idiom" : "watch", 32 | "scale" : "2x", 33 | "role" : "appLauncher", 34 | "subtype" : "38mm" 35 | }, 36 | { 37 | "size" : "86x86", 38 | "idiom" : "watch", 39 | "scale" : "2x", 40 | "role" : "quickLook", 41 | "subtype" : "38mm" 42 | }, 43 | { 44 | "size" : "98x98", 45 | "idiom" : "watch", 46 | "scale" : "2x", 47 | "role" : "quickLook", 48 | "subtype" : "42mm" 49 | } 50 | ], 51 | "info" : { 52 | "version" : 1, 53 | "author" : "xcode" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Alley/NetworkError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkError.swift 3 | // Alley 4 | // 5 | // Copyright © 2019 Radiant Tap 6 | // MIT License · http://choosealicense.com/licenses/mit/ 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Declaration of errors that Alley can throw/return. 13 | 14 | Since this is all about networking, it should pass-through any URLErrors that happen but also add its own 15 | */ 16 | public enum NetworkError: Error { 17 | /// When network conditions are so bad that after `maxRetries` the request did not succeed. 18 | case inaccessible 19 | 20 | /// `URLSession` errors are passed-through, handle as appropriate. 21 | case urlError(URLError) 22 | 23 | /// URLSession returned an `Error` object which is not `URLError` 24 | case generalError(Swift.Error) 25 | 26 | /// When no `URLResponse` is returned but also no `URLError` or any other `Error` instance. 27 | case noResponse 28 | 29 | /// When `URLResponse` is not `HTTPURLResponse`. 30 | case invalidResponseType(URLResponse) 31 | 32 | /// Status code is in `200...299` range, but response body is empty. This can be both valid and invalid, depending on HTTP method and/or specific behavior of the service being called. 33 | case noResponseData(HTTPURLResponse) 34 | 35 | /// Status code is `400` or higher thus return the entire `HTTPURLResponse` and `Data` so caller can figure out what happened. 36 | case endpointError(HTTPURLResponse, Data?) 37 | } 38 | -------------------------------------------------------------------------------- /Avenue.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Avenue' 3 | s.version = '4.0' 4 | s.summary = 'Micro-library designed to allow seamless cooperation between URLSession(Data)Task and Operation(Queue) APIs.' 5 | 6 | s.description = <<-DESC 7 | URLSession framework is, on its own, incompatible with Operation API. A bit of trickery is required to make them cooperate. 8 | I have extended URLSessionTask with additional properties of specific closure types which allows you to overcome this incompatibility. 9 | 10 | OperationQueue and Operation are great API to use when... 11 | 12 | * your network requests are inter-dependent on each other 13 | * need to implement OAuth2 or any other kind of asynchronous 3rd-party authentication mechanism 14 | * tight control over the number of concurrent requests towards a particular host is paramount 15 | * etc. 16 | DESC 17 | 18 | s.homepage = 'https://github.com/radianttap/Avenue' 19 | s.license = { :type => "MIT", :file => "LICENSE" } 20 | s.author = { 'Aleksandar Vacić' => 'radianttap.com' } 21 | s.social_media_url = "https://twitter.com/radiantav" 22 | 23 | s.ios.deployment_target = "8.0" 24 | s.watchos.deployment_target = "3.0" 25 | s.tvos.deployment_target = "10.0" 26 | 27 | s.source = { :git => "https://github.com/radianttap/Avenue.git" } 28 | s.source_files = ['Avenue/**/*.swift', 'Alley/**/*.swift', 'ServerTrust/**/*.swift', 'Essentials/**/*.swift'] 29 | 30 | s.swift_version = '5.0' 31 | end 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /Demo/Demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Demo/Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Avenue 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSExceptionDomains 28 | 29 | self-signed.badssl.com 30 | 31 | NSExceptionAllowsInsecureHTTPLoads 32 | 33 | 34 | 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIMainStoryboardFile 39 | Main 40 | UIRequiredDeviceCapabilities 41 | 42 | armv7 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationPortraitUpsideDown 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Avenue/URLSessionTask-Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkTask.swift 3 | // Avenue 4 | // 5 | // Copyright © 2017 Radiant Tap 6 | // MIT License · http://choosealicense.com/licenses/mit/ 7 | // 8 | // See this blog post for more information why are these extensions needed: 9 | // http://aplus.rs/2017/urlsession-in-operation/ 10 | 11 | import Foundation 12 | 13 | extension URLSessionTask { 14 | public typealias NetworkTaskErrorCallback = (NetworkError) -> Void 15 | public typealias NetworkTaskResponseCallback = (HTTPURLResponse) -> Void 16 | public typealias NetworkTaskDataCallback = (Data) -> Void 17 | public typealias NetworkTaskFinishCallback = () -> Void 18 | 19 | 20 | private struct AssociatedKeys { 21 | static var error = "Error" 22 | static var response = "Response" 23 | static var data = "Data" 24 | static var finish = "Finish" 25 | } 26 | 27 | public var errorCallback: NetworkTaskErrorCallback { 28 | get { 29 | return objc_getAssociatedObject(self, &AssociatedKeys.error) as? NetworkTaskErrorCallback ?? {_ in} 30 | } 31 | set { 32 | objc_setAssociatedObject(self, &AssociatedKeys.error, newValue, .OBJC_ASSOCIATION_RETAIN) 33 | } 34 | } 35 | 36 | public var responseCallback: NetworkTaskResponseCallback { 37 | get { 38 | return objc_getAssociatedObject(self, &AssociatedKeys.response) as? NetworkTaskResponseCallback ?? {_ in} 39 | } 40 | set { 41 | objc_setAssociatedObject(self, &AssociatedKeys.response, newValue, .OBJC_ASSOCIATION_RETAIN) 42 | } 43 | } 44 | 45 | public var dataCallback: NetworkTaskDataCallback { 46 | get { 47 | return objc_getAssociatedObject(self, &AssociatedKeys.data) as? NetworkTaskDataCallback ?? {_ in} 48 | } 49 | set { 50 | objc_setAssociatedObject(self, &AssociatedKeys.data, newValue, .OBJC_ASSOCIATION_RETAIN) 51 | } 52 | } 53 | 54 | public var finishCallback: NetworkTaskFinishCallback { 55 | get { 56 | return objc_getAssociatedObject(self, &AssociatedKeys.finish) as? NetworkTaskFinishCallback ?? {} 57 | } 58 | set { 59 | objc_setAssociatedObject(self, &AssociatedKeys.finish, newValue, .OBJC_ASSOCIATION_RETAIN) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ServerTrust/NetworkSession-ServerTrust.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkSession-ServerTrust.swift 3 | // Avenue 4 | // 5 | // Copyright © 2017 Radiant Tap 6 | // MIT License · http://choosealicense.com/licenses/mit/ 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: Authentication callbacks 12 | 13 | extension NetworkSession { 14 | func handleURLSession(_ session: URLSession, 15 | task: URLSessionDataTask?, 16 | didReceive challenge: URLAuthenticationChallenge, 17 | completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) 18 | { 19 | if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { 20 | guard let trust = challenge.protectionSpace.serverTrust else { 21 | completionHandler(.performDefaultHandling, nil) 22 | return 23 | } 24 | let host = challenge.protectionSpace.host 25 | 26 | guard session.serverTrustPolicy.evaluate(trust, forHost: host) else { 27 | if let dataTask = task { 28 | let authError = NetworkError.urlError( URLError(.userCancelledAuthentication) ) 29 | dataTask.errorCallback(authError) 30 | } 31 | completionHandler(.rejectProtectionSpace, nil) 32 | return 33 | } 34 | 35 | let credential = URLCredential(trust: trust) 36 | completionHandler(.useCredential, credential) 37 | return 38 | } 39 | 40 | completionHandler(.performDefaultHandling, nil) 41 | } 42 | } 43 | 44 | extension NetworkSession: URLSessionDelegate { 45 | 46 | public final func urlSession(_ session: URLSession, 47 | didReceive challenge: URLAuthenticationChallenge, 48 | completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) 49 | { 50 | handleURLSession(session, task: nil, didReceive: challenge, completionHandler: completionHandler) 51 | } 52 | 53 | } 54 | 55 | extension NetworkSession: URLSessionTaskDelegate { 56 | public final func urlSession(_ session: URLSession, 57 | task: URLSessionTask, 58 | didReceive challenge: URLAuthenticationChallenge, 59 | completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) 60 | { 61 | handleURLSession(session, task: task as? URLSessionDataTask, didReceive: challenge, completionHandler: completionHandler) 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Avenue/NetworkPayload.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkPayload.swift 3 | // Avenue 4 | // 5 | // Copyright © 2017 Radiant Tap 6 | // MIT License · http://choosealicense.com/licenses/mit/ 7 | // 8 | 9 | import Foundation 10 | 11 | /// A simple struct to use as network "result". 12 | /// As `NetworkOperation` is processing, its varuous propertues will be populated along the way. 13 | public struct NetworkPayload { 14 | /// The original value of URLRequest at the start of the operation 15 | public let originalRequest: URLRequest 16 | 17 | /// At the start, this is identical to `originalRequest`. 18 | /// But, you may need to alter the original as the network processing is ongoing. 19 | /// i.e. you can pass the original request through OAuth library and thus update it. 20 | public var urlRequest: URLRequest 21 | 22 | public init(urlRequest: URLRequest) { 23 | self.originalRequest = urlRequest 24 | self.urlRequest = urlRequest 25 | } 26 | 27 | 28 | // MARK: Result properties 29 | 30 | /// Any error that URLSession may populate (timeouts, no connection etc) 31 | public var error: NetworkError? 32 | 33 | /// Received HTTP response. Use it to process status code and headers 34 | public var response: HTTPURLResponse? 35 | 36 | /// Received stream of bytes 37 | public var data: Data? 38 | 39 | 40 | // MARK: Timestamps 41 | 42 | /// Moment when the payload was prepared. May not be the same as `tsStart` 43 | public let tsCreated = Date() 44 | 45 | /// Moment when network task is started (you called `task.resume()` for the first time). 46 | /// Call `.start()` to set it. 47 | /// 48 | /// If this value is `nil`, related network request was never even attemted. 49 | public private(set) var tsStart: Date? 50 | 51 | /// Moment when network task has ended. Used together with `tsStart` makes for simple speed metering. 52 | /// Call `.end()` to set it. 53 | /// 54 | /// Note that if this value is `nil`, it means that request was mostly likely cancelled by end-customer (the Operation itself was cancelled before URLResponse arrived). 55 | public private(set) var tsEnd: Date? 56 | } 57 | 58 | extension NetworkPayload { 59 | /// Call this along with `task.resume()` 60 | mutating func start() { 61 | self.tsStart = Date() 62 | } 63 | 64 | /// Called when URLSessionDataTask ends. 65 | mutating func end() { 66 | self.tsEnd = Date() 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /Alley/NetworkError-Localized.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkError-Localized.swift 3 | // Alley 4 | // 5 | // Copyright © 2019 Radiant Tap 6 | // MIT License · http://choosealicense.com/licenses/mit/ 7 | // 8 | 9 | import Foundation 10 | 11 | extension NetworkError: LocalizedError { 12 | public var errorDescription: String? { 13 | switch self { 14 | case .generalError(let error): 15 | return error.localizedDescription 16 | 17 | case .urlError(let urlError): 18 | return urlError.localizedDescription 19 | 20 | case .invalidResponseType, .noResponse: 21 | return NSLocalizedString("Internal error", comment: "") 22 | 23 | case .noResponseData: 24 | return nil 25 | 26 | case .endpointError(let httpURLResponse, _): 27 | let s = "\( httpURLResponse.statusCode ) \( HTTPURLResponse.localizedString(forStatusCode: httpURLResponse.statusCode) )" 28 | return s 29 | 30 | case .inaccessible: 31 | return NSLocalizedString("Service is not accessible", comment: "") 32 | } 33 | } 34 | 35 | public var failureReason: String? { 36 | switch self { 37 | case .generalError(let error): 38 | return (error as NSError).localizedFailureReason 39 | 40 | case .urlError(let urlError): 41 | return (urlError as NSError).localizedFailureReason 42 | 43 | case .noResponse: 44 | return NSLocalizedString("Request apparently succeeded (no errors) but URLResponse was not received.", comment: "") 45 | 46 | case .invalidResponseType(let response): 47 | return String(format: NSLocalizedString("Response is not HTTP response.\n\n%@", comment: ""), response) 48 | 49 | case .inaccessible: 50 | return nil 51 | 52 | case .noResponseData: 53 | return NSLocalizedString("Request succeeded, no response body received", comment: "") 54 | 55 | case .endpointError(let httpURLResponse, let data): 56 | let s = "\( httpURLResponse.formattedHeaders )\n\n\( data?.utf8StringRepresentation ?? "" )" 57 | return s 58 | } 59 | } 60 | } 61 | 62 | private extension HTTPURLResponse { 63 | var formattedHeaders: String { 64 | return allHeaderFields.map { "\( $0.key ) : \( $0.value )" }.joined(separator: "\n") 65 | } 66 | } 67 | 68 | private extension Data { 69 | var utf8StringRepresentation: String? { 70 | guard 71 | let str = String(data: self, encoding: .utf8) 72 | else { return nil } 73 | 74 | return str 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Essentials/Atomic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Atomic.swift 3 | // Radiant Tap Essentials 4 | // https://github.com/radianttap/swift-essentials 5 | // 6 | // Copyright © 2018 Radiant Tap 7 | // MIT License · http://choosealicense.com/licenses/mit/ 8 | // 9 | /// Credits: 10 | /// https://gist.github.com/ole/5034ce19c62d248018581b1db0eabb2b#file-atomic-swift 11 | /// https://talk.objc.io/episodes/S01E42-thread-safety-reactive-programming-5 12 | 13 | import Dispatch 14 | 15 | /// A wrapper for atomic read/write access to a value. 16 | /// The value is protected by a serial `DispatchQueue`. 17 | final class Atomic { 18 | private var _value: A 19 | private let queue: DispatchQueue 20 | 21 | /// Creates an instance of `Atomic` with the specified value. 22 | /// 23 | /// - Paramater value: The object's initial value. 24 | /// - Parameter targetQueue: The target dispatch queue for the "lock queue". 25 | /// Use this to place the atomic value into an existing queue hierarchy 26 | /// (e.g. for the subsystem that uses this object). 27 | /// See Apple's WWDC 2017 session 706, Modernizing Grand Central Dispatch 28 | /// Usage (https://developer.apple.com/videos/play/wwdc2017/706/), for 29 | /// more information on how to use target queues effectively. 30 | /// 31 | /// The default value is `nil`, which means no target queue will be set. 32 | init(_ value: A, targetQueue: DispatchQueue? = nil) { 33 | _value = value 34 | queue = DispatchQueue(label: "com.olebegemann.Atomic", target: targetQueue) 35 | } 36 | 37 | /// Read access to the wrapped value. 38 | var atomic: A { 39 | return queue.sync { _value } 40 | } 41 | 42 | /// Mutations of `value` must be performed via this method. 43 | /// 44 | /// If `Atomic` exposed a setter for `value`, constructs that used the getter 45 | /// and setter inside the same statement would not be atomic. 46 | /// 47 | /// Examples that would not actually be atomic: 48 | /// 49 | /// let atomicInt = Atomic(42) 50 | /// // Calls getter and setter, but value may have been mutated in between 51 | /// atomicInt.value += 1 52 | /// 53 | /// let atomicArray = Atomic([1,2,3]) 54 | /// // Mutating the array through a subscript causes both a get and a set, 55 | /// // acquiring and releasing the lock twice. 56 | /// atomicArray[1] = 42 57 | /// 58 | /// See also: https://github.com/ReactiveCocoa/ReactiveSwift/issues/269 59 | public func mutate(_ transform: (inout A) -> Void) { 60 | queue.sync { 61 | transform(&_value) 62 | } 63 | } 64 | } 65 | 66 | extension Atomic: Equatable where A: Equatable { 67 | static func ==(lhs: Atomic, rhs: Atomic) -> Bool { 68 | return lhs.atomic == rhs.atomic 69 | } 70 | } 71 | 72 | extension Atomic: Hashable where A: Hashable { 73 | func hash(into hasher: inout Hasher) { 74 | hasher.combine(atomic) 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /Demo/DemoWatch Extension/ExtensionDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionDelegate.swift 3 | // DemoWatch Extension 4 | // 5 | // Created by Aleksandar Vacić on 27.9.17.. 6 | // Copyright © 2017. Radiant Tap. All rights reserved. 7 | // 8 | 9 | import WatchKit 10 | 11 | class ExtensionDelegate: NSObject, WKExtensionDelegate { 12 | 13 | func applicationDidFinishLaunching() { 14 | // Perform any final initialization of your application. 15 | } 16 | 17 | func applicationDidBecomeActive() { 18 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 19 | ping() 20 | } 21 | 22 | func applicationWillResignActive() { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, etc. 25 | } 26 | 27 | func handle(_ backgroundTasks: Set) { 28 | // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one. 29 | for task in backgroundTasks { 30 | // Use a switch statement to check the task type 31 | switch task { 32 | case let backgroundTask as WKApplicationRefreshBackgroundTask: 33 | // Be sure to complete the background task once you’re done. 34 | backgroundTask.setTaskCompletedWithSnapshot(false) 35 | case let snapshotTask as WKSnapshotRefreshBackgroundTask: 36 | // Snapshot tasks have a unique completion call, make sure to set your expiration date 37 | snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil) 38 | case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask: 39 | // Be sure to complete the connectivity task once you’re done. 40 | connectivityTask.setTaskCompletedWithSnapshot(false) 41 | case let urlSessionTask as WKURLSessionRefreshBackgroundTask: 42 | // Be sure to complete the URL session task once you’re done. 43 | urlSessionTask.setTaskCompletedWithSnapshot(false) 44 | default: 45 | // make sure to complete unhandled task types 46 | task.setTaskCompletedWithSnapshot(false) 47 | } 48 | } 49 | } 50 | 51 | func ping() { 52 | IvkoService.shared.call(path: .promotions) { 53 | _, _ in 54 | // process data 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Avenue.xcodeproj/xcshareddata/xcschemes/Avenue.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Demo/ExampleWrappers/AssetManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssetManager.swift 3 | // CoordinatorExample 4 | // 5 | // Created by Aleksandar Vacić on 20.8.17.. 6 | // Copyright © 2017. Radiant Tap. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Avenue 11 | 12 | final class AssetManager: NetworkSession { 13 | static let shared = AssetManager() 14 | 15 | private init() { 16 | queue = { 17 | let oq = OperationQueue() 18 | oq.qualityOfService = .userInitiated 19 | return oq 20 | }() 21 | 22 | let urlSessionConfiguration: URLSessionConfiguration = { 23 | let c = URLSessionConfiguration.default 24 | c.allowsCellularAccess = true 25 | c.httpCookieAcceptPolicy = .never 26 | c.httpShouldSetCookies = false 27 | c.requestCachePolicy = .reloadIgnoringLocalCacheData 28 | return c 29 | }() 30 | super.init(urlSessionConfiguration: urlSessionConfiguration) 31 | } 32 | 33 | // Local stuff 34 | 35 | private var queue: OperationQueue 36 | } 37 | 38 | extension AssetManager { 39 | func cleanurl() -> URL? { 40 | return baseURL 41 | } 42 | 43 | func url(forProductPath path: String) -> URL? { 44 | return baseURL.appendingPathComponent("products", isDirectory: true).appendingPathComponent(path) 45 | } 46 | 47 | func url(forPromoPath path: String) -> URL? { 48 | return baseURL.appendingPathComponent("slides", isDirectory: true).appendingPathComponent(path) 49 | } 50 | } 51 | 52 | extension AssetManager { 53 | typealias ServiceCallback = ( JSON?, Error? ) -> Void 54 | 55 | func call(url: URL, callback: @escaping ServiceCallback) { 56 | let urlRequest = URLRequest(url: url) 57 | execute(urlRequest, callback: callback) 58 | } 59 | } 60 | 61 | 62 | 63 | fileprivate extension AssetManager { 64 | // MARK:- Common params and types 65 | 66 | var baseURL : URL { 67 | guard let url = URL(string: "https://self-signed.badssl.com") else { fatalError("Can't create base URL!") } 68 | return url 69 | } 70 | 71 | static let commonHeaders: [String: String] = { 72 | return [ 73 | "User-Agent": userAgent, 74 | "Accept-Charset": "utf-8", 75 | "Accept-Encoding": "gzip, deflate" 76 | ] 77 | }() 78 | 79 | static var userAgent: String = { 80 | #if os(watchOS) 81 | let osName = "watchOS" 82 | let osVersion = "" 83 | let deviceVersion = "Apple Watch" 84 | #else 85 | let osName = UIDevice.current.systemName 86 | let osVersion = UIDevice.current.systemVersion 87 | let deviceVersion = UIDevice.current.model 88 | #endif 89 | 90 | let locale = Locale.current.identifier 91 | return "\( Bundle.appName ) \( Bundle.appVersion ) (\( Bundle.appBuild )); \( deviceVersion ); \( osName ) \( osVersion ); \( locale )" 92 | }() 93 | 94 | // MARK:- Execution 95 | 96 | func execute(_ urlRequest: URLRequest, callback: @escaping ServiceCallback) { 97 | let op = NetworkOperation(urlRequest: urlRequest, urlSession: urlSession) { 98 | // [unowned self] 99 | payload in 100 | 101 | if let tsStart = payload.tsStart, let tsEnd = payload.tsEnd { 102 | let period = tsEnd.timeIntervalSince(tsStart) * 1000 103 | print("\t⏱: \( period ) ms") 104 | } 105 | 106 | print(payload) 107 | } 108 | 109 | queue.addOperation(op) 110 | } 111 | } 112 | 113 | -------------------------------------------------------------------------------- /Essentials/AsyncOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOperation.swift 3 | // Radiant Tap Essentials 4 | // https://github.com/radianttap/swift-essentials 5 | // 6 | // Copyright © 2017 Radiant Tap 7 | // MIT License · http://choosealicense.com/licenses/mit/ 8 | // 9 | 10 | import Foundation 11 | 12 | public class AsyncOperation : Operation { 13 | private enum State { 14 | case ready 15 | case executing 16 | case finished 17 | 18 | fileprivate var key: String { 19 | switch self { 20 | case .ready: 21 | return "isReady" 22 | case .executing: 23 | return "isExecuting" 24 | case .finished: 25 | return "isFinished" 26 | } 27 | } 28 | } 29 | 30 | private let queue = DispatchQueue(label: "com.radianttap.Essentials.AsyncOperation") 31 | 32 | /// Private backing store for `state` 33 | private var _state: Atomic = Atomic(.ready) 34 | 35 | /// The state of the operation 36 | private var state: State { 37 | get { 38 | return _state.atomic 39 | } 40 | set { 41 | // A state mutation should be a single atomic transaction. We can't simply perform 42 | // everything on the isolation queue for `_state` because the KVO willChange/didChange 43 | // notifications have to be sent from outside the isolation queue. Otherwise we would 44 | // deadlock because KVO observers will in turn try to read `state` (by calling 45 | // `isReady`, `isExecuting`, `isFinished`. Use a second queue to wrap the entire 46 | // transaction. 47 | queue.sync { 48 | // Retrieve the existing value first. Necessary for sending fine-grained KVO 49 | // willChange/didChange notifications only for the key paths that actually change. 50 | let oldValue = _state.atomic 51 | guard newValue != oldValue else { 52 | return 53 | } 54 | willChangeValue(forKey: oldValue.key) 55 | willChangeValue(forKey: newValue.key) 56 | _state.mutate { 57 | $0 = newValue 58 | } 59 | didChangeValue(forKey: oldValue.key) 60 | didChangeValue(forKey: newValue.key) 61 | } 62 | } 63 | } 64 | 65 | final override public var isAsynchronous: Bool { 66 | return true 67 | } 68 | 69 | final override public var isExecuting: Bool { 70 | return state == .executing 71 | } 72 | 73 | final override public var isFinished: Bool { 74 | return state == .finished 75 | } 76 | 77 | final override public var isReady: Bool { 78 | return state == .ready 79 | } 80 | 81 | 82 | //MARK: Setup 83 | 84 | /// Do not override this method, ever. Call it from `workItem()` instead 85 | final public func markFinished() { 86 | state = .finished 87 | } 88 | 89 | /// You **should** override this method and start and/or do your async work here. 90 | /// **Must** call `markFinished()` inside your override 91 | /// when async work is done since operation needs to be mark `finished`. 92 | open func workItem() { 93 | markFinished() 94 | } 95 | 96 | required override public init() { 97 | } 98 | 99 | //MARK: Control 100 | 101 | final override public func start() { 102 | if isCancelled { 103 | state = .finished 104 | return 105 | } 106 | 107 | main() 108 | } 109 | 110 | final override public func main() { 111 | if isCancelled { 112 | state = .finished 113 | return 114 | } 115 | 116 | state = .executing 117 | workItem() 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Demo/Demo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Avenue/NetworkSession.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkSession.swift 3 | // Avenue 4 | // 5 | // Copyright © 2017 Radiant Tap 6 | // MIT License · http://choosealicense.com/licenses/mit/ 7 | // 8 | // Relevant documentation: 9 | // (1) ATS (Advanced Transport Security): 10 | // https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW33 11 | // (2) TN2232: HTTPS Server Trust Evaluation 12 | // https://developer.apple.com/library/content/technotes/tn2232/ 13 | // 14 | // Helpful articles: 15 | // https://www.nowsecure.com/blog/2017/08/31/security-analysts-guide-nsapptransportsecurity-nsallowsarbitraryloads-app-transport-security-ats-exceptions/ 16 | // https://github.com/Alamofire/Alamofire#app-transport-security 17 | // https://github.com/Alamofire/Alamofire/issues/876 18 | // https://infinum.co/the-capsized-eight/how-to-make-your-ios-apps-more-secure-with-ssl-pinning 19 | // 20 | // Tools: 21 | // https://badssl.com 22 | // nscurl --help (in your macOS Terminal) 23 | 24 | 25 | 26 | import Foundation 27 | 28 | /// Base class that handles URLSession-level stuff. Subclass it to build your API / web-endpoint wrapper. 29 | /// 30 | /// This is very shallow class; its purpose is to handle Authentication challenges, but due to 31 | /// general URLSession/DataTask architecture, it also must handle the task-level URLSessionDelegate methods. 32 | /// 33 | /// This is accomplished by forcefully expanding URLSessionDataTask, see NetworkTask.swift 34 | /// 35 | /// Auth challenges like ServerTrust will be automatically handled, using URLSession.serverTrustPolicy value (defined elsewhere). 36 | /// `userCancelledAuthentication` error will be returned if evaluation fails. 37 | open class NetworkSession: NSObject { 38 | public private(set) var urlSessionConfiguration: URLSessionConfiguration 39 | public private(set) var urlSession: URLSession! 40 | 41 | private override init() { 42 | fatalError("Must use `init(urlSessionConfiguration:)") 43 | } 44 | 45 | public init(urlSessionConfiguration: URLSessionConfiguration = .default) { 46 | self.urlSessionConfiguration = urlSessionConfiguration 47 | super.init() 48 | 49 | urlSession = URLSession(configuration: urlSessionConfiguration, 50 | delegate: self, 51 | delegateQueue: nil) 52 | } 53 | 54 | deinit { 55 | // this cancels immediatelly 56 | // urlSession.invalidateAndCancel() 57 | 58 | // this will allow background tasks to finish-up first 59 | urlSession.finishTasksAndInvalidate() 60 | } 61 | } 62 | 63 | // MARK: Data callbacks 64 | 65 | extension NetworkSession: URLSessionDataDelegate { 66 | // this checks the response headers 67 | public final func urlSession(_ session: URLSession, 68 | dataTask: URLSessionDataTask, 69 | didReceive response: URLResponse, 70 | completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) 71 | { 72 | guard let httpURLResponse = response as? HTTPURLResponse else { 73 | completionHandler(.cancel) 74 | dataTask.errorCallback(.invalidResponseType(response)) 75 | return 76 | } 77 | 78 | // for now, just save the response headers 79 | dataTask.responseCallback(httpURLResponse) 80 | // checking statusCode will be done in task.finishCallback, 81 | // in order to first receive possible error message in the body 82 | 83 | // always allow data to arrive in order to 84 | // extract possible API error messages 85 | completionHandler(.allow) 86 | } 87 | 88 | // this will be called multiple times while the data is coming in 89 | public final func urlSession(_ session: URLSession, 90 | dataTask: URLSessionDataTask, 91 | didReceive data: Data) 92 | { 93 | dataTask.dataCallback(data) 94 | } 95 | 96 | // this is called once, either on URLError or when entire response arrives 97 | public final func urlSession(_ session: URLSession, 98 | task: URLSessionTask, 99 | didCompleteWithError error: Swift.Error?) 100 | { 101 | guard let dataTask = task as? URLSessionDataTask else { return } 102 | 103 | if let urlError = error as? URLError { 104 | dataTask.errorCallback( .urlError(urlError) ) 105 | return 106 | 107 | } else if let otherError = error { 108 | dataTask.errorCallback( .generalError(otherError) ) 109 | return 110 | } 111 | 112 | dataTask.finishCallback() 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /Alley/URLSession-extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLSession-extensions.swift 3 | // Alley 4 | // 5 | // Copyright © 2019 Radiant Tap 6 | // MIT License · http://choosealicense.com/licenses/mit/ 7 | // 8 | 9 | import Foundation 10 | 11 | public extension URLSession { 12 | /// Output types 13 | typealias DataResult = Result 14 | typealias Callback = (DataResult) -> Void 15 | 16 | /// Executes given URLRequest instance, possibly retrying the said number of times. Through `callback` returns either `Data` from the response or `NetworkError` instance. 17 | /// If any authentication needs to be done, it's handled internally by this methods and its derivatives. 18 | /// - Parameters: 19 | /// - urlRequest: URLRequest instance to execute. 20 | /// - maxRetries: Number of automatic retries (default is 10). 21 | /// - allowEmptyData: Should empty response `Data` be treated as failure (this is default) even if no other errors are returned by URLSession. Default is `false`. 22 | /// - callback: Closure to return the result of the request's execution. 23 | func perform(_ urlRequest: URLRequest, 24 | maxRetries: Int = 10, 25 | allowEmptyData: Bool = false, 26 | callback: @escaping Callback) 27 | { 28 | if maxRetries <= 0 { 29 | fatalError("maxRetries must be 1 or larger.") 30 | } 31 | 32 | let networkRequest = NetworkRequest(urlRequest, 0, maxRetries, allowEmptyData, callback) 33 | authenticate(networkRequest) 34 | } 35 | } 36 | 37 | private extension URLSession { 38 | /// Helper type which groups `URLRequest` (input), `Callback` from the caller (output) 39 | /// along with helpful processing properties, like number of retries. 40 | typealias NetworkRequest = ( 41 | urlRequest: URLRequest, 42 | currentRetries: Int, 43 | maxRetries: Int, 44 | allowEmptyData: Bool, 45 | callback: Callback 46 | ) 47 | 48 | /// Extra-step where `URLRequest`'s authorization should be handled, before actually performing the URLRequest in `execute()` 49 | func authenticate(_ networkRequest: NetworkRequest) { 50 | let currentRetries = networkRequest.currentRetries 51 | let max = networkRequest.maxRetries 52 | let callback = networkRequest.callback 53 | 54 | if currentRetries >= max { 55 | // Too many unsuccessful attemps 56 | callback( .failure( .inaccessible ) ) 57 | } 58 | 59 | // NOTE: this is the place to handle OAuth2 60 | // or some other form of URLRequest‘s authorization 61 | 62 | // now execute the request 63 | execute(networkRequest) 64 | } 65 | 66 | /// Creates the instance of `URLSessionDataTask`, performs it then lightly processes the response before calling `validate`. 67 | func execute(_ networkRequest: NetworkRequest) { 68 | let urlRequest = networkRequest.urlRequest 69 | 70 | let task = dataTask(with: urlRequest) { 71 | [unowned self] data, urlResponse, error in 72 | 73 | let dataResult = self.process(data, urlResponse, error, for: networkRequest) 74 | self.validate(dataResult, for: networkRequest) 75 | } 76 | 77 | task.resume() 78 | } 79 | 80 | /// Process results of `URLSessionDataTask` and converts it into `DataResult` instance 81 | func process(_ data: Data?, _ urlResponse: URLResponse?, _ error: Error?, for networkRequest: NetworkRequest) -> DataResult { 82 | let allowEmptyData = networkRequest.allowEmptyData 83 | 84 | if let urlError = error as? URLError { 85 | return .failure( NetworkError.urlError(urlError) ) 86 | 87 | } else if let otherError = error { 88 | return .failure( NetworkError.generalError(otherError) ) 89 | } 90 | 91 | guard let httpURLResponse = urlResponse as? HTTPURLResponse else { 92 | if let urlResponse = urlResponse { 93 | return .failure( NetworkError.invalidResponseType(urlResponse) ) 94 | } else { 95 | return .failure( NetworkError.noResponse ) 96 | } 97 | } 98 | 99 | if httpURLResponse.statusCode >= 400 { 100 | return .failure( NetworkError.endpointError(httpURLResponse, data) ) 101 | } 102 | 103 | guard let data = data, !data.isEmpty else { 104 | if allowEmptyData { 105 | return .success(Data()) 106 | } 107 | 108 | return .failure( NetworkError.noResponseData(httpURLResponse) ) 109 | } 110 | 111 | return .success(data) 112 | } 113 | 114 | /// Checks the result of URLSessionDataTask and if there were errors, should the URLRequest be retried. 115 | func validate(_ result: DataResult, for networkRequest: NetworkRequest) { 116 | let callback = networkRequest.callback 117 | 118 | switch result { 119 | case .success: 120 | break 121 | 122 | case .failure(let networkError): 123 | switch networkError { 124 | case .inaccessible: 125 | // too many failed network calls 126 | break 127 | 128 | default: 129 | if networkError.shouldRetry { 130 | // update retries count and 131 | var newRequest = networkRequest 132 | newRequest.currentRetries += 1 133 | // try again, going through authentication again 134 | // (since it's quite possible that Auth token or whatever has expired) 135 | self.authenticate(newRequest) 136 | return 137 | } 138 | } 139 | } 140 | 141 | callback(result) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Avenue/NetworkOperation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkOperation.swift 3 | // Avenue 4 | // 5 | // Copyright © 2017 Radiant Tap 6 | // MIT License · http://choosealicense.com/licenses/mit/ 7 | // 8 | 9 | import Foundation 10 | 11 | /// Subclass of [AsyncOperation](https://github.com/radianttap/Swift-Essentials/blob/master/Operation/AsyncOperation.swift) 12 | /// that handles most aspects of direct data download over network. 13 | /// 14 | /// You need 3 things for the init: `URLSession` instance, `URLRequest` as input and `Callback` as output. 15 | public class NetworkOperation: AsyncOperation { 16 | public typealias Callback = (NetworkPayload) -> Void 17 | 18 | required init() { 19 | fatalError("Use the `init(urlRequest:urlSession:callback:)`") 20 | } 21 | 22 | 23 | /// Designated initializer, it will execute the URLRequest using supplied URLSession instance. 24 | /// 25 | /// It‘s assumed that URLSessionDelegate is defined elsewhere (see `NetworkSession`) and stuff will be called-in here (see `setupCallbacks()`). 26 | /// 27 | /// - Parameters: 28 | /// - urlRequest: `URLRequest` value to execute. 29 | /// - urlSession: `URLSession` instance to use for execution of task created inside this Operation. 30 | /// - maxRetries: Number of automatic retries (default is set during `NetworkSession.init`). 31 | /// - allowEmptyData: Should empty response `Data` be treated as failure (this is default) even if no other errors are returned by URLSession. Default is `false`. 32 | /// - callback: A closure to pass the result back 33 | public init(urlRequest: URLRequest, 34 | urlSession: URLSession, 35 | maxRetries: Int = 10, 36 | allowEmptyData: Bool = false, 37 | callback: @escaping (NetworkPayload) -> Void) 38 | { 39 | if maxRetries <= 0 { 40 | fatalError("maxRetries must be 1 or larger.") 41 | } 42 | 43 | self.payload = NetworkPayload(urlRequest: urlRequest) 44 | self.maxRetries = maxRetries 45 | self.allowEmptyData = allowEmptyData 46 | self.callback = callback 47 | self.urlSession = urlSession 48 | 49 | super.init() 50 | } 51 | 52 | //MARK:- Properties 53 | 54 | private(set) var payload: NetworkPayload 55 | 56 | private(set) var callback: Callback 57 | 58 | /// Maximum number of retries 59 | private var maxRetries: Int 60 | private var currentRetries: Int = 0 61 | 62 | /// If `false`, HTTPURLResponse must have some content in its body (if not, it will be treated as error) 63 | private var allowEmptyData: Bool 64 | 65 | /// URLSession that will be used for this particular request. 66 | /// If you don't supply it in the `init`, it will be created locally for this one request 67 | private var urlSession: URLSession 68 | 69 | /// Actual network task, executed by `urlSession` 70 | private(set) var task: URLSessionDataTask? 71 | 72 | /// This collects incoming data chunks 73 | private var incomingData = Data() 74 | 75 | 76 | 77 | 78 | 79 | // MARK: AsyncOperation 80 | 81 | /// Set network start timestamp, creates URLSessionDataTask and starts it (resume) 82 | public final override func workItem() { 83 | // First create the task 84 | task = urlSession.dataTask(with: payload.urlRequest) 85 | 86 | // then setup handlers for URLSessionDelegate calls 87 | setupCallbacks() 88 | 89 | // save the timestamp 90 | payload.start() 91 | 92 | // and execute 93 | performTask() 94 | } 95 | 96 | private func finish() { 97 | payload.end() 98 | 99 | markFinished() 100 | 101 | callback(payload) 102 | } 103 | 104 | public final override func cancel() { 105 | super.cancel() 106 | 107 | task?.cancel() 108 | 109 | // since the Operation is cancelled, clear out any results that we may have received so far 110 | payload.data = nil 111 | payload.error = nil 112 | payload.response = nil 113 | 114 | // not calling `finish()`, to avoid setting `payload.tsEnd` since it never actually finished 115 | // but must mark Operation as complete so OperationQueue can continue with next one 116 | markFinished() 117 | // report back with the payload as it is 118 | callback(payload) 119 | } 120 | } 121 | 122 | // MARK:- Internal 123 | 124 | private extension NetworkOperation { 125 | func performTask() { 126 | if currentRetries >= maxRetries { 127 | // Too many unsuccessful attemps 128 | payload.error = .inaccessible 129 | finish() 130 | 131 | return 132 | } 133 | 134 | task?.resume() 135 | } 136 | 137 | /// Makes URLSession [cooperate nicely](https://aplus.rs/2017/urlsession-in-operation/) with Operation(Queue) 138 | func setupCallbacks() { 139 | guard let task = task else { return } 140 | 141 | task.errorCallback = { 142 | [weak self] networkError in 143 | guard let self = self else { return } 144 | 145 | switch networkError { 146 | case .inaccessible: 147 | // too many failed network calls 148 | break 149 | 150 | default: 151 | if networkError.shouldRetry, self.maxRetries > 1 { 152 | // update retries count and 153 | self.currentRetries += 1 154 | // try again 155 | self.performTask() 156 | return 157 | } 158 | } 159 | 160 | self.payload.error = networkError 161 | self.finish() 162 | } 163 | 164 | task.responseCallback = { 165 | [weak self] httpResponse in 166 | self?.payload.response = httpResponse 167 | } 168 | 169 | task.dataCallback = { 170 | [weak self] data in 171 | self?.incomingData.append(data) 172 | } 173 | 174 | task.finishCallback = { 175 | [weak self] in 176 | guard let self = self else { return } 177 | guard let httpURLResponse = self.payload.response else { return } 178 | 179 | if httpURLResponse.statusCode >= 400 { 180 | task.errorCallback( NetworkError.endpointError(httpURLResponse, self.incomingData) ) 181 | return 182 | } 183 | 184 | if !self.allowEmptyData, self.incomingData.isEmpty { 185 | task.errorCallback( NetworkError.noResponseData(httpURLResponse) ) 186 | return 187 | } 188 | 189 | self.payload.data = self.incomingData 190 | self.finish() 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /Demo/ExampleWrappers/IvkoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IvkoService.swift 3 | // CoordinatorExample 4 | // 5 | // Created by Aleksandar Vacić on 20.8.17.. 6 | // Copyright © 2017. Radiant Tap. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #endif 12 | 13 | #if os(watchOS) 14 | import Foundation 15 | #endif 16 | 17 | import Avenue 18 | 19 | 20 | public typealias JSON = [String: Any] 21 | 22 | final class IvkoService: NetworkSession { 23 | static let shared = IvkoService() 24 | 25 | private override init(urlSessionConfiguration: URLSessionConfiguration) { 26 | fatalError("Not implemented, use `init()`") 27 | } 28 | 29 | private init() { 30 | queue = { 31 | let oq = OperationQueue() 32 | oq.qualityOfService = .userInitiated 33 | return oq 34 | }() 35 | 36 | let urlSessionConfiguration: URLSessionConfiguration = { 37 | let c = URLSessionConfiguration.default 38 | c.allowsCellularAccess = true 39 | c.httpCookieAcceptPolicy = .never 40 | c.httpShouldSetCookies = false 41 | c.httpAdditionalHeaders = IvkoService.commonHeaders 42 | c.requestCachePolicy = .reloadIgnoringLocalCacheData 43 | return c 44 | }() 45 | super.init(urlSessionConfiguration: urlSessionConfiguration) 46 | } 47 | 48 | // Local stuff 49 | 50 | fileprivate var queue: OperationQueue 51 | } 52 | 53 | 54 | 55 | 56 | 57 | extension IvkoService { 58 | // MARK:- Endpoint wrappers 59 | enum Path { 60 | case promotions 61 | case seasons(seasonCode: Int?) 62 | case products 63 | case details(styleCode: String) 64 | 65 | 66 | fileprivate var method: String { 67 | return "GET" 68 | } 69 | 70 | private var headers: [String: String] { 71 | var h: [String: String] = [:] 72 | 73 | switch self { 74 | default: 75 | h["Accept"] = "application/json" 76 | } 77 | 78 | return h 79 | } 80 | 81 | private var url: URL { 82 | var url = IvkoService.shared.baseURL 83 | 84 | switch self { 85 | case .promotions: 86 | url.appendPathComponent("slides.json") 87 | case .seasons(let seasonCode): 88 | url.appendPathComponent("seasons") 89 | if let seasonCode = seasonCode { 90 | url = url.appendingPathComponent("\( seasonCode )") 91 | } 92 | case .products: 93 | url.appendPathComponent("products.json") 94 | case .details: 95 | url.appendPathComponent("details") 96 | } 97 | 98 | return url 99 | } 100 | 101 | private var params: [String: Any] { 102 | var p: [String: Any] = [:] 103 | 104 | switch self { 105 | case .details(let styleCode): 106 | p["style"] = styleCode 107 | default: 108 | break 109 | } 110 | 111 | return p 112 | } 113 | 114 | private var queryItems: [URLQueryItem] { 115 | var arr: [URLQueryItem] = [] 116 | 117 | for (key, value) in params { 118 | let qi = URLQueryItem(name: key, value: "\( value )") 119 | arr.append( qi ) 120 | } 121 | 122 | return arr 123 | } 124 | 125 | private func jsonEncoded(params: JSON) -> Data? { 126 | return try? JSONSerialization.data(withJSONObject: params) 127 | } 128 | 129 | fileprivate var urlRequest: URLRequest { 130 | guard var comps = URLComponents(url: url, resolvingAgainstBaseURL: true) else { 131 | fatalError("Invalid path-based URL") 132 | } 133 | comps.queryItems = queryItems 134 | 135 | guard let finalURL = comps.url else { 136 | fatalError("Invalid query items...(probably)") 137 | } 138 | 139 | var req = URLRequest(url: finalURL) 140 | req.httpMethod = method 141 | req.allHTTPHeaderFields = headers 142 | 143 | switch method { 144 | case "POST": 145 | req.httpBody = jsonEncoded(params: params) 146 | break 147 | default: 148 | break 149 | } 150 | 151 | return req 152 | } 153 | } 154 | } 155 | 156 | 157 | extension IvkoService { 158 | typealias ServiceCallback = ( JSON?, IvkoServiceError? ) -> Void 159 | 160 | func call(path: Path, callback: @escaping ServiceCallback) { 161 | let urlRequest = path.urlRequest 162 | execute(urlRequest, path: path, callback: callback) 163 | } 164 | } 165 | 166 | 167 | 168 | fileprivate extension IvkoService { 169 | // MARK:- Common params and types 170 | 171 | var baseURL : URL { 172 | guard let url = URL(string: "https://t1.aplus.rs/coordinator/api") else { fatalError("Can't create base URL!") } 173 | return url 174 | } 175 | 176 | static let commonHeaders: [String: String] = { 177 | return [ 178 | "User-Agent": userAgent, 179 | "Accept-Charset": "utf-8", 180 | "Accept-Encoding": "gzip, deflate" 181 | ] 182 | }() 183 | 184 | static var userAgent: String = { 185 | #if os(watchOS) 186 | let osName = "watchOS" 187 | let osVersion = "" 188 | let deviceVersion = "Apple Watch" 189 | #else 190 | let osName = UIDevice.current.systemName 191 | let osVersion = UIDevice.current.systemVersion 192 | let deviceVersion = UIDevice.current.model 193 | #endif 194 | 195 | let locale = Locale.current.identifier 196 | return "\( Bundle.appName ) \( Bundle.appVersion ) (\( Bundle.appBuild )); \( deviceVersion ); \( osName ) \( osVersion ); \( locale )" 197 | }() 198 | 199 | // MARK:- Execution 200 | 201 | func execute(_ urlRequest: URLRequest, path: Path, callback: @escaping ServiceCallback) { 202 | let op = NetworkOperation(urlRequest: urlRequest, urlSession: urlSession) { 203 | payload in 204 | 205 | if let tsStart = payload.tsStart, let tsEnd = payload.tsEnd { 206 | let period = tsEnd.timeIntervalSince(tsStart) * 1000 207 | print("\tURL: \( urlRequest.url?.absoluteString ?? "" )\n\t⏱: \( period ) ms") 208 | } 209 | 210 | // process the returned stuff, now 211 | if let error = payload.error { 212 | callback(nil, IvkoServiceError.network(error) ) 213 | return 214 | } 215 | 216 | guard 217 | let httpURLResponse = payload.response, 218 | let data = payload.data 219 | else { 220 | return 221 | } 222 | 223 | guard 224 | let obj = try? JSONSerialization.jsonObject(with: data, options: [.allowFragments]) 225 | else { 226 | // convert to string, so it logged what‘s actually returned 227 | let str = String(data: data, encoding: .utf8) 228 | callback(nil, IvkoServiceError.unexpectedResponse(httpURLResponse, str)) 229 | return 230 | } 231 | 232 | switch path { 233 | case .promotions: 234 | guard let jsons = obj as? [JSON] else { 235 | callback(nil, IvkoServiceError.unexpectedResponse(httpURLResponse, nil)) 236 | return 237 | } 238 | callback(["promotions": jsons], nil) 239 | 240 | default: 241 | guard let json = obj as? JSON else { 242 | callback(nil, IvkoServiceError.unexpectedResponse(httpURLResponse, nil)) 243 | return 244 | } 245 | callback(json, nil) 246 | } 247 | } 248 | 249 | queue.addOperation(op) 250 | } 251 | } 252 | 253 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/github/tag/radianttap/Avenue.svg?label=current)](https://github.com/radianttap/Avenue/releases) 2 | ![platforms: iOS|tvOS|watchOS](https://img.shields.io/badge/platform-iOS|tvOS|watchOS-blue.svg) 3 | [![](https://img.shields.io/github/license/radianttap/Avenue.svg)](https://github.com/radianttap/Avenue/blob/master/LICENSE) 4 |
5 | ![](https://img.shields.io/badge/swift-5-223344.svg?logo=swift&labelColor=FA7343&logoColor=white) 6 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-AD4709.svg?style=flat)](https://github.com/Carthage/Carthage) 7 | [![CocoaPods compatible](https://img.shields.io/badge/CocoaPods-compatible-fb0006.svg)](https://cocoapods.org) 8 | 9 | # Avenue 10 | 11 | Micro-library designed to allow seamless cooperation between URLSession(Data)Task and Operation(Queue) APIs. 12 | 13 | ## Why? 14 | 15 | URLSession framework is, on its own, [incompatible](https://aplus.rs/2017/thoughts-on-urlsession/) with Operation API. A bit of [trickery is required](https://aplus.rs/2017/urlsession-in-operation/) to make them cooperate. 16 | (note: do read those blog posts) 17 | 18 | I have [extended URLSessionTask](https://github.com/radianttap/Avenue/blob/master/Avenue/URLSessionTask-Extensions.swift) with additional properties of specific closure types which allows you to overcome this incompatibility. 19 | 20 | `OperationQueue` and `Operation` are great API to use when... 21 | 22 | * your network requests are inter-dependent on each other 23 | * need to implement OAuth2 or any other kind of asynchronous 3rd-party authentication mechanism 24 | * tight control over the number of concurrent requests towards a particular host is paramount 25 | * etc. 26 | 27 | > If this is too complex for your needs, take a look at [Alley](https://github.com/radianttap/Alley) — it’s much simpler but surprisingly capable. 28 | 29 | ## Installation 30 | 31 | ### Manually 32 | 33 | - If you are not using [Swift Essentials](https://github.com/radianttap/Swift-Essentials) already, make sure to include `Essentials` folder from here into your project 34 | - Also add `Avenue` and `Alley`, just copy them into your project. 35 | - To handle self-signed SSL, pinned certificates and other similar security stuff - add `ServerTrust` as well. 36 | 37 | · · · 38 | 39 | If you prefer to use dependency managers, see below. 40 | Releases are tagged with [Semantic Versioning](https://semver.org) in mind. 41 | 42 | ### CocoaPods 43 | 44 | [CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate Avenue into your Xcode project using CocoaPods, specify it in your `Podfile`: 45 | 46 | ```ruby 47 | pod 'Avenue', :git => 'https://github.com/radianttap/Avenue.git' 48 | ``` 49 | 50 | ### Carthage 51 | 52 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application. 53 | 54 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 55 | 56 | ```bash 57 | $ brew update 58 | $ brew install carthage 59 | ``` 60 | 61 | To integrate Avenue into your Xcode project using Carthage, specify it in your `Cartfile`: 62 | 63 | ```ogdl 64 | github "radianttap/Avenue" 65 | ``` 66 | 67 | ## Usage 68 | 69 | (1) Subclass `NetworkSession` to create your API wrapper, configure `URLSession` for the given service endpoints and make an `OperationQueue` instance. 70 | 71 | ```swift 72 | final class WebService: NetworkSession { 73 | private init() { 74 | queue = { 75 | let oq = OperationQueue() 76 | oq.qualityOfService = .userInitiated 77 | return oq 78 | }() 79 | 80 | let urlSessionConfiguration: URLSessionConfiguration = { 81 | let c = URLSessionConfiguration.default 82 | c.allowsCellularAccess = true 83 | c.httpCookieAcceptPolicy = .never 84 | c.httpShouldSetCookies = false 85 | c.requestCachePolicy = .reloadIgnoringLocalCacheData 86 | return c 87 | }() 88 | 89 | super.init(urlSessionConfiguration: urlSessionConfiguration) 90 | } 91 | 92 | // Local stuff 93 | 94 | private var queue: OperationQueue 95 | } 96 | ``` 97 | 98 | (2) Model API endpoints in any way you want. See _IvkoService_ example in the Demo app for one possible way, using enum with associated values. 99 | 100 | The end result of that model would be `URLRequest` instance. 101 | 102 | (3) Create an instance of `NetworkOperation` and add it to the `queue` 103 | 104 | ```swift 105 | let op = NetworkOperation(urlRequest: urlRequest, urlSession: urlSession) { 106 | payload in 107 | // ...process NetworkPayload... 108 | } 109 | queue.addOperation(op) 110 | ``` 111 | 112 | It will be automatically executed. You can also supply the desired number of automatic retries, among other arguments. 113 | 114 | > See `AssetManager` and `IvkoService` in the Demo project, as examples. Write as many of these as you need. 115 | 116 | ### Tips 117 | 118 | * Avenue handles just the URLSession boilerplate: URLErrors, HTTP Auth challenges, Server Trust Policy etc. 119 | 120 | * The only assumption Avenue makes is that web service you connect to is HTTP(S) based. 121 | 122 | * `NetworkPayload` is particularly useful struct since it aggregates `URLRequest` + response headers, data and error _and_ gives you simple speed metering capability by recording start and end of each network call. 123 | 124 | * `ServerTrustPolicy` is directly picked up from [Alamofire v4](https://github.com/Alamofire/Alamofire/tree/4.8.1); it’s great as it is and there’s no need for me to reinvent the wheel. 125 | 126 | * Set `ServerTrustPolicy.defaultPolicy` in your project configuration file (or wherever is appropriate) to the value you need for each app target you have. For example, if you connect to some self-signed demo API host:\ 127 | `ServerTrustPolicy.defaultPolicy = .disableEvaluation` 128 | 129 | Note: `AsyncOperation` is my own [simple subclass](https://github.com/radianttap/Swift-Essentials/blob/master/Operation/AsyncOperation.swift) which makes sure that `Operation` is marked `finished` only when the network async callback returns. `Atomic.swift` is required by `AsyncOperation`. 130 | 131 | ## Compatibility 132 | 133 | Platform and Swift compatibility is listed at the top of this document. 134 | 135 | ## License 136 | 137 | [MIT License,](https://github.com/radianttap/Avenue/blob/v2/LICENSE) like all my open source code. 138 | 139 | ## Credits 140 | 141 | * **Alamofire** community for their invaluable work over the years. I don’t use the library itself, but there are re-usable gems in it (like ServerTrustPolicy handling). 142 | 143 | * **Marcus Zarra** for this [great talk](https://academy.realm.io/posts/slug-marcus-zarra-exploring-mvcn-swift/) which got me started to write this library. There’s a [blog post](http://www.cimgf.com/2016/01/28/a-modern-network-operation/) on his blog too. 144 | 145 | I want re-iterate what Marcus said at the end of his talk: 146 | 147 | > Write it [network code] yourself. I guarantee code you write yourself will be faster than any generic code, that is the law. Whenever you write something that is very specific, it is going to be faster than generics. 148 | 149 | ## Learn more 150 | 151 | * [Alley](https://github.com/radianttap/Alley) – automatic retries for `URLSessionDataTask` 152 | 153 | * ATS ([Advanced Transport Security](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW33)) 154 | 155 | * TN2232: [HTTPS Server Trust Evaluation](https://developer.apple.com/library/content/technotes/tn2232/) 156 | 157 | * WWDC 2017, Session 709: [Advances in Networking, Part 2](http://developer.apple.com/videos/play/wwdc2017/709) 158 | 159 | ### Helpful articles 160 | 161 | * [Security analysis](https://www.nowsecure.com/blog/2017/08/31/security-analysts-guide-nsapptransportsecurity-nsallowsarbitraryloads-app-transport-security-ats-exceptions/) of ATS 162 | 163 | * Alamofire [notes on ATS](https://github.com/Alamofire/Alamofire#app-transport-security) 164 | * Resolving [ATS issues](https://github.com/Alamofire/Alamofire/issues/876) 165 | 166 | * [Use SSL pinning](https://infinum.co/the-capsized-eight/how-to-make-your-ios-apps-more-secure-with-ssl-pinning) 167 | 168 | ### Tools 169 | 170 | * [Bad SSL](https://badssl.com) in many ways, fantastic resource to test your code. 171 | 172 | * `nscurl --help` (in your macOS Terminal) 173 | -------------------------------------------------------------------------------- /ServerTrust/ServerTrustPolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServerTrustPolicy.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | // MARK: - ServerTrustPolicy 28 | 29 | /// The `ServerTrustPolicy` evaluates the server trust generally provided by an `NSURLAuthenticationChallenge` when 30 | /// connecting to a server over a secure HTTPS connection. The policy configuration then evaluates the server trust 31 | /// with a given set of criteria to determine whether the server trust is valid and the connection should be made. 32 | /// 33 | /// Using pinned certificates or public keys for evaluation helps prevent man-in-the-middle (MITM) attacks and other 34 | /// vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged 35 | /// to route all communication over an HTTPS connection with pinning enabled. 36 | /// 37 | /// - performDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to 38 | /// validate the host provided by the challenge. Applications are encouraged to always 39 | /// validate the host in production environments to guarantee the validity of the server's 40 | /// certificate chain. 41 | /// 42 | /// - performRevokedEvaluation: Uses the default and revoked server trust evaluations allowing you to control whether to 43 | /// validate the host provided by the challenge as well as specify the revocation flags for 44 | /// testing for revoked certificates. Apple platforms did not start testing for revoked 45 | /// certificates automatically until iOS 10.1, macOS 10.12 and tvOS 10.1 which is 46 | /// demonstrated in our TLS tests. Applications are encouraged to always validate the host 47 | /// in production environments to guarantee the validity of the server's certificate chain. 48 | /// 49 | /// - pinCertificates: Uses the pinned certificates to validate the server trust. The server trust is 50 | /// considered valid if one of the pinned certificates match one of the server certificates. 51 | /// By validating both the certificate chain and host, certificate pinning provides a very 52 | /// secure form of server trust validation mitigating most, if not all, MITM attacks. 53 | /// Applications are encouraged to always validate the host and require a valid certificate 54 | /// chain in production environments. 55 | /// 56 | /// - pinPublicKeys: Uses the pinned public keys to validate the server trust. The server trust is considered 57 | /// valid if one of the pinned public keys match one of the server certificate public keys. 58 | /// By validating both the certificate chain and host, public key pinning provides a very 59 | /// secure form of server trust validation mitigating most, if not all, MITM attacks. 60 | /// Applications are encouraged to always validate the host and require a valid certificate 61 | /// chain in production environments. 62 | /// 63 | /// - disableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid. 64 | /// 65 | /// - customEvaluation: Uses the associated closure to evaluate the validity of the server trust. 66 | public enum ServerTrustPolicy { 67 | case performDefaultEvaluation(validateHost: Bool) 68 | case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags) 69 | case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool) 70 | case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool) 71 | case disableEvaluation 72 | case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool) 73 | 74 | // MARK: - Bundle Location 75 | 76 | /// Returns all certificates within the given bundle with a `.cer` file extension. 77 | /// 78 | /// - parameter bundle: The bundle to search for all `.cer` files. 79 | /// 80 | /// - returns: All certificates within the given bundle. 81 | public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] { 82 | var certificates: [SecCertificate] = [] 83 | 84 | let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in 85 | bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil) 86 | }.joined()) 87 | 88 | for path in paths { 89 | if 90 | let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData, 91 | let certificate = SecCertificateCreateWithData(nil, certificateData) 92 | { 93 | certificates.append(certificate) 94 | } 95 | } 96 | 97 | return certificates 98 | } 99 | 100 | /// Returns all public keys within the given bundle with a `.cer` file extension. 101 | /// 102 | /// - parameter bundle: The bundle to search for all `*.cer` files. 103 | /// 104 | /// - returns: All public keys within the given bundle. 105 | public static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] { 106 | var publicKeys: [SecKey] = [] 107 | 108 | for certificate in certificates(in: bundle) { 109 | if let publicKey = publicKey(for: certificate) { 110 | publicKeys.append(publicKey) 111 | } 112 | } 113 | 114 | return publicKeys 115 | } 116 | 117 | // MARK: - Evaluation 118 | 119 | /// Evaluates whether the server trust is valid for the given host. 120 | /// 121 | /// - parameter serverTrust: The server trust to evaluate. 122 | /// - parameter host: The host of the challenge protection space. 123 | /// 124 | /// - returns: Whether the server trust is valid. 125 | public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool { 126 | var serverTrustIsValid = false 127 | 128 | switch self { 129 | case let .performDefaultEvaluation(validateHost): 130 | let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) 131 | SecTrustSetPolicies(serverTrust, policy) 132 | 133 | serverTrustIsValid = trustIsValid(serverTrust) 134 | case let .performRevokedEvaluation(validateHost, revocationFlags): 135 | let defaultPolicy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) 136 | let revokedPolicy = SecPolicyCreateRevocation(revocationFlags) 137 | SecTrustSetPolicies(serverTrust, [defaultPolicy, revokedPolicy] as CFTypeRef) 138 | 139 | serverTrustIsValid = trustIsValid(serverTrust) 140 | case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost): 141 | if validateCertificateChain { 142 | let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) 143 | SecTrustSetPolicies(serverTrust, policy) 144 | 145 | SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray) 146 | SecTrustSetAnchorCertificatesOnly(serverTrust, true) 147 | 148 | serverTrustIsValid = trustIsValid(serverTrust) 149 | } else { 150 | let serverCertificatesDataArray = certificateData(for: serverTrust) 151 | let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates) 152 | 153 | outerLoop: for serverCertificateData in serverCertificatesDataArray { 154 | for pinnedCertificateData in pinnedCertificatesDataArray { 155 | if serverCertificateData == pinnedCertificateData { 156 | serverTrustIsValid = true 157 | break outerLoop 158 | } 159 | } 160 | } 161 | } 162 | case let .pinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost): 163 | var certificateChainEvaluationPassed = true 164 | 165 | if validateCertificateChain { 166 | let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil) 167 | SecTrustSetPolicies(serverTrust, policy) 168 | 169 | certificateChainEvaluationPassed = trustIsValid(serverTrust) 170 | } 171 | 172 | if certificateChainEvaluationPassed { 173 | outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeys(for: serverTrust) { 174 | for pinnedPublicKey in pinnedPublicKeys { 175 | if serverPublicKey == pinnedPublicKey { 176 | serverTrustIsValid = true 177 | break outerLoop 178 | } 179 | } 180 | } 181 | } 182 | case .disableEvaluation: 183 | serverTrustIsValid = true 184 | case let .customEvaluation(closure): 185 | serverTrustIsValid = closure(serverTrust, host) 186 | } 187 | 188 | return serverTrustIsValid 189 | } 190 | 191 | // MARK: - Private - Trust Validation 192 | 193 | private func trustIsValid(_ trust: SecTrust) -> Bool { 194 | var isValid = false 195 | 196 | var result = SecTrustResultType.invalid 197 | let status = SecTrustEvaluate(trust, &result) 198 | 199 | if status == errSecSuccess { 200 | let unspecified = SecTrustResultType.unspecified 201 | let proceed = SecTrustResultType.proceed 202 | 203 | 204 | isValid = result == unspecified || result == proceed 205 | } 206 | 207 | return isValid 208 | } 209 | 210 | // MARK: - Private - Certificate Data 211 | 212 | private func certificateData(for trust: SecTrust) -> [Data] { 213 | var certificates: [SecCertificate] = [] 214 | 215 | for index in 0.. [Data] { 225 | return certificates.map { SecCertificateCopyData($0) as Data } 226 | } 227 | 228 | // MARK: - Private - Public Key Extraction 229 | 230 | private static func publicKeys(for trust: SecTrust) -> [SecKey] { 231 | var publicKeys: [SecKey] = [] 232 | 233 | for index in 0.. SecKey? { 246 | var publicKey: SecKey? 247 | 248 | let policy = SecPolicyCreateBasicX509() 249 | var trust: SecTrust? 250 | let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust) 251 | 252 | if let trust = trust, trustCreationStatus == errSecSuccess { 253 | publicKey = SecTrustCopyPublicKey(trust) 254 | } 255 | 256 | return publicKey 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /Avenue.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D061899A22197BEF00C2F698 /* Avenue.h in Headers */ = {isa = PBXBuildFile; fileRef = D061899822197BEF00C2F698 /* Avenue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | D06189AC22197C3800C2F698 /* NetworkOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06189A122197C3800C2F698 /* NetworkOperation.swift */; }; 12 | D06189AD22197C3800C2F698 /* AsyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06189A322197C3800C2F698 /* AsyncOperation.swift */; }; 13 | D06189AF22197C3800C2F698 /* NetworkSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06189A522197C3800C2F698 /* NetworkSession.swift */; }; 14 | D06189B022197C3800C2F698 /* URLSessionTask-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06189A622197C3800C2F698 /* URLSessionTask-Extensions.swift */; }; 15 | D06189B122197C3800C2F698 /* NetworkPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06189A722197C3800C2F698 /* NetworkPayload.swift */; }; 16 | D06189B222197C3800C2F698 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06189A822197C3800C2F698 /* Atomic.swift */; }; 17 | D06189B322197C3800C2F698 /* ServerTrust-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06189A922197C3800C2F698 /* ServerTrust-Extensions.swift */; }; 18 | D06189B522197C3800C2F698 /* ServerTrustPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06189AB22197C3800C2F698 /* ServerTrustPolicy.swift */; }; 19 | D08994D623B381FE00B6B4E7 /* URLSession-extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08994D223B381FE00B6B4E7 /* URLSession-extensions.swift */; }; 20 | D08994D723B381FE00B6B4E7 /* NetworkError-Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08994D323B381FE00B6B4E7 /* NetworkError-Localized.swift */; }; 21 | D08994D823B381FE00B6B4E7 /* NetworkError-Retries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08994D423B381FE00B6B4E7 /* NetworkError-Retries.swift */; }; 22 | D08994D923B381FE00B6B4E7 /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08994D523B381FE00B6B4E7 /* NetworkError.swift */; }; 23 | D08994DC23B3A3A900B6B4E7 /* NetworkSession-ServerTrust.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08994DB23B3A3A900B6B4E7 /* NetworkSession-ServerTrust.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | D034A6AF23BA1C5B00E84985 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 28 | D061899522197BEF00C2F698 /* Avenue.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Avenue.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | D061899822197BEF00C2F698 /* Avenue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Avenue.h; sourceTree = ""; }; 30 | D061899922197BEF00C2F698 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | D06189A122197C3800C2F698 /* NetworkOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkOperation.swift; sourceTree = ""; }; 32 | D06189A322197C3800C2F698 /* AsyncOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOperation.swift; sourceTree = ""; }; 33 | D06189A522197C3800C2F698 /* NetworkSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkSession.swift; sourceTree = ""; }; 34 | D06189A622197C3800C2F698 /* URLSessionTask-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URLSessionTask-Extensions.swift"; sourceTree = ""; }; 35 | D06189A722197C3800C2F698 /* NetworkPayload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkPayload.swift; sourceTree = ""; }; 36 | D06189A822197C3800C2F698 /* Atomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = ""; }; 37 | D06189A922197C3800C2F698 /* ServerTrust-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ServerTrust-Extensions.swift"; sourceTree = ""; }; 38 | D06189AB22197C3800C2F698 /* ServerTrustPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerTrustPolicy.swift; sourceTree = ""; }; 39 | D08994D223B381FE00B6B4E7 /* URLSession-extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URLSession-extensions.swift"; sourceTree = ""; }; 40 | D08994D323B381FE00B6B4E7 /* NetworkError-Localized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NetworkError-Localized.swift"; sourceTree = ""; }; 41 | D08994D423B381FE00B6B4E7 /* NetworkError-Retries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NetworkError-Retries.swift"; sourceTree = ""; }; 42 | D08994D523B381FE00B6B4E7 /* NetworkError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; 43 | D08994DB23B3A3A900B6B4E7 /* NetworkSession-ServerTrust.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkSession-ServerTrust.swift"; sourceTree = ""; }; 44 | D0BBC112226903BD0050AA1A /* Avenue.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = Avenue.podspec; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | D061899222197BEF00C2F698 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | D061898B22197BEF00C2F698 = { 59 | isa = PBXGroup; 60 | children = ( 61 | D034A6AF23BA1C5B00E84985 /* README.md */, 62 | D0BBC112226903BD0050AA1A /* Avenue.podspec */, 63 | D08994D123B381FE00B6B4E7 /* Alley */, 64 | D06189A222197C3800C2F698 /* Essentials */, 65 | D08994DA23B3A29D00B6B4E7 /* ServerTrust */, 66 | D06189A022197C3800C2F698 /* Avenue */, 67 | D061899722197BEF00C2F698 /* Framework */, 68 | D061899622197BEF00C2F698 /* Products */, 69 | ); 70 | sourceTree = ""; 71 | }; 72 | D061899622197BEF00C2F698 /* Products */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | D061899522197BEF00C2F698 /* Avenue.framework */, 76 | ); 77 | name = Products; 78 | sourceTree = ""; 79 | }; 80 | D061899722197BEF00C2F698 /* Framework */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | D061899822197BEF00C2F698 /* Avenue.h */, 84 | D061899922197BEF00C2F698 /* Info.plist */, 85 | ); 86 | path = Framework; 87 | sourceTree = ""; 88 | }; 89 | D06189A022197C3800C2F698 /* Avenue */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | D06189A722197C3800C2F698 /* NetworkPayload.swift */, 93 | D06189A122197C3800C2F698 /* NetworkOperation.swift */, 94 | D06189A522197C3800C2F698 /* NetworkSession.swift */, 95 | D06189A622197C3800C2F698 /* URLSessionTask-Extensions.swift */, 96 | ); 97 | path = Avenue; 98 | sourceTree = ""; 99 | }; 100 | D06189A222197C3800C2F698 /* Essentials */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | D06189A822197C3800C2F698 /* Atomic.swift */, 104 | D06189A322197C3800C2F698 /* AsyncOperation.swift */, 105 | ); 106 | path = Essentials; 107 | sourceTree = ""; 108 | }; 109 | D08994D123B381FE00B6B4E7 /* Alley */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | D08994D223B381FE00B6B4E7 /* URLSession-extensions.swift */, 113 | D08994D323B381FE00B6B4E7 /* NetworkError-Localized.swift */, 114 | D08994D423B381FE00B6B4E7 /* NetworkError-Retries.swift */, 115 | D08994D523B381FE00B6B4E7 /* NetworkError.swift */, 116 | ); 117 | path = Alley; 118 | sourceTree = ""; 119 | }; 120 | D08994DA23B3A29D00B6B4E7 /* ServerTrust */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | D06189AB22197C3800C2F698 /* ServerTrustPolicy.swift */, 124 | D06189A922197C3800C2F698 /* ServerTrust-Extensions.swift */, 125 | D08994DB23B3A3A900B6B4E7 /* NetworkSession-ServerTrust.swift */, 126 | ); 127 | path = ServerTrust; 128 | sourceTree = ""; 129 | }; 130 | /* End PBXGroup section */ 131 | 132 | /* Begin PBXHeadersBuildPhase section */ 133 | D061899022197BEF00C2F698 /* Headers */ = { 134 | isa = PBXHeadersBuildPhase; 135 | buildActionMask = 2147483647; 136 | files = ( 137 | D061899A22197BEF00C2F698 /* Avenue.h in Headers */, 138 | ); 139 | runOnlyForDeploymentPostprocessing = 0; 140 | }; 141 | /* End PBXHeadersBuildPhase section */ 142 | 143 | /* Begin PBXNativeTarget section */ 144 | D061899422197BEF00C2F698 /* Avenue */ = { 145 | isa = PBXNativeTarget; 146 | buildConfigurationList = D061899D22197BEF00C2F698 /* Build configuration list for PBXNativeTarget "Avenue" */; 147 | buildPhases = ( 148 | D061899022197BEF00C2F698 /* Headers */, 149 | D061899122197BEF00C2F698 /* Sources */, 150 | D061899222197BEF00C2F698 /* Frameworks */, 151 | D061899322197BEF00C2F698 /* Resources */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = Avenue; 158 | productName = Avenue; 159 | productReference = D061899522197BEF00C2F698 /* Avenue.framework */; 160 | productType = "com.apple.product-type.framework"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | D061898C22197BEF00C2F698 /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | LastUpgradeCheck = 1010; 169 | ORGANIZATIONNAME = "Radiant Tap"; 170 | TargetAttributes = { 171 | D061899422197BEF00C2F698 = { 172 | CreatedOnToolsVersion = 10.1; 173 | LastSwiftMigration = 1020; 174 | }; 175 | }; 176 | }; 177 | buildConfigurationList = D061898F22197BEF00C2F698 /* Build configuration list for PBXProject "Avenue" */; 178 | compatibilityVersion = "Xcode 9.3"; 179 | developmentRegion = en; 180 | hasScannedForEncodings = 0; 181 | knownRegions = ( 182 | en, 183 | Base, 184 | ); 185 | mainGroup = D061898B22197BEF00C2F698; 186 | productRefGroup = D061899622197BEF00C2F698 /* Products */; 187 | projectDirPath = ""; 188 | projectRoot = ""; 189 | targets = ( 190 | D061899422197BEF00C2F698 /* Avenue */, 191 | ); 192 | }; 193 | /* End PBXProject section */ 194 | 195 | /* Begin PBXResourcesBuildPhase section */ 196 | D061899322197BEF00C2F698 /* Resources */ = { 197 | isa = PBXResourcesBuildPhase; 198 | buildActionMask = 2147483647; 199 | files = ( 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXResourcesBuildPhase section */ 204 | 205 | /* Begin PBXSourcesBuildPhase section */ 206 | D061899122197BEF00C2F698 /* Sources */ = { 207 | isa = PBXSourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | D08994DC23B3A3A900B6B4E7 /* NetworkSession-ServerTrust.swift in Sources */, 211 | D06189AD22197C3800C2F698 /* AsyncOperation.swift in Sources */, 212 | D08994D923B381FE00B6B4E7 /* NetworkError.swift in Sources */, 213 | D06189B022197C3800C2F698 /* URLSessionTask-Extensions.swift in Sources */, 214 | D06189B322197C3800C2F698 /* ServerTrust-Extensions.swift in Sources */, 215 | D06189AF22197C3800C2F698 /* NetworkSession.swift in Sources */, 216 | D06189AC22197C3800C2F698 /* NetworkOperation.swift in Sources */, 217 | D08994D823B381FE00B6B4E7 /* NetworkError-Retries.swift in Sources */, 218 | D08994D723B381FE00B6B4E7 /* NetworkError-Localized.swift in Sources */, 219 | D08994D623B381FE00B6B4E7 /* URLSession-extensions.swift in Sources */, 220 | D06189B522197C3800C2F698 /* ServerTrustPolicy.swift in Sources */, 221 | D06189B222197C3800C2F698 /* Atomic.swift in Sources */, 222 | D06189B122197C3800C2F698 /* NetworkPayload.swift in Sources */, 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | }; 226 | /* End PBXSourcesBuildPhase section */ 227 | 228 | /* Begin XCBuildConfiguration section */ 229 | D061899B22197BEF00C2F698 /* Debug */ = { 230 | isa = XCBuildConfiguration; 231 | buildSettings = { 232 | ALWAYS_SEARCH_USER_PATHS = NO; 233 | CLANG_ANALYZER_NONNULL = YES; 234 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 235 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 236 | CLANG_CXX_LIBRARY = "libc++"; 237 | CLANG_ENABLE_MODULES = YES; 238 | CLANG_ENABLE_OBJC_ARC = YES; 239 | CLANG_ENABLE_OBJC_WEAK = YES; 240 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 241 | CLANG_WARN_BOOL_CONVERSION = YES; 242 | CLANG_WARN_COMMA = YES; 243 | CLANG_WARN_CONSTANT_CONVERSION = YES; 244 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 245 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 246 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 247 | CLANG_WARN_EMPTY_BODY = YES; 248 | CLANG_WARN_ENUM_CONVERSION = YES; 249 | CLANG_WARN_INFINITE_RECURSION = YES; 250 | CLANG_WARN_INT_CONVERSION = YES; 251 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 252 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 253 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 255 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 256 | CLANG_WARN_STRICT_PROTOTYPES = YES; 257 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 258 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 259 | CLANG_WARN_UNREACHABLE_CODE = YES; 260 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 261 | CODE_SIGN_IDENTITY = "iPhone Developer"; 262 | COPY_PHASE_STRIP = NO; 263 | CURRENT_PROJECT_VERSION = 83; 264 | DEBUG_INFORMATION_FORMAT = dwarf; 265 | DYLIB_CURRENT_VERSION = "$(CURRENT_PROJECT_VERSION)"; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | ENABLE_TESTABILITY = YES; 268 | GCC_C_LANGUAGE_STANDARD = gnu11; 269 | GCC_DYNAMIC_NO_PIC = NO; 270 | GCC_NO_COMMON_BLOCKS = YES; 271 | GCC_OPTIMIZATION_LEVEL = 0; 272 | GCC_PREPROCESSOR_DEFINITIONS = ( 273 | "DEBUG=1", 274 | "$(inherited)", 275 | ); 276 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 277 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 278 | GCC_WARN_UNDECLARED_SELECTOR = YES; 279 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 280 | GCC_WARN_UNUSED_FUNCTION = YES; 281 | GCC_WARN_UNUSED_VARIABLE = YES; 282 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 283 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 284 | MTL_FAST_MATH = YES; 285 | ONLY_ACTIVE_ARCH = YES; 286 | SDKROOT = iphoneos; 287 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvos appletvsimulator watchos watchsimulator"; 288 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 289 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 290 | SWIFT_VERSION = 5.0; 291 | TVOS_DEPLOYMENT_TARGET = 10.0; 292 | VERSIONING_SYSTEM = "apple-generic"; 293 | VERSION_INFO_PREFIX = ""; 294 | WATCHOS_DEPLOYMENT_TARGET = 3.0; 295 | }; 296 | name = Debug; 297 | }; 298 | D061899C22197BEF00C2F698 /* Release */ = { 299 | isa = XCBuildConfiguration; 300 | buildSettings = { 301 | ALWAYS_SEARCH_USER_PATHS = NO; 302 | CLANG_ANALYZER_NONNULL = YES; 303 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 304 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 305 | CLANG_CXX_LIBRARY = "libc++"; 306 | CLANG_ENABLE_MODULES = YES; 307 | CLANG_ENABLE_OBJC_ARC = YES; 308 | CLANG_ENABLE_OBJC_WEAK = YES; 309 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 310 | CLANG_WARN_BOOL_CONVERSION = YES; 311 | CLANG_WARN_COMMA = YES; 312 | CLANG_WARN_CONSTANT_CONVERSION = YES; 313 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 314 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 315 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 316 | CLANG_WARN_EMPTY_BODY = YES; 317 | CLANG_WARN_ENUM_CONVERSION = YES; 318 | CLANG_WARN_INFINITE_RECURSION = YES; 319 | CLANG_WARN_INT_CONVERSION = YES; 320 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 322 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 323 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 324 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 325 | CLANG_WARN_STRICT_PROTOTYPES = YES; 326 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 327 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | CODE_SIGN_IDENTITY = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | CURRENT_PROJECT_VERSION = 83; 333 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 334 | DYLIB_CURRENT_VERSION = "$(CURRENT_PROJECT_VERSION)"; 335 | ENABLE_NS_ASSERTIONS = NO; 336 | ENABLE_STRICT_OBJC_MSGSEND = YES; 337 | GCC_C_LANGUAGE_STANDARD = gnu11; 338 | GCC_NO_COMMON_BLOCKS = YES; 339 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 340 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 341 | GCC_WARN_UNDECLARED_SELECTOR = YES; 342 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 343 | GCC_WARN_UNUSED_FUNCTION = YES; 344 | GCC_WARN_UNUSED_VARIABLE = YES; 345 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 346 | MTL_ENABLE_DEBUG_INFO = NO; 347 | MTL_FAST_MATH = YES; 348 | SDKROOT = iphoneos; 349 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvos appletvsimulator watchos watchsimulator"; 350 | SWIFT_COMPILATION_MODE = wholemodule; 351 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 352 | SWIFT_VERSION = 5.0; 353 | TVOS_DEPLOYMENT_TARGET = 10.0; 354 | VALIDATE_PRODUCT = YES; 355 | VERSIONING_SYSTEM = "apple-generic"; 356 | VERSION_INFO_PREFIX = ""; 357 | WATCHOS_DEPLOYMENT_TARGET = 3.0; 358 | }; 359 | name = Release; 360 | }; 361 | D061899E22197BEF00C2F698 /* Debug */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | APPLICATION_EXTENSION_API_ONLY = YES; 365 | CODE_SIGN_IDENTITY = ""; 366 | CODE_SIGN_STYLE = Automatic; 367 | DEFINES_MODULE = YES; 368 | DYLIB_COMPATIBILITY_VERSION = 1; 369 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 370 | INFOPLIST_FILE = "$(SRCROOT)/Framework/Info.plist"; 371 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 372 | LD_RUNPATH_SEARCH_PATHS = ( 373 | "$(inherited)", 374 | "@executable_path/Frameworks", 375 | "@loader_path/Frameworks", 376 | ); 377 | ONLY_ACTIVE_ARCH = NO; 378 | PRODUCT_BUNDLE_IDENTIFIER = com.radianttap.Avenue; 379 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 380 | SKIP_INSTALL = YES; 381 | TARGETED_DEVICE_FAMILY = "1,2,3,4"; 382 | }; 383 | name = Debug; 384 | }; 385 | D061899F22197BEF00C2F698 /* Release */ = { 386 | isa = XCBuildConfiguration; 387 | buildSettings = { 388 | APPLICATION_EXTENSION_API_ONLY = YES; 389 | CODE_SIGN_IDENTITY = ""; 390 | CODE_SIGN_STYLE = Automatic; 391 | DEFINES_MODULE = YES; 392 | DYLIB_COMPATIBILITY_VERSION = 1; 393 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 394 | INFOPLIST_FILE = "$(SRCROOT)/Framework/Info.plist"; 395 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 396 | LD_RUNPATH_SEARCH_PATHS = ( 397 | "$(inherited)", 398 | "@executable_path/Frameworks", 399 | "@loader_path/Frameworks", 400 | ); 401 | ONLY_ACTIVE_ARCH = NO; 402 | PRODUCT_BUNDLE_IDENTIFIER = com.radianttap.Avenue; 403 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 404 | SKIP_INSTALL = YES; 405 | TARGETED_DEVICE_FAMILY = "1,2,3,4"; 406 | }; 407 | name = Release; 408 | }; 409 | /* End XCBuildConfiguration section */ 410 | 411 | /* Begin XCConfigurationList section */ 412 | D061898F22197BEF00C2F698 /* Build configuration list for PBXProject "Avenue" */ = { 413 | isa = XCConfigurationList; 414 | buildConfigurations = ( 415 | D061899B22197BEF00C2F698 /* Debug */, 416 | D061899C22197BEF00C2F698 /* Release */, 417 | ); 418 | defaultConfigurationIsVisible = 0; 419 | defaultConfigurationName = Release; 420 | }; 421 | D061899D22197BEF00C2F698 /* Build configuration list for PBXNativeTarget "Avenue" */ = { 422 | isa = XCConfigurationList; 423 | buildConfigurations = ( 424 | D061899E22197BEF00C2F698 /* Debug */, 425 | D061899F22197BEF00C2F698 /* Release */, 426 | ); 427 | defaultConfigurationIsVisible = 0; 428 | defaultConfigurationName = Release; 429 | }; 430 | /* End XCConfigurationList section */ 431 | }; 432 | rootObject = D061898C22197BEF00C2F698 /* Project object */; 433 | } 434 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D01EB0631F7BBBE60042C837 /* AssetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EB0601F7BBBE60042C837 /* AssetManager.swift */; }; 11 | D01EB0641F7BBBE60042C837 /* IvkoService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EB0611F7BBBE60042C837 /* IvkoService.swift */; }; 12 | D01EB0651F7BBBE60042C837 /* IvkoServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EB0621F7BBBE60042C837 /* IvkoServiceError.swift */; }; 13 | D01EB0671F7BBC5C0042C837 /* Bundle-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EB0661F7BBC5C0042C837 /* Bundle-Extensions.swift */; }; 14 | D01EB06E1F7BC0030042C837 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D01EB06C1F7BC0030042C837 /* Interface.storyboard */; }; 15 | D01EB0701F7BC0030042C837 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D01EB06F1F7BC0030042C837 /* Assets.xcassets */; }; 16 | D01EB0771F7BC0040042C837 /* DemoWatch Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D01EB0761F7BC0040042C837 /* DemoWatch Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 17 | D01EB07C1F7BC0040042C837 /* InterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EB07B1F7BC0040042C837 /* InterfaceController.swift */; }; 18 | D01EB07E1F7BC0040042C837 /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EB07D1F7BC0040042C837 /* ExtensionDelegate.swift */; }; 19 | D01EB0801F7BC0040042C837 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D01EB07F1F7BC0040042C837 /* Assets.xcassets */; }; 20 | D01EB0841F7BC0040042C837 /* DemoWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = D01EB06A1F7BC0030042C837 /* DemoWatch.app */; }; 21 | D01EB08D1F7BC08A0042C837 /* IvkoService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EB0611F7BBBE60042C837 /* IvkoService.swift */; }; 22 | D01EB08E1F7BC08A0042C837 /* IvkoServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EB0621F7BBBE60042C837 /* IvkoServiceError.swift */; }; 23 | D01EB08F1F7BC0900042C837 /* Bundle-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EB0661F7BBC5C0042C837 /* Bundle-Extensions.swift */; }; 24 | D05E0D181F48466C0000EAF1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05E0D171F48466C0000EAF1 /* AppDelegate.swift */; }; 25 | D05E0D1A1F48466C0000EAF1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05E0D191F48466C0000EAF1 /* ViewController.swift */; }; 26 | D05E0D1D1F48466C0000EAF1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D05E0D1B1F48466C0000EAF1 /* Main.storyboard */; }; 27 | D05E0D1F1F48466C0000EAF1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D05E0D1E1F48466C0000EAF1 /* Assets.xcassets */; }; 28 | D05E0D221F48466C0000EAF1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D05E0D201F48466C0000EAF1 /* LaunchScreen.storyboard */; }; 29 | D08994EB23B3D30C00B6B4E7 /* Avenue.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D08994EA23B3D30300B6B4E7 /* Avenue.framework */; }; 30 | D08994EC23B3D30C00B6B4E7 /* Avenue.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D08994EA23B3D30300B6B4E7 /* Avenue.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 31 | D08994EE23B3D31100B6B4E7 /* Avenue.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D08994EA23B3D30300B6B4E7 /* Avenue.framework */; }; 32 | D08994EF23B3D31100B6B4E7 /* Avenue.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D08994EA23B3D30300B6B4E7 /* Avenue.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXContainerItemProxy section */ 36 | D01EB0781F7BC0040042C837 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = D05E0D0C1F48466B0000EAF1 /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = D01EB0751F7BC0040042C837; 41 | remoteInfo = "DemoWatch Extension"; 42 | }; 43 | D01EB0821F7BC0040042C837 /* PBXContainerItemProxy */ = { 44 | isa = PBXContainerItemProxy; 45 | containerPortal = D05E0D0C1F48466B0000EAF1 /* Project object */; 46 | proxyType = 1; 47 | remoteGlobalIDString = D01EB0691F7BC0030042C837; 48 | remoteInfo = DemoWatch; 49 | }; 50 | D08994E923B3D30300B6B4E7 /* PBXContainerItemProxy */ = { 51 | isa = PBXContainerItemProxy; 52 | containerPortal = D08994E523B3D30300B6B4E7 /* Avenue.xcodeproj */; 53 | proxyType = 2; 54 | remoteGlobalIDString = D061899522197BEF00C2F698; 55 | remoteInfo = Avenue; 56 | }; 57 | D08994F223B3D44400B6B4E7 /* PBXContainerItemProxy */ = { 58 | isa = PBXContainerItemProxy; 59 | containerPortal = D08994E523B3D30300B6B4E7 /* Avenue.xcodeproj */; 60 | proxyType = 1; 61 | remoteGlobalIDString = D061899422197BEF00C2F698; 62 | remoteInfo = Avenue; 63 | }; 64 | D08994F523B3D44F00B6B4E7 /* PBXContainerItemProxy */ = { 65 | isa = PBXContainerItemProxy; 66 | containerPortal = D08994E523B3D30300B6B4E7 /* Avenue.xcodeproj */; 67 | proxyType = 1; 68 | remoteGlobalIDString = D061899422197BEF00C2F698; 69 | remoteInfo = Avenue; 70 | }; 71 | /* End PBXContainerItemProxy section */ 72 | 73 | /* Begin PBXCopyFilesBuildPhase section */ 74 | D01EB0881F7BC0040042C837 /* Embed App Extensions */ = { 75 | isa = PBXCopyFilesBuildPhase; 76 | buildActionMask = 2147483647; 77 | dstPath = ""; 78 | dstSubfolderSpec = 13; 79 | files = ( 80 | D01EB0771F7BC0040042C837 /* DemoWatch Extension.appex in Embed App Extensions */, 81 | ); 82 | name = "Embed App Extensions"; 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | D01EB08C1F7BC0040042C837 /* Embed Watch Content */ = { 86 | isa = PBXCopyFilesBuildPhase; 87 | buildActionMask = 2147483647; 88 | dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; 89 | dstSubfolderSpec = 16; 90 | files = ( 91 | D01EB0841F7BC0040042C837 /* DemoWatch.app in Embed Watch Content */, 92 | ); 93 | name = "Embed Watch Content"; 94 | runOnlyForDeploymentPostprocessing = 0; 95 | }; 96 | D08994ED23B3D30C00B6B4E7 /* Embed Frameworks */ = { 97 | isa = PBXCopyFilesBuildPhase; 98 | buildActionMask = 2147483647; 99 | dstPath = ""; 100 | dstSubfolderSpec = 10; 101 | files = ( 102 | D08994EC23B3D30C00B6B4E7 /* Avenue.framework in Embed Frameworks */, 103 | ); 104 | name = "Embed Frameworks"; 105 | runOnlyForDeploymentPostprocessing = 0; 106 | }; 107 | D08994F023B3D31100B6B4E7 /* Embed Frameworks */ = { 108 | isa = PBXCopyFilesBuildPhase; 109 | buildActionMask = 2147483647; 110 | dstPath = ""; 111 | dstSubfolderSpec = 10; 112 | files = ( 113 | D08994EF23B3D31100B6B4E7 /* Avenue.framework in Embed Frameworks */, 114 | ); 115 | name = "Embed Frameworks"; 116 | runOnlyForDeploymentPostprocessing = 0; 117 | }; 118 | /* End PBXCopyFilesBuildPhase section */ 119 | 120 | /* Begin PBXFileReference section */ 121 | D01EB0601F7BBBE60042C837 /* AssetManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetManager.swift; sourceTree = ""; }; 122 | D01EB0611F7BBBE60042C837 /* IvkoService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IvkoService.swift; sourceTree = ""; }; 123 | D01EB0621F7BBBE60042C837 /* IvkoServiceError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IvkoServiceError.swift; sourceTree = ""; }; 124 | D01EB0661F7BBC5C0042C837 /* Bundle-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle-Extensions.swift"; sourceTree = ""; }; 125 | D01EB06A1F7BC0030042C837 /* DemoWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DemoWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; 126 | D01EB06D1F7BC0030042C837 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = ""; }; 127 | D01EB06F1F7BC0030042C837 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 128 | D01EB0711F7BC0030042C837 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 129 | D01EB0761F7BC0040042C837 /* DemoWatch Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "DemoWatch Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 130 | D01EB07B1F7BC0040042C837 /* InterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceController.swift; sourceTree = ""; }; 131 | D01EB07D1F7BC0040042C837 /* ExtensionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDelegate.swift; sourceTree = ""; }; 132 | D01EB07F1F7BC0040042C837 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 133 | D01EB0811F7BC0040042C837 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 134 | D05E0D141F48466C0000EAF1 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 135 | D05E0D171F48466C0000EAF1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 136 | D05E0D191F48466C0000EAF1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 137 | D05E0D1C1F48466C0000EAF1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 138 | D05E0D1E1F48466C0000EAF1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 139 | D05E0D211F48466C0000EAF1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 140 | D05E0D231F48466C0000EAF1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 141 | D08556052191B5AC00721800 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 142 | D08556062191B5AD00721800 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 143 | D08994DD23B3D26700B6B4E7 /* Avenue.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Avenue.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 144 | D08994E123B3D27700B6B4E7 /* Avenue.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Avenue.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 145 | D08994E523B3D30300B6B4E7 /* Avenue.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Avenue.xcodeproj; path = ../Avenue.xcodeproj; sourceTree = ""; }; 146 | D0B1F480221AC2A4000913B4 /* Avenue.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Avenue.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 147 | D0B1F482221AC376000913B4 /* Avenue.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Avenue.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 148 | /* End PBXFileReference section */ 149 | 150 | /* Begin PBXFrameworksBuildPhase section */ 151 | D01EB0731F7BC0040042C837 /* Frameworks */ = { 152 | isa = PBXFrameworksBuildPhase; 153 | buildActionMask = 2147483647; 154 | files = ( 155 | D08994EE23B3D31100B6B4E7 /* Avenue.framework in Frameworks */, 156 | ); 157 | runOnlyForDeploymentPostprocessing = 0; 158 | }; 159 | D05E0D111F48466C0000EAF1 /* Frameworks */ = { 160 | isa = PBXFrameworksBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | D08994EB23B3D30C00B6B4E7 /* Avenue.framework in Frameworks */, 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | /* End PBXFrameworksBuildPhase section */ 168 | 169 | /* Begin PBXGroup section */ 170 | D01EB05F1F7BBBE60042C837 /* ExampleWrappers */ = { 171 | isa = PBXGroup; 172 | children = ( 173 | D01EB0601F7BBBE60042C837 /* AssetManager.swift */, 174 | D01EB0611F7BBBE60042C837 /* IvkoService.swift */, 175 | D01EB0621F7BBBE60042C837 /* IvkoServiceError.swift */, 176 | ); 177 | path = ExampleWrappers; 178 | sourceTree = ""; 179 | }; 180 | D01EB06B1F7BC0030042C837 /* DemoWatch */ = { 181 | isa = PBXGroup; 182 | children = ( 183 | D01EB06C1F7BC0030042C837 /* Interface.storyboard */, 184 | D01EB06F1F7BC0030042C837 /* Assets.xcassets */, 185 | D01EB0711F7BC0030042C837 /* Info.plist */, 186 | ); 187 | path = DemoWatch; 188 | sourceTree = ""; 189 | }; 190 | D01EB07A1F7BC0040042C837 /* DemoWatch Extension */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | D01EB07B1F7BC0040042C837 /* InterfaceController.swift */, 194 | D01EB07D1F7BC0040042C837 /* ExtensionDelegate.swift */, 195 | D01EB07F1F7BC0040042C837 /* Assets.xcassets */, 196 | D01EB0811F7BC0040042C837 /* Info.plist */, 197 | ); 198 | path = "DemoWatch Extension"; 199 | sourceTree = ""; 200 | }; 201 | D05E0D0B1F48466B0000EAF1 = { 202 | isa = PBXGroup; 203 | children = ( 204 | D08994E523B3D30300B6B4E7 /* Avenue.xcodeproj */, 205 | D08556062191B5AD00721800 /* LICENSE */, 206 | D08556052191B5AC00721800 /* README.md */, 207 | D05E0D161F48466C0000EAF1 /* Demo */, 208 | D01EB06B1F7BC0030042C837 /* DemoWatch */, 209 | D01EB07A1F7BC0040042C837 /* DemoWatch Extension */, 210 | D01EB05F1F7BBBE60042C837 /* ExampleWrappers */, 211 | D05E0D2F1F4846D70000EAF1 /* Vendor */, 212 | D05E0D151F48466C0000EAF1 /* Products */, 213 | D0B1F47F221AC2A4000913B4 /* Frameworks */, 214 | ); 215 | sourceTree = ""; 216 | }; 217 | D05E0D151F48466C0000EAF1 /* Products */ = { 218 | isa = PBXGroup; 219 | children = ( 220 | D05E0D141F48466C0000EAF1 /* Demo.app */, 221 | D01EB06A1F7BC0030042C837 /* DemoWatch.app */, 222 | D01EB0761F7BC0040042C837 /* DemoWatch Extension.appex */, 223 | ); 224 | name = Products; 225 | sourceTree = ""; 226 | }; 227 | D05E0D161F48466C0000EAF1 /* Demo */ = { 228 | isa = PBXGroup; 229 | children = ( 230 | D05E0D171F48466C0000EAF1 /* AppDelegate.swift */, 231 | D05E0D191F48466C0000EAF1 /* ViewController.swift */, 232 | D05E0D1B1F48466C0000EAF1 /* Main.storyboard */, 233 | D05E0D1E1F48466C0000EAF1 /* Assets.xcassets */, 234 | D05E0D201F48466C0000EAF1 /* LaunchScreen.storyboard */, 235 | D05E0D231F48466C0000EAF1 /* Info.plist */, 236 | ); 237 | path = Demo; 238 | sourceTree = ""; 239 | }; 240 | D05E0D2F1F4846D70000EAF1 /* Vendor */ = { 241 | isa = PBXGroup; 242 | children = ( 243 | D01EB0661F7BBC5C0042C837 /* Bundle-Extensions.swift */, 244 | ); 245 | path = Vendor; 246 | sourceTree = ""; 247 | }; 248 | D08994E623B3D30300B6B4E7 /* Products */ = { 249 | isa = PBXGroup; 250 | children = ( 251 | D08994EA23B3D30300B6B4E7 /* Avenue.framework */, 252 | ); 253 | name = Products; 254 | sourceTree = ""; 255 | }; 256 | D0B1F47F221AC2A4000913B4 /* Frameworks */ = { 257 | isa = PBXGroup; 258 | children = ( 259 | D08994E123B3D27700B6B4E7 /* Avenue.framework */, 260 | D08994DD23B3D26700B6B4E7 /* Avenue.framework */, 261 | D0B1F482221AC376000913B4 /* Avenue.framework */, 262 | D0B1F480221AC2A4000913B4 /* Avenue.framework */, 263 | ); 264 | name = Frameworks; 265 | sourceTree = ""; 266 | }; 267 | /* End PBXGroup section */ 268 | 269 | /* Begin PBXNativeTarget section */ 270 | D01EB0691F7BC0030042C837 /* DemoWatch */ = { 271 | isa = PBXNativeTarget; 272 | buildConfigurationList = D01EB0891F7BC0040042C837 /* Build configuration list for PBXNativeTarget "DemoWatch" */; 273 | buildPhases = ( 274 | D01EB0681F7BC0030042C837 /* Resources */, 275 | D01EB0881F7BC0040042C837 /* Embed App Extensions */, 276 | ); 277 | buildRules = ( 278 | ); 279 | dependencies = ( 280 | D01EB0791F7BC0040042C837 /* PBXTargetDependency */, 281 | ); 282 | name = DemoWatch; 283 | productName = DemoWatch; 284 | productReference = D01EB06A1F7BC0030042C837 /* DemoWatch.app */; 285 | productType = "com.apple.product-type.application.watchapp2"; 286 | }; 287 | D01EB0751F7BC0040042C837 /* DemoWatch Extension */ = { 288 | isa = PBXNativeTarget; 289 | buildConfigurationList = D01EB0851F7BC0040042C837 /* Build configuration list for PBXNativeTarget "DemoWatch Extension" */; 290 | buildPhases = ( 291 | D01EB0721F7BC0040042C837 /* Sources */, 292 | D01EB0731F7BC0040042C837 /* Frameworks */, 293 | D01EB0741F7BC0040042C837 /* Resources */, 294 | D08994F023B3D31100B6B4E7 /* Embed Frameworks */, 295 | ); 296 | buildRules = ( 297 | ); 298 | dependencies = ( 299 | D08994F323B3D44400B6B4E7 /* PBXTargetDependency */, 300 | ); 301 | name = "DemoWatch Extension"; 302 | productName = "DemoWatch Extension"; 303 | productReference = D01EB0761F7BC0040042C837 /* DemoWatch Extension.appex */; 304 | productType = "com.apple.product-type.watchkit2-extension"; 305 | }; 306 | D05E0D131F48466C0000EAF1 /* Demo */ = { 307 | isa = PBXNativeTarget; 308 | buildConfigurationList = D05E0D261F48466C0000EAF1 /* Build configuration list for PBXNativeTarget "Demo" */; 309 | buildPhases = ( 310 | D05E0D101F48466C0000EAF1 /* Sources */, 311 | D05E0D111F48466C0000EAF1 /* Frameworks */, 312 | D05E0D121F48466C0000EAF1 /* Resources */, 313 | D01EB08C1F7BC0040042C837 /* Embed Watch Content */, 314 | D08994ED23B3D30C00B6B4E7 /* Embed Frameworks */, 315 | ); 316 | buildRules = ( 317 | ); 318 | dependencies = ( 319 | D08994F623B3D44F00B6B4E7 /* PBXTargetDependency */, 320 | D01EB0831F7BC0040042C837 /* PBXTargetDependency */, 321 | ); 322 | name = Demo; 323 | productName = Demo; 324 | productReference = D05E0D141F48466C0000EAF1 /* Demo.app */; 325 | productType = "com.apple.product-type.application"; 326 | }; 327 | /* End PBXNativeTarget section */ 328 | 329 | /* Begin PBXProject section */ 330 | D05E0D0C1F48466B0000EAF1 /* Project object */ = { 331 | isa = PBXProject; 332 | attributes = { 333 | LastSwiftUpdateCheck = 0900; 334 | LastUpgradeCheck = 0940; 335 | ORGANIZATIONNAME = "Radiant Tap"; 336 | TargetAttributes = { 337 | D01EB0691F7BC0030042C837 = { 338 | CreatedOnToolsVersion = 9.0; 339 | ProvisioningStyle = Automatic; 340 | }; 341 | D01EB0751F7BC0040042C837 = { 342 | CreatedOnToolsVersion = 9.0; 343 | LastSwiftMigration = 1000; 344 | ProvisioningStyle = Automatic; 345 | }; 346 | D05E0D131F48466C0000EAF1 = { 347 | CreatedOnToolsVersion = 9.0; 348 | LastSwiftMigration = 1000; 349 | }; 350 | }; 351 | }; 352 | buildConfigurationList = D05E0D0F1F48466C0000EAF1 /* Build configuration list for PBXProject "Demo" */; 353 | compatibilityVersion = "Xcode 8.0"; 354 | developmentRegion = en; 355 | hasScannedForEncodings = 0; 356 | knownRegions = ( 357 | en, 358 | Base, 359 | ); 360 | mainGroup = D05E0D0B1F48466B0000EAF1; 361 | productRefGroup = D05E0D151F48466C0000EAF1 /* Products */; 362 | projectDirPath = ""; 363 | projectReferences = ( 364 | { 365 | ProductGroup = D08994E623B3D30300B6B4E7 /* Products */; 366 | ProjectRef = D08994E523B3D30300B6B4E7 /* Avenue.xcodeproj */; 367 | }, 368 | ); 369 | projectRoot = ""; 370 | targets = ( 371 | D05E0D131F48466C0000EAF1 /* Demo */, 372 | D01EB0691F7BC0030042C837 /* DemoWatch */, 373 | D01EB0751F7BC0040042C837 /* DemoWatch Extension */, 374 | ); 375 | }; 376 | /* End PBXProject section */ 377 | 378 | /* Begin PBXReferenceProxy section */ 379 | D08994EA23B3D30300B6B4E7 /* Avenue.framework */ = { 380 | isa = PBXReferenceProxy; 381 | fileType = wrapper.framework; 382 | path = Avenue.framework; 383 | remoteRef = D08994E923B3D30300B6B4E7 /* PBXContainerItemProxy */; 384 | sourceTree = BUILT_PRODUCTS_DIR; 385 | }; 386 | /* End PBXReferenceProxy section */ 387 | 388 | /* Begin PBXResourcesBuildPhase section */ 389 | D01EB0681F7BC0030042C837 /* Resources */ = { 390 | isa = PBXResourcesBuildPhase; 391 | buildActionMask = 2147483647; 392 | files = ( 393 | D01EB0701F7BC0030042C837 /* Assets.xcassets in Resources */, 394 | D01EB06E1F7BC0030042C837 /* Interface.storyboard in Resources */, 395 | ); 396 | runOnlyForDeploymentPostprocessing = 0; 397 | }; 398 | D01EB0741F7BC0040042C837 /* Resources */ = { 399 | isa = PBXResourcesBuildPhase; 400 | buildActionMask = 2147483647; 401 | files = ( 402 | D01EB0801F7BC0040042C837 /* Assets.xcassets in Resources */, 403 | ); 404 | runOnlyForDeploymentPostprocessing = 0; 405 | }; 406 | D05E0D121F48466C0000EAF1 /* Resources */ = { 407 | isa = PBXResourcesBuildPhase; 408 | buildActionMask = 2147483647; 409 | files = ( 410 | D05E0D221F48466C0000EAF1 /* LaunchScreen.storyboard in Resources */, 411 | D05E0D1F1F48466C0000EAF1 /* Assets.xcassets in Resources */, 412 | D05E0D1D1F48466C0000EAF1 /* Main.storyboard in Resources */, 413 | ); 414 | runOnlyForDeploymentPostprocessing = 0; 415 | }; 416 | /* End PBXResourcesBuildPhase section */ 417 | 418 | /* Begin PBXSourcesBuildPhase section */ 419 | D01EB0721F7BC0040042C837 /* Sources */ = { 420 | isa = PBXSourcesBuildPhase; 421 | buildActionMask = 2147483647; 422 | files = ( 423 | D01EB07E1F7BC0040042C837 /* ExtensionDelegate.swift in Sources */, 424 | D01EB08F1F7BC0900042C837 /* Bundle-Extensions.swift in Sources */, 425 | D01EB08E1F7BC08A0042C837 /* IvkoServiceError.swift in Sources */, 426 | D01EB08D1F7BC08A0042C837 /* IvkoService.swift in Sources */, 427 | D01EB07C1F7BC0040042C837 /* InterfaceController.swift in Sources */, 428 | ); 429 | runOnlyForDeploymentPostprocessing = 0; 430 | }; 431 | D05E0D101F48466C0000EAF1 /* Sources */ = { 432 | isa = PBXSourcesBuildPhase; 433 | buildActionMask = 2147483647; 434 | files = ( 435 | D01EB0651F7BBBE60042C837 /* IvkoServiceError.swift in Sources */, 436 | D05E0D1A1F48466C0000EAF1 /* ViewController.swift in Sources */, 437 | D01EB0641F7BBBE60042C837 /* IvkoService.swift in Sources */, 438 | D05E0D181F48466C0000EAF1 /* AppDelegate.swift in Sources */, 439 | D01EB0631F7BBBE60042C837 /* AssetManager.swift in Sources */, 440 | D01EB0671F7BBC5C0042C837 /* Bundle-Extensions.swift in Sources */, 441 | ); 442 | runOnlyForDeploymentPostprocessing = 0; 443 | }; 444 | /* End PBXSourcesBuildPhase section */ 445 | 446 | /* Begin PBXTargetDependency section */ 447 | D01EB0791F7BC0040042C837 /* PBXTargetDependency */ = { 448 | isa = PBXTargetDependency; 449 | target = D01EB0751F7BC0040042C837 /* DemoWatch Extension */; 450 | targetProxy = D01EB0781F7BC0040042C837 /* PBXContainerItemProxy */; 451 | }; 452 | D01EB0831F7BC0040042C837 /* PBXTargetDependency */ = { 453 | isa = PBXTargetDependency; 454 | target = D01EB0691F7BC0030042C837 /* DemoWatch */; 455 | targetProxy = D01EB0821F7BC0040042C837 /* PBXContainerItemProxy */; 456 | }; 457 | D08994F323B3D44400B6B4E7 /* PBXTargetDependency */ = { 458 | isa = PBXTargetDependency; 459 | name = Avenue; 460 | targetProxy = D08994F223B3D44400B6B4E7 /* PBXContainerItemProxy */; 461 | }; 462 | D08994F623B3D44F00B6B4E7 /* PBXTargetDependency */ = { 463 | isa = PBXTargetDependency; 464 | name = Avenue; 465 | targetProxy = D08994F523B3D44F00B6B4E7 /* PBXContainerItemProxy */; 466 | }; 467 | /* End PBXTargetDependency section */ 468 | 469 | /* Begin PBXVariantGroup section */ 470 | D01EB06C1F7BC0030042C837 /* Interface.storyboard */ = { 471 | isa = PBXVariantGroup; 472 | children = ( 473 | D01EB06D1F7BC0030042C837 /* Base */, 474 | ); 475 | name = Interface.storyboard; 476 | sourceTree = ""; 477 | }; 478 | D05E0D1B1F48466C0000EAF1 /* Main.storyboard */ = { 479 | isa = PBXVariantGroup; 480 | children = ( 481 | D05E0D1C1F48466C0000EAF1 /* Base */, 482 | ); 483 | name = Main.storyboard; 484 | sourceTree = ""; 485 | }; 486 | D05E0D201F48466C0000EAF1 /* LaunchScreen.storyboard */ = { 487 | isa = PBXVariantGroup; 488 | children = ( 489 | D05E0D211F48466C0000EAF1 /* Base */, 490 | ); 491 | name = LaunchScreen.storyboard; 492 | sourceTree = ""; 493 | }; 494 | /* End PBXVariantGroup section */ 495 | 496 | /* Begin XCBuildConfiguration section */ 497 | D01EB0861F7BC0040042C837 /* Debug */ = { 498 | isa = XCBuildConfiguration; 499 | buildSettings = { 500 | ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; 501 | CODE_SIGN_STYLE = Automatic; 502 | DEVELOPMENT_TEAM = ""; 503 | INFOPLIST_FILE = "DemoWatch Extension/Info.plist"; 504 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; 505 | PRODUCT_BUNDLE_IDENTIFIER = com.radianttap.AvenueDemo.watchkitapp.watchkitextension; 506 | PRODUCT_NAME = "${TARGET_NAME}"; 507 | SDKROOT = watchos; 508 | SKIP_INSTALL = YES; 509 | TARGETED_DEVICE_FAMILY = 4; 510 | WATCHOS_DEPLOYMENT_TARGET = 5.0; 511 | }; 512 | name = Debug; 513 | }; 514 | D01EB0871F7BC0040042C837 /* Release */ = { 515 | isa = XCBuildConfiguration; 516 | buildSettings = { 517 | ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; 518 | CODE_SIGN_STYLE = Automatic; 519 | DEVELOPMENT_TEAM = ""; 520 | INFOPLIST_FILE = "DemoWatch Extension/Info.plist"; 521 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; 522 | PRODUCT_BUNDLE_IDENTIFIER = com.radianttap.AvenueDemo.watchkitapp.watchkitextension; 523 | PRODUCT_NAME = "${TARGET_NAME}"; 524 | SDKROOT = watchos; 525 | SKIP_INSTALL = YES; 526 | TARGETED_DEVICE_FAMILY = 4; 527 | WATCHOS_DEPLOYMENT_TARGET = 5.0; 528 | }; 529 | name = Release; 530 | }; 531 | D01EB08A1F7BC0040042C837 /* Debug */ = { 532 | isa = XCBuildConfiguration; 533 | buildSettings = { 534 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 535 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 536 | CODE_SIGN_STYLE = Automatic; 537 | IBSC_MODULE = DemoWatch_Extension; 538 | INFOPLIST_FILE = DemoWatch/Info.plist; 539 | PRODUCT_BUNDLE_IDENTIFIER = com.radianttap.AvenueDemo.watchkitapp; 540 | PRODUCT_NAME = "$(TARGET_NAME)"; 541 | SDKROOT = watchos; 542 | SKIP_INSTALL = YES; 543 | TARGETED_DEVICE_FAMILY = 4; 544 | WATCHOS_DEPLOYMENT_TARGET = 5.0; 545 | }; 546 | name = Debug; 547 | }; 548 | D01EB08B1F7BC0040042C837 /* Release */ = { 549 | isa = XCBuildConfiguration; 550 | buildSettings = { 551 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 552 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 553 | CODE_SIGN_STYLE = Automatic; 554 | IBSC_MODULE = DemoWatch_Extension; 555 | INFOPLIST_FILE = DemoWatch/Info.plist; 556 | PRODUCT_BUNDLE_IDENTIFIER = com.radianttap.AvenueDemo.watchkitapp; 557 | PRODUCT_NAME = "$(TARGET_NAME)"; 558 | SDKROOT = watchos; 559 | SKIP_INSTALL = YES; 560 | TARGETED_DEVICE_FAMILY = 4; 561 | WATCHOS_DEPLOYMENT_TARGET = 5.0; 562 | }; 563 | name = Release; 564 | }; 565 | D05E0D241F48466C0000EAF1 /* Debug */ = { 566 | isa = XCBuildConfiguration; 567 | buildSettings = { 568 | ALWAYS_SEARCH_USER_PATHS = NO; 569 | CLANG_ANALYZER_NONNULL = YES; 570 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 571 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 572 | CLANG_CXX_LIBRARY = "libc++"; 573 | CLANG_ENABLE_MODULES = YES; 574 | CLANG_ENABLE_OBJC_ARC = YES; 575 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 576 | CLANG_WARN_BOOL_CONVERSION = YES; 577 | CLANG_WARN_COMMA = YES; 578 | CLANG_WARN_CONSTANT_CONVERSION = YES; 579 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 580 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 581 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 582 | CLANG_WARN_EMPTY_BODY = YES; 583 | CLANG_WARN_ENUM_CONVERSION = YES; 584 | CLANG_WARN_INFINITE_RECURSION = YES; 585 | CLANG_WARN_INT_CONVERSION = YES; 586 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 587 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 588 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 589 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 590 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 591 | CLANG_WARN_STRICT_PROTOTYPES = YES; 592 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 593 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 594 | CLANG_WARN_UNREACHABLE_CODE = YES; 595 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 596 | CODE_SIGN_IDENTITY = "iPhone Developer"; 597 | COPY_PHASE_STRIP = NO; 598 | DEBUG_INFORMATION_FORMAT = dwarf; 599 | ENABLE_STRICT_OBJC_MSGSEND = YES; 600 | ENABLE_TESTABILITY = YES; 601 | GCC_C_LANGUAGE_STANDARD = gnu11; 602 | GCC_DYNAMIC_NO_PIC = NO; 603 | GCC_NO_COMMON_BLOCKS = YES; 604 | GCC_OPTIMIZATION_LEVEL = 0; 605 | GCC_PREPROCESSOR_DEFINITIONS = ( 606 | "DEBUG=1", 607 | "$(inherited)", 608 | ); 609 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 610 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 611 | GCC_WARN_UNDECLARED_SELECTOR = YES; 612 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 613 | GCC_WARN_UNUSED_FUNCTION = YES; 614 | GCC_WARN_UNUSED_VARIABLE = YES; 615 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 616 | MTL_ENABLE_DEBUG_INFO = YES; 617 | ONLY_ACTIVE_ARCH = YES; 618 | SDKROOT = iphoneos; 619 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 620 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 621 | SWIFT_VERSION = 5.0; 622 | }; 623 | name = Debug; 624 | }; 625 | D05E0D251F48466C0000EAF1 /* Release */ = { 626 | isa = XCBuildConfiguration; 627 | buildSettings = { 628 | ALWAYS_SEARCH_USER_PATHS = NO; 629 | CLANG_ANALYZER_NONNULL = YES; 630 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 631 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 632 | CLANG_CXX_LIBRARY = "libc++"; 633 | CLANG_ENABLE_MODULES = YES; 634 | CLANG_ENABLE_OBJC_ARC = YES; 635 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 636 | CLANG_WARN_BOOL_CONVERSION = YES; 637 | CLANG_WARN_COMMA = YES; 638 | CLANG_WARN_CONSTANT_CONVERSION = YES; 639 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 640 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 641 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 642 | CLANG_WARN_EMPTY_BODY = YES; 643 | CLANG_WARN_ENUM_CONVERSION = YES; 644 | CLANG_WARN_INFINITE_RECURSION = YES; 645 | CLANG_WARN_INT_CONVERSION = YES; 646 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 647 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 648 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 649 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 650 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 651 | CLANG_WARN_STRICT_PROTOTYPES = YES; 652 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 653 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 654 | CLANG_WARN_UNREACHABLE_CODE = YES; 655 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 656 | CODE_SIGN_IDENTITY = "iPhone Developer"; 657 | COPY_PHASE_STRIP = NO; 658 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 659 | ENABLE_NS_ASSERTIONS = NO; 660 | ENABLE_STRICT_OBJC_MSGSEND = YES; 661 | GCC_C_LANGUAGE_STANDARD = gnu11; 662 | GCC_NO_COMMON_BLOCKS = YES; 663 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 664 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 665 | GCC_WARN_UNDECLARED_SELECTOR = YES; 666 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 667 | GCC_WARN_UNUSED_FUNCTION = YES; 668 | GCC_WARN_UNUSED_VARIABLE = YES; 669 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 670 | MTL_ENABLE_DEBUG_INFO = NO; 671 | SDKROOT = iphoneos; 672 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 673 | SWIFT_VERSION = 5.0; 674 | VALIDATE_PRODUCT = YES; 675 | }; 676 | name = Release; 677 | }; 678 | D05E0D271F48466C0000EAF1 /* Debug */ = { 679 | isa = XCBuildConfiguration; 680 | buildSettings = { 681 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 682 | INFOPLIST_FILE = Demo/Info.plist; 683 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 684 | PRODUCT_BUNDLE_IDENTIFIER = com.radianttap.AvenueDemo; 685 | PRODUCT_NAME = "$(TARGET_NAME)"; 686 | TARGETED_DEVICE_FAMILY = "1,2"; 687 | }; 688 | name = Debug; 689 | }; 690 | D05E0D281F48466C0000EAF1 /* Release */ = { 691 | isa = XCBuildConfiguration; 692 | buildSettings = { 693 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 694 | INFOPLIST_FILE = Demo/Info.plist; 695 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 696 | PRODUCT_BUNDLE_IDENTIFIER = com.radianttap.AvenueDemo; 697 | PRODUCT_NAME = "$(TARGET_NAME)"; 698 | TARGETED_DEVICE_FAMILY = "1,2"; 699 | }; 700 | name = Release; 701 | }; 702 | /* End XCBuildConfiguration section */ 703 | 704 | /* Begin XCConfigurationList section */ 705 | D01EB0851F7BC0040042C837 /* Build configuration list for PBXNativeTarget "DemoWatch Extension" */ = { 706 | isa = XCConfigurationList; 707 | buildConfigurations = ( 708 | D01EB0861F7BC0040042C837 /* Debug */, 709 | D01EB0871F7BC0040042C837 /* Release */, 710 | ); 711 | defaultConfigurationIsVisible = 0; 712 | defaultConfigurationName = Release; 713 | }; 714 | D01EB0891F7BC0040042C837 /* Build configuration list for PBXNativeTarget "DemoWatch" */ = { 715 | isa = XCConfigurationList; 716 | buildConfigurations = ( 717 | D01EB08A1F7BC0040042C837 /* Debug */, 718 | D01EB08B1F7BC0040042C837 /* Release */, 719 | ); 720 | defaultConfigurationIsVisible = 0; 721 | defaultConfigurationName = Release; 722 | }; 723 | D05E0D0F1F48466C0000EAF1 /* Build configuration list for PBXProject "Demo" */ = { 724 | isa = XCConfigurationList; 725 | buildConfigurations = ( 726 | D05E0D241F48466C0000EAF1 /* Debug */, 727 | D05E0D251F48466C0000EAF1 /* Release */, 728 | ); 729 | defaultConfigurationIsVisible = 0; 730 | defaultConfigurationName = Release; 731 | }; 732 | D05E0D261F48466C0000EAF1 /* Build configuration list for PBXNativeTarget "Demo" */ = { 733 | isa = XCConfigurationList; 734 | buildConfigurations = ( 735 | D05E0D271F48466C0000EAF1 /* Debug */, 736 | D05E0D281F48466C0000EAF1 /* Release */, 737 | ); 738 | defaultConfigurationIsVisible = 0; 739 | defaultConfigurationName = Release; 740 | }; 741 | /* End XCConfigurationList section */ 742 | }; 743 | rootObject = D05E0D0C1F48466B0000EAF1 /* Project object */; 744 | } 745 | --------------------------------------------------------------------------------