├── .gitignore
├── .swift-version
├── .travis.yml
├── Example
├── AppDelegate.swift
├── AppRoutes.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ ├── Login.imageset
│ │ ├── Contents.json
│ │ ├── login-1.png
│ │ ├── login-2.png
│ │ └── login.png
│ ├── Logout.imageset
│ │ ├── Contents.json
│ │ ├── logout-1.png
│ │ ├── logout-2.png
│ │ └── logout.png
│ ├── Privileged.imageset
│ │ ├── Contents.json
│ │ ├── privilege-1.png
│ │ ├── privilege-2.png
│ │ └── privilege.png
│ ├── Secret.imageset
│ │ ├── Contents.json
│ │ ├── secret-1.png
│ │ ├── secret-2.png
│ │ └── secret.png
│ └── Settings.imageset
│ │ ├── Contents.json
│ │ ├── settings-1.png
│ │ ├── settings-2.png
│ │ └── settings.png
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── HomeViewController.swift
├── Info.plist
├── LoginViewController.swift
├── PrivilegedInfoViewController.swift
├── SecretViewController.swift
└── SettingsViewController.swift
├── LICENSE
├── Package.swift
├── README.md
├── Routing.podspec
├── Routing.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── xcshareddata
│ └── xcschemes
│ ├── Example.xcscheme
│ ├── Routing OSX.xcscheme
│ ├── Routing iOS.xcscheme
│ ├── Routing tvOS.xcscheme
│ └── Routing watchOS.xcscheme
├── Routing.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Source
├── Info.plist
├── Routable.swift
├── Routing.h
├── Routing.swift
└── iOS.swift
└── Tests
├── Info.plist
└── RoutingTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | build/
4 | DerivedData
5 | *.pbxuser
6 | !default.pbxuser
7 | *.mode1v3
8 | !default.mode1v3
9 | *.mode2v3
10 | !default.mode2v3
11 | *.perspectivev3
12 | !default.perspectivev3
13 | xcuserdata
14 | *.xccheckout
15 | *.moved-aside
16 | *.xcuserstate
17 | *.xcscmblueprint
18 |
19 | Carthage/Build
20 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode10.2
3 |
4 | install:
5 | - gem install xcpretty
6 |
7 | env:
8 | global:
9 | - LC_CTYPE=en_US.UTF-8
10 | - LANG=en_US.UTF-8
11 | - FRAMEWORK_NAME="Routing"
12 | - IOS_SDK=iphonesimulator12.2
13 | - OSX_SDK=macosx10.14
14 | - TVOS_SDK=appletvsimulator12.2
15 | - WATCHOS_SDK=watchsimulator5.2
16 | matrix:
17 | - DESTINATION="OS=12.2,name=iPhone X" SCHEME="iOS" SDK="$IOS_SDK" ACTION="test"
18 | - DESTINATION="arch=x86_64" SCHEME="OSX" SDK="$OSX_SDK" ACTION="test"
19 | - DESTINATION="OS=12.2,name=Apple TV" SCHEME="tvOS" SDK="$TVOS_SDK" ACTION="build"
20 | - DESTINATION="OS=5.2,name=Apple Watch Series 4 - 44mm" SCHEME="watchOS" SDK="$WATCHOS_SDK" ACTION="build"
21 |
22 | script:
23 | - set -o pipefail
24 | - xcodebuild -version
25 | - xcodebuild -showsdks
26 | - xcodebuild
27 | -project "$FRAMEWORK_NAME.xcodeproj"
28 | -scheme "$FRAMEWORK_NAME $SCHEME"
29 | -sdk "$SDK"
30 | -destination "$DESTINATION"
31 | -configuration Debug
32 | ONLY_ACTIVE_ARCH=NO
33 | "$ACTION"
34 | | xcpretty -c
35 |
36 | after_success:
37 | - bash <(curl -s https://codecov.io/bash)
38 |
39 |
--------------------------------------------------------------------------------
/Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Routing
3 |
4 | @UIApplicationMain
5 | class AppDelegate: UIResponder, UIApplicationDelegate {
6 | var window: UIWindow?
7 |
8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
9 | registerRoutes()
10 | return true
11 | }
12 |
13 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
14 | return router["Views"].open(url)
15 | }
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/Example/AppRoutes.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Routing
3 |
4 | public let router = Routing()
5 |
6 | public func registerRoutes() {
7 | let presentationSetup: PresentationSetup = { vc, _, _ in
8 | vc.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel,
9 | target: vc,
10 | action: #selector(vc.cancel))
11 | }
12 |
13 | router.map("routingexample://push/login",
14 | source: .storyboard(storyboard: "Main", identifier: "LoginViewController", bundle: nil),
15 | style: .push(animated: true))
16 |
17 | router.map("routingexample://present/login",
18 | source: .storyboard(storyboard: "Main", identifier: "LoginViewController", bundle: nil),
19 | style: .inNavigationController(.present(animated: true)),
20 | setup: presentationSetup)
21 |
22 | router.map("routingexample://push/privilegedinfo",
23 | source: .storyboard(storyboard: "Main", identifier: "PrivilegedInfoViewController", bundle: nil),
24 | style: .push(animated: true))
25 |
26 | router.map("routingexample://present/privilegedinfo",
27 | source: .storyboard(storyboard: "Main", identifier: "PrivilegedInfoViewController", bundle: nil),
28 | style: .inNavigationController(.present(animated: true)),
29 | setup: presentationSetup)
30 |
31 | router.map("routingexample://push/settings",
32 | source: .storyboard(storyboard: "Main", identifier: "SettingsViewController", bundle: nil),
33 | style: .push(animated: true))
34 |
35 | router.map("routingexample://present/settings",
36 | source: .storyboard(storyboard: "Main", identifier: "SettingsViewController", bundle: nil),
37 | style: .inNavigationController(.present(animated: true)),
38 | setup: presentationSetup)
39 |
40 | router.proxy("routingexample://*", tags: ["Views"]) { route, _, _, next in
41 | if shouldPresentViewControllers {
42 | let route = route.replacingOccurrences(of: "push", with: "present")
43 | next((route, Parameters(), nil))
44 | } else {
45 | next(nil)
46 | }
47 | }
48 |
49 | router.proxy("/*/privilegedinfo", tags: ["Views"]) { route, parameters, any, next in
50 | if authenticated {
51 | next(nil)
52 | } else {
53 | next(("routingexample://present/login?callback=\(route)", parameters, any))
54 | }
55 | }
56 |
57 | router.proxy("/*", tags: ["Views"]) { route, parameters, any, next in
58 | print("opened: route (\(route)) with parameters (\(parameters)) & passing (\(String(describing: any)))")
59 | next(nil)
60 | }
61 | }
62 |
63 | extension UIViewController {
64 | @objc func cancel() {
65 | self.dismiss(animated: true, completion: nil)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "83.5x83.5",
66 | "scale" : "2x"
67 | }
68 | ],
69 | "info" : {
70 | "version" : 1,
71 | "author" : "xcode"
72 | }
73 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Login.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "login.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "login-1.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "login-2.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Login.imageset/login-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Login.imageset/login-1.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Login.imageset/login-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Login.imageset/login-2.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Login.imageset/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Login.imageset/login.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Logout.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "logout.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "logout-1.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "logout-2.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Logout.imageset/logout-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Logout.imageset/logout-1.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Logout.imageset/logout-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Logout.imageset/logout-2.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Logout.imageset/logout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Logout.imageset/logout.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Privileged.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "privilege.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "privilege-1.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "privilege-2.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Privileged.imageset/privilege-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Privileged.imageset/privilege-1.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Privileged.imageset/privilege-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Privileged.imageset/privilege-2.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Privileged.imageset/privilege.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Privileged.imageset/privilege.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Secret.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "secret.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "secret-1.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "secret-2.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Secret.imageset/secret-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Secret.imageset/secret-1.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Secret.imageset/secret-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Secret.imageset/secret-2.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Secret.imageset/secret.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Secret.imageset/secret.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Settings.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "settings.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "settings-1.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "settings-2.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Settings.imageset/settings-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Settings.imageset/settings-1.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Settings.imageset/settings-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Settings.imageset/settings-2.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Settings.imageset/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/obj-p/Routing/a534870f2d513cf3f183276b66ce18a634fd2dba/Example/Assets.xcassets/Settings.imageset/settings.png
--------------------------------------------------------------------------------
/Example/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 |
27 |
28 |
--------------------------------------------------------------------------------
/Example/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 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
--------------------------------------------------------------------------------
/Example/HomeViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class HomeViewController: UITableViewController {
4 | fileprivate enum Row: Int {
5 | case login
6 | case logout
7 | case privilegedInfo
8 | case settings
9 | }
10 |
11 | override func viewWillAppear(_ animated: Bool) {
12 | super.viewWillAppear(animated)
13 | self.tableView.reloadData()
14 | }
15 | }
16 |
17 | // MARK: Table View Delegate
18 | extension HomeViewController {
19 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
20 | switch Row(rawValue: (indexPath as NSIndexPath).row)! {
21 | case .login:
22 | router["Views"].open("routingexample://push/login")
23 | case .logout:
24 | authenticated = false
25 | self.tableView.reloadData()
26 | case .privilegedInfo:
27 | router["Views"].open("routingexample://push/privilegedinfo", passing: ["opened from the home view": Date()])
28 | case .settings:
29 | router["Views"].open("routingexample://push/settings")
30 | }
31 | }
32 | }
33 |
34 | // MARK: Table View Datasource
35 | extension HomeViewController {
36 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
37 | switch Row(rawValue: (indexPath as NSIndexPath).row)! {
38 | case .login where authenticated == true: fallthrough
39 | case .logout where authenticated == false:
40 | return 0.0
41 | default:
42 | return 44.0
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | com.routing.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.4.0
19 | CFBundleSignature
20 | ????
21 | CFBundleURLTypes
22 |
23 |
24 | CFBundleTypeRole
25 | Editor
26 | CFBundleURLName
27 | com.routing
28 | CFBundleURLSchemes
29 |
30 | routingexample
31 |
32 |
33 |
34 | CFBundleVersion
35 | $(CURRENT_PROJECT_VERSION)
36 | LSRequiresIPhoneOS
37 |
38 | UILaunchStoryboardName
39 | LaunchScreen
40 | UIMainStoryboardFile
41 | Main
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | UISupportedInterfaceOrientations~ipad
53 |
54 | UIInterfaceOrientationPortrait
55 | UIInterfaceOrientationPortraitUpsideDown
56 | UIInterfaceOrientationLandscapeLeft
57 | UIInterfaceOrientationLandscapeRight
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Example/LoginViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Routing
3 |
4 | var authenticated = false
5 |
6 | class LoginViewController: UIViewController, RoutingPresentationSetup {
7 | @IBOutlet weak var username: UITextField!
8 | @IBOutlet weak var password: UITextField!
9 | var callback: String?
10 |
11 | func setup(_ route: String, with parameters: Parameters, passing any: Any?) {
12 | if let callbackURL = parameters["callback"] {
13 | self.callback = callbackURL
14 | }
15 | }
16 |
17 | @IBAction func login() {
18 | guard let username = username.text, let password = password.text , username != "" && password != "" else {
19 | return
20 | }
21 |
22 | let completion = {
23 | if let callback = self.callback {
24 | router.open(callback, passing: ["opened from login": Date()])
25 | }
26 | }
27 |
28 | authenticated = true
29 | if isModal {
30 | self.dismiss(animated: true, completion: completion)
31 | } else {
32 | _ = self.navigationController?.popViewControllerAnimated(true, completion: completion)
33 | }
34 | }
35 | }
36 |
37 | extension LoginViewController {
38 | var isModal: Bool {
39 | if let presented = self.presentingViewController?.presentedViewController,
40 | let navigationController = self.navigationController
41 | , presented == navigationController {
42 | return true
43 | }
44 | return false
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Example/PrivilegedInfoViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Routing
3 |
4 | class PrivilegedInfoViewController: UIViewController, RouteOwner {
5 | var routeUUID: RouteUUID = ""
6 |
7 | override func viewDidLoad() {
8 | router.map("routingexample://push/secret",
9 | owner: self,
10 | source: .storyboard(storyboard: "Main", identifier: "SecretViewController", bundle: nil),
11 | style: .push(animated: true))
12 |
13 | routeUUID = router.map("routingexample://present/secret",
14 | source: .storyboard(storyboard: "Main", identifier: "SecretViewController", bundle: nil),
15 | style: .inNavigationController(.present(animated: true))) { vc, _, _ in
16 | vc.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel,
17 | target: vc,
18 | action: #selector(vc.cancel))
19 | }
20 | }
21 |
22 | deinit {
23 | router.dispose(of: routeUUID)
24 | }
25 | }
26 |
27 | extension PrivilegedInfoViewController: RoutingPresentationSetup {
28 |
29 | func setup(_ route: String, with parameters: Parameters, passing any: Any?) {
30 | if let any = any as? [String: Date] {
31 | print("Passed date: \(any)")
32 | }
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/Example/SecretViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class SecretViewController: UIViewController {}
4 |
--------------------------------------------------------------------------------
/Example/SettingsViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | var shouldPresentViewControllers = false
4 |
5 | class SettingsViewController: UITableViewController {
6 | @IBOutlet weak var presentViewControllers: UISwitch!
7 | override func viewDidLoad() {
8 | super.viewDidLoad()
9 | self.presentViewControllers.setOn(shouldPresentViewControllers, animated: false)
10 | }
11 |
12 | @IBAction func presentViewControllersChanged(_ sender: UISwitch) {
13 | shouldPresentViewControllers = sender.isOn
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License (MIT)
2 |
3 | Copyright (c) 2016 Jason Prasad
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 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | import PackageDescription
2 |
3 | let package = Package(
4 | name: "Routing"
5 | )
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Routing
2 |
3 | [](https://travis-ci.org/jjgp/Routing)
4 | [](http://codecov.io/github/jjgp/Routing)
5 | [](https://img.shields.io/badge/platform-ios%20%7C%20osx%20%7C%20tvos%20%7C%20watchos-lightgrey.svg?style=flat-square)
6 | [](https://github.com/Carthage/Carthage)
7 | [](https://cocoapods.org/pods/Routing)
8 | [](https://github.com/Routing/Routing/blob/master/LICENSE)
9 |
10 | ## Usage
11 |
12 | Let's say you have a table view controller that displays privileged information once a user selects a cell. An implementation of tableView:didSelectRowAtIndexPath: may look as such.
13 |
14 | ```swift
15 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
16 | switch Row(rawValue: indexPath.row)! {
17 | // ...
18 | case .PrivilegedInfo:
19 | router["Views"].open("routingexample://push/privilegedinfo")
20 | }
21 | // ...
22 | }
23 | ```
24 |
25 | Perhaps the privileged information is only available after a user authenticates with the service. After logging in we want the privileged information presented to the user right away. Without changing the above implementation we may proxy the intent and display a log in view, after which, a call back may present the privileged information screen.
26 |
27 | ```swift
28 | router.proxy("/*/privilegedinfo", tags: ["Views"]) { route, parameters, any, next in
29 | if authenticated {
30 | next(nil)
31 | } else {
32 | next(("routingexample://present/login?callback=\(route)", parameters, any))
33 | }
34 | }
35 | ```
36 |
37 | 
38 |
39 | Eventually we may need to support a deep link to the privileged information from outside of the application. This can be handled in the AppDelegate simply as follows.
40 |
41 | ```swift
42 | func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
43 | return router["Views"].open(url)
44 | }
45 | ```
46 |
47 | 
48 |
49 | An example of other routes in an application may look like this.
50 |
51 | ```swift
52 | let presentationSetup: PresentationSetup = { vc, _, _ in
53 | vc.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel,
54 | target: vc,
55 | action: #selector(vc.cancel))
56 | }
57 |
58 | router.map("routingexample://present/login",
59 | source: .storyboard(storyboard: "Main", identifier: "LoginViewController", bundle: nil),
60 | style: .inNavigationController(.present(animated: true)),
61 | setup: presentationSetup)
62 |
63 | router.map("routingexample://push/privilegedinfo",
64 | source: .storyboard(storyboard: "Main", identifier: "PrivilegedInfoViewController", bundle: nil),
65 | style: .push(animated: true))
66 |
67 | router.map("routingexample://present/settings",
68 | source: .storyboard(storyboard: "Main", identifier: "SettingsViewController", bundle: nil),
69 | style: .inNavigationController(.present(animated: true)),
70 | setup: presentationSetup)
71 |
72 | router.proxy("/*", tags: ["Views"]) { route, parameters, any, next in
73 | print("opened: route (\(route)) with parameters (\(parameters)) & passing (\(any))")
74 | next(nil)
75 | }
76 | ```
77 |
78 | At its simplest, Routing allows the association of string patterns to closures. This allows for the expression of intent in certain areas of code and the implementation of it in another. UI may only be concerned with expressing the intent of transitioning to another view and the business logic may be handled elsewhere. Routing allows for the explicit documentation of an application's behavior and views through mappings and proxies.
79 |
80 | ## Installation
81 |
82 | ### CocoaPods
83 |
84 | Via [CocoaPods](https://cocoapods.org/pods/Routing):
85 |
86 | ```ruby
87 | source 'https://github.com/CocoaPods/Specs.git'
88 | platform :ios, '10.0'
89 | use_frameworks!
90 |
91 | pod 'Routing', '~> 1.4.0'
92 | ```
93 |
94 | ### Carthage
95 |
96 | Via [Carthage](https://github.com/Carthage/Carthage):
97 |
98 | ```ogdl
99 | github "jjgp/Routing"
100 | ```
101 | ## Further Detail
102 |
103 | ### Map
104 |
105 | A router instance may map a string pattern to view controller navigation, as covered in the [Usage](#usage) section above, or just a closure as presented below. The closure will have four parameters. The route it matched, the parameters (both query and segments in the URL), any data passed through open, and a completion closure that must be called or the router will halt all subsequent calls to *#open*.
106 |
107 | ```swift
108 | router.map("routingexample://route/:argument") { route, parameters, any, completed in
109 | argument = parameters["argument"]
110 | completed()
111 | }
112 | ```
113 |
114 | ### Proxy
115 |
116 | A router instance may proxy any string pattern. The closure will also have four parameters. The route it matched, the parameters, any data passed, and a next closure. The next closure accepts a *ProxyCommit?* tuple with arguments *String*, *Parameters*, and *Any?*. If nil is passed to *Next* then the router will continue to another proxy if it exists or subsequently to a mapped route. If a proxy were to pass a *ProxyCommit* tuple to the next closure, the router will skip any subsequent proxy and attempt to match a mapped route. Failure to call next will halt the router and all subsequent calls to *#open*.
117 |
118 | ```swift
119 | router.proxy("routingexample://route/one") { route, parameters, any, next -> Void in
120 | next(("routingexample://route/two", parameters, any))
121 | }
122 | ```
123 |
124 | ### Order of Map or Proxy
125 |
126 | In general, the last call to register a map or proxy to the router will be first called in the event of a matched URL respectively. Proxies will be serviced first and then a map.
127 |
128 | ### Tags
129 |
130 | A tag may be passed to maps or proxies. The default tag for maps to view controller navigation is *"Views"*. Tags allow for the router to be subscripted to a specific context. If a router is subscripted with *"Views"*, then it will only attempt to find routes that are tagged as such.
131 |
132 | ```swift
133 | router.proxy("/*", tags: ["Views, Logs"]) { route, parameters, any, next in
134 | print("opened: route (\(route)) with parameters (\(parameters)) & passing (\(any))")
135 | next(nil)
136 | }
137 |
138 | router["Views", "Logs", "Actions"].open(url)
139 |
140 | router["Views"].open(url, passing: NSDate()) // pass any data if needed
141 |
142 | router.open(url) // - or - to search all routes...
143 |
144 | ```
145 |
146 | ### Route Owner
147 |
148 | Routes may have a *RouteOwner* specified when using *#map* or *#proxy*. When the *RouteOwner* is deallocated, the route is removed from the *Routing* instance.
149 |
150 | ```swift
151 | public protocol RouteOwner: class {}
152 |
153 | class PrivilegedInfoViewController: UIViewController, RouteOwner {
154 | override func viewDidLoad() {
155 | router.map("routingexample://secret",
156 | owner: self,
157 | source: .storyboard(storyboard: "Main", identifier: "SecretViewController", bundle: nil),
158 | style: .push(animated: true))
159 | }
160 | }
161 | ```
162 |
163 | ### RouteUUID and Disposing of a Route
164 |
165 | When a route is added via *#map* or *#proxy*, a *RouteUUID* is returned. This *RouteUUID* can be used to dispose of the route.
166 |
167 | ```swift
168 | routeUUID = router.map("routingexample://present/secret",
169 | source: .storyboard(storyboard: "Main", identifier: "SecretViewController", bundle: nil),
170 | style: .inNavigationController(.present(animated: true)))
171 |
172 | router.dispose(of: routeUUID)
173 | ```
174 |
175 | ### Callback Queues
176 |
177 | A queue may be passed to maps or proxies. This queue will be the queue that a *RouteHandler* or *ProxyHandler* closure is called back on. By default, maps that are used for view controller navigation are called back on the main queue.
178 |
179 | ```swift
180 | let callbackQueue = DispatchQueue(label: "Call Back Queue", attributes: [])
181 | router.map("routingexample://route", queue: callbackQueue) { _, _, _, completed in
182 | completed()
183 | }
184 | ```
185 |
186 | ### Presentation Setup
187 |
188 | View controllers mapped to the router will have the opportunity to be informed of a opened route through either a closure or the *RoutingPresentationSetup* protocol. In either implementation, the view controller will have access to the parameters passed through the URL. An example of the closure approach is in the [Usage](#usage) section above. The protocol looks as follows.
189 |
190 | ```swift
191 | class LoginViewController: UIViewController, RoutingPresentationSetup {
192 | var callback: String?
193 |
194 | func setup(_ route: String, with parameters: Parameters, passing any: Any?) {
195 | if let callbackURL = parameters["callback"] {
196 | self.callback = callbackURL
197 | }
198 |
199 | if let date = any as? NSDate {
200 | self.passedDate = date
201 | }
202 | }
203 | }
204 | ```
205 |
206 | ### Presentation Styles
207 |
208 | ```swift
209 | indirect public enum PresentationStyle {
210 | case show
211 | case showDetail
212 | case present(animated: Bool)
213 | case push(animated: Bool)
214 | case custom(custom: (presenting: UIViewController, presented: UIViewController, completed: Routing.Completed) -> Void)
215 | case inNavigationController(Routing.PresentationStyle)
216 | }
217 | ```
218 |
219 | The above presentation styles are made available. The recursive *.InNavigationController(PresentationStyle)* enumeration will result in the view controller being wrapped in a navigation controller before being presented in whatever fashion. There is also the ability to provide custom presentation styles.
220 |
221 | ### View Controller Sources
222 |
223 | The following view controller sources are utilized.
224 |
225 | ```swift
226 | public enum ControllerSource {
227 | case storyboard(storyboard: String, identifier: String, bundle: NSBundle?)
228 | case nib(controller: UIViewController.Type, name: String?, bundle: NSBundle?)
229 | case provided(() -> UIViewController)
230 | }
231 | ```
232 |
233 | ### Presentation Extensions
234 |
235 | The following has been extended to allow for a completion closure to be passed in.
236 |
237 | ```swift
238 | extension UIViewController {
239 | public func showViewController(vc: UIViewController, sender: AnyObject?, completion: Routing.Completed)
240 | public func showDetailViewController(vc: UIViewController, sender: AnyObject?, completion: Routing.Completed)
241 | }
242 |
243 | extension UINavigationController {
244 | public func pushViewController(vc: UIViewController, animated: Bool, completion: Routing.Completed)
245 | public func popViewControllerAnimated(animated: Bool, completion: Routing.Completed) -> UIViewController?
246 | public func popToViewControllerAnimated(viewController: UIViewController, animated: Bool, completion: Routing.Completed) -> [UIViewController]?
247 | public func popToRootViewControllerAnimated(animated: Bool, completion: Routing.Completed) -> [UIViewController]?
248 | }
249 | ```
250 |
--------------------------------------------------------------------------------
/Routing.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "Routing"
3 | s.version = "1.4.0"
4 | s.summary = "A Swift router implementation"
5 | s.description = <<-DESC
6 | Routing allows for routing URLs matched by string patterns to associated closures.
7 | DESC
8 | s.homepage = "https://github.com/jjgp/Routing"
9 | s.license = { :type => "MIT", :file => "LICENSE" }
10 | s.author = { "Jason Prasad" => "jasongprasad@gmail.com" }
11 | s.source = { :git => "https://github.com/jjgp/Routing.git", :tag => s.version.to_s }
12 | s.ios.deployment_target = '10.0'
13 | s.osx.deployment_target = '10.11'
14 | s.tvos.deployment_target = '12.2'
15 | s.watchos.deployment_target = '5.2'
16 | s.frameworks = 'Foundation'
17 | s.ios.framework = 'UIKit', 'QuartzCore'
18 | s.source_files = 'Source/Ro*.swift'
19 | s.ios.source_files = 'Source/iOS.swift'
20 | end
21 |
--------------------------------------------------------------------------------
/Routing.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 92FCFA051BB963CD00DF05C3 /* Routing.h in Headers */ = {isa = PBXBuildFile; fileRef = 92FCFA041BB963CD00DF05C3 /* Routing.h */; settings = {ATTRIBUTES = (Public, ); }; };
11 | 92FCFA0C1BB963CD00DF05C3 /* Routing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92FCFA011BB963CD00DF05C3 /* Routing.framework */; };
12 | 92FCFA1C1BB9658D00DF05C3 /* Routing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FCFA1B1BB9658D00DF05C3 /* Routing.swift */; };
13 | D24501A21D146B2F006BBDC8 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24501A11D146B2F006BBDC8 /* Routable.swift */; };
14 | D24501A31D14D1AF006BBDC8 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24501A11D146B2F006BBDC8 /* Routable.swift */; };
15 | D24501A41D14D1B0006BBDC8 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24501A11D146B2F006BBDC8 /* Routable.swift */; };
16 | D24501A51D14D1B1006BBDC8 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24501A11D146B2F006BBDC8 /* Routable.swift */; };
17 | D253AA411D8DE2C300F35644 /* RoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D253AA401D8DE2C300F35644 /* RoutingTests.swift */; };
18 | D253AA421D8DE2C300F35644 /* RoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D253AA401D8DE2C300F35644 /* RoutingTests.swift */; };
19 | D253AA431D8DE2D700F35644 /* RoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D253AA401D8DE2C300F35644 /* RoutingTests.swift */; };
20 | D273364E1CFE78980004A92A /* iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = D273364D1CFE78970004A92A /* iOS.swift */; };
21 | D295F41F1C6C2612003CAC2F /* Routing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D295F4151C6C2612003CAC2F /* Routing.framework */; };
22 | D295F43B1C6C265D003CAC2F /* Routing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D295F4311C6C265D003CAC2F /* Routing.framework */; };
23 | D295F44A1C6C27B1003CAC2F /* Routing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FCFA1B1BB9658D00DF05C3 /* Routing.swift */; };
24 | D295F44B1C6C27B2003CAC2F /* Routing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FCFA1B1BB9658D00DF05C3 /* Routing.swift */; };
25 | D295F44C1C6C27B4003CAC2F /* Routing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92FCFA1B1BB9658D00DF05C3 /* Routing.swift */; };
26 | D2A0FCF51D652D5E007BF1F8 /* SecretViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A0FCF41D652D5E007BF1F8 /* SecretViewController.swift */; };
27 | D2B653961D5799B300A38633 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B653951D5799B300A38633 /* HomeViewController.swift */; };
28 | D2B6539A1D57AB8C00A38633 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B653991D57AB8C00A38633 /* LoginViewController.swift */; };
29 | D2B6539C1D57ACE300A38633 /* PrivilegedInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B6539B1D57ACE300A38633 /* PrivilegedInfoViewController.swift */; };
30 | D2B6539E1D57AE1200A38633 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B6539D1D57AE1200A38633 /* SettingsViewController.swift */; };
31 | D2FCC6081D5593D5005C474B /* Routing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92FCFA011BB963CD00DF05C3 /* Routing.framework */; };
32 | D2FCC6091D5593D5005C474B /* Routing.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 92FCFA011BB963CD00DF05C3 /* Routing.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
33 | D2FCC61B1D5593F0005C474B /* AppRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FCC60D1D5593F0005C474B /* AppRoutes.swift */; };
34 | D2FCC61C1D5593F0005C474B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FCC60E1D5593F0005C474B /* AppDelegate.swift */; };
35 | D2FCC6231D5593F0005C474B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2FCC6161D5593F0005C474B /* Main.storyboard */; };
36 | D2FCC6241D5593F0005C474B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D2FCC6171D5593F0005C474B /* Assets.xcassets */; };
37 | D2FCC6251D5593F0005C474B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2FCC6191D5593F0005C474B /* LaunchScreen.storyboard */; };
38 | /* End PBXBuildFile section */
39 |
40 | /* Begin PBXContainerItemProxy section */
41 | 92FCFA0D1BB963CD00DF05C3 /* PBXContainerItemProxy */ = {
42 | isa = PBXContainerItemProxy;
43 | containerPortal = 92FCF9F81BB963CD00DF05C3 /* Project object */;
44 | proxyType = 1;
45 | remoteGlobalIDString = 92FCFA001BB963CD00DF05C3;
46 | remoteInfo = Routing;
47 | };
48 | D24099D11D55958B009A3FD4 /* PBXContainerItemProxy */ = {
49 | isa = PBXContainerItemProxy;
50 | containerPortal = 92FCF9F81BB963CD00DF05C3 /* Project object */;
51 | proxyType = 1;
52 | remoteGlobalIDString = D2FCC5F51D5593BF005C474B;
53 | remoteInfo = Example;
54 | };
55 | D295F4201C6C2612003CAC2F /* PBXContainerItemProxy */ = {
56 | isa = PBXContainerItemProxy;
57 | containerPortal = 92FCF9F81BB963CD00DF05C3 /* Project object */;
58 | proxyType = 1;
59 | remoteGlobalIDString = D295F4141C6C2612003CAC2F;
60 | remoteInfo = "Routing tvOS";
61 | };
62 | D295F43C1C6C265D003CAC2F /* PBXContainerItemProxy */ = {
63 | isa = PBXContainerItemProxy;
64 | containerPortal = 92FCF9F81BB963CD00DF05C3 /* Project object */;
65 | proxyType = 1;
66 | remoteGlobalIDString = D295F4301C6C265D003CAC2F;
67 | remoteInfo = "Routing OSX";
68 | };
69 | D2FCC60A1D5593D5005C474B /* PBXContainerItemProxy */ = {
70 | isa = PBXContainerItemProxy;
71 | containerPortal = 92FCF9F81BB963CD00DF05C3 /* Project object */;
72 | proxyType = 1;
73 | remoteGlobalIDString = 92FCFA001BB963CD00DF05C3;
74 | remoteInfo = "Routing iOS";
75 | };
76 | /* End PBXContainerItemProxy section */
77 |
78 | /* Begin PBXCopyFilesBuildPhase section */
79 | D2FCC60C1D5593D5005C474B /* Embed Frameworks */ = {
80 | isa = PBXCopyFilesBuildPhase;
81 | buildActionMask = 2147483647;
82 | dstPath = "";
83 | dstSubfolderSpec = 10;
84 | files = (
85 | D2FCC6091D5593D5005C474B /* Routing.framework in Embed Frameworks */,
86 | );
87 | name = "Embed Frameworks";
88 | runOnlyForDeploymentPostprocessing = 0;
89 | };
90 | /* End PBXCopyFilesBuildPhase section */
91 |
92 | /* Begin PBXFileReference section */
93 | 92FCFA011BB963CD00DF05C3 /* Routing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Routing.framework; sourceTree = BUILT_PRODUCTS_DIR; };
94 | 92FCFA041BB963CD00DF05C3 /* Routing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Routing.h; sourceTree = ""; };
95 | 92FCFA061BB963CD00DF05C3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
96 | 92FCFA0B1BB963CD00DF05C3 /* Routing iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Routing iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
97 | 92FCFA121BB963CD00DF05C3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
98 | 92FCFA1B1BB9658D00DF05C3 /* Routing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Routing.swift; sourceTree = ""; };
99 | D2204C7F1C8FE1B400ABA4E5 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
100 | D24501A11D146B2F006BBDC8 /* Routable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Routable.swift; sourceTree = ""; };
101 | D253AA401D8DE2C300F35644 /* RoutingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutingTests.swift; sourceTree = ""; };
102 | D273364D1CFE78970004A92A /* iOS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iOS.swift; sourceTree = ""; };
103 | D295F4081C6C25ED003CAC2F /* Routing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Routing.framework; sourceTree = BUILT_PRODUCTS_DIR; };
104 | D295F4151C6C2612003CAC2F /* Routing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Routing.framework; sourceTree = BUILT_PRODUCTS_DIR; };
105 | D295F41E1C6C2612003CAC2F /* Routing tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Routing tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
106 | D295F4311C6C265D003CAC2F /* Routing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Routing.framework; sourceTree = BUILT_PRODUCTS_DIR; };
107 | D295F43A1C6C265D003CAC2F /* Routing OSX Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Routing OSX Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
108 | D2A0FCF41D652D5E007BF1F8 /* SecretViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretViewController.swift; sourceTree = ""; };
109 | D2B653951D5799B300A38633 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; };
110 | D2B653991D57AB8C00A38633 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; };
111 | D2B6539B1D57ACE300A38633 /* PrivilegedInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivilegedInfoViewController.swift; sourceTree = ""; };
112 | D2B6539D1D57AE1200A38633 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; };
113 | D2FCC5F61D5593BF005C474B /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
114 | D2FCC60D1D5593F0005C474B /* AppRoutes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = ""; };
115 | D2FCC60E1D5593F0005C474B /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
116 | D2FCC6151D5593F0005C474B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
117 | D2FCC6171D5593F0005C474B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
118 | D2FCC6181D5593F0005C474B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
119 | D2FCC61A1D5593F0005C474B /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
120 | /* End PBXFileReference section */
121 |
122 | /* Begin PBXFrameworksBuildPhase section */
123 | 92FCF9FD1BB963CD00DF05C3 /* Frameworks */ = {
124 | isa = PBXFrameworksBuildPhase;
125 | buildActionMask = 2147483647;
126 | files = (
127 | );
128 | runOnlyForDeploymentPostprocessing = 0;
129 | };
130 | 92FCFA081BB963CD00DF05C3 /* Frameworks */ = {
131 | isa = PBXFrameworksBuildPhase;
132 | buildActionMask = 2147483647;
133 | files = (
134 | 92FCFA0C1BB963CD00DF05C3 /* Routing.framework in Frameworks */,
135 | );
136 | runOnlyForDeploymentPostprocessing = 0;
137 | };
138 | D295F4041C6C25ED003CAC2F /* Frameworks */ = {
139 | isa = PBXFrameworksBuildPhase;
140 | buildActionMask = 2147483647;
141 | files = (
142 | );
143 | runOnlyForDeploymentPostprocessing = 0;
144 | };
145 | D295F4111C6C2612003CAC2F /* Frameworks */ = {
146 | isa = PBXFrameworksBuildPhase;
147 | buildActionMask = 2147483647;
148 | files = (
149 | );
150 | runOnlyForDeploymentPostprocessing = 0;
151 | };
152 | D295F41B1C6C2612003CAC2F /* Frameworks */ = {
153 | isa = PBXFrameworksBuildPhase;
154 | buildActionMask = 2147483647;
155 | files = (
156 | D295F41F1C6C2612003CAC2F /* Routing.framework in Frameworks */,
157 | );
158 | runOnlyForDeploymentPostprocessing = 0;
159 | };
160 | D295F42D1C6C265D003CAC2F /* Frameworks */ = {
161 | isa = PBXFrameworksBuildPhase;
162 | buildActionMask = 2147483647;
163 | files = (
164 | );
165 | runOnlyForDeploymentPostprocessing = 0;
166 | };
167 | D295F4371C6C265D003CAC2F /* Frameworks */ = {
168 | isa = PBXFrameworksBuildPhase;
169 | buildActionMask = 2147483647;
170 | files = (
171 | D295F43B1C6C265D003CAC2F /* Routing.framework in Frameworks */,
172 | );
173 | runOnlyForDeploymentPostprocessing = 0;
174 | };
175 | D2FCC5F31D5593BF005C474B /* Frameworks */ = {
176 | isa = PBXFrameworksBuildPhase;
177 | buildActionMask = 2147483647;
178 | files = (
179 | D2FCC6081D5593D5005C474B /* Routing.framework in Frameworks */,
180 | );
181 | runOnlyForDeploymentPostprocessing = 0;
182 | };
183 | /* End PBXFrameworksBuildPhase section */
184 |
185 | /* Begin PBXGroup section */
186 | 92FCF9F71BB963CD00DF05C3 = {
187 | isa = PBXGroup;
188 | children = (
189 | D2F5D0591C570ED600317401 /* Frameworks */,
190 | D2FCC5F71D5593BF005C474B /* Example */,
191 | 92FCFA031BB963CD00DF05C3 /* Source */,
192 | 92FCFA0F1BB963CD00DF05C3 /* Tests */,
193 | 92FCFA021BB963CD00DF05C3 /* Products */,
194 | );
195 | sourceTree = "";
196 | };
197 | 92FCFA021BB963CD00DF05C3 /* Products */ = {
198 | isa = PBXGroup;
199 | children = (
200 | 92FCFA011BB963CD00DF05C3 /* Routing.framework */,
201 | 92FCFA0B1BB963CD00DF05C3 /* Routing iOS Tests.xctest */,
202 | D295F4081C6C25ED003CAC2F /* Routing.framework */,
203 | D295F4151C6C2612003CAC2F /* Routing.framework */,
204 | D295F41E1C6C2612003CAC2F /* Routing tvOS Tests.xctest */,
205 | D295F4311C6C265D003CAC2F /* Routing.framework */,
206 | D295F43A1C6C265D003CAC2F /* Routing OSX Tests.xctest */,
207 | D2FCC5F61D5593BF005C474B /* Example.app */,
208 | );
209 | name = Products;
210 | sourceTree = "";
211 | };
212 | 92FCFA031BB963CD00DF05C3 /* Source */ = {
213 | isa = PBXGroup;
214 | children = (
215 | 92FCFA041BB963CD00DF05C3 /* Routing.h */,
216 | D273364D1CFE78970004A92A /* iOS.swift */,
217 | D24501A11D146B2F006BBDC8 /* Routable.swift */,
218 | 92FCFA1B1BB9658D00DF05C3 /* Routing.swift */,
219 | 92FCFA061BB963CD00DF05C3 /* Info.plist */,
220 | );
221 | path = Source;
222 | sourceTree = "";
223 | };
224 | 92FCFA0F1BB963CD00DF05C3 /* Tests */ = {
225 | isa = PBXGroup;
226 | children = (
227 | D253AA401D8DE2C300F35644 /* RoutingTests.swift */,
228 | 92FCFA121BB963CD00DF05C3 /* Info.plist */,
229 | );
230 | path = Tests;
231 | sourceTree = "";
232 | };
233 | D2F5D0591C570ED600317401 /* Frameworks */ = {
234 | isa = PBXGroup;
235 | children = (
236 | D2204C7F1C8FE1B400ABA4E5 /* UIKit.framework */,
237 | );
238 | name = Frameworks;
239 | sourceTree = "";
240 | };
241 | D2FCC5F71D5593BF005C474B /* Example */ = {
242 | isa = PBXGroup;
243 | children = (
244 | D2FCC60E1D5593F0005C474B /* AppDelegate.swift */,
245 | D2FCC60D1D5593F0005C474B /* AppRoutes.swift */,
246 | D2B653951D5799B300A38633 /* HomeViewController.swift */,
247 | D2B653991D57AB8C00A38633 /* LoginViewController.swift */,
248 | D2B6539B1D57ACE300A38633 /* PrivilegedInfoViewController.swift */,
249 | D2B6539D1D57AE1200A38633 /* SettingsViewController.swift */,
250 | D2A0FCF41D652D5E007BF1F8 /* SecretViewController.swift */,
251 | D2FCC6161D5593F0005C474B /* Main.storyboard */,
252 | D2FCC6171D5593F0005C474B /* Assets.xcassets */,
253 | D2FCC6191D5593F0005C474B /* LaunchScreen.storyboard */,
254 | D2FCC61A1D5593F0005C474B /* Info.plist */,
255 | );
256 | path = Example;
257 | sourceTree = "";
258 | };
259 | /* End PBXGroup section */
260 |
261 | /* Begin PBXHeadersBuildPhase section */
262 | 92FCF9FE1BB963CD00DF05C3 /* Headers */ = {
263 | isa = PBXHeadersBuildPhase;
264 | buildActionMask = 2147483647;
265 | files = (
266 | 92FCFA051BB963CD00DF05C3 /* Routing.h in Headers */,
267 | );
268 | runOnlyForDeploymentPostprocessing = 0;
269 | };
270 | D295F4051C6C25ED003CAC2F /* Headers */ = {
271 | isa = PBXHeadersBuildPhase;
272 | buildActionMask = 2147483647;
273 | files = (
274 | );
275 | runOnlyForDeploymentPostprocessing = 0;
276 | };
277 | D295F4121C6C2612003CAC2F /* Headers */ = {
278 | isa = PBXHeadersBuildPhase;
279 | buildActionMask = 2147483647;
280 | files = (
281 | );
282 | runOnlyForDeploymentPostprocessing = 0;
283 | };
284 | D295F42E1C6C265D003CAC2F /* Headers */ = {
285 | isa = PBXHeadersBuildPhase;
286 | buildActionMask = 2147483647;
287 | files = (
288 | );
289 | runOnlyForDeploymentPostprocessing = 0;
290 | };
291 | /* End PBXHeadersBuildPhase section */
292 |
293 | /* Begin PBXNativeTarget section */
294 | 92FCFA001BB963CD00DF05C3 /* Routing iOS */ = {
295 | isa = PBXNativeTarget;
296 | buildConfigurationList = 92FCFA151BB963CD00DF05C3 /* Build configuration list for PBXNativeTarget "Routing iOS" */;
297 | buildPhases = (
298 | 92FCF9FC1BB963CD00DF05C3 /* Sources */,
299 | 92FCF9FD1BB963CD00DF05C3 /* Frameworks */,
300 | 92FCF9FE1BB963CD00DF05C3 /* Headers */,
301 | 92FCF9FF1BB963CD00DF05C3 /* Resources */,
302 | );
303 | buildRules = (
304 | );
305 | dependencies = (
306 | );
307 | name = "Routing iOS";
308 | productName = Routing;
309 | productReference = 92FCFA011BB963CD00DF05C3 /* Routing.framework */;
310 | productType = "com.apple.product-type.framework";
311 | };
312 | 92FCFA0A1BB963CD00DF05C3 /* Routing iOS Tests */ = {
313 | isa = PBXNativeTarget;
314 | buildConfigurationList = 92FCFA181BB963CD00DF05C3 /* Build configuration list for PBXNativeTarget "Routing iOS Tests" */;
315 | buildPhases = (
316 | 92FCFA071BB963CD00DF05C3 /* Sources */,
317 | 92FCFA081BB963CD00DF05C3 /* Frameworks */,
318 | 92FCFA091BB963CD00DF05C3 /* Resources */,
319 | );
320 | buildRules = (
321 | );
322 | dependencies = (
323 | 92FCFA0E1BB963CD00DF05C3 /* PBXTargetDependency */,
324 | D24099D21D55958B009A3FD4 /* PBXTargetDependency */,
325 | );
326 | name = "Routing iOS Tests";
327 | productName = RoutingTests;
328 | productReference = 92FCFA0B1BB963CD00DF05C3 /* Routing iOS Tests.xctest */;
329 | productType = "com.apple.product-type.bundle.unit-test";
330 | };
331 | D295F4071C6C25ED003CAC2F /* Routing watchOS */ = {
332 | isa = PBXNativeTarget;
333 | buildConfigurationList = D295F40F1C6C25ED003CAC2F /* Build configuration list for PBXNativeTarget "Routing watchOS" */;
334 | buildPhases = (
335 | D295F4031C6C25ED003CAC2F /* Sources */,
336 | D295F4041C6C25ED003CAC2F /* Frameworks */,
337 | D295F4051C6C25ED003CAC2F /* Headers */,
338 | D295F4061C6C25ED003CAC2F /* Resources */,
339 | );
340 | buildRules = (
341 | );
342 | dependencies = (
343 | );
344 | name = "Routing watchOS";
345 | productName = "Routing watchOS";
346 | productReference = D295F4081C6C25ED003CAC2F /* Routing.framework */;
347 | productType = "com.apple.product-type.framework";
348 | };
349 | D295F4141C6C2612003CAC2F /* Routing tvOS */ = {
350 | isa = PBXNativeTarget;
351 | buildConfigurationList = D295F4261C6C2612003CAC2F /* Build configuration list for PBXNativeTarget "Routing tvOS" */;
352 | buildPhases = (
353 | D295F4101C6C2612003CAC2F /* Sources */,
354 | D295F4111C6C2612003CAC2F /* Frameworks */,
355 | D295F4121C6C2612003CAC2F /* Headers */,
356 | D295F4131C6C2612003CAC2F /* Resources */,
357 | );
358 | buildRules = (
359 | );
360 | dependencies = (
361 | );
362 | name = "Routing tvOS";
363 | productName = "Routing tvOS";
364 | productReference = D295F4151C6C2612003CAC2F /* Routing.framework */;
365 | productType = "com.apple.product-type.framework";
366 | };
367 | D295F41D1C6C2612003CAC2F /* Routing tvOS Tests */ = {
368 | isa = PBXNativeTarget;
369 | buildConfigurationList = D295F4291C6C2612003CAC2F /* Build configuration list for PBXNativeTarget "Routing tvOS Tests" */;
370 | buildPhases = (
371 | D295F41A1C6C2612003CAC2F /* Sources */,
372 | D295F41B1C6C2612003CAC2F /* Frameworks */,
373 | D295F41C1C6C2612003CAC2F /* Resources */,
374 | );
375 | buildRules = (
376 | );
377 | dependencies = (
378 | D295F4211C6C2612003CAC2F /* PBXTargetDependency */,
379 | );
380 | name = "Routing tvOS Tests";
381 | productName = "Routing tvOSTests";
382 | productReference = D295F41E1C6C2612003CAC2F /* Routing tvOS Tests.xctest */;
383 | productType = "com.apple.product-type.bundle.unit-test";
384 | };
385 | D295F4301C6C265D003CAC2F /* Routing OSX */ = {
386 | isa = PBXNativeTarget;
387 | buildConfigurationList = D295F4421C6C265D003CAC2F /* Build configuration list for PBXNativeTarget "Routing OSX" */;
388 | buildPhases = (
389 | D295F42C1C6C265D003CAC2F /* Sources */,
390 | D295F42D1C6C265D003CAC2F /* Frameworks */,
391 | D295F42E1C6C265D003CAC2F /* Headers */,
392 | D295F42F1C6C265D003CAC2F /* Resources */,
393 | );
394 | buildRules = (
395 | );
396 | dependencies = (
397 | );
398 | name = "Routing OSX";
399 | productName = "Routing OSX";
400 | productReference = D295F4311C6C265D003CAC2F /* Routing.framework */;
401 | productType = "com.apple.product-type.framework";
402 | };
403 | D295F4391C6C265D003CAC2F /* Routing OSX Tests */ = {
404 | isa = PBXNativeTarget;
405 | buildConfigurationList = D295F4451C6C265D003CAC2F /* Build configuration list for PBXNativeTarget "Routing OSX Tests" */;
406 | buildPhases = (
407 | D295F4361C6C265D003CAC2F /* Sources */,
408 | D295F4371C6C265D003CAC2F /* Frameworks */,
409 | D295F4381C6C265D003CAC2F /* Resources */,
410 | );
411 | buildRules = (
412 | );
413 | dependencies = (
414 | D295F43D1C6C265D003CAC2F /* PBXTargetDependency */,
415 | );
416 | name = "Routing OSX Tests";
417 | productName = "Routing OSXTests";
418 | productReference = D295F43A1C6C265D003CAC2F /* Routing OSX Tests.xctest */;
419 | productType = "com.apple.product-type.bundle.unit-test";
420 | };
421 | D2FCC5F51D5593BF005C474B /* Example */ = {
422 | isa = PBXNativeTarget;
423 | buildConfigurationList = D2FCC6071D5593BF005C474B /* Build configuration list for PBXNativeTarget "Example" */;
424 | buildPhases = (
425 | D2FCC5F21D5593BF005C474B /* Sources */,
426 | D2FCC5F31D5593BF005C474B /* Frameworks */,
427 | D2FCC5F41D5593BF005C474B /* Resources */,
428 | D2FCC60C1D5593D5005C474B /* Embed Frameworks */,
429 | );
430 | buildRules = (
431 | );
432 | dependencies = (
433 | D2FCC60B1D5593D5005C474B /* PBXTargetDependency */,
434 | );
435 | name = Example;
436 | productName = Example;
437 | productReference = D2FCC5F61D5593BF005C474B /* Example.app */;
438 | productType = "com.apple.product-type.application";
439 | };
440 | /* End PBXNativeTarget section */
441 |
442 | /* Begin PBXProject section */
443 | 92FCF9F81BB963CD00DF05C3 /* Project object */ = {
444 | isa = PBXProject;
445 | attributes = {
446 | LastSwiftUpdateCheck = 0730;
447 | LastUpgradeCheck = 1020;
448 | ORGANIZATIONNAME = Routing;
449 | TargetAttributes = {
450 | 92FCFA001BB963CD00DF05C3 = {
451 | CreatedOnToolsVersion = 7.0;
452 | LastSwiftMigration = 0800;
453 | };
454 | 92FCFA0A1BB963CD00DF05C3 = {
455 | CreatedOnToolsVersion = 7.0;
456 | LastSwiftMigration = 0800;
457 | TestTargetID = D2FCC5F51D5593BF005C474B;
458 | };
459 | D295F4071C6C25ED003CAC2F = {
460 | CreatedOnToolsVersion = 7.2.1;
461 | LastSwiftMigration = 0800;
462 | };
463 | D295F4141C6C2612003CAC2F = {
464 | CreatedOnToolsVersion = 7.2.1;
465 | LastSwiftMigration = 0800;
466 | };
467 | D295F41D1C6C2612003CAC2F = {
468 | CreatedOnToolsVersion = 7.2.1;
469 | LastSwiftMigration = 0800;
470 | };
471 | D295F4301C6C265D003CAC2F = {
472 | CreatedOnToolsVersion = 7.2.1;
473 | LastSwiftMigration = 0800;
474 | };
475 | D295F4391C6C265D003CAC2F = {
476 | CreatedOnToolsVersion = 7.2.1;
477 | LastSwiftMigration = 0800;
478 | };
479 | D2FCC5F51D5593BF005C474B = {
480 | CreatedOnToolsVersion = 7.3.1;
481 | LastSwiftMigration = 0800;
482 | };
483 | };
484 | };
485 | buildConfigurationList = 92FCF9FB1BB963CD00DF05C3 /* Build configuration list for PBXProject "Routing" */;
486 | compatibilityVersion = "Xcode 3.2";
487 | developmentRegion = en;
488 | hasScannedForEncodings = 0;
489 | knownRegions = (
490 | en,
491 | Base,
492 | );
493 | mainGroup = 92FCF9F71BB963CD00DF05C3;
494 | productRefGroup = 92FCFA021BB963CD00DF05C3 /* Products */;
495 | projectDirPath = "";
496 | projectRoot = "";
497 | targets = (
498 | D2FCC5F51D5593BF005C474B /* Example */,
499 | 92FCFA001BB963CD00DF05C3 /* Routing iOS */,
500 | 92FCFA0A1BB963CD00DF05C3 /* Routing iOS Tests */,
501 | D295F4301C6C265D003CAC2F /* Routing OSX */,
502 | D295F4391C6C265D003CAC2F /* Routing OSX Tests */,
503 | D295F4141C6C2612003CAC2F /* Routing tvOS */,
504 | D295F41D1C6C2612003CAC2F /* Routing tvOS Tests */,
505 | D295F4071C6C25ED003CAC2F /* Routing watchOS */,
506 | );
507 | };
508 | /* End PBXProject section */
509 |
510 | /* Begin PBXResourcesBuildPhase section */
511 | 92FCF9FF1BB963CD00DF05C3 /* Resources */ = {
512 | isa = PBXResourcesBuildPhase;
513 | buildActionMask = 2147483647;
514 | files = (
515 | );
516 | runOnlyForDeploymentPostprocessing = 0;
517 | };
518 | 92FCFA091BB963CD00DF05C3 /* Resources */ = {
519 | isa = PBXResourcesBuildPhase;
520 | buildActionMask = 2147483647;
521 | files = (
522 | );
523 | runOnlyForDeploymentPostprocessing = 0;
524 | };
525 | D295F4061C6C25ED003CAC2F /* Resources */ = {
526 | isa = PBXResourcesBuildPhase;
527 | buildActionMask = 2147483647;
528 | files = (
529 | );
530 | runOnlyForDeploymentPostprocessing = 0;
531 | };
532 | D295F4131C6C2612003CAC2F /* Resources */ = {
533 | isa = PBXResourcesBuildPhase;
534 | buildActionMask = 2147483647;
535 | files = (
536 | );
537 | runOnlyForDeploymentPostprocessing = 0;
538 | };
539 | D295F41C1C6C2612003CAC2F /* Resources */ = {
540 | isa = PBXResourcesBuildPhase;
541 | buildActionMask = 2147483647;
542 | files = (
543 | );
544 | runOnlyForDeploymentPostprocessing = 0;
545 | };
546 | D295F42F1C6C265D003CAC2F /* Resources */ = {
547 | isa = PBXResourcesBuildPhase;
548 | buildActionMask = 2147483647;
549 | files = (
550 | );
551 | runOnlyForDeploymentPostprocessing = 0;
552 | };
553 | D295F4381C6C265D003CAC2F /* Resources */ = {
554 | isa = PBXResourcesBuildPhase;
555 | buildActionMask = 2147483647;
556 | files = (
557 | );
558 | runOnlyForDeploymentPostprocessing = 0;
559 | };
560 | D2FCC5F41D5593BF005C474B /* Resources */ = {
561 | isa = PBXResourcesBuildPhase;
562 | buildActionMask = 2147483647;
563 | files = (
564 | D2FCC6251D5593F0005C474B /* LaunchScreen.storyboard in Resources */,
565 | D2FCC6241D5593F0005C474B /* Assets.xcassets in Resources */,
566 | D2FCC6231D5593F0005C474B /* Main.storyboard in Resources */,
567 | );
568 | runOnlyForDeploymentPostprocessing = 0;
569 | };
570 | /* End PBXResourcesBuildPhase section */
571 |
572 | /* Begin PBXSourcesBuildPhase section */
573 | 92FCF9FC1BB963CD00DF05C3 /* Sources */ = {
574 | isa = PBXSourcesBuildPhase;
575 | buildActionMask = 2147483647;
576 | files = (
577 | D24501A21D146B2F006BBDC8 /* Routable.swift in Sources */,
578 | D273364E1CFE78980004A92A /* iOS.swift in Sources */,
579 | 92FCFA1C1BB9658D00DF05C3 /* Routing.swift in Sources */,
580 | );
581 | runOnlyForDeploymentPostprocessing = 0;
582 | };
583 | 92FCFA071BB963CD00DF05C3 /* Sources */ = {
584 | isa = PBXSourcesBuildPhase;
585 | buildActionMask = 2147483647;
586 | files = (
587 | D253AA411D8DE2C300F35644 /* RoutingTests.swift in Sources */,
588 | );
589 | runOnlyForDeploymentPostprocessing = 0;
590 | };
591 | D295F4031C6C25ED003CAC2F /* Sources */ = {
592 | isa = PBXSourcesBuildPhase;
593 | buildActionMask = 2147483647;
594 | files = (
595 | D295F44C1C6C27B4003CAC2F /* Routing.swift in Sources */,
596 | D24501A51D14D1B1006BBDC8 /* Routable.swift in Sources */,
597 | );
598 | runOnlyForDeploymentPostprocessing = 0;
599 | };
600 | D295F4101C6C2612003CAC2F /* Sources */ = {
601 | isa = PBXSourcesBuildPhase;
602 | buildActionMask = 2147483647;
603 | files = (
604 | D295F44A1C6C27B1003CAC2F /* Routing.swift in Sources */,
605 | D24501A41D14D1B0006BBDC8 /* Routable.swift in Sources */,
606 | );
607 | runOnlyForDeploymentPostprocessing = 0;
608 | };
609 | D295F41A1C6C2612003CAC2F /* Sources */ = {
610 | isa = PBXSourcesBuildPhase;
611 | buildActionMask = 2147483647;
612 | files = (
613 | D253AA431D8DE2D700F35644 /* RoutingTests.swift in Sources */,
614 | );
615 | runOnlyForDeploymentPostprocessing = 0;
616 | };
617 | D295F42C1C6C265D003CAC2F /* Sources */ = {
618 | isa = PBXSourcesBuildPhase;
619 | buildActionMask = 2147483647;
620 | files = (
621 | D295F44B1C6C27B2003CAC2F /* Routing.swift in Sources */,
622 | D24501A31D14D1AF006BBDC8 /* Routable.swift in Sources */,
623 | );
624 | runOnlyForDeploymentPostprocessing = 0;
625 | };
626 | D295F4361C6C265D003CAC2F /* Sources */ = {
627 | isa = PBXSourcesBuildPhase;
628 | buildActionMask = 2147483647;
629 | files = (
630 | D253AA421D8DE2C300F35644 /* RoutingTests.swift in Sources */,
631 | );
632 | runOnlyForDeploymentPostprocessing = 0;
633 | };
634 | D2FCC5F21D5593BF005C474B /* Sources */ = {
635 | isa = PBXSourcesBuildPhase;
636 | buildActionMask = 2147483647;
637 | files = (
638 | D2B6539C1D57ACE300A38633 /* PrivilegedInfoViewController.swift in Sources */,
639 | D2A0FCF51D652D5E007BF1F8 /* SecretViewController.swift in Sources */,
640 | D2B6539A1D57AB8C00A38633 /* LoginViewController.swift in Sources */,
641 | D2FCC61C1D5593F0005C474B /* AppDelegate.swift in Sources */,
642 | D2FCC61B1D5593F0005C474B /* AppRoutes.swift in Sources */,
643 | D2B653961D5799B300A38633 /* HomeViewController.swift in Sources */,
644 | D2B6539E1D57AE1200A38633 /* SettingsViewController.swift in Sources */,
645 | );
646 | runOnlyForDeploymentPostprocessing = 0;
647 | };
648 | /* End PBXSourcesBuildPhase section */
649 |
650 | /* Begin PBXTargetDependency section */
651 | 92FCFA0E1BB963CD00DF05C3 /* PBXTargetDependency */ = {
652 | isa = PBXTargetDependency;
653 | target = 92FCFA001BB963CD00DF05C3 /* Routing iOS */;
654 | targetProxy = 92FCFA0D1BB963CD00DF05C3 /* PBXContainerItemProxy */;
655 | };
656 | D24099D21D55958B009A3FD4 /* PBXTargetDependency */ = {
657 | isa = PBXTargetDependency;
658 | target = D2FCC5F51D5593BF005C474B /* Example */;
659 | targetProxy = D24099D11D55958B009A3FD4 /* PBXContainerItemProxy */;
660 | };
661 | D295F4211C6C2612003CAC2F /* PBXTargetDependency */ = {
662 | isa = PBXTargetDependency;
663 | target = D295F4141C6C2612003CAC2F /* Routing tvOS */;
664 | targetProxy = D295F4201C6C2612003CAC2F /* PBXContainerItemProxy */;
665 | };
666 | D295F43D1C6C265D003CAC2F /* PBXTargetDependency */ = {
667 | isa = PBXTargetDependency;
668 | target = D295F4301C6C265D003CAC2F /* Routing OSX */;
669 | targetProxy = D295F43C1C6C265D003CAC2F /* PBXContainerItemProxy */;
670 | };
671 | D2FCC60B1D5593D5005C474B /* PBXTargetDependency */ = {
672 | isa = PBXTargetDependency;
673 | target = 92FCFA001BB963CD00DF05C3 /* Routing iOS */;
674 | targetProxy = D2FCC60A1D5593D5005C474B /* PBXContainerItemProxy */;
675 | };
676 | /* End PBXTargetDependency section */
677 |
678 | /* Begin PBXVariantGroup section */
679 | D2FCC6161D5593F0005C474B /* Main.storyboard */ = {
680 | isa = PBXVariantGroup;
681 | children = (
682 | D2FCC6151D5593F0005C474B /* Base */,
683 | );
684 | name = Main.storyboard;
685 | sourceTree = "";
686 | };
687 | D2FCC6191D5593F0005C474B /* LaunchScreen.storyboard */ = {
688 | isa = PBXVariantGroup;
689 | children = (
690 | D2FCC6181D5593F0005C474B /* Base */,
691 | );
692 | name = LaunchScreen.storyboard;
693 | sourceTree = "";
694 | };
695 | /* End PBXVariantGroup section */
696 |
697 | /* Begin XCBuildConfiguration section */
698 | 92FCFA131BB963CD00DF05C3 /* Debug */ = {
699 | isa = XCBuildConfiguration;
700 | buildSettings = {
701 | ALWAYS_SEARCH_USER_PATHS = NO;
702 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
703 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
704 | CLANG_CXX_LIBRARY = "libc++";
705 | CLANG_ENABLE_MODULES = YES;
706 | CLANG_ENABLE_OBJC_ARC = YES;
707 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
708 | CLANG_WARN_BOOL_CONVERSION = YES;
709 | CLANG_WARN_COMMA = YES;
710 | CLANG_WARN_CONSTANT_CONVERSION = YES;
711 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
712 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
713 | CLANG_WARN_EMPTY_BODY = YES;
714 | CLANG_WARN_ENUM_CONVERSION = YES;
715 | CLANG_WARN_INFINITE_RECURSION = YES;
716 | CLANG_WARN_INT_CONVERSION = YES;
717 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
718 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
719 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
720 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
721 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
722 | CLANG_WARN_STRICT_PROTOTYPES = YES;
723 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
724 | CLANG_WARN_UNREACHABLE_CODE = YES;
725 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
726 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
727 | COPY_PHASE_STRIP = NO;
728 | CURRENT_PROJECT_VERSION = 1;
729 | DEBUG_INFORMATION_FORMAT = dwarf;
730 | ENABLE_STRICT_OBJC_MSGSEND = YES;
731 | ENABLE_TESTABILITY = YES;
732 | GCC_C_LANGUAGE_STANDARD = gnu99;
733 | GCC_DYNAMIC_NO_PIC = NO;
734 | GCC_NO_COMMON_BLOCKS = YES;
735 | GCC_OPTIMIZATION_LEVEL = 0;
736 | GCC_PREPROCESSOR_DEFINITIONS = (
737 | "DEBUG=1",
738 | "$(inherited)",
739 | );
740 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
741 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
742 | GCC_WARN_UNDECLARED_SELECTOR = YES;
743 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
744 | GCC_WARN_UNUSED_FUNCTION = YES;
745 | GCC_WARN_UNUSED_VARIABLE = YES;
746 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
747 | MACOSX_DEPLOYMENT_TARGET = 10.11;
748 | MTL_ENABLE_DEBUG_INFO = YES;
749 | ONLY_ACTIVE_ARCH = YES;
750 | SDKROOT = iphoneos;
751 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
752 | SWIFT_VERSION = 5.0;
753 | TARGETED_DEVICE_FAMILY = "1,2";
754 | VERSIONING_SYSTEM = "apple-generic";
755 | VERSION_INFO_PREFIX = "";
756 | };
757 | name = Debug;
758 | };
759 | 92FCFA141BB963CD00DF05C3 /* Release */ = {
760 | isa = XCBuildConfiguration;
761 | buildSettings = {
762 | ALWAYS_SEARCH_USER_PATHS = NO;
763 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
764 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
765 | CLANG_CXX_LIBRARY = "libc++";
766 | CLANG_ENABLE_MODULES = YES;
767 | CLANG_ENABLE_OBJC_ARC = YES;
768 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
769 | CLANG_WARN_BOOL_CONVERSION = YES;
770 | CLANG_WARN_COMMA = YES;
771 | CLANG_WARN_CONSTANT_CONVERSION = YES;
772 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
773 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
774 | CLANG_WARN_EMPTY_BODY = YES;
775 | CLANG_WARN_ENUM_CONVERSION = YES;
776 | CLANG_WARN_INFINITE_RECURSION = YES;
777 | CLANG_WARN_INT_CONVERSION = YES;
778 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
779 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
780 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
781 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
782 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
783 | CLANG_WARN_STRICT_PROTOTYPES = YES;
784 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
785 | CLANG_WARN_UNREACHABLE_CODE = YES;
786 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
787 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
788 | COPY_PHASE_STRIP = NO;
789 | CURRENT_PROJECT_VERSION = 1;
790 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
791 | ENABLE_NS_ASSERTIONS = NO;
792 | ENABLE_STRICT_OBJC_MSGSEND = YES;
793 | GCC_C_LANGUAGE_STANDARD = gnu99;
794 | GCC_NO_COMMON_BLOCKS = YES;
795 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
796 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
797 | GCC_WARN_UNDECLARED_SELECTOR = YES;
798 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
799 | GCC_WARN_UNUSED_FUNCTION = YES;
800 | GCC_WARN_UNUSED_VARIABLE = YES;
801 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
802 | MACOSX_DEPLOYMENT_TARGET = 10.11;
803 | MTL_ENABLE_DEBUG_INFO = NO;
804 | SDKROOT = iphoneos;
805 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
806 | SWIFT_VERSION = 5.0;
807 | TARGETED_DEVICE_FAMILY = "1,2";
808 | VALIDATE_PRODUCT = YES;
809 | VERSIONING_SYSTEM = "apple-generic";
810 | VERSION_INFO_PREFIX = "";
811 | };
812 | name = Release;
813 | };
814 | 92FCFA161BB963CD00DF05C3 /* Debug */ = {
815 | isa = XCBuildConfiguration;
816 | buildSettings = {
817 | CLANG_ENABLE_MODULES = YES;
818 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
819 | DEFINES_MODULE = YES;
820 | DYLIB_COMPATIBILITY_VERSION = 1;
821 | DYLIB_CURRENT_VERSION = 1;
822 | DYLIB_INSTALL_NAME_BASE = "@rpath";
823 | INFOPLIST_FILE = Source/Info.plist;
824 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
825 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
826 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
827 | PRODUCT_NAME = Routing;
828 | SKIP_INSTALL = YES;
829 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
830 | };
831 | name = Debug;
832 | };
833 | 92FCFA171BB963CD00DF05C3 /* Release */ = {
834 | isa = XCBuildConfiguration;
835 | buildSettings = {
836 | CLANG_ENABLE_MODULES = YES;
837 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
838 | DEFINES_MODULE = YES;
839 | DYLIB_COMPATIBILITY_VERSION = 1;
840 | DYLIB_CURRENT_VERSION = 1;
841 | DYLIB_INSTALL_NAME_BASE = "@rpath";
842 | INFOPLIST_FILE = Source/Info.plist;
843 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
844 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
845 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
846 | PRODUCT_NAME = Routing;
847 | SKIP_INSTALL = YES;
848 | };
849 | name = Release;
850 | };
851 | 92FCFA191BB963CD00DF05C3 /* Debug */ = {
852 | isa = XCBuildConfiguration;
853 | buildSettings = {
854 | FRAMEWORK_SEARCH_PATHS = "$(inherited)";
855 | INFOPLIST_FILE = Tests/Info.plist;
856 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
857 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
858 | PRODUCT_NAME = "$(TARGET_NAME)";
859 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example";
860 | };
861 | name = Debug;
862 | };
863 | 92FCFA1A1BB963CD00DF05C3 /* Release */ = {
864 | isa = XCBuildConfiguration;
865 | buildSettings = {
866 | FRAMEWORK_SEARCH_PATHS = "$(inherited)";
867 | INFOPLIST_FILE = Tests/Info.plist;
868 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
869 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
870 | PRODUCT_NAME = "$(TARGET_NAME)";
871 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example";
872 | };
873 | name = Release;
874 | };
875 | D295F40D1C6C25ED003CAC2F /* Debug */ = {
876 | isa = XCBuildConfiguration;
877 | buildSettings = {
878 | APPLICATION_EXTENSION_API_ONLY = YES;
879 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
880 | DEFINES_MODULE = YES;
881 | DYLIB_COMPATIBILITY_VERSION = 1;
882 | DYLIB_CURRENT_VERSION = 1;
883 | DYLIB_INSTALL_NAME_BASE = "@rpath";
884 | INFOPLIST_FILE = Source/Info.plist;
885 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
886 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
887 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
888 | PRODUCT_NAME = Routing;
889 | SDKROOT = watchos;
890 | SKIP_INSTALL = YES;
891 | TARGETED_DEVICE_FAMILY = 4;
892 | };
893 | name = Debug;
894 | };
895 | D295F40E1C6C25ED003CAC2F /* Release */ = {
896 | isa = XCBuildConfiguration;
897 | buildSettings = {
898 | APPLICATION_EXTENSION_API_ONLY = YES;
899 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
900 | DEFINES_MODULE = YES;
901 | DYLIB_COMPATIBILITY_VERSION = 1;
902 | DYLIB_CURRENT_VERSION = 1;
903 | DYLIB_INSTALL_NAME_BASE = "@rpath";
904 | INFOPLIST_FILE = Source/Info.plist;
905 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
906 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
907 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
908 | PRODUCT_NAME = Routing;
909 | SDKROOT = watchos;
910 | SKIP_INSTALL = YES;
911 | TARGETED_DEVICE_FAMILY = 4;
912 | };
913 | name = Release;
914 | };
915 | D295F4271C6C2612003CAC2F /* Debug */ = {
916 | isa = XCBuildConfiguration;
917 | buildSettings = {
918 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
919 | DEFINES_MODULE = YES;
920 | DYLIB_COMPATIBILITY_VERSION = 1;
921 | DYLIB_CURRENT_VERSION = 1;
922 | DYLIB_INSTALL_NAME_BASE = "@rpath";
923 | INFOPLIST_FILE = Source/Info.plist;
924 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
925 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
926 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
927 | PRODUCT_NAME = Routing;
928 | SDKROOT = appletvos;
929 | SKIP_INSTALL = YES;
930 | TARGETED_DEVICE_FAMILY = 3;
931 | };
932 | name = Debug;
933 | };
934 | D295F4281C6C2612003CAC2F /* Release */ = {
935 | isa = XCBuildConfiguration;
936 | buildSettings = {
937 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
938 | DEFINES_MODULE = YES;
939 | DYLIB_COMPATIBILITY_VERSION = 1;
940 | DYLIB_CURRENT_VERSION = 1;
941 | DYLIB_INSTALL_NAME_BASE = "@rpath";
942 | INFOPLIST_FILE = Source/Info.plist;
943 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
944 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
945 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
946 | PRODUCT_NAME = Routing;
947 | SDKROOT = appletvos;
948 | SKIP_INSTALL = YES;
949 | TARGETED_DEVICE_FAMILY = 3;
950 | };
951 | name = Release;
952 | };
953 | D295F42A1C6C2612003CAC2F /* Debug */ = {
954 | isa = XCBuildConfiguration;
955 | buildSettings = {
956 | INFOPLIST_FILE = Tests/Info.plist;
957 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
958 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
959 | PRODUCT_NAME = "$(TARGET_NAME)";
960 | SDKROOT = appletvos;
961 | };
962 | name = Debug;
963 | };
964 | D295F42B1C6C2612003CAC2F /* Release */ = {
965 | isa = XCBuildConfiguration;
966 | buildSettings = {
967 | INFOPLIST_FILE = Tests/Info.plist;
968 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
969 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
970 | PRODUCT_NAME = "$(TARGET_NAME)";
971 | SDKROOT = appletvos;
972 | };
973 | name = Release;
974 | };
975 | D295F4431C6C265D003CAC2F /* Debug */ = {
976 | isa = XCBuildConfiguration;
977 | buildSettings = {
978 | CODE_SIGN_IDENTITY = "";
979 | COMBINE_HIDPI_IMAGES = YES;
980 | DEFINES_MODULE = YES;
981 | DYLIB_COMPATIBILITY_VERSION = 1;
982 | DYLIB_CURRENT_VERSION = 1;
983 | DYLIB_INSTALL_NAME_BASE = "@rpath";
984 | FRAMEWORK_VERSION = A;
985 | INFOPLIST_FILE = Source/Info.plist;
986 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
987 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
988 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
989 | PRODUCT_NAME = Routing;
990 | SDKROOT = macosx;
991 | SKIP_INSTALL = YES;
992 | };
993 | name = Debug;
994 | };
995 | D295F4441C6C265D003CAC2F /* Release */ = {
996 | isa = XCBuildConfiguration;
997 | buildSettings = {
998 | CODE_SIGN_IDENTITY = "";
999 | COMBINE_HIDPI_IMAGES = YES;
1000 | DEFINES_MODULE = YES;
1001 | DYLIB_COMPATIBILITY_VERSION = 1;
1002 | DYLIB_CURRENT_VERSION = 1;
1003 | DYLIB_INSTALL_NAME_BASE = "@rpath";
1004 | FRAMEWORK_VERSION = A;
1005 | INFOPLIST_FILE = Source/Info.plist;
1006 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
1007 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
1008 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
1009 | PRODUCT_NAME = Routing;
1010 | SDKROOT = macosx;
1011 | SKIP_INSTALL = YES;
1012 | };
1013 | name = Release;
1014 | };
1015 | D295F4461C6C265D003CAC2F /* Debug */ = {
1016 | isa = XCBuildConfiguration;
1017 | buildSettings = {
1018 | CODE_SIGN_IDENTITY = "-";
1019 | COMBINE_HIDPI_IMAGES = YES;
1020 | FRAMEWORK_SEARCH_PATHS = "$(inherited)";
1021 | INFOPLIST_FILE = Tests/Info.plist;
1022 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
1023 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
1024 | PRODUCT_NAME = "$(TARGET_NAME)";
1025 | SDKROOT = macosx;
1026 | };
1027 | name = Debug;
1028 | };
1029 | D295F4471C6C265D003CAC2F /* Release */ = {
1030 | isa = XCBuildConfiguration;
1031 | buildSettings = {
1032 | CODE_SIGN_IDENTITY = "-";
1033 | COMBINE_HIDPI_IMAGES = YES;
1034 | FRAMEWORK_SEARCH_PATHS = "$(inherited)";
1035 | INFOPLIST_FILE = Tests/Info.plist;
1036 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
1037 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
1038 | PRODUCT_NAME = "$(TARGET_NAME)";
1039 | SDKROOT = macosx;
1040 | };
1041 | name = Release;
1042 | };
1043 | D2FCC6051D5593BF005C474B /* Debug */ = {
1044 | isa = XCBuildConfiguration;
1045 | buildSettings = {
1046 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
1047 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
1048 | CLANG_ANALYZER_NONNULL = YES;
1049 | INFOPLIST_FILE = Example/Info.plist;
1050 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
1051 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
1052 | PRODUCT_NAME = "$(TARGET_NAME)";
1053 | };
1054 | name = Debug;
1055 | };
1056 | D2FCC6061D5593BF005C474B /* Release */ = {
1057 | isa = XCBuildConfiguration;
1058 | buildSettings = {
1059 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
1060 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
1061 | CLANG_ANALYZER_NONNULL = YES;
1062 | INFOPLIST_FILE = Example/Info.plist;
1063 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
1064 | PRODUCT_BUNDLE_IDENTIFIER = "com.routing.$(PRODUCT_NAME:rfc1034identifier)";
1065 | PRODUCT_NAME = "$(TARGET_NAME)";
1066 | };
1067 | name = Release;
1068 | };
1069 | /* End XCBuildConfiguration section */
1070 |
1071 | /* Begin XCConfigurationList section */
1072 | 92FCF9FB1BB963CD00DF05C3 /* Build configuration list for PBXProject "Routing" */ = {
1073 | isa = XCConfigurationList;
1074 | buildConfigurations = (
1075 | 92FCFA131BB963CD00DF05C3 /* Debug */,
1076 | 92FCFA141BB963CD00DF05C3 /* Release */,
1077 | );
1078 | defaultConfigurationIsVisible = 0;
1079 | defaultConfigurationName = Release;
1080 | };
1081 | 92FCFA151BB963CD00DF05C3 /* Build configuration list for PBXNativeTarget "Routing iOS" */ = {
1082 | isa = XCConfigurationList;
1083 | buildConfigurations = (
1084 | 92FCFA161BB963CD00DF05C3 /* Debug */,
1085 | 92FCFA171BB963CD00DF05C3 /* Release */,
1086 | );
1087 | defaultConfigurationIsVisible = 0;
1088 | defaultConfigurationName = Release;
1089 | };
1090 | 92FCFA181BB963CD00DF05C3 /* Build configuration list for PBXNativeTarget "Routing iOS Tests" */ = {
1091 | isa = XCConfigurationList;
1092 | buildConfigurations = (
1093 | 92FCFA191BB963CD00DF05C3 /* Debug */,
1094 | 92FCFA1A1BB963CD00DF05C3 /* Release */,
1095 | );
1096 | defaultConfigurationIsVisible = 0;
1097 | defaultConfigurationName = Release;
1098 | };
1099 | D295F40F1C6C25ED003CAC2F /* Build configuration list for PBXNativeTarget "Routing watchOS" */ = {
1100 | isa = XCConfigurationList;
1101 | buildConfigurations = (
1102 | D295F40D1C6C25ED003CAC2F /* Debug */,
1103 | D295F40E1C6C25ED003CAC2F /* Release */,
1104 | );
1105 | defaultConfigurationIsVisible = 0;
1106 | defaultConfigurationName = Release;
1107 | };
1108 | D295F4261C6C2612003CAC2F /* Build configuration list for PBXNativeTarget "Routing tvOS" */ = {
1109 | isa = XCConfigurationList;
1110 | buildConfigurations = (
1111 | D295F4271C6C2612003CAC2F /* Debug */,
1112 | D295F4281C6C2612003CAC2F /* Release */,
1113 | );
1114 | defaultConfigurationIsVisible = 0;
1115 | defaultConfigurationName = Release;
1116 | };
1117 | D295F4291C6C2612003CAC2F /* Build configuration list for PBXNativeTarget "Routing tvOS Tests" */ = {
1118 | isa = XCConfigurationList;
1119 | buildConfigurations = (
1120 | D295F42A1C6C2612003CAC2F /* Debug */,
1121 | D295F42B1C6C2612003CAC2F /* Release */,
1122 | );
1123 | defaultConfigurationIsVisible = 0;
1124 | defaultConfigurationName = Release;
1125 | };
1126 | D295F4421C6C265D003CAC2F /* Build configuration list for PBXNativeTarget "Routing OSX" */ = {
1127 | isa = XCConfigurationList;
1128 | buildConfigurations = (
1129 | D295F4431C6C265D003CAC2F /* Debug */,
1130 | D295F4441C6C265D003CAC2F /* Release */,
1131 | );
1132 | defaultConfigurationIsVisible = 0;
1133 | defaultConfigurationName = Release;
1134 | };
1135 | D295F4451C6C265D003CAC2F /* Build configuration list for PBXNativeTarget "Routing OSX Tests" */ = {
1136 | isa = XCConfigurationList;
1137 | buildConfigurations = (
1138 | D295F4461C6C265D003CAC2F /* Debug */,
1139 | D295F4471C6C265D003CAC2F /* Release */,
1140 | );
1141 | defaultConfigurationIsVisible = 0;
1142 | defaultConfigurationName = Release;
1143 | };
1144 | D2FCC6071D5593BF005C474B /* Build configuration list for PBXNativeTarget "Example" */ = {
1145 | isa = XCConfigurationList;
1146 | buildConfigurations = (
1147 | D2FCC6051D5593BF005C474B /* Debug */,
1148 | D2FCC6061D5593BF005C474B /* Release */,
1149 | );
1150 | defaultConfigurationIsVisible = 0;
1151 | defaultConfigurationName = Release;
1152 | };
1153 | /* End XCConfigurationList section */
1154 | };
1155 | rootObject = 92FCF9F81BB963CD00DF05C3 /* Project object */;
1156 | }
1157 |
--------------------------------------------------------------------------------
/Routing.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Routing.xcodeproj/xcshareddata/xcschemes/Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Routing.xcodeproj/xcshareddata/xcschemes/Routing OSX.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/Routing.xcodeproj/xcshareddata/xcschemes/Routing iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/Routing.xcodeproj/xcshareddata/xcschemes/Routing tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/Routing.xcodeproj/xcshareddata/xcschemes/Routing watchOS.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 |
--------------------------------------------------------------------------------
/Routing.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Routing.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Source/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | 1.4.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Source/Routable.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public protocol RouteOwner: class {}
4 | public typealias RouteUUID = String
5 | public typealias Parameters = [String: String]
6 |
7 | /**
8 | The closure type associated with #map
9 |
10 | - Parameter Parameters: Any query parameters or dynamic segments found in the URL
11 | - Parameter Any: Any data that could be passed with a routing
12 | - Parameter Completed: Must be called for Routing to continue processing other routes with #open
13 | */
14 |
15 | public typealias RouteHandler = (String, Parameters, Any?, @escaping Completed) -> Void
16 | public typealias Completed = () -> Void
17 |
18 | /**
19 | The closure type associated with #proxy
20 |
21 | - Parameter String: The route being opened
22 | - Parameter Parameters: Any query parameters or dynamic segments found in the URL
23 | - Parameter Any: Any data that could be passed with a routing
24 | - Parameter Next: Must be called for Routing to continue processing. Calling #Next with
25 | nil arguments will continue executing other matching proxies. Calling #Next with non nil
26 | arguments will continue to process the route.
27 | */
28 |
29 | public typealias ProxyHandler = (String, Parameters, Any?, @escaping Next) -> Void
30 | public typealias ProxyCommit = (route: String, parameters: Parameters, data: Any?)
31 | public typealias Next = (ProxyCommit?) -> Void
32 |
33 | internal typealias Route = Routable
34 | internal typealias Proxy = Routable
35 |
36 | internal struct Routable {
37 | let uuid = { UUID().uuidString }()
38 | let pattern: String
39 | let tags: [String]
40 | weak var owner: RouteOwner?
41 | let queue: DispatchQueue
42 | let handler: T
43 | let dynamicSegments: [String]
44 |
45 | init(_ pattern: String, tags: [String], owner: RouteOwner, queue: DispatchQueue, handler: T) {
46 | var pattern = pattern
47 | var dynamicSegments = [String]()
48 | let options: NSString.CompareOptions = [.regularExpression, .caseInsensitive]
49 | while let range = pattern.range(of: ":[a-zA-Z0-9-_]+", options: options) {
50 | dynamicSegments.append(String(pattern[pattern.index(range.lowerBound, offsetBy: 1).. Routing {
20 | get {
21 | let set = Set(tags)
22 | var sub: Routing!
23 | accessQueue.sync {
24 | sub = Routing(routes: self.routes.filter({ set.intersection($0.tags).isEmpty == false }),
25 | proxies: self.proxies.filter({ set.intersection($0.tags).isEmpty == false }),
26 | accessQueue: self.accessQueue,
27 | openQueue: self.openQueue,
28 | processingQueue: self.processQueue)
29 | }
30 |
31 | return sub
32 | }
33 | }
34 |
35 | public init() {
36 | accessQueue = DispatchQueue(label: "Routing Access Queue", attributes: [])
37 | openQueue = DispatchQueue(label: "Routing Open Queue", attributes: [])
38 | processQueue = DispatchQueue(label: "Routing Process Queue", attributes: [])
39 | }
40 |
41 | private init(routes: [Route],
42 | proxies: [Proxy],
43 | accessQueue: DispatchQueue,
44 | openQueue: DispatchQueue,
45 | processingQueue: DispatchQueue) {
46 | self.accessQueue = accessQueue
47 | self.openQueue = openQueue
48 | self.processQueue = processingQueue
49 | self.routes = routes
50 | self.proxies = proxies
51 | }
52 |
53 | /**
54 | Associates a closure to a string pattern. A Routing instance will execute the closure in the
55 | event of a matching URL using #open. Routing will only execute the first matching mapped route.
56 | This will be the last route added with #map.
57 |
58 | ```code
59 | let router = Routing()
60 | router.map("routing://route") { parameters, completed in
61 | completed() // Must call completed or the router will halt!
62 | }
63 | ```
64 |
65 | - Parameter pattern: A String pattern
66 | - Parameter tag: A tag to reference when subscripting a Routing object
67 | - Parameter owner: The routes owner. If deallocated the route will be removed.
68 | - Parameter queue: A dispatch queue for the callback
69 | - Parameter handler: A MapHandler
70 | - Returns: The RouteUUID
71 | */
72 |
73 | @discardableResult
74 | public func map(_ pattern: String,
75 | tags: [String] = [],
76 | queue: DispatchQueue = DispatchQueue.main,
77 | owner: RouteOwner? = nil,
78 | handler: @escaping RouteHandler) -> RouteUUID {
79 | let route = Route(pattern, tags: tags, owner: owner ?? self, queue: queue, handler: handler)
80 | accessQueue.async {
81 | self.routes.insert(route, at: 0)
82 | }
83 |
84 | return route.uuid
85 | }
86 |
87 | /**
88 | Associates a closure to a string pattern. A Routing instance will execute the closure in the
89 | event of a matching URL using #open. Routing will execute all proxies unless #next() is called
90 | with non nil arguments.
91 |
92 | ```code
93 | let router = Routing()
94 | router.proxy("routing://route") { route, parameters, next in
95 | next(route, parameters) // Must call next or the router will halt!
96 | /* alternatively, next(nil, nil) allowing additional proxies to execute */
97 | }
98 | ```
99 |
100 | - Parameter pattern: A String pattern
101 | - Parameter tag: A tag to reference when subscripting a Routing object
102 | - Parameter owner: The routes owner. If deallocated the route will be removed.
103 | - Parameter queue: A dispatch queue for the callback
104 | - Parameter handler: A ProxyHandler
105 | - Returns: The RouteUUID
106 | */
107 |
108 | @discardableResult
109 | public func proxy(_ pattern: String,
110 | tags: [String] = [],
111 | owner: RouteOwner? = nil,
112 | queue: DispatchQueue = DispatchQueue.main,
113 | handler: @escaping ProxyHandler) -> RouteUUID {
114 | let proxy = Proxy(pattern, tags: tags, owner: owner ?? self, queue: queue, handler: handler)
115 | accessQueue.async {
116 | self.proxies.insert(proxy, at: 0)
117 | }
118 |
119 | return proxy.uuid
120 | }
121 |
122 | /**
123 | Will execute the first mapped closure and any proxies with matching patterns. Mapped closures
124 | are read in a last to be mapped first executed order.
125 |
126 | - Parameter string: A string represeting a URL
127 | - Parameter passing: Any data that will be passed with a routing
128 | - Returns: A Bool. True if the string is a valid URL and it can open the URL, false otherwise
129 | */
130 |
131 | @discardableResult
132 | public func open(_ string: String, passing any: Any? = nil) -> Bool {
133 | guard let URL = URL(string: string) else {
134 | return false
135 | }
136 |
137 | return open(URL, passing: any)
138 | }
139 |
140 | /**
141 | Will execute the first mapped closure and any proxies with matching patterns. Mapped closures
142 | are read in a last to be mapped first executed order.
143 |
144 | - Parameter URL: A URL
145 | - Parameter passing: Any data that will be passed with a routing
146 | - Returns: A Bool. True if it can open the URL, false otherwise
147 | */
148 |
149 | @discardableResult
150 | public func open(_ URL: Foundation.URL, passing any: Any? = nil) -> Bool {
151 | var result = true
152 |
153 | openQueue.sync {
154 | var work = RoutableWork()
155 | guard let searchRoute = searchRoute(from: URL, with: &work.parameters) else {
156 | result = false
157 | return
158 | }
159 | work.searchRoute = searchRoute
160 |
161 | accessQueue.sync {
162 | routes = routes.filter { $0.owner != nil }
163 | proxies = proxies.filter { $0.owner != nil }
164 | work.routes = routes
165 | work.proxies = proxies
166 | for route in work.routes where self.searchRoute(work.searchRoute, matches: route, updating: &work.parameters) {
167 | work.initialRoutable = route
168 | break
169 | }
170 | }
171 |
172 | if work.initialRoutable == nil {
173 | result = false
174 | }
175 |
176 | work.passedAny = any
177 | self.process(work: work)
178 | }
179 |
180 | return result
181 | }
182 |
183 | /**
184 | Removes the route with the given RouteUUID.
185 |
186 | - Parameter of: A RouteUUID
187 | */
188 |
189 | public func dispose(of uuid: RouteUUID) {
190 | accessQueue.async {
191 | self.routes = self.routes.filter { $0.uuid != uuid }
192 | self.proxies = self.proxies.filter { $0.uuid != uuid }
193 | }
194 | }
195 |
196 | private func process(work: RoutableWork) {
197 | processQueue.async {
198 | let semaphore = DispatchSemaphore(value: 0)
199 | var proxyCommit: ProxyCommit?
200 | for proxy in work.proxies where self.searchRoute(work.searchRoute, matches: proxy) {
201 | proxy.queue.async {
202 | proxy.handler(work.searchRoute, work.parameters, work.passedAny) { commit in
203 | proxyCommit = commit
204 | semaphore.signal()
205 | }
206 | }
207 | semaphore.wait()
208 | if proxyCommit != nil {
209 | break
210 | }
211 | }
212 |
213 | var work = work
214 | var resultingRoutable: Route?
215 | if let commit = proxyCommit {
216 | work.searchRoute = self.searchRoute(from: commit.route, updating: &work.parameters) ?? ""
217 | resultingRoutable = nil
218 | for route in work.routes where self.searchRoute(work.searchRoute, matches: route) {
219 | resultingRoutable = route
220 | break
221 | }
222 |
223 | commit.parameters.forEach {
224 | work.parameters[$0.0] = $0.1
225 | }
226 | work.passedAny = commit.data
227 | } else {
228 | resultingRoutable = work.initialRoutable
229 | }
230 |
231 | if let resultingRoute = resultingRoutable {
232 | resultingRoute.queue.async {
233 | resultingRoute.handler(work.searchRoute, work.parameters, work.passedAny) {
234 | semaphore.signal()
235 | }
236 | }
237 | semaphore.wait()
238 | }
239 | }
240 | }
241 |
242 | private func searchRoute(from URL: URL, with parameters: inout Parameters) -> String? {
243 | guard var components = URLComponents(url: URL, resolvingAgainstBaseURL: false) else {
244 | return nil
245 | }
246 |
247 | components.queryItems?.forEach {
248 | parameters[$0.name] = ($0.value ?? "")
249 | }
250 | components.query = nil
251 |
252 | return components.string
253 | }
254 |
255 | private func searchRoute(from URLString: String, updating parameters: inout Parameters) -> String? {
256 | return URL(string: URLString).flatMap { return searchRoute(from: $0, with: ¶meters) }
257 | }
258 |
259 | private func searchRoute(_ URLString: String, matches routable: Routable) -> Bool {
260 | return _searchRoute(URLString, matches: routable.pattern) != nil
261 | }
262 |
263 | private func searchRoute(_ URLString: String, matches routable: Routable, updating parameters: inout Parameters) -> Bool {
264 | guard let matches = _searchRoute(URLString, matches: routable.pattern) else {
265 | return false
266 | }
267 |
268 | if routable.dynamicSegments.count > 0 && routable.dynamicSegments.count == matches.numberOfRanges - 1 {
269 | for i in (1 ..< matches.numberOfRanges) {
270 | parameters[routable.dynamicSegments[i-1]] = (URLString as NSString)
271 | .substring(with: matches.range(at: i))
272 | }
273 | }
274 |
275 | return true
276 | }
277 |
278 | private func _searchRoute(_ URLString: String, matches pattern: String) -> NSTextCheckingResult? {
279 | return (try? NSRegularExpression(pattern: pattern, options: .caseInsensitive))
280 | .flatMap {
281 | $0.matches(in: URLString, options: [], range: NSMakeRange(0, URLString.count))
282 | }?.first
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/Source/iOS.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import QuartzCore
3 |
4 | public enum ControllerSource {
5 | case storyboard(storyboard: String, identifier: String, bundle: Bundle?)
6 | case nib(controller: UIViewController.Type, name: String?, bundle: Bundle?)
7 | case provided(() -> UIViewController)
8 | }
9 |
10 | public indirect enum PresentationStyle {
11 | case show
12 | case showDetail
13 | case present(animated: Bool)
14 | case push(animated: Bool)
15 | case custom(custom: (_ presenting: UIViewController,
16 | _ presented: UIViewController,
17 | _ completed: Completed) -> Void)
18 | case inNavigationController(PresentationStyle)
19 | }
20 |
21 | public typealias PresentationSetup = (UIViewController, Parameters, Any?) -> Void
22 |
23 | public protocol RoutingPresentationSetup {
24 | func setup(_ route: String, with parameters: Parameters, passing any: Any?)
25 | }
26 |
27 | public extension UINavigationController {
28 | func pushViewController(_ vc: UIViewController, animated: Bool, completion: @escaping Completed) {
29 | self.commit(completion) {
30 | self.pushViewController(vc, animated: animated)
31 | }
32 | }
33 |
34 | @discardableResult
35 | func popViewControllerAnimated(_ animated: Bool, completion: @escaping Completed) -> UIViewController? {
36 | var vc: UIViewController?
37 | self.commit(completion) {
38 | vc = self.popViewController(animated: animated)
39 | }
40 |
41 | return vc
42 | }
43 |
44 | @discardableResult
45 | func popToViewControllerAnimated(_ viewController: UIViewController, animated: Bool, completion: @escaping Completed) -> [UIViewController]? {
46 | var vc: [UIViewController]?
47 | self.commit(completion) {
48 | vc = self.popToViewController(viewController, animated: animated)
49 | }
50 |
51 | return vc
52 | }
53 |
54 | @discardableResult
55 | func popToRootViewControllerAnimated(_ animated: Bool, completion: @escaping Completed) -> [UIViewController]? {
56 | var vc: [UIViewController]?
57 | self.commit(completion) {
58 | vc = self.popToRootViewController(animated: animated)
59 | }
60 |
61 | return vc
62 | }
63 | }
64 |
65 | public extension UIViewController {
66 | func showViewController(_ vc: UIViewController, sender: AnyObject?, completion: @escaping Completed) {
67 | self.commit(completion) {
68 | self.show(vc, sender: sender)
69 | }
70 | }
71 |
72 | func showDetailViewController(_ vc: UIViewController, sender: AnyObject?, completion: @escaping Completed) {
73 | self.commit(completion) {
74 | self.showDetailViewController(vc, sender: sender)
75 | }
76 | }
77 |
78 | fileprivate func commit(_ completed: @escaping Completed, transition: () -> Void) {
79 | CATransaction.begin()
80 | CATransaction.setCompletionBlock(completed)
81 | transition()
82 | CATransaction.commit()
83 | }
84 | }
85 |
86 | @objc internal protocol ControllerIterator {
87 | @objc func nextViewController() -> UIViewController?
88 | }
89 |
90 | extension UITabBarController {
91 | @objc internal override func nextViewController() -> UIViewController? {
92 | return selectedViewController
93 | }
94 | }
95 |
96 | extension UINavigationController {
97 | @objc internal override func nextViewController() -> UIViewController? {
98 | return visibleViewController
99 | }
100 | }
101 |
102 | extension UIViewController : ControllerIterator {
103 | internal func nextViewController() -> UIViewController? {
104 | return presentedViewController
105 | }
106 | }
107 |
108 | public extension Routing {
109 | /**
110 | Associates a view controller presentation to a string pattern. A Routing instance present the
111 | view controller in the event of a matching URL using #open. Routing will only execute the first
112 | matching mapped route. This will be the last route added with #map.
113 |
114 | ```code
115 | let router = Routing()
116 | router.map("routingexample://route",
117 | instance: .Storyboard(storyboard: "Main", identifier: "ViewController", bundle: nil),
118 | style: .Present(animated: true)) { vc, parameters in
119 | ... // Useful callback for setup such as embedding in navigation controller
120 | return vc
121 | }
122 | ```
123 |
124 | - Parameter pattern: A String pattern
125 | - Parameter tag: A tag to reference when subscripting a Routing object
126 | - Parameter owner: The routes owner. If deallocated the route will be removed.
127 | - Parameter source: The source of the view controller instance
128 | - Parameter style: The presentation style in presenting the view controller
129 | - Parameter setup: A closure provided for additional setup
130 | - Returns: The RouteUUID
131 | */
132 |
133 | @discardableResult
134 | func map(_ pattern: String,
135 | tags: [String] = ["Views"],
136 | owner: RouteOwner? = nil,
137 | source: ControllerSource,
138 | style: PresentationStyle = .show,
139 | setup: PresentationSetup? = nil) -> RouteUUID {
140 | let routeHandler: RouteHandler = { [unowned self] (route, parameters, any, completed) in
141 | guard let root = UIApplication.shared.keyWindow?.rootViewController else {
142 | completed()
143 | return
144 | }
145 |
146 | let strongSelf = self
147 | let vc = strongSelf.controller(from: source)
148 | (vc as? RoutingPresentationSetup)?.setup(route, with: parameters, passing: any)
149 | setup?(vc, parameters, any)
150 |
151 | var presenter = root
152 | while let nextVC = presenter.nextViewController() {
153 | presenter = nextVC
154 | }
155 |
156 | strongSelf.showController(vc, from: presenter, with: style, completion: completed)
157 | }
158 |
159 | return map(pattern, tags: tags, queue: DispatchQueue.main, owner: owner, handler: routeHandler)
160 | }
161 |
162 | private func controller(from source: ControllerSource) -> UIViewController {
163 | switch source {
164 | case let .storyboard(storyboard, identifier, bundle):
165 | let storyboard = UIStoryboard(name: storyboard, bundle: bundle)
166 | return storyboard.instantiateViewController(withIdentifier: identifier)
167 | case let .nib(controller, name, bundle):
168 | return controller.init(nibName: name, bundle: bundle)
169 | case let .provided(provider):
170 | return provider()
171 | }
172 | }
173 |
174 | private func showController(_ presented: UIViewController,
175 | from presenting: UIViewController,
176 | with style: PresentationStyle,
177 | completion: @escaping Completed) {
178 | switch style {
179 | case .show:
180 | presenting.showViewController(presented, sender: self, completion: completion)
181 | break
182 | case .showDetail:
183 | presenting.showDetailViewController(presented, sender: self, completion: completion)
184 | break
185 | case let .present(animated):
186 | presenting.present(presented, animated: animated, completion: completion)
187 | break
188 | case let .push(animated):
189 | if let presenting = presenting as? UINavigationController {
190 | presenting.pushViewController(presented, animated: animated, completion: completion)
191 | } else {
192 | presenting.navigationController?.pushViewController(presented, animated: animated, completion: completion)
193 | }
194 | case let .custom(custom):
195 | custom(presenting, presented, completion)
196 | break
197 | case let .inNavigationController(style):
198 | showController(UINavigationController(rootViewController: presented),
199 | from: presenting,
200 | with: style,
201 | completion: completion)
202 | break
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Tests/RoutingTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import Routing
3 |
4 | class RoutingMapTests: XCTestCase {
5 | var router: Routing!
6 | var testingQueue: DispatchQueue!
7 | override func setUp() {
8 | super.setUp()
9 | router = Routing()
10 | testingQueue = DispatchQueue(label: "Testing Queue", attributes: DispatchQueue.Attributes.concurrent)
11 | }
12 |
13 | func testReturnsTrueIfItCanOpenURL() {
14 | router.map("routingexample://route") { _, _, _, completed in completed() }
15 |
16 | XCTAssertTrue(router.open(URL(string: "routingexample://route/")!))
17 | }
18 |
19 | func testReturnsTrueIfItCanOpenString() {
20 | router.map("routingexample://route") { _, _, _, completed in completed() }
21 |
22 | XCTAssertTrue(router.open("routingexample://route/"))
23 | }
24 |
25 | func testReturnsFalseIfItCannotOpenURL() {
26 | XCTAssertFalse(router.open(URL(string: "routingexample://incorrectroute/")!))
27 | }
28 |
29 | func testReturnsFalseIfItCannotOpenString() {
30 | XCTAssertFalse(router.open("routingexample://incorrectroute/"))
31 | }
32 |
33 | func testRouteHandlerIsCalled() {
34 | let expect = expectation(description: "RouteHandler is called.")
35 | router.map("routingexample://route") { _, _, _, completed in
36 | expect.fulfill()
37 | completed()
38 | }
39 |
40 | router.open("routingexample://route")
41 | waitForExpectations(timeout: 0.1, handler: nil)
42 | }
43 |
44 | func testOnlyLatestAddedRouteHandlerIsCalled() {
45 | let expect = expectation(description: "Only latest #mapped RouteHandler is called.")
46 |
47 | var routeCalled = 0
48 | router.map("routingexample://route") { _, _, _, completed in
49 | routeCalled = 1
50 | expect.fulfill()
51 | completed()
52 | }
53 |
54 | router.map("routingexample://route") { _, _, _, completed in
55 | routeCalled = 2
56 | expect.fulfill()
57 | completed()
58 | }
59 |
60 | router.open("routingexample://route")
61 | waitForExpectations(timeout: 0.1, handler: nil)
62 | XCTAssert(routeCalled == 2)
63 | }
64 |
65 | func testMatchingRouteStringPassedToRouteHandler() {
66 | let expect = expectation(description: "Route string is passed to RouteHandler.")
67 |
68 | var matched: String?
69 | router.map("routingexample://route") { route, _, _, completed in
70 | matched = route
71 | expect.fulfill()
72 | completed()
73 | }
74 |
75 | router.open("routingexample://route")
76 | waitForExpectations(timeout: 0.1, handler: nil)
77 | XCTAssert(matched == "routingexample://route")
78 | }
79 |
80 | func testURLArgumentsArePassedToRouteHandler() {
81 | let expect = expectation(description: "URL Arguments are passed to RouteHandler.")
82 | var argument: String?
83 | router.map("routingexample://route/:argument") { _, parameters, _, completed in
84 | argument = parameters["argument"]
85 | expect.fulfill()
86 | completed()
87 | }
88 |
89 | router.open("routingexample://route/expected")
90 | waitForExpectations(timeout: 0.1, handler: nil)
91 | XCTAssert(argument == "expected")
92 | }
93 |
94 | func testQueryParametersArePassedToRouteHandler() {
95 | let expect = expectation(description: "Query param is passed to RouteHandler.")
96 |
97 | var param: String?
98 | router.map("routingexample://route") { _, parameters, _, completed in
99 | param = parameters["param"]
100 | expect.fulfill()
101 | completed()
102 | }
103 |
104 | router.open("routingexample://route?param=expected")
105 | waitForExpectations(timeout: 0.1, handler: nil)
106 | XCTAssert(param == "expected")
107 | }
108 |
109 | func testAnyCanBePassedToRouteHandler() {
110 | let expect = expectation(description: "Any is passed to RouteHandler.")
111 |
112 | var passed: Any?
113 | router.map("routingexample://route") { _, _, any, completed in
114 | passed = any
115 | expect.fulfill()
116 | completed()
117 | }
118 |
119 | router.open("routingexample://route", passing: "any")
120 | waitForExpectations(timeout: 0.1, handler: nil)
121 | if let passed = passed as? String {
122 | XCTAssert(passed == "any")
123 | } else {
124 | XCTFail()
125 | }
126 | }
127 |
128 | func testRouteHandlersAreCalledInSerialOrder() {
129 | let expect = expectation(description: "RouteHandlers are called in serial order.")
130 |
131 | var results = [String]()
132 | router.map("routingexample://route/:append") { _, parameters, _, completed in
133 | results.append(parameters["append"]!)
134 |
135 | self.testingQueue.asyncAfter(deadline: .now() + 1) {
136 | completed()
137 | }
138 | }
139 |
140 | router.map("routingexample://route/two/:append") { _, parameters, _, completed in
141 | results.append(parameters["append"]!)
142 | expect.fulfill()
143 | completed()
144 | }
145 |
146 | router.open(URL(string: "routingexample://route/one")!)
147 | router.open(URL(string: "routingexample://route/two/two")!)
148 | waitForExpectations(timeout: 1.5, handler: nil)
149 | XCTAssert(results == ["one", "two"])
150 | }
151 |
152 | func testRouterIsAbleToOpenDespiteConcurrentReadWriteAccesses() {
153 | router.map("routingexample://route") { _, _, _, completed in completed() }
154 |
155 | testingQueue.async {
156 | for i in 1...1000 {
157 | self.router.map("\(i)") { _, _, _, completed in completed() }
158 | }
159 | }
160 |
161 | testingQueue.async {
162 | for i in 1...1000 {
163 | self.router.map("\(i)") { _, _, _, completed in completed() }
164 | }
165 | }
166 |
167 | XCTAssertTrue(router.open("routingexample://route"))
168 | }
169 |
170 | func testShouldAllowTheSettingOfARouteHandlerCallbackQueue() {
171 | let expect = expectation(description: "Should allow setting of RouteHandler callback queue.")
172 |
173 | let callbackQueue = DispatchQueue(label: "Testing Call Back Queue", attributes: [])
174 | let key = DispatchSpecificKey()
175 | callbackQueue.setSpecific(key:key, value:())
176 | router.map("routingexample://route", queue: callbackQueue) { _, _, _, completed in
177 | if let _ = DispatchQueue.getSpecific(key: key) {
178 | expect.fulfill()
179 | }
180 | completed()
181 | }
182 |
183 | router.open("routingexample://route")
184 | waitForExpectations(timeout: 0.1, handler: nil)
185 | }
186 | }
187 |
188 | class RoutingProxyTests: XCTestCase {
189 | var router: Routing!
190 | var testingQueue: DispatchQueue!
191 | override func setUp() {
192 | super.setUp()
193 | router = Routing()
194 | testingQueue = DispatchQueue(label: "Testing Queue", attributes: DispatchQueue.Attributes.concurrent)
195 | }
196 |
197 | func testCanRedirectOpenedRoute() {
198 | let expect = expectation(description: "Proxy can redirect opened route.")
199 |
200 | var routeCalled = 0
201 | router.map("routingexample://route/one") { _, _, _, completed in
202 | routeCalled = 1
203 | expect.fulfill()
204 | completed()
205 | }
206 |
207 | router.map("routingexample://route/two") { _, _, _, completed in
208 | routeCalled = 2
209 | expect.fulfill()
210 | completed()
211 | }
212 |
213 | router.proxy("routingexample://route/one") { route, parameters, any, next in
214 | next(("routingexample://route/two", parameters, any))
215 | }
216 |
217 | router.open("routingexample://route/one")
218 | waitForExpectations(timeout: 0.1, handler: nil)
219 | XCTAssert(routeCalled == 2)
220 | }
221 |
222 | func testCanMatchRouteWithWildCard() {
223 | let expect = expectation(description: "Proxy matches route with wildcard.")
224 |
225 | router.map("/route/one") { _, _, _, completed in completed() }
226 |
227 | var isProxied = false
228 | router.proxy("/route/*") { route, parameters, _, next -> Void in
229 | isProxied = true
230 | expect.fulfill()
231 | next(nil)
232 | }
233 |
234 | router.open("routingexample://route/one")
235 | waitForExpectations(timeout: 0.1, handler: nil)
236 | XCTAssertTrue(isProxied)
237 | }
238 |
239 | func testCanModifyURLArgumentsPassedToRouteHandler() {
240 | let expect = expectation(description: "Proxy modifies URL arguments passed to route.")
241 |
242 | var argument: String?
243 | router.map("routingexample://route/:argument") { _, parameters, _, completed in
244 | argument = parameters["argument"]
245 | expect.fulfill()
246 | completed()
247 | }
248 |
249 | router.proxy("routingexample://route/:argument") { route, parameters, any, next in
250 | var parameters = parameters
251 | parameters["argument"] = "two"
252 | next((route, parameters, any))
253 | }
254 |
255 | router.open("routingexample://route/one")
256 | waitForExpectations(timeout: 0.1, handler: nil)
257 | XCTAssert(argument == "two")
258 | }
259 |
260 | func testCanModifyQueryParametersPassedToRouteHandler() {
261 | let expect = expectation(description: "Proxy modifies query arameters passed to route.")
262 |
263 | var query: String?
264 | router.map("routingexample://route") { _, parameters, _, completed in
265 | query = parameters["query"]
266 | expect.fulfill()
267 | completed()
268 | }
269 |
270 | router.proxy("routingexample://route") { route, parameters, data, next in
271 | var parameters = parameters
272 | parameters["query"] = "bar"
273 | next((route, parameters, data))
274 | }
275 |
276 | router.open("routingexample://route?query=foo")
277 | waitForExpectations(timeout: 0.1, handler: nil)
278 | XCTAssert(query == "bar")
279 | }
280 |
281 | func testCanModifyAnyPassedToRouteHandler() {
282 | let expect = expectation(description: "Proxy modifies any passed to route.")
283 |
284 | var passed: Any?
285 | router.map("routingexample://route") { _, _, any, completed in
286 | passed = any
287 | expect.fulfill()
288 | completed()
289 | }
290 |
291 | router.proxy("routingexample://route") { route, parameters, any, next in
292 | next((route, parameters, "anotherany"))
293 | }
294 |
295 | router.open("routingexample://route", passing: "any")
296 | waitForExpectations(timeout: 0.1, handler: nil)
297 | if let passed = passed as? String {
298 | XCTAssert(passed == "anotherany")
299 | } else {
300 | XCTFail()
301 | }
302 | }
303 |
304 | func testProxiesAreProcessedUntilAProxyCommitsChangesToNext() {
305 | let expect = expectation(description: "Proxies are called until a commit is made to Next().")
306 |
307 | router.map("routingexample://route") { _, parameters, _, completed in completed() }
308 |
309 | var results = [String]()
310 | router.proxy("routingexample://route") { route, parameters, _, next in
311 | results.append("three")
312 | next(nil)
313 | }
314 |
315 | router.proxy("routingexample://route") { route, parameters, _, next in
316 | results.append("two")
317 | expect.fulfill()
318 | next((route, Parameters(), nil))
319 | }
320 |
321 | router.proxy("routingexample://route") { route, parameters, _, next in
322 | results.append("one")
323 | next(nil)
324 | }
325 |
326 | router.open(URL(string: "routingexample://route")!)
327 | waitForExpectations(timeout: 0.1, handler: nil)
328 | XCTAssert(results == ["one", "two"])
329 | }
330 |
331 | func testRouterIsAbleToOpenDespiteConcurrentReadWriteAccesses() {
332 | router.map("routingexample://route") { (_, _, _, completed) in completed() }
333 |
334 | testingQueue.async {
335 | for i in 1...1000 {
336 | self.router.proxy("\(i)") { route, parameters, any, next in next((route, parameters, any)) }
337 | }
338 | }
339 |
340 | testingQueue.async {
341 | for i in 1...1000 {
342 | self.router.proxy("\(i)") { route, parameters, any, next in next((route, parameters, any)) }
343 | }
344 | }
345 |
346 | XCTAssertTrue(router.open("routingexample://route"))
347 | }
348 |
349 | func testParametersAreMaintainedThroughProxyAndRouteHandlers() {
350 | let expect = expectation(description: "Parameters are maintained through proxy and route handlers.")
351 |
352 | var proxiedArgument, proxiedQuery: String?
353 | router.proxy("routingexample://route/:argument") { route, parameters, any, next in
354 | (proxiedArgument, proxiedQuery) = (parameters["argument"], parameters["query"])
355 | next((route, parameters, any))
356 | }
357 |
358 | var argument, query: String?
359 | router.map("routingexample://route/:argument") { _, parameters, _, completed in
360 | (argument, query) = (parameters["argument"], parameters["query"])
361 | expect.fulfill()
362 | completed()
363 | }
364 |
365 | router.open("routingexample://route/foo?query=bar")
366 | waitForExpectations(timeout: 0.1, handler: nil)
367 | XCTAssert(proxiedArgument == "foo")
368 | XCTAssert(argument == "foo")
369 | XCTAssert(proxiedQuery == "bar")
370 | XCTAssert(query == "bar")
371 | }
372 |
373 | func testPassedAnyIsMaintainedThroughProxyAndRouteHandlers() {
374 | let expect = expectation(description: "Passed any is maintained through proxy and route handlers.")
375 |
376 | var proxiedPassed: Any?
377 | router.proxy("routingexample://route") { route, parameters, any, next in
378 | proxiedPassed = any
379 | next((route, parameters, any))
380 | }
381 |
382 | var passed: Any?
383 | router.map("routingexample://route") { _, _, any, completed in
384 | passed = any
385 | expect.fulfill()
386 | completed()
387 | }
388 |
389 | router.open("routingexample://route", passing: "any")
390 | waitForExpectations(timeout: 0.1, handler: nil)
391 |
392 | if let proxiedPassed = proxiedPassed as? String, let passed = passed as? String {
393 | XCTAssert(proxiedPassed == "any")
394 | XCTAssert(passed == "any")
395 | } else {
396 | XCTFail()
397 | }
398 | }
399 |
400 | func testShouldAllowTheSettingOfAProxyHandlerCallbackQueue() {
401 | let expect = expectation(description: "Should allow setting of ProxyHandler callback queue.")
402 |
403 | let callbackQueue = DispatchQueue(label: "Testing Call Back Queue", attributes: [])
404 | let key = DispatchSpecificKey()
405 | callbackQueue.setSpecific(key:key, value:())
406 |
407 | router.map("routingexample://route") { (_, _, _, completed) in completed() }
408 |
409 | router.proxy("routingexample://route", queue: callbackQueue) { route, parameters, any, next in
410 | if let _ = DispatchQueue.getSpecific(key: key) {
411 | expect.fulfill()
412 | }
413 | next((route, parameters, any))
414 | }
415 |
416 | router.open("routingexample://route")
417 | waitForExpectations(timeout: 0.1, handler: nil)
418 | }
419 | }
420 |
421 | class RoutingDisposeTests: XCTestCase {
422 | var router: Routing!
423 | var testingQueue: DispatchQueue!
424 | override func setUp() {
425 | super.setUp()
426 | router = Routing()
427 | testingQueue = DispatchQueue(label: "Testing Queue", attributes: DispatchQueue.Attributes.concurrent)
428 | }
429 |
430 | func testDisposingOfRouteUUID() {
431 | let uuid = router.map("routingexample://route") { _, _, _, completed in completed() }
432 | XCTAssertTrue(router.open("routingexample://route"))
433 | router.dispose(of: uuid)
434 | XCTAssertFalse(router.open("routingexample://route"))
435 | }
436 |
437 | func testDisposingOfProxyUUID() {
438 | let expect = expectation(description: "Proxy will be disposed and not redirect opened route.")
439 |
440 | var routeCalled = 0
441 | router.map("routingexample://route/one") { _, _, _, completed in
442 | routeCalled = 1
443 | expect.fulfill()
444 | completed()
445 | }
446 |
447 | router.map("routingexample://route/two") { _, _, _, completed in
448 | routeCalled = 2
449 | expect.fulfill()
450 | completed()
451 | }
452 |
453 | let uuid = router.proxy("routingexample://route/one") { route, parameters, any, next in
454 | next(("routingexample://route/two", parameters, any))
455 | }
456 | router.dispose(of: uuid)
457 | router.open("routingexample://route/one")
458 | waitForExpectations(timeout: 0.1, handler: nil)
459 | XCTAssert(routeCalled == 1)
460 | }
461 |
462 | class testRouteOwner: RouteOwner {}
463 |
464 | func testDeallocatingOfRouterOwnerDisposesMappedRoute() {
465 | var owner: RouteOwner? = testRouteOwner()
466 | router.map("routingexample://route", owner: owner) { _, _, _, completed in completed() }
467 | XCTAssertTrue(router.open("routingexample://route"))
468 | owner = nil
469 | XCTAssertFalse(router.open("routingexample://route"))
470 | }
471 |
472 | func testDeallocatingOfRouterOwnerDisposesProxiedRoute() {
473 | let expect = expectation(description: "Proxy will be disposed and not redirect opened route.")
474 |
475 | var routeCalled = 0
476 | router.map("routingexample://route/one") { _, _, _, completed in
477 | routeCalled = 1
478 | expect.fulfill()
479 | completed()
480 | }
481 |
482 | router.map("routingexample://route/two") { _, _, _, completed in
483 | routeCalled = 2
484 | expect.fulfill()
485 | completed()
486 | }
487 |
488 | var owner: RouteOwner? = testRouteOwner()
489 | router.proxy("routingexample://route/one", owner: owner) { route, parameters, any, next in
490 | next(("routingexample://route/two", parameters, any))
491 | }
492 | owner = nil
493 | router.open("routingexample://route/one")
494 | waitForExpectations(timeout: 0.1, handler: nil)
495 | XCTAssert(routeCalled == 1)
496 | }
497 | }
498 |
499 | class RoutingTagTests: XCTestCase {
500 | var router: Routing!
501 | var testingQueue: DispatchQueue!
502 | override func setUp() {
503 | super.setUp()
504 | router = Routing()
505 | testingQueue = DispatchQueue(label: "Testing Queue", attributes: DispatchQueue.Attributes.concurrent)
506 | }
507 |
508 | func testSubscriptingRouterOpen() {
509 | let expect = expectation(description: "When subscripted first #mapped RouteHandler is called.")
510 |
511 | var routeCalled = 0
512 | router.map("routingexample://route", tags: ["First"]) { _, _, _, completed in
513 | routeCalled = 1
514 | expect.fulfill()
515 | completed()
516 | }
517 |
518 | router.map("routingexample://route") { _, _, _, completed in
519 | routeCalled = 2
520 | expect.fulfill()
521 | completed()
522 | }
523 |
524 | router["First"].open("routingexample://route")
525 | waitForExpectations(timeout: 0.1, handler: nil)
526 | XCTAssert(routeCalled == 1)
527 |
528 | }
529 |
530 | func testSubscriptingRouterQueue() {
531 | let expect = expectation(description: "When subscripted order is still preserved.")
532 |
533 | var results = [String]()
534 | router.map("routingexample://route", tags: ["First"]) { _, _, _, completed in
535 | results.append("one")
536 | completed()
537 | }
538 |
539 | router.map("routingexample://route", tags: ["Second"]) { _, _, _, completed in
540 | expect.fulfill()
541 | results.append("two")
542 | completed()
543 | }
544 |
545 | router["First"].open("routingexample://route")
546 | router["Second"].open("routingexample://route")
547 | waitForExpectations(timeout: 0.1, handler: nil)
548 | XCTAssert(results == ["one", "two"])
549 | }
550 | }
551 |
552 | class RoutingPerformanceTests: XCTestCase {
553 | var router: Routing!
554 | var testingQueue: DispatchQueue!
555 | override func setUp() {
556 | super.setUp()
557 | router = Routing()
558 | testingQueue = DispatchQueue(label: "Testing Queue", attributes: DispatchQueue.Attributes.concurrent)
559 | }
560 |
561 | func testPerfomance() {
562 | // NOTE: 1000 takes ~8 seconds seems exponential
563 | let num = 100
564 | for i in 1...num {
565 | print("\(i)")
566 | router.map("\(i)") { _, _, _, completed in completed() }
567 | }
568 |
569 | measure {
570 | let expect = self.expectation(description: "Waiting on num + 1.")
571 | let uuid = self.router.map("\(num + 1)") { _, _, _, completed in
572 | expect.fulfill()
573 | completed()
574 | }
575 |
576 | for i in 1...(num + 1) {
577 | self.router.open("\(i)")
578 | }
579 | self.waitForExpectations(timeout: 5, handler: nil)
580 | self.router.dispose(of: uuid)
581 | }
582 | }
583 | }
584 |
--------------------------------------------------------------------------------