├── 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://github.com/radianttap/Avenue/releases)
2 | 
3 | [](https://github.com/radianttap/Avenue/blob/master/LICENSE)
4 |
5 | 
6 | [](https://github.com/Carthage/Carthage)
7 | [](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 |
--------------------------------------------------------------------------------