├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── URLServiceRouter
│ ├── URLServiceRouter
│ ├── Extensions.swift
│ ├── URLServiceDecision.swift
│ ├── URLServiceError.swift
│ ├── URLServiceNodeParser.swift
│ ├── URLServiceNodeParserDecision.swift
│ ├── URLServiceNodel.swift
│ ├── URLServiceRequest.swift
│ ├── URLServiceRequestResponse.swift
│ ├── URLServiceRouteResult.swift
│ └── URLServiceRouter.swift
│ └── URLServiceRouterProtocol.swift
├── Tests
├── LinuxMain.swift
└── URLServiceRouterTests
│ ├── URLServiceRouterTests.swift
│ └── XCTestManifests.swift
├── URLServiceRouter.podspec
├── URLServiceRouter.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── URLServiceRouter
├── URLServiceRouter.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── URLServiceRouter.xcscheme
├── URLServiceRouter
│ └── Info.plist
└── URLServiceRouterTests
│ ├── Info.plist
│ └── URLServiceRouterTests.swift
├── URLServiceRouterApp
├── URLServiceRouterApp.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── URLServiceRouterApp.xcscheme
├── URLServiceRouterApp
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Demo.swift
│ ├── Info.plist
│ └── ViewController.swift
├── URLServiceRouterAppTests
│ ├── Info.plist
│ └── URLServiceRouterAppTests.swift
└── URLServiceRouterAppUITests
│ ├── Info.plist
│ └── URLServiceRouterAppUITests.swift
└── service_router.drawio
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | # Swift package
34 | #
35 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
36 | .DS_Store
37 | /.build
38 | /Packages
39 | /*.xcodeproj
40 | xcuserdata/
41 |
42 | # CocoaPods
43 | #
44 | # We recommend against adding the Pods directory to your .gitignore. However
45 | # you should judge for yourself, the pros and cons are mentioned at:
46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
47 | #
48 | Pods/
49 | #
50 | # Add this line if you want to avoid checking in source code from the Xcode workspace
51 | # *.xcworkspace
52 |
53 | # Carthage
54 | #
55 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
56 | # Carthage/Checkouts
57 |
58 | Carthage/Build/
59 |
60 | # fastlane
61 | #
62 | # It is recommended to not store the screenshots in the git repo.
63 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
64 | # For more information about the recommended setup visit:
65 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
66 |
67 | fastlane/report.xml
68 | fastlane/Preview.html
69 | fastlane/screenshots/**/*.png
70 | fastlane/test_output
71 |
72 | # Code Injection
73 | #
74 | # After new code Injection tools there's a generated folder /iOSInjectionProject
75 | # https://github.com/johnno1962/injectionforxcode
76 |
77 | iOSInjectionProject/
78 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright conwnet and other contributors
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.
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "URLServiceRouter",
8 | products: [
9 | // Products define the executables and libraries a package produces, and make them visible to other packages.
10 | .library(
11 | name: "URLServiceRouter",
12 | targets: ["URLServiceRouter"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
21 | .target(
22 | name: "URLServiceRouter",
23 | dependencies: []),
24 | .testTarget(
25 | name: "URLServiceRouterTests",
26 | dependencies: ["URLServiceRouter"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # URLServiceRouter
2 |
3 | [](https://cocoapods.org/pods/URLServiceRouter)
4 | [](https://github.com/Carthage/Carthage)
5 | [](https://swift.org/package-manager/)
6 | [](https://swift.org)
7 | [](https://developer.apple.com/xcode)
8 |
9 | ==================
10 |
11 |
12 | ## 这是什么?
13 |
14 | URLServiceRouter 是一个基于 URL 用 Swift 写的一个路由分发库,由一个高自由度的 nodeTree 跟 RPC 来实现的。
15 |
16 | ## 安装
17 |
18 | ### CocoaPods
19 |
20 | 1. 在 Podfile 中添加 `pod 'URLServiceRouter'`.
21 | 2. 执行 `pod install` 或 `pod update`.
22 | 3. 使用时,导入文件:`import URLServiceRouter`
23 |
24 | ### Carthage
25 |
26 | 1. 在 Cartfile 文件中添加 `github "lightank/URLServiceRouter"`.
27 | 2. 执行 `carthage update --platform ios` 并把 framework 添加到你的 Target 中.
28 | 3. 使用时,导入文件:`import URLServiceRouter`
29 |
30 | ### SPM
31 |
32 | 1. 在添加 Swift Package 时,输入 `https://github.com/lightank/URLServiceRouter.git`
33 | 2. 选择最新的tag.
34 | 3. 使用时,导入文件:`import URLServiceRouter`
35 |
36 | ## 要求
37 |
38 | * iOS 10.0
39 | * Swift 5.x
40 | * Xcode 12.x
41 |
42 | ## 开始使用
43 |
44 | 我们假设
45 |
46 | - 在一个真实世界的服务器中, 产线环境是:`www.realword.com`, 测试环境是:`*.realword.io`
47 | - `http://china.realword.io/owner//info` 代表查询 `owner_id` 对应的用户信息
48 |
49 | 注册 owner 服务
50 |
51 | ```swift
52 | URLServiceRouter.shared.registerService(name: "user://info") {
53 | URLOwnerInfoService()
54 | }
55 | ```
56 |
57 | 注册将测试环境host转为生产环境host的解析器
58 |
59 | ```swift
60 | URLServiceRouter.shared.registerNode(from: "https") {
61 | [URLServiceRedirectTestHostParser()]
62 | }
63 | ```
64 |
65 | 注册 owner URL 到 nodeTree 中
66 |
67 | ```swift
68 | do {
69 | URLServiceRouter.shared.registerNode(from: "https://www.realword.com/owner/info") {
70 | [URLServiceNoramlParser(parserType: .post, parseBlock: { nodeParser, _, decision in
71 | decision.complete(nodeParser, "user://info")
72 | })]
73 | }
74 | }
75 |
76 | do {
77 | URLServiceRouter.shared.registerNode(from: "https://www.realword.com/owner/") {
78 | let preParser = URLServiceNoramlParser(parserType: .pre, parseBlock: { nodeParser, request, decision in
79 | var nodeNames = request.nodeNames
80 | if let first = nodeNames.first, first.isPureInt {
81 | request.merge(params: ["id": nodeNames.remove(at: 0)], from: nodeParser)
82 | request.replace(nodeNames: nodeNames, from: nodeParser)
83 | }
84 | decision.next()
85 | })
86 |
87 | let postParser = URLServiceNoramlParser(parserType: .post, parseBlock: { _, _, decision in
88 | decision.next()
89 | })
90 | return [preParser, postParser]
91 | }
92 | }
93 | ```
94 |
95 | 请求 owner 服务
96 |
97 | ```swift
98 | if let url = URL(string: "http://china.realword.io/owner/1/info") {
99 | URLServiceRequest(url: url).start(callback: { request in
100 | if let data = request.response?.data {
101 | // 正确的数据
102 | self.showAlertMessge(title: "回调的业务数据", message: String(describing: data))
103 | }
104 | URLServiceRouter.shared.logInfo("\(String(describing: request.response?.data))")
105 | })
106 | }
107 | ```
108 |
109 | 直接调用服务
110 |
111 | ```swift
112 | URLServiceRouter.shared.callService(name: "user://info", params: "1") { _, _ in
113 | } callback: { result, _ in
114 | self.showAlertMessge(title: "回调的业务数据", message: String(describing: result))
115 | URLServiceRouter.shared.logInfo("\(String(describing: result))")
116 | }
117 | ```
--------------------------------------------------------------------------------
/Sources/URLServiceRouter/URLServiceRouter/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Extensions.swift
3 | // URLServiceRouter
4 | //
5 | // Created by huanyu.li on 2021/7/31.
6 | // Copyright © 2021 huanyu.li. All rights reserved.https://github.com/lightank/URLServiceRouter
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Array where Element == String {
12 | /// 将数组中的字符串用 / 连接起来
13 | var nodeUrlKey: String {
14 | return joined(separator: URLComponentSeparator)
15 | }
16 | }
17 |
18 | public extension RandomAccessCollection {
19 | /// 二分法查找符合谓词的最佳插入index
20 | /// - Parameter predicate: 谓词
21 | /// - Returns: 最佳index
22 | func binarySearch(predicate: (Iterator.Element) -> Bool) -> Index {
23 | var low = startIndex
24 | var high = endIndex
25 | while low != high {
26 | let mid = index(low, offsetBy: distance(from: low, to: high) / 2)
27 | if predicate(self[mid]) {
28 | low = index(after: mid)
29 | } else {
30 | high = mid
31 | }
32 | }
33 | return low
34 | }
35 | }
36 |
37 | public let URLComponentSeparator = "/"
38 |
39 | public extension URL {
40 | /// 将 URL 的 Components 转为 key、value 均为 String 的字典
41 | var nodeQueryItems: [String: String] {
42 | var params = [String: String]()
43 | return URLComponents(url: self, resolvingAgainstBaseURL: false)?
44 | .queryItems?
45 | .reduce([:]) { _, item -> [String: String] in
46 | params[item.name] = item.value
47 | return params
48 | } ?? [:]
49 | }
50 |
51 | /// 将 URL 的 scheme、host、pathComponents 中的元素按左到右的顺序添加到一个字符串数组中,其中 scheme、host 会统一转为小写
52 | var nodeNames: [String] {
53 | var nodeNames = [String]()
54 |
55 | if let scheme = scheme?.lowercased() {
56 | nodeNames.append(scheme)
57 | }
58 | if let host = host?.lowercased() {
59 | nodeNames.append(host)
60 | }
61 |
62 | var paths = pathComponents
63 | if paths.first == URLComponentSeparator {
64 | paths.remove(at: 0)
65 | }
66 | if !path.isEmpty {
67 | nodeNames += paths
68 | }
69 |
70 | return nodeNames
71 | }
72 |
73 | /// 将 URL 的 scheme、host 转为小写
74 | var nodeUrl: URL {
75 | var nodeUrl = ""
76 | if let scheme = scheme?.lowercased() {
77 | nodeUrl += "\(scheme)://"
78 | }
79 |
80 | var paths = pathComponents
81 | if paths.first == URLComponentSeparator {
82 | paths.remove(at: 0)
83 | }
84 | if let host = host?.lowercased() {
85 | paths.insert(host, at: 0)
86 | }
87 | nodeUrl += paths.joined(separator: URLComponentSeparator)
88 | if let url = URL(string: nodeUrl) {
89 | return url
90 | } else {
91 | URLServiceRouter.shared.logError("this url:\(self) can not turn to node url")
92 | assert(true, "this url:\(absoluteURL) can not turn to node url")
93 | return self
94 | }
95 | }
96 | }
97 |
98 | public extension String {
99 | var nodeUrl: String {
100 | if let url = URL(string: self) {
101 | return url.nodeUrl.absoluteString
102 | } else {
103 | URLServiceRouter.shared.logError("this string:\(self) can not turn to node url")
104 | assert(true, "this string:\(self) can not turn to node url")
105 | return self
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Sources/URLServiceRouter/URLServiceRouter/URLServiceDecision.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLServiceDecision.swift
3 | // URLServiceRouter
4 | //
5 | // Created by huanyu on 2023/8/21.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct URLServiceDecision: URLServiceDecisionProtocol {
11 | public var next: () -> Void
12 | public var complete: () -> Void
13 |
14 | public init(next: @escaping () -> Void, complete: @escaping () -> Void) {
15 | self.next = next
16 | self.complete = complete
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/URLServiceRouter/URLServiceRouter/URLServiceError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLServiceError.swift
3 | // URLServiceRouter
4 | //
5 | // Created by huanyu.li on 2021/8/3.
6 | // Copyright © 2021 huanyu.li. All rights reserved.https://github.com/lightank/URLServiceRouter
7 | //
8 |
9 | import Foundation
10 |
11 | public let URLServiceErrorForbiddenCode = "403"
12 | public let URLServiceErrorForbidden = URLServiceError(code: URLServiceErrorForbiddenCode, message: "url service router refused this request")
13 | public let URLServiceErrorNotFoundCode = "404"
14 | public let URLServiceErrorNotFound = URLServiceError(code: URLServiceErrorNotFoundCode, message: "service not found")
15 | public let URLServiceErrorRequestTimeoutCode = "408"
16 | public let URLServiceErrorRequestTimeout = URLServiceError(code: URLServiceErrorRequestTimeoutCode, message: "service request time out")
17 |
18 | public struct URLServiceError: URLServiceErrorProtocol {
19 | public var code: String
20 | public var message: String
21 |
22 | public init(code: String, message: String = "") {
23 | self.code = code
24 | self.message = message
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/URLServiceRouter/URLServiceRouter/URLServiceNodeParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLNodeParser.swift
3 | // URLServiceRouter
4 | //
5 | // Created by huanyu.li on 2021/7/31.
6 | // Copyright © 2021 huanyu.li. All rights reserved.https://github.com/lightank/URLServiceRouter
7 | //
8 |
9 | import Foundation
10 |
11 | public struct URLServiceNoramlParser: URLServiceNodeParserProtocol {
12 | public let priority: Int
13 | public let parserType: URLServiceNodeParserType
14 | public let parseBlock: (URLServiceNodeParserProtocol, URLServiceRequestProtocol, URLServiceNodeParserDecisionProtocol) -> Void
15 |
16 | public func parse(request: URLServiceRequestProtocol, decision: URLServiceNodeParserDecisionProtocol) {
17 | parseBlock(self, request, decision)
18 | }
19 |
20 | public init(priority: Int = URLServiceNodeParserPriorityDefault, parserType: URLServiceNodeParserType, parseBlock: @escaping (URLServiceNodeParserProtocol, URLServiceRequestProtocol, URLServiceNodeParserDecisionProtocol) -> Void) {
21 | self.priority = priority
22 | self.parserType = parserType
23 | self.parseBlock = parseBlock
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/URLServiceRouter/URLServiceRouter/URLServiceNodeParserDecision.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLNodeParserDecision.swift
3 | // URLServiceRouter
4 | //
5 | // Created by huanyu.li on 2021/7/31.
6 | // Copyright © 2021 huanyu.li. All rights reserved.https://github.com/lightank/URLServiceRouter
7 | //
8 |
9 | import Foundation
10 |
11 | public struct URLServiceNodeParserDecision: URLServiceNodeParserDecisionProtocol {
12 | public let next: () -> Void
13 | public let complete: (URLServiceNodeParserProtocol, String) -> Void
14 |
15 | public init(next: @escaping () -> Void, complete: @escaping (URLServiceNodeParserProtocol, String) -> Void) {
16 | self.next = next
17 | self.complete = complete
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/URLServiceRouter/URLServiceRouter/URLServiceNodel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLNode.swift
3 | // URLServiceRouter
4 | //
5 | // Created by huanyu.li on 2021/7/30.
6 | // Copyright © 2021 huanyu.li. All rights reserved.https://github.com/lightank/URLServiceRouter
7 | //
8 |
9 | import Foundation
10 |
11 | public class URServiceNode: URLServiceNodeProtocol {
12 | public let name: String
13 | public let parentNode: URLServiceNodeProtocol?
14 | public private(set) var preParsers: [URLServiceNodeParserProtocol] = []
15 | public private(set) var postParsers: [URLServiceNodeParserProtocol] = []
16 | public var parsersBuilder: URLServiceNodeParsersBuilder?
17 | private var subNodesDict: [String: URLServiceNodeProtocol] = [:]
18 |
19 | init(name: String, parentNode: URLServiceNodeProtocol?) {
20 | self.name = name
21 | self.parentNode = parentNode
22 | }
23 |
24 | public func routedNodeNames() -> [String] {
25 | var routedNodeNames = [String]()
26 | if parentNode != nil {
27 | var currentNode: URLServiceNodeProtocol? = self
28 | while let current = currentNode, let parent = current.parentNode {
29 | routedNodeNames.append(current.name)
30 | currentNode = parent
31 | }
32 | }
33 | return routedNodeNames.reversed()
34 | }
35 |
36 | public func registerSubNode(with name: String) -> URLServiceNodeProtocol {
37 | if let subNode = subNodesDict[name] {
38 | return subNode
39 | }
40 |
41 | let node = URServiceNode(name: name, parentNode: self)
42 | register(subNode: node)
43 | return node
44 | }
45 |
46 | public func register(subNode: URLServiceNodeProtocol) {
47 | if exitedSubNode(subNode) {
48 | assert(true, "node: \(name) have register subNode: \(subNode.name)")
49 | }
50 |
51 | subNodesDict[subNode.name] = subNode
52 | }
53 |
54 | func exitedSubNode(_ subNode: URLServiceNodeProtocol) -> Bool {
55 | return subNodesDict.contains { (_: String, value: URLServiceNodeProtocol) in
56 | subNode.name == value.name
57 | }
58 | }
59 |
60 | public func register(parser: URLServiceNodeParserProtocol) {
61 | switch parser.parserType {
62 | case .pre:
63 | let index = preParsers.binarySearch(predicate: { $0.priority >= parser.priority })
64 | preParsers.insert(parser, at: index)
65 | case .post:
66 | let index = postParsers.binarySearch(predicate: { $0.priority >= parser.priority })
67 | postParsers.insert(parser, at: index)
68 | }
69 | }
70 |
71 | public func route(request: URLServiceRequestProtocol, result: URLServiceRouteResultProtocol) {
72 | if let parsers = parsersBuilder?() {
73 | parsers.forEach { register(parser: $0) }
74 | parsersBuilder = nil
75 | }
76 | // 路由查找
77 | routePreParser(request: request, result: result)
78 | }
79 |
80 | public func routePreParser(request: URLServiceRequestProtocol, result: URLServiceRouteResultProtocol) {
81 | preParser(request: request, parserIndex: 0, decision: URLServiceNodeParserDecision(next: { [self] in
82 | if let nodeName = request.nodeNames.first, let node = self.subNodesDict[nodeName] {
83 | request.reduceOneNodeName(from: node)
84 | node.route(request: request, result: result)
85 | } else {
86 | result.recordEndNode(self)
87 | // 路由回溯
88 | routePostParser(request: request, result: result)
89 | }
90 | }, complete: { nodeParser, service in
91 | result.routerCompletion(self, nodeParser, service)
92 | }))
93 | }
94 |
95 | public func preParser(request: URLServiceRequestProtocol, parserIndex: Int, decision: URLServiceNodeParserDecisionProtocol) {
96 | if preParsers.count > parserIndex {
97 | preParsers[parserIndex].parse(request: request, decision: URLServiceNodeParserDecision(next: { [self] in
98 | preParser(request: request, parserIndex: parserIndex + 1, decision: decision)
99 | }, complete: { nodeParser, result in
100 | decision.complete(nodeParser, result)
101 | }))
102 | } else {
103 | decision.next()
104 | }
105 | }
106 |
107 | public func routePostParser(request: URLServiceRequestProtocol, result: URLServiceRouteResultProtocol) {
108 | postParser(request: request, parserIndex: 0, decision: URLServiceNodeParserDecision(next: { [self] in
109 | if let parentNode = parentNode {
110 | request.restoreOneNodeName(from: self)
111 | parentNode.routePostParser(request: request, result: result)
112 | } else {
113 | result.routerCompletion(self, nil, nil)
114 | }
115 | }, complete: { nodeParser, service in
116 | result.routerCompletion(self, nodeParser, service)
117 | }))
118 | }
119 |
120 | func postParser(request: URLServiceRequestProtocol, parserIndex: Int, decision: URLServiceNodeParserDecisionProtocol) {
121 | if postParsers.count > parserIndex {
122 | postParsers[parserIndex].parse(request: request, decision: URLServiceNodeParserDecision(next: { [self] in
123 | postParser(request: request, parserIndex: parserIndex + 1, decision: decision)
124 | }, complete: { nodeParser, result in
125 | decision.complete(nodeParser, result)
126 | }))
127 | } else {
128 | decision.next()
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Sources/URLServiceRouter/URLServiceRouter/URLServiceRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLServiceRequest.swift
3 | // URLServiceRouter
4 | //
5 | // Created by huanyu.li on 2021/8/2.
6 | // Copyright © 2021 huanyu.li. All rights reserved.https://github.com/lightank/URLServiceRouter
7 | //
8 |
9 | import Foundation
10 |
11 | public func MainThreadExecute(_ block: @escaping () -> Void) {
12 | if Thread.isMainThread {
13 | block()
14 | } else {
15 | DispatchQueue.main.async {
16 | block()
17 | }
18 | }
19 | }
20 |
21 | public class URLServiceRequest: URLServiceRequestProtocol {
22 | public private(set) var url: URL
23 | public let serviceRouter: URLServiceRouterProtocol
24 | public private(set) var nodeNames: [String]
25 | private var params: [String: Any]
26 | public var response: URLServiceRequestResponseProtocol?
27 | public var success: URLServiceRequestCompletionBlock?
28 | public var failure: URLServiceRequestCompletionBlock?
29 | public var callback: URLServiceRequestCompletionBlock?
30 | private var serviceCallback: URLServiceExecutionCallback?
31 | private var isCanceled: Bool = false
32 | private let requestTimeoutInterval: TimeInterval
33 | private var timer: Timer?
34 | public let isOnlyRouting: Bool
35 |
36 | /// 初始化方法
37 | /// - Parameters:
38 | /// - url: 请求url
39 | /// - params: 额外的请求参数
40 | /// - requestTimeoutInterval: 请求超时时长,默认值为0,也就是说没有超时时长,时长大于0的时候时长才会生效,到时间就自动请求失败,返回 URLServiceErrorTimeout 错误
41 | /// - serviceRouter: 处理请求的服务路由器
42 | /// - isOnlyRouting: 是否仅路由请求但并不执行服务,默认值为 false
43 | public init(url: URL, params: [String: Any] = [String: Any](), requestTimeoutInterval: TimeInterval = 0, serviceRouter: URLServiceRouterProtocol = URLServiceRouter.shared, isOnlyRouting: Bool = false) {
44 | self.url = url
45 | self.serviceRouter = serviceRouter
46 | self.nodeNames = url.nodeNames
47 | self.params = url.nodeQueryItems
48 | self.params.merge(params) { _, new in new }
49 | self.requestTimeoutInterval = requestTimeoutInterval
50 | self.isOnlyRouting = isOnlyRouting
51 | }
52 |
53 | // MARK: - route
54 |
55 | public func requestParams() -> Any? {
56 | params[URLServiceRequestOriginalURLKey] = url.absoluteURL
57 | return params
58 | }
59 |
60 | public func updateResponse(_ response: URLServiceRequestResponseProtocol?) {
61 | self.response = response
62 | }
63 |
64 | public func routingCompletion() {
65 | MainThreadExecute { [self] in
66 | if let serviceName = self.response?.serviceName, serviceRouter.isRegisteredService(serviceName) {
67 | requestSucceeded(serviceName: serviceName)
68 | } else {
69 | requestFailed()
70 | }
71 | }
72 | }
73 |
74 | public func replace(nodeNames: [String], from nodeParser: URLServiceNodeParserProtocol) {
75 | if nodeParser.parserType == .pre {
76 | self.nodeNames = nodeNames
77 | }
78 | }
79 |
80 | public func reduceOneNodeName(from node: URLServiceNodeProtocol) {
81 | if node.parentNode != nil {
82 | nodeNames.remove(at: 0)
83 | }
84 | }
85 |
86 | public func restoreOneNodeName(from node: URLServiceNodeProtocol) {
87 | if node.routedNodeNames().isEmpty {
88 | return
89 | }
90 | nodeNames.insert(node.name, at: 0)
91 | }
92 |
93 | public func merge(params: Any, from nodeParser: URLServiceNodeParserProtocol) {
94 | if nodeParser.parserType == .pre, params is [String: Any] {
95 | self.params.merge(params as! [String: Any]) { _, new in new }
96 | }
97 | }
98 |
99 | public func replace(params: Any?, from nodeParser: URLServiceNodeParserProtocol) {
100 | if nodeParser.parserType == .pre, params is [String: Any]? {
101 | if params != nil {
102 | self.params = params as! [String: Any]
103 | } else {
104 | self.params.removeAll()
105 | }
106 | }
107 | }
108 |
109 | // MARK: - request
110 |
111 | public func start(success: URLServiceRequestCompletionBlock? = nil, failure: URLServiceRequestCompletionBlock? = nil, callback: URLServiceRequestCompletionBlock? = nil) {
112 | let existCallback = !(success == nil && failure == nil && callback == nil)
113 | if requestTimeoutInterval > 0, existCallback {
114 | let timer = Timer(timeInterval: requestTimeoutInterval, repeats: false) { [self] _ in
115 | requestTimeout()
116 | }
117 | RunLoop.current.add(timer, forMode: .default)
118 | timer.fire()
119 | self.timer = timer
120 | }
121 |
122 | self.success = success
123 | self.failure = failure
124 | self.callback = callback
125 | if callback != nil {
126 | serviceCallback = { [self] (result: Any?, error: URLServiceErrorProtocol?) in
127 | response?.data = result
128 | response?.error = error
129 | MainThreadExecute { [self] in
130 | callback?(self)
131 | stop()
132 | }
133 | }
134 | }
135 | serviceRouter.route(request: self)
136 | }
137 |
138 | private func requestSucceeded(serviceName: String) {
139 | success?(self)
140 | success = nil
141 |
142 | if isOnlyRouting {
143 | stop()
144 | }
145 |
146 | if !isCanceled {
147 | serviceRouter.callService(name: serviceName, params: requestParams(), completion: nil, callback: serviceCallback)
148 | }
149 | }
150 |
151 | private func requestFailed() {
152 | failure?(self)
153 | failure = nil
154 |
155 | stop()
156 | }
157 |
158 | private func requestTimeout() {
159 | updateResponse(URLServiceRequestResponse(serviceName: nil, error: URLServiceErrorRequestTimeout))
160 | requestFailed()
161 | }
162 |
163 | public func stop() {
164 | isCanceled = true
165 | timer?.invalidate()
166 |
167 | success = nil
168 | failure = nil
169 | callback = nil
170 | serviceCallback = nil
171 | }
172 |
173 | public var description: String {
174 | "URLServiceRequest - url: \(String(describing: url)), params:\(String(describing: params))"
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/Sources/URLServiceRouter/URLServiceRouter/URLServiceRequestResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLServiceRequestResponse.swift
3 | // URLServiceRouter
4 | //
5 | // Created by huanyu.li on 2021/8/2.
6 | // Copyright © 2021 huanyu.li. All rights reserved.https://github.com/lightank/URLServiceRouter
7 | //
8 |
9 | import Foundation
10 |
11 | public struct URLServiceRequestResponse: URLServiceRequestResponseProtocol {
12 | public var serviceName: String?
13 | public var error: URLServiceErrorProtocol?
14 | public var data: Any?
15 |
16 | init(serviceName: String? = nil, error: URLServiceErrorProtocol? = nil) {
17 | self.serviceName = serviceName
18 | self.error = error
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/URLServiceRouter/URLServiceRouter/URLServiceRouteResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLServiceRouteResult.swift
3 | // URLServiceRouter
4 | //
5 | // Created by huanyu.li on 2021/8/2.
6 | // Copyright © 2021 huanyu.li. All rights reserved.https://github.com/lightank/URLServiceRouter
7 | //
8 |
9 | import Foundation
10 |
11 | public class URLServiceRouteResult: URLServiceRouteResultProtocol {
12 | public private(set) var endNode: URLServiceNodeProtocol?
13 | public private(set) var responseNode: URLServiceNodeProtocol?
14 | public private(set) var responseNodeParser: URLServiceNodeParserProtocol?
15 | public private(set) var responseServiceName: String?
16 | public private(set) lazy var recordEndNode = { (node: URLServiceNodeProtocol) in
17 | self.endNode = node
18 | }
19 |
20 | public private(set) lazy var routerCompletion = { (node: URLServiceNodeProtocol, nodeParser: URLServiceNodeParserProtocol?, serviceName: String?) in
21 | self.responseNode = node
22 | self.responseNodeParser = nodeParser
23 | self.responseServiceName = serviceName
24 | self.completion(self)
25 | }
26 |
27 | public var completion: (URLServiceRouteResultProtocol) -> Void
28 |
29 | public init(endNode: URLServiceNodeProtocol? = nil, responseNode: URLServiceNodeProtocol? = nil, responseServiceName: String? = nil, completion: @escaping (URLServiceRouteResultProtocol) -> Void) {
30 | self.endNode = endNode
31 | self.responseNode = responseNode
32 | self.responseServiceName = responseServiceName
33 | self.completion = completion
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/URLServiceRouter/URLServiceRouter/URLServiceRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLServiceRounter.swift
3 | // URLServiceRouter
4 | //
5 | // Created by huanyu.li on 2021/8/1.
6 | // Copyright © 2021 huanyu.li. All rights reserved.https://github.com/lightank/URLServiceRouter
7 | //
8 |
9 | import Foundation
10 |
11 | public class URLServiceRouter: URLServiceRouterProtocol {
12 | public var rootNodeParsersBuilder: URLServiceNodeParsersBuilder?
13 |
14 | public var delegate: URLServiceRouterDelegateProtocol?
15 | let rootNode = URServiceNode(name: "root node", parentNode: nil)
16 | private var serviceBuilderMap = [String: () -> URLServiceProtocol]()
17 | var nodesMap = [String: URLServiceNodeProtocol]()
18 | let queue = DispatchQueue(label: "com.URLServiceRouter.queue", attributes: .concurrent)
19 |
20 | public static let shared = URLServiceRouter()
21 |
22 | // MARK: - node
23 |
24 | public func registerNode(from url: String, parsersBuilder: URLServiceNodeParsersBuilder? = nil) {
25 | if let newUrl = URL(string: url)?.nodeUrl {
26 | let nodeUrlKey = newUrl.nodeUrl.absoluteString
27 | assert(!isRegisteredNode(key: nodeUrlKey), "url: \(nodeUrlKey) already registered")
28 | let names = newUrl.nodeNames
29 | let nodeNamesKey = names.nodeUrlKey
30 | assert(!isRegisteredNode(key: nodeNamesKey), "url: \(nodeNamesKey) already registered")
31 |
32 | recordNodeInfo(key: nodeUrlKey, node: privateRegisterNode(from: names, parsersBuilder: parsersBuilder))
33 | } else {
34 | logInfo("register url:\(url) is invalid")
35 | }
36 | }
37 |
38 | public func isRegisteredNode(_ url: String) -> Bool {
39 | return isRegisteredNode(key: url.nodeUrl)
40 | }
41 |
42 | public func allRegisteredNodeUrls() -> [String] {
43 | return nodesMap.keys.sorted { $0 < $1 }
44 | }
45 |
46 | private func privateRegisterNode(from names: [String], parsersBuilder: URLServiceNodeParsersBuilder?) -> URLServiceNodeProtocol {
47 | queue.sync(flags: .barrier) { [self] in
48 | var currentNode: URLServiceNodeProtocol = rootNode
49 | names.forEach { currentNode = currentNode.registerSubNode(with: $0) }
50 | currentNode.parsersBuilder = parsersBuilder
51 | return currentNode
52 | }
53 | }
54 |
55 | private func isRegisteredNode(key: String) -> Bool {
56 | return nodesMap[key] != nil
57 | }
58 |
59 | private func recordNodeInfo(key: String, node: URLServiceNodeProtocol) {
60 | assert(!isRegisteredNode(key: key), "url: \(key) already registered")
61 | nodesMap[key] = node
62 | }
63 |
64 | // MARK: 服务
65 |
66 | public func registerService(name: String, builder: @escaping () -> URLServiceProtocol) {
67 | queue.sync(flags: .barrier) { [self] in
68 | assert(vaildServiceWithName(name) == nil, "service: \(name) already exist")
69 | serviceBuilderMap[name] = builder
70 | }
71 | }
72 |
73 | public func isRegisteredService(_ name: String) -> Bool {
74 | return vaildServiceWithName(name) != nil
75 | }
76 |
77 | public func callService(name: String, params: Any? = nil, completion: ((URLServiceProtocol?, URLServiceErrorProtocol?) -> Void)?, callback: URLServiceExecutionCallback?) {
78 | let resultService = vaildServiceWithName(name)
79 | assert(resultService != nil, "service:\(name) is not registered")
80 | let error: URLServiceErrorProtocol? = resultService != nil ? nil : URLServiceErrorNotFound
81 | completion?(resultService, error)
82 |
83 | if let service = resultService {
84 | var preServiceNames = service.preServiceNames
85 | preServiceNames.removeAll { preServiceName in
86 | let isNotRegistered = !isRegisteredService(preServiceName)
87 | assert(!isNotRegistered, "service:\(name) 's preServiceName:\(preServiceName) is note registered")
88 | return isNotRegistered
89 | }
90 | if preServiceNames.isEmpty {
91 | // 如果没有前置服务,则直接调用服务
92 | service.execute(params: params, callback: callback)
93 | } else {
94 | // 有前置服务,则走前置服务调用流程
95 | excutPreService(currentService: service, params: params, preServiceNames: preServiceNames, complete: {
96 | // 前置服务调用的终点就是当前服务的调用
97 | service.execute(params: params, callback: callback)
98 | })
99 | }
100 | }
101 | }
102 |
103 | private func excutPreService(currentService: URLServiceProtocol, params: Any?, preServiceNames: [String], complete: @escaping () -> Void) {
104 | excutPreService(currentService: currentService, preServiceNames: preServiceNames, index: 0, decision: URLServiceDecision(next: {
105 | // 执行完最后一个前置服务,就完成前置服务调用
106 | complete()
107 | }, complete: {
108 | // 被前置服务直接终止链路,则直接完成前置服务调用
109 | complete()
110 | }))
111 | }
112 |
113 | private func excutPreService(currentService: URLServiceProtocol, preServiceNames: [String], index: Int, decision: URLServiceDecisionProtocol) {
114 | if preServiceNames.count > index {
115 | let preServiceName = preServiceNames[index]
116 | if let preService = vaildServiceWithName(preServiceName) {
117 | preService.execute(params: currentService.paramsForPreService(name: preServiceName)) { result, error in
118 | currentService.preServiceCallBack(name: preServiceName, result: result, error: error, decision: URLServiceDecision(next: { [self] in
119 | excutPreService(currentService: currentService, preServiceNames: preServiceNames, index: index + 1, decision: decision)
120 | }, complete: {
121 | decision.complete()
122 | }))
123 | }
124 | } else {
125 | /// 如果该前置服务不存在,则执行下一个前置服务
126 | decision.next()
127 | }
128 | } else {
129 | decision.next()
130 | }
131 | }
132 |
133 | public func allRegisteredServiceNames() -> [String] {
134 | return serviceBuilderMap.keys.sorted { $0 < $1 }
135 | }
136 |
137 | private func vaildServiceName(name: String?) -> String? {
138 | if let newName = name, isRegisteredService(newName) {
139 | return name
140 | }
141 | return nil
142 | }
143 |
144 | private func vaildServiceWithName(_ name: String?) -> URLServiceProtocol? {
145 | if let newName = name {
146 | return serviceBuilderMap[newName]?()
147 | }
148 | return nil
149 | }
150 |
151 | // MARK: 服务请求
152 |
153 | public func route(request: URLServiceRequestProtocol) {
154 | if let rootNodeParsers = rootNodeParsersBuilder?() {
155 | rootNodeParsers.forEach { rootNode.register(parser: $0) }
156 | rootNodeParsersBuilder = nil
157 | }
158 | queue.sync { [self] in
159 | if let newDelegate = delegate, !newDelegate.shouldRoute(request: request) {
160 | logInfo("URLServiceRouter request: \(request.description) is refused by \(String(describing: delegate))")
161 | request.updateResponse(URLServiceRequestResponse(error: URLServiceErrorForbidden))
162 | request.routingCompletion()
163 | return
164 | }
165 |
166 | logInfo("URLServiceRouter start router \nrequest: \(request.description)")
167 | rootNode.route(request: request, result: URLServiceRouteResult(completion: { [self] routerResult in
168 | logInfo("URLServiceRouter router completed: \(request.description) is response by \(String(describing: routerResult.responseNode)), the response chain end node is \(String(describing: routerResult.endNode)), the response service name is \(String(describing: routerResult.responseServiceName))")
169 | if let serviceName = routerResult.responseServiceName, isRegisteredService(serviceName) {
170 | logError("URLServiceRouter router completed: \(request.description) is response by \(String(describing: routerResult.responseNode)) but the given service name: \(serviceName) is invaild")
171 | }
172 | // 先将路由结果给到请求,因为代理可能会处理这个请求结果
173 | request.updateResponse(URLServiceRequestResponse(serviceName: vaildServiceName(name: routerResult.responseServiceName)))
174 | // 路由处理请求结果
175 | delegate?.dynamicProcessingServiceRequest(request)
176 |
177 | // 获取最新的响应对象,注意:这个对象可能被代理处理过
178 | let response = request.response
179 | var error: URLServiceErrorProtocol?
180 | let responseServiceName = vaildServiceName(name: response?.serviceName)
181 |
182 | if let serviceName = responseServiceName, let _ = vaildServiceWithName(serviceName) {
183 | error = nil
184 | } else {
185 | error = URLServiceErrorNotFound
186 | }
187 | // 最后设置一下路由结果
188 | request.updateResponse(URLServiceRequestResponse(serviceName: responseServiceName, error: error))
189 |
190 | // 告知请求路由结束,可以进行相关回调与服务调用了
191 | request.routingCompletion()
192 | logInfo("URLServiceRouter end router \nrequest: \(request.description), \nservice:\(String(describing: responseServiceName)) \nerrorCode:\(String(describing: error?.code)) \nerrorMessage:\(String(describing: error?.message))")
193 | }))
194 | }
195 | }
196 |
197 | public func canHandleUrl(url: String) -> Bool {
198 | guard let newUrl = URL(string: url)?.nodeUrl else {
199 | return false
200 | }
201 | var canHandle = false
202 | URLServiceRequest(url: newUrl, isOnlyRouting: true).start(success: { _ in
203 | canHandle = true
204 | })
205 |
206 | return canHandle
207 | }
208 |
209 | // MARK: - 服务请求
210 |
211 | public func unitTestRequest(url: String, shouldDelegateProcessingRouterResult: Bool = false, completion: @escaping ((URLServiceRequestProtocol, URLServiceRouteResultProtocol) -> Void)) {
212 | #if DEBUG
213 | queue.sync { [self] in
214 | assert(URL(string: url) != nil, "unitTest request url:\(url) is inviald")
215 | if let newUrl = URL(string: url) {
216 | let request = URLServiceRequest(url: newUrl)
217 | logInfo("URLServiceRouter start unitTest: \nrequest: \(request.description)")
218 | rootNode.route(request: request, result: URLServiceRouteResult(completion: { [self] routerResult in
219 | request.updateResponse(URLServiceRequestResponse(serviceName: vaildServiceName(name: routerResult.responseServiceName)))
220 | if shouldDelegateProcessingRouterResult {
221 | delegate?.dynamicProcessingServiceRequest(request)
222 | }
223 |
224 | let newRouterResult = URLServiceRouteResult(endNode: routerResult.endNode, responseNode: routerResult.responseNode, responseServiceName: vaildServiceName(name: request.response?.serviceName)) { _ in }
225 | completion(request, newRouterResult)
226 | }))
227 | }
228 | }
229 | #endif
230 | }
231 |
232 | // MARK: - 日志
233 |
234 | public func logInfo(_ message: String) {
235 | delegate?.logInfo(message)
236 | }
237 |
238 | public func logError(_ message: String) {
239 | delegate?.logError(message)
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/Sources/URLServiceRouter/URLServiceRouterProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLRouterProtocol.swift
3 | // URLServiceRouter
4 | //
5 | // Created by huanyu.li on 2021/1/28.
6 | // Copyright © 2021 huanyu.li. All rights reserved.https://github.com/lightank/URLServiceRouter
7 | //
8 |
9 | import Foundation
10 | #if canImport(UIKit)
11 | import UIKit
12 | #endif
13 |
14 | public let URLServiceRequestOriginalURLKey = "origin_request_url"
15 | public let URLServiceNodeParserPriorityDefault = 100
16 |
17 | public typealias URLServiceNodeParsersBuilder = () -> [URLServiceNodeParserProtocol]
18 |
19 | // MARK: - URLServiceRouterDelegate
20 |
21 | public protocol URLServiceRouterDelegateProtocol {
22 | // MARK: 控制器信息
23 |
24 | #if canImport(UIKit)
25 | /// 返回 app 当前的显示的 vc,方便页面级的 URLService 进行跳转
26 | func currentViewController() -> UIViewController?
27 | /// 返回 app 当前的显示的 vc 所在的导航控制器,方便页面级的 URLService 进行跳转
28 | func currentNavigationController() -> UINavigationController?
29 | #endif
30 |
31 | // MARK: 服务请求
32 |
33 | /// 拦截并决定是否发起此次 URLServiceRouter 中的服务路由请求
34 | /// - Parameter request: 当前 URLServiceRouter 将要发起的服务请求
35 | func shouldRoute(request: URLServiceRequestProtocol) -> Bool
36 | /// 动态处理服务请求结果
37 | /// - Parameters:
38 | /// - request: 当前的服务请求
39 | func dynamicProcessingServiceRequest(_ request: URLServiceRequestProtocol)
40 |
41 | // MARK: 日志
42 |
43 | /// 记录从 URLServiceRouter 来的错误日志
44 | /// - Parameter message: error message
45 | func logError(_ message: String)
46 | /// 记录从 URLServiceRouter 来的普通日志
47 | /// - Parameter message: info message
48 | func logInfo(_ message: String)
49 | }
50 |
51 | // MARK: - URLServiceRouter
52 |
53 | public protocol URLServiceRouterProtocol {
54 | /// 返回将要在根节点中注册的解析器数组
55 | var rootNodeParsersBuilder: URLServiceNodeParsersBuilder? { get set }
56 |
57 | // MARK: 代理
58 |
59 | /// 代理,提供:当前vc信息、拦截请求、动态处理请求结果、记录日志等功能
60 | var delegate: URLServiceRouterDelegateProtocol? { get set }
61 |
62 | // MARK: node
63 |
64 | /// 从字符串 url 中注册一个 node chain,并在 endNode 中注册解析器数组。如果除 endNode 外的 node 如果已经存在,将不会重复注册,仅取值
65 | /// - Parameters:
66 | /// - url: 字符串 url
67 | /// - parsersBuilder: 将在 endNode 中注册解析器数组 Builder
68 | func registerNode(from url: String, parsersBuilder: URLServiceNodeParsersBuilder?)
69 | /// 检测给的名称的node url字符串是否注册过
70 | /// - Parameter url: node url字符串
71 | func isRegisteredNode(_ url: String) -> Bool
72 | /// 返回所有已经注册过的 node URL 数组
73 | func allRegisteredNodeUrls() -> [String]
74 |
75 | // MARK: 服务
76 |
77 | /// 注册服务
78 | /// - Parameter name: 服务名称
79 | /// - Parameter builder: 构造器
80 | func registerService(name: String, builder: @escaping () -> URLServiceProtocol)
81 |
82 | /// 调用服务
83 | /// - Parameters:
84 | /// - name: 服务名称
85 | /// - params: 请求参数
86 | /// - completion: 路由完成回调,如果服务存在,将在 completion 后真正开始调用服务
87 | /// - callback: 服务调用完成回调,如果关注服务调用结果的话,应该给它赋值
88 | func callService(name: String, params: Any?, completion: ((URLServiceProtocol?, URLServiceErrorProtocol?) -> Void)?, callback: URLServiceExecutionCallback?)
89 | /// 检测给定的服务名称对应的服务是否有注册
90 | /// - Parameter name: 服务名称
91 | func isRegisteredService(_ name: String) -> Bool
92 | /// 返回所有已经注册过的服务的名称数组
93 | func allRegisteredServiceNames() -> [String]
94 |
95 | // MARK: 服务请求
96 |
97 | /// 路由一个服务请求
98 | /// - Parameter request: 服务请求
99 | func route(request: URLServiceRequestProtocol)
100 |
101 | /// 能否处理给定 url,即 url 是否有命中的服务
102 | /// - Returns: true 代表可处理,false 代表不可处理
103 | func canHandleUrl(url: String) -> Bool
104 |
105 | // MARK: 日志
106 |
107 | /// 记录错误日志,内部会交给代理处理
108 | /// - Parameter message: 信息内容
109 | func logError(_ message: String)
110 | /// 记录普通日志,内部会交给代理处理
111 | /// - Parameter message: 信息内容
112 | func logInfo(_ message: String)
113 |
114 | // MARK: 测试
115 |
116 | /// 测试请求解析流程、是否达到服务调用条件,但并不会调用服务。注意:仅在 DEBUG 下执行
117 | /// - Parameters:
118 | /// - url: 请求url
119 | /// - shouldDelegateProcessingRouterResult: 代理是否处理请求结果
120 | /// - completion: 完成请求命中服务的回调
121 | func unitTestRequest(url: String, shouldDelegateProcessingRouterResult: Bool, completion: @escaping ((URLServiceRequestProtocol, URLServiceRouteResultProtocol) -> Void))
122 | }
123 |
124 | // MARK: - URLServiceRouteResult
125 |
126 | public protocol URLServiceRouteResultProtocol {
127 | /// 请求命中的 node chain 的 end node
128 | var endNode: URLServiceNodeProtocol? { get }
129 | /// 请求命中的 node chain 的最终响应的 node
130 | var responseNode: URLServiceNodeProtocol? { get }
131 | /// 给出请求结果的 node 解析器
132 | var responseNodeParser: URLServiceNodeParserProtocol? { get }
133 | /// 请求命中的服务名称
134 | var responseServiceName: String? { get }
135 |
136 | /// 记录 end node 的 block
137 | var recordEndNode: (URLServiceNodeProtocol) -> Void { get }
138 | /// 当请求路由完成的时候,记录最终响应的 node 回调。如果没有 node 响应,那么将记录 root node
139 | var routerCompletion: (URLServiceNodeProtocol, URLServiceNodeParserProtocol?, String?) -> Void { get }
140 | /// 请求路由完成回调,我们一般在这里通知请求
141 | var completion: (URLServiceRouteResultProtocol) -> Void { get }
142 | }
143 |
144 | // MARK: - URLServiceRequest
145 |
146 | public typealias URLServiceRequestCompletionBlock = (URLServiceRequestProtocol) -> Void
147 | public protocol URLServiceRequestProtocol {
148 | /// 请求 url
149 | var url: URL { get }
150 | /// 处理请求的服务路由器
151 | var serviceRouter: URLServiceRouterProtocol { get }
152 | /// 在节点解析过程中会不断变化,始终代表当前节点后面的路径字符串数组
153 | /// 例如: https://www.example.com/path/to/myfile, 如果当前节点名称是 “path” , 它的值将会是 [“to”, “myfile”]
154 | var nodeNames: [String] { get }
155 | /// 响应结果,包含:响应的服务、错误、数据
156 | var response: URLServiceRequestResponseProtocol? { get }
157 | /// 请求成功回调,当请求被路由时找到响应的服务将被执行
158 | var success: URLServiceRequestCompletionBlock? { get }
159 | /// 请求失败回调,当请求被路由时发现没有服务响应时将执行
160 | var failure: URLServiceRequestCompletionBlock? { get }
161 | /// 服务执行的回调,在响应的服务执行后会调用,如果没有服务命中将不会被调用
162 | var callback: URLServiceRequestCompletionBlock? { get }
163 | /// 当前请求的描述信息
164 | var description: String { get }
165 | /// 是否仅路由请求但并不执行服务,默认值为 false
166 | var isOnlyRouting: Bool { get }
167 |
168 | func updateResponse(_ response: URLServiceRequestResponseProtocol?)
169 | /// 在服务路由器完成请求路由时将执行的回调,您应该在调用此方法之前为 响应结果(response) 赋值
170 | func routingCompletion()
171 | /// 请求参数,一般是提供给命中的路由服务使用
172 | func requestParams() -> Any?
173 |
174 | /// 处理来自节点解析器的替换当前服务请求的节点名称的请求。一般来讲,我们只允许前置解析器的请求
175 | /// - Parameters:
176 | /// - nodeNames: 节点解析器提供的节点名称数组
177 | /// - nodeParser: 发起该修改的节点解析器
178 | func replace(nodeNames: [String], from nodeParser: URLServiceNodeParserProtocol)
179 | /// 路由查找过程中,删除当前请求 nodeNames 的第一个元素
180 | /// - Parameter node: 路由查找过程中的当前节点
181 | func reduceOneNodeName(from node: URLServiceNodeProtocol)
182 | /// 路由回溯过程中,将上一个节点名称添加到 nodeNames 中
183 | /// - Parameter node: 路由回溯过程中的当前节点
184 | func restoreOneNodeName(from node: URLServiceNodeProtocol)
185 | /// 合并某个节点解析器提供的请求参数到当前请求的请求参数。一般来讲,我们只允许前置解析器的请求
186 | /// - Parameters:
187 | /// - params: 需要合并的参数
188 | /// - nodeParser: 想要合并参数的节点解析器
189 | func merge(params: Any, from nodeParser: URLServiceNodeParserProtocol)
190 | /// 使用某个节点解析器提供的请求参数来代替当前请求的请求参数。一般来讲,我们只允许前置解析器的请求
191 | /// - Parameters:
192 | /// - params: 节点解析器提供的将要代替当前请求参数的参数
193 | /// - nodeParser: 发起修改参数的节点解析器
194 | func replace(params: Any?, from nodeParser: URLServiceNodeParserProtocol)
195 |
196 | /// 开始当前服务请求的路由,如果你需要关注请求结果,你需要为 callback 赋值
197 | /// - Parameters:
198 | /// - success: 路由完成回调
199 | /// - failure: 路由失败回调
200 | /// - callback: 服务调用完成回调
201 | func start(success: URLServiceRequestCompletionBlock?, failure: URLServiceRequestCompletionBlock?, callback: URLServiceRequestCompletionBlock?)
202 | /// 停止当前请求,调用后将不会执行命中的服务
203 | func stop()
204 | }
205 |
206 | // MARK: - URLServiceRequestResponse
207 |
208 | public protocol URLServiceRequestResponseProtocol {
209 | /// 响应的服务名称
210 | var serviceName: String? { get set }
211 | /// 错误信息,比如:不满足执行条件时返回的错误、找不到服务的错误、服务执行的错误等
212 | var error: URLServiceErrorProtocol? { get set }
213 | /// 响应的数据
214 | var data: Any? { get set }
215 | }
216 |
217 | // MARK: - URLServiceNode
218 |
219 | public protocol URLServiceNodeProtocol {
220 | /// 节点名称,可能代表根节点或者 URL 中的一个 components 的某一个元素
221 | var name: String { get }
222 |
223 | /// 父节点,将在路由回溯中用到
224 | var parentNode: URLServiceNodeProtocol? { get }
225 | /// 注册子节点
226 | /// - Parameter subNode: 子节点
227 | func register(subNode: URLServiceNodeProtocol)
228 | /// 使用名称注册子节点
229 | /// - Parameters:
230 | /// - name: 子节点名称
231 | func registerSubNode(with name: String) -> URLServiceNodeProtocol
232 |
233 | /// 解析器数组 builder,在初次解析url时调用去注册解析器。用于懒加载解析器数组
234 | var parsersBuilder: URLServiceNodeParsersBuilder? { get set }
235 | /// 前序解析器数组,将在路由查找过程中执行,优先级越高越先被执行
236 | var preParsers: [URLServiceNodeParserProtocol] { get }
237 | /// 后序解析器数组,将在路由回溯过程中执行,优先级越高越先被执行
238 | var postParsers: [URLServiceNodeParserProtocol] { get }
239 | /// 注册解析器
240 | /// - Parameter parser: 解析器
241 | func register(parser: URLServiceNodeParserProtocol)
242 |
243 | /// 路由服务请求,并给出路由结果
244 | /// - Parameters:
245 | /// - request: 服务请求
246 | /// - result: 路由结果
247 | func route(request: URLServiceRequestProtocol, result: URLServiceRouteResultProtocol)
248 | /// 执行前序解析器,并给出结果。注意:这个是在路由查找过程中执行
249 | /// - Parameters:
250 | /// - request: 服务请求
251 | /// - result: 路由结果
252 | func routePreParser(request: URLServiceRequestProtocol, result: URLServiceRouteResultProtocol)
253 | /// 执行后序解析器,并给出结果。注意:这个是在路由回溯过程中执行
254 | /// - Parameters:
255 | /// - request: 服务请求
256 | /// - result: 路由结果
257 | func routePostParser(request: URLServiceRequestProtocol, result: URLServiceRouteResultProtocol)
258 | /// 从根节点到当前节点的 节点链(node chain) 中所有节点名称数组(根节点名称是第一个原生)
259 | func routedNodeNames() -> [String]
260 | }
261 |
262 | // MARK: - URLServiceNodeParser
263 |
264 | /// 节点解析器类型,pre-type 解析器在路由查找过程中执行,post-type 解析器在路由回溯过程中执行
265 | public enum URLServiceNodeParserType: String {
266 | case pre
267 | case post
268 | }
269 |
270 | public protocol URLServiceNodeParserProtocol {
271 | /// 优先级,决定执行顺序,优先级越高越先被执行,默认值是100
272 | var priority: Int { get }
273 | /// 解析器类型,决定在哪个流程中执行,前序解析器在路由查找过程中执行,后续解析器在路由回溯过程中执行
274 | var parserType: URLServiceNodeParserType { get }
275 | /// 解析服务请求,可以改名请求参数、请求的 nodeNames (可以改变下一个节点)
276 | /// - Parameters:
277 | /// - request: 服务请求
278 | /// - decision: 解析器给出的决定
279 | func parse(request: URLServiceRequestProtocol, decision: URLServiceNodeParserDecisionProtocol)
280 | }
281 |
282 | // MARK: - URLServiceNodeParserDecisionProtocol
283 |
284 | public protocol URLServiceNodeParserDecisionProtocol {
285 | /// 通知解析器执行下一步
286 | var next: () -> Void { get }
287 | /// 通知节点解析器命中的服务名称回调
288 | var complete: (URLServiceNodeParserProtocol, String) -> Void { get }
289 | }
290 |
291 | // MARK: - URLService
292 |
293 | public typealias URLServiceExecutionCallback = (Any?, URLServiceErrorProtocol?) -> Void
294 | public protocol URLServiceProtocol {
295 | /// 前置服务的名称
296 | var preServiceNames: [String] { get }
297 | func paramsForPreService(name: String) -> Any?
298 |
299 | /// 获取前置服务的接口,并决定是否下一步
300 | func preServiceCallBack(name: String, result: Any?, error: URLServiceErrorProtocol?, decision: URLServiceDecisionProtocol)
301 |
302 | /// 执行当前服务,你可能需要自行决定在不满足执行条件时是否执行服务
303 | /// - Parameter params: 执行的参数
304 | /// - Parameter callback: 完成回调
305 | func execute(params: Any?, callback: URLServiceExecutionCallback?)
306 | }
307 |
308 | extension URLServiceProtocol {
309 | /// 默认没有前置服务
310 | var preServiceNames: [String] { [] }
311 | /// 默认没有入参
312 | func paramsForPreService(name: String) -> Any? {
313 | return nil
314 | }
315 |
316 | /// 提供默认实现
317 | func preServiceCallBack(name: String, result: Any?, error: URLServiceErrorProtocol, decision: URLServiceDecisionProtocol) { decision.next()
318 | }
319 | }
320 |
321 | // MARK: - URLServiceDecisionProtocol
322 |
323 | public protocol URLServiceDecisionProtocol {
324 | /// 通知前置解析器执行下一步
325 | var next: () -> Void { get }
326 | /// 通知节前置服务完成毁掉
327 | var complete: () -> Void { get }
328 | }
329 |
330 | // MARK: - URLServiceError
331 |
332 | public protocol URLServiceErrorProtocol {
333 | /// 错误码
334 | var code: String { get }
335 | /// 错误信息
336 | var message: String { get }
337 | }
338 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import URLServiceRouterTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += URLServiceRouterTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Tests/URLServiceRouterTests/URLServiceRouterTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import URLServiceRouter
3 |
4 | final class URLServiceRouterTests: XCTestCase {
5 | func testExample() {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | // XCTAssertEqual(URLServiceRouter().text, "Hello, World!")
10 | }
11 |
12 | static var allTests = [
13 | ("testExample", testExample),
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/URLServiceRouterTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(URLServiceRouterTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/URLServiceRouter.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod spec lint URLServiceRouter.podspec' to ensure this is a
3 | # valid spec and to remove all comments including this before submitting the spec.
4 | #
5 | # To learn more about Podspec attributes see https://guides.cocoapods.org/syntax/podspec.html
6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
7 | #
8 |
9 | Pod::Spec.new do |spec|
10 |
11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
12 | #
13 | # These will help people to find your library, and whilst it
14 | # can feel like a chore to fill in it's definitely to your advantage. The
15 | # summary should be tweet-length, and the description more in depth.
16 | #
17 |
18 | spec.name = "URLServiceRouter"
19 | spec.version = "2.0.0"
20 | spec.summary = "A Swift URL router implemented by a high-degree-of-freedom nodeTree and RPC."
21 | spec.swift_versions = '5.0'
22 |
23 | # This description is used to generate tags and improve search results.
24 | # * Think: What does it do? Why did you write it? What is the focus?
25 | # * Try to keep it short, snappy and to the point.
26 | # * Write the description between the DESC delimiters below.
27 | # * Finally, don't worry about the indent, CocoaPods strips it!
28 | spec.description = <<-DESC
29 | URLServiceRouter is a Swift URL router implemented by a high-degree-of-freedom nodeTree and RPC.
30 | DESC
31 |
32 | spec.homepage = "https://github.com/lightank/URLServiceRouter"
33 | # spec.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif"
34 |
35 |
36 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
37 | #
38 | # Licensing your code is important. See https://choosealicense.com for more info.
39 | # CocoaPods will detect a license file if there is a named LICENSE*
40 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'.
41 | #
42 |
43 | spec.license = "MIT"
44 | # spec.license = { :type => "MIT", :file => "FILE_LICENSE" }
45 |
46 |
47 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
48 | #
49 | # Specify the authors of the library, with email addresses. Email addresses
50 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also
51 | # accepts just a name if you'd rather not provide an email address.
52 | #
53 | # Specify a social_media_url where others can refer to, for example a twitter
54 | # profile URL.
55 | #
56 |
57 | spec.author = { "lightank" => "1303908401@qq.com" }
58 | # Or just: spec.author = "lightank"
59 | # spec.authors = { "lightank" => "1303908401@qq.com" }
60 | # spec.social_media_url = "https://twitter.com/"
61 |
62 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
63 | #
64 | # If this Pod runs only on iOS or OS X, then specify the platform and
65 | # the deployment target. You can optionally include the target after the platform.
66 | #
67 |
68 | # spec.platform = :ios
69 | # spec.platform = :ios, "0.0"
70 |
71 | # When using multiple platforms
72 | spec.ios.deployment_target = "10.0"
73 | spec.osx.deployment_target = "10.15"
74 | # spec.watchos.deployment_target = "2.0"
75 | # spec.tvos.deployment_target = "9.0"
76 |
77 |
78 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
79 | #
80 | # Specify the location from where the source should be retrieved.
81 | # Supports git, hg, bzr, svn and HTTP.
82 | #
83 |
84 | spec.source = { :git => "https://github.com/lightank/URLServiceRouter.git", :tag => "#{spec.version}" }
85 |
86 |
87 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
88 | #
89 | # CocoaPods is smart about how it includes source code. For source files
90 | # giving a folder will include any swift, h, m, mm, c & cpp files.
91 | # For header files it will include any header in the folder.
92 | # Not including the public_header_files will make all headers public.
93 | #
94 |
95 | spec.source_files = "Sources/URLServiceRouter/*.swift", "Sources/URLServiceRouter/**/*.swift"
96 | spec.exclude_files = "URLServiceRouter/URLServiceRouter/*.plist"
97 |
98 | # spec.public_header_files = "Classes/**/*.h"
99 |
100 |
101 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
102 | #
103 | # A list of resources included with the Pod. These are copied into the
104 | # target bundle with a build phase script. Anything else will be cleaned.
105 | # You can preserve files from being cleaned, please don't preserve
106 | # non-essential files like tests, examples and documentation.
107 | #
108 |
109 | # spec.resource = "icon.png"
110 | # spec.resources = "Resources/*.png"
111 |
112 | # spec.preserve_paths = "FilesToSave", "MoreFilesToSave"
113 |
114 |
115 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
116 | #
117 | # Link your library with frameworks, or libraries. Libraries do not include
118 | # the lib prefix of their name.
119 | #
120 |
121 | # spec.framework = "SomeFramework"
122 | # spec.frameworks = "SomeFramework", "AnotherFramework"
123 |
124 | # spec.library = "iconv"
125 | # spec.libraries = "iconv", "xml2"
126 |
127 |
128 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
129 | #
130 | # If your library depends on compiler flags you can set them in the xcconfig hash
131 | # where they will only apply to your library. If you depend on other Podspecs
132 | # you can include multiple dependencies to ensure it works.
133 |
134 | # spec.requires_arc = true
135 |
136 | # spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
137 | # spec.dependency "JSONKit", "~> 1.4"
138 |
139 | end
140 |
--------------------------------------------------------------------------------
/URLServiceRouter.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/URLServiceRouter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/URLServiceRouter/URLServiceRouter.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 3D2FCD7F26CDF624008CD97F /* URLServiceRouter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D2FCD7526CDF623008CD97F /* URLServiceRouter.framework */; };
11 | 3D2FCD8426CDF624008CD97F /* URLServiceRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2FCD8326CDF624008CD97F /* URLServiceRouterTests.swift */; };
12 | 3D5DBF5626D4DC49009607EA /* URLServiceRouterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF4B26D4DC49009607EA /* URLServiceRouterProtocol.swift */; };
13 | 3D5DBF5726D4DC49009607EA /* URLServiceRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF4D26D4DC49009607EA /* URLServiceRequest.swift */; };
14 | 3D5DBF5826D4DC49009607EA /* URLServiceRouteResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF4E26D4DC49009607EA /* URLServiceRouteResult.swift */; };
15 | 3D5DBF5926D4DC49009607EA /* URLServiceRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF4F26D4DC49009607EA /* URLServiceRouter.swift */; };
16 | 3D5DBF5A26D4DC49009607EA /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF5026D4DC49009607EA /* Extensions.swift */; };
17 | 3D5DBF5B26D4DC49009607EA /* URLServiceNodel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF5126D4DC49009607EA /* URLServiceNodel.swift */; };
18 | 3D5DBF5C26D4DC49009607EA /* URLServiceNodeParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF5226D4DC49009607EA /* URLServiceNodeParser.swift */; };
19 | 3D5DBF5D26D4DC49009607EA /* URLServiceNodeParserDecision.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF5326D4DC49009607EA /* URLServiceNodeParserDecision.swift */; };
20 | 3D5DBF5E26D4DC49009607EA /* URLServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF5426D4DC49009607EA /* URLServiceError.swift */; };
21 | 3D5DBF5F26D4DC49009607EA /* URLServiceRequestResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF5526D4DC49009607EA /* URLServiceRequestResponse.swift */; };
22 | 3D5DBFDC26D4E1E8009607EA /* URLServiceRequest.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3D5DBF4D26D4DC49009607EA /* URLServiceRequest.swift */; settings = {ATTRIBUTES = (Public, ); }; };
23 | 3D5DBFDD26D4E1E8009607EA /* URLServiceRouteResult.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3D5DBF4E26D4DC49009607EA /* URLServiceRouteResult.swift */; settings = {ATTRIBUTES = (Public, ); }; };
24 | 3D5DBFDE26D4E1E8009607EA /* URLServiceRouter.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3D5DBF4F26D4DC49009607EA /* URLServiceRouter.swift */; settings = {ATTRIBUTES = (Public, ); }; };
25 | 3D5DBFDF26D4E1E8009607EA /* Extensions.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3D5DBF5026D4DC49009607EA /* Extensions.swift */; settings = {ATTRIBUTES = (Public, ); }; };
26 | 3D5DBFE026D4E1E8009607EA /* URLServiceNodel.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3D5DBF5126D4DC49009607EA /* URLServiceNodel.swift */; settings = {ATTRIBUTES = (Public, ); }; };
27 | 3D5DBFE126D4E1E8009607EA /* URLServiceNodeParser.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3D5DBF5226D4DC49009607EA /* URLServiceNodeParser.swift */; settings = {ATTRIBUTES = (Public, ); }; };
28 | 3D5DBFE226D4E1E8009607EA /* URLServiceNodeParserDecision.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3D5DBF5326D4DC49009607EA /* URLServiceNodeParserDecision.swift */; settings = {ATTRIBUTES = (Public, ); }; };
29 | 3D5DBFE326D4E1E8009607EA /* URLServiceError.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3D5DBF5426D4DC49009607EA /* URLServiceError.swift */; settings = {ATTRIBUTES = (Public, ); }; };
30 | 3D5DBFE426D4E1E8009607EA /* URLServiceRequestResponse.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3D5DBF5526D4DC49009607EA /* URLServiceRequestResponse.swift */; settings = {ATTRIBUTES = (Public, ); }; };
31 | 3D5DBFE526D4E1E8009607EA /* URLServiceRouterProtocol.swift in Headers */ = {isa = PBXBuildFile; fileRef = 3D5DBF4B26D4DC49009607EA /* URLServiceRouterProtocol.swift */; settings = {ATTRIBUTES = (Public, ); }; };
32 | AAD0719D2A93BCFC00A6536C /* URLServiceDecision.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD0719C2A93BCFC00A6536C /* URLServiceDecision.swift */; };
33 | /* End PBXBuildFile section */
34 |
35 | /* Begin PBXContainerItemProxy section */
36 | 3D2FCD8026CDF624008CD97F /* PBXContainerItemProxy */ = {
37 | isa = PBXContainerItemProxy;
38 | containerPortal = 3D2FCD6C26CDF623008CD97F /* Project object */;
39 | proxyType = 1;
40 | remoteGlobalIDString = 3D2FCD7426CDF623008CD97F;
41 | remoteInfo = URLServiceRouter;
42 | };
43 | /* End PBXContainerItemProxy section */
44 |
45 | /* Begin PBXFileReference section */
46 | 3D2FCD7526CDF623008CD97F /* URLServiceRouter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = URLServiceRouter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
47 | 3D2FCD7926CDF623008CD97F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
48 | 3D2FCD7E26CDF624008CD97F /* URLServiceRouterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = URLServiceRouterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
49 | 3D2FCD8326CDF624008CD97F /* URLServiceRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLServiceRouterTests.swift; sourceTree = ""; };
50 | 3D2FCD8526CDF624008CD97F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
51 | 3D5DBF4B26D4DC49009607EA /* URLServiceRouterProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = URLServiceRouterProtocol.swift; path = ../../Sources/URLServiceRouter/URLServiceRouterProtocol.swift; sourceTree = ""; };
52 | 3D5DBF4D26D4DC49009607EA /* URLServiceRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLServiceRequest.swift; sourceTree = ""; };
53 | 3D5DBF4E26D4DC49009607EA /* URLServiceRouteResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLServiceRouteResult.swift; sourceTree = ""; };
54 | 3D5DBF4F26D4DC49009607EA /* URLServiceRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLServiceRouter.swift; sourceTree = ""; };
55 | 3D5DBF5026D4DC49009607EA /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; };
56 | 3D5DBF5126D4DC49009607EA /* URLServiceNodel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLServiceNodel.swift; sourceTree = ""; };
57 | 3D5DBF5226D4DC49009607EA /* URLServiceNodeParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLServiceNodeParser.swift; sourceTree = ""; };
58 | 3D5DBF5326D4DC49009607EA /* URLServiceNodeParserDecision.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLServiceNodeParserDecision.swift; sourceTree = ""; };
59 | 3D5DBF5426D4DC49009607EA /* URLServiceError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLServiceError.swift; sourceTree = ""; };
60 | 3D5DBF5526D4DC49009607EA /* URLServiceRequestResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLServiceRequestResponse.swift; sourceTree = ""; };
61 | AAD0719C2A93BCFC00A6536C /* URLServiceDecision.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLServiceDecision.swift; sourceTree = ""; };
62 | /* End PBXFileReference section */
63 |
64 | /* Begin PBXFrameworksBuildPhase section */
65 | 3D2FCD7226CDF623008CD97F /* Frameworks */ = {
66 | isa = PBXFrameworksBuildPhase;
67 | buildActionMask = 2147483647;
68 | files = (
69 | );
70 | runOnlyForDeploymentPostprocessing = 0;
71 | };
72 | 3D2FCD7B26CDF624008CD97F /* Frameworks */ = {
73 | isa = PBXFrameworksBuildPhase;
74 | buildActionMask = 2147483647;
75 | files = (
76 | 3D2FCD7F26CDF624008CD97F /* URLServiceRouter.framework in Frameworks */,
77 | );
78 | runOnlyForDeploymentPostprocessing = 0;
79 | };
80 | /* End PBXFrameworksBuildPhase section */
81 |
82 | /* Begin PBXGroup section */
83 | 3D2FCD6B26CDF623008CD97F = {
84 | isa = PBXGroup;
85 | children = (
86 | 3D2FCD7726CDF623008CD97F /* URLServiceRouter */,
87 | 3D2FCD8226CDF624008CD97F /* URLServiceRouterTests */,
88 | 3D2FCD7626CDF623008CD97F /* Products */,
89 | );
90 | sourceTree = "";
91 | };
92 | 3D2FCD7626CDF623008CD97F /* Products */ = {
93 | isa = PBXGroup;
94 | children = (
95 | 3D2FCD7526CDF623008CD97F /* URLServiceRouter.framework */,
96 | 3D2FCD7E26CDF624008CD97F /* URLServiceRouterTests.xctest */,
97 | );
98 | name = Products;
99 | sourceTree = "";
100 | };
101 | 3D2FCD7726CDF623008CD97F /* URLServiceRouter */ = {
102 | isa = PBXGroup;
103 | children = (
104 | 3D5DBF4C26D4DC49009607EA /* URLServiceRouter */,
105 | 3D5DBF4B26D4DC49009607EA /* URLServiceRouterProtocol.swift */,
106 | 3D2FCD7926CDF623008CD97F /* Info.plist */,
107 | );
108 | path = URLServiceRouter;
109 | sourceTree = "";
110 | };
111 | 3D2FCD8226CDF624008CD97F /* URLServiceRouterTests */ = {
112 | isa = PBXGroup;
113 | children = (
114 | 3D2FCD8326CDF624008CD97F /* URLServiceRouterTests.swift */,
115 | 3D2FCD8526CDF624008CD97F /* Info.plist */,
116 | );
117 | path = URLServiceRouterTests;
118 | sourceTree = "";
119 | };
120 | 3D5DBF4C26D4DC49009607EA /* URLServiceRouter */ = {
121 | isa = PBXGroup;
122 | children = (
123 | 3D5DBF5026D4DC49009607EA /* Extensions.swift */,
124 | 3D5DBF5426D4DC49009607EA /* URLServiceError.swift */,
125 | 3D5DBF5126D4DC49009607EA /* URLServiceNodel.swift */,
126 | 3D5DBF5226D4DC49009607EA /* URLServiceNodeParser.swift */,
127 | AAD0719C2A93BCFC00A6536C /* URLServiceDecision.swift */,
128 | 3D5DBF5326D4DC49009607EA /* URLServiceNodeParserDecision.swift */,
129 | 3D5DBF4D26D4DC49009607EA /* URLServiceRequest.swift */,
130 | 3D5DBF5526D4DC49009607EA /* URLServiceRequestResponse.swift */,
131 | 3D5DBF4F26D4DC49009607EA /* URLServiceRouter.swift */,
132 | 3D5DBF4E26D4DC49009607EA /* URLServiceRouteResult.swift */,
133 | );
134 | name = URLServiceRouter;
135 | path = ../../Sources/URLServiceRouter/URLServiceRouter;
136 | sourceTree = "";
137 | };
138 | /* End PBXGroup section */
139 |
140 | /* Begin PBXHeadersBuildPhase section */
141 | 3D2FCD7026CDF623008CD97F /* Headers */ = {
142 | isa = PBXHeadersBuildPhase;
143 | buildActionMask = 2147483647;
144 | files = (
145 | 3D5DBFDC26D4E1E8009607EA /* URLServiceRequest.swift in Headers */,
146 | 3D5DBFDD26D4E1E8009607EA /* URLServiceRouteResult.swift in Headers */,
147 | 3D5DBFDE26D4E1E8009607EA /* URLServiceRouter.swift in Headers */,
148 | 3D5DBFDF26D4E1E8009607EA /* Extensions.swift in Headers */,
149 | 3D5DBFE326D4E1E8009607EA /* URLServiceError.swift in Headers */,
150 | 3D5DBFE226D4E1E8009607EA /* URLServiceNodeParserDecision.swift in Headers */,
151 | 3D5DBFE126D4E1E8009607EA /* URLServiceNodeParser.swift in Headers */,
152 | 3D5DBFE426D4E1E8009607EA /* URLServiceRequestResponse.swift in Headers */,
153 | 3D5DBFE526D4E1E8009607EA /* URLServiceRouterProtocol.swift in Headers */,
154 | 3D5DBFE026D4E1E8009607EA /* URLServiceNodel.swift in Headers */,
155 | );
156 | runOnlyForDeploymentPostprocessing = 0;
157 | };
158 | /* End PBXHeadersBuildPhase section */
159 |
160 | /* Begin PBXNativeTarget section */
161 | 3D2FCD7426CDF623008CD97F /* URLServiceRouter */ = {
162 | isa = PBXNativeTarget;
163 | buildConfigurationList = 3D2FCD8926CDF624008CD97F /* Build configuration list for PBXNativeTarget "URLServiceRouter" */;
164 | buildPhases = (
165 | 3D2FCD7026CDF623008CD97F /* Headers */,
166 | 3D2FCD7126CDF623008CD97F /* Sources */,
167 | 3D2FCD7226CDF623008CD97F /* Frameworks */,
168 | );
169 | buildRules = (
170 | );
171 | dependencies = (
172 | );
173 | name = URLServiceRouter;
174 | productName = URLServiceRouter;
175 | productReference = 3D2FCD7526CDF623008CD97F /* URLServiceRouter.framework */;
176 | productType = "com.apple.product-type.framework";
177 | };
178 | 3D2FCD7D26CDF624008CD97F /* URLServiceRouterTests */ = {
179 | isa = PBXNativeTarget;
180 | buildConfigurationList = 3D2FCD8C26CDF624008CD97F /* Build configuration list for PBXNativeTarget "URLServiceRouterTests" */;
181 | buildPhases = (
182 | 3D2FCD7A26CDF624008CD97F /* Sources */,
183 | 3D2FCD7B26CDF624008CD97F /* Frameworks */,
184 | 3D2FCD7C26CDF624008CD97F /* Resources */,
185 | );
186 | buildRules = (
187 | );
188 | dependencies = (
189 | 3D2FCD8126CDF624008CD97F /* PBXTargetDependency */,
190 | );
191 | name = URLServiceRouterTests;
192 | productName = URLServiceRouterTests;
193 | productReference = 3D2FCD7E26CDF624008CD97F /* URLServiceRouterTests.xctest */;
194 | productType = "com.apple.product-type.bundle.unit-test";
195 | };
196 | /* End PBXNativeTarget section */
197 |
198 | /* Begin PBXProject section */
199 | 3D2FCD6C26CDF623008CD97F /* Project object */ = {
200 | isa = PBXProject;
201 | attributes = {
202 | LastSwiftUpdateCheck = 1240;
203 | LastUpgradeCheck = 1240;
204 | TargetAttributes = {
205 | 3D2FCD7426CDF623008CD97F = {
206 | CreatedOnToolsVersion = 12.4;
207 | LastSwiftMigration = 1240;
208 | };
209 | 3D2FCD7D26CDF624008CD97F = {
210 | CreatedOnToolsVersion = 12.4;
211 | };
212 | };
213 | };
214 | buildConfigurationList = 3D2FCD6F26CDF623008CD97F /* Build configuration list for PBXProject "URLServiceRouter" */;
215 | compatibilityVersion = "Xcode 9.3";
216 | developmentRegion = en;
217 | hasScannedForEncodings = 0;
218 | knownRegions = (
219 | en,
220 | Base,
221 | );
222 | mainGroup = 3D2FCD6B26CDF623008CD97F;
223 | productRefGroup = 3D2FCD7626CDF623008CD97F /* Products */;
224 | projectDirPath = "";
225 | projectRoot = "";
226 | targets = (
227 | 3D2FCD7426CDF623008CD97F /* URLServiceRouter */,
228 | 3D2FCD7D26CDF624008CD97F /* URLServiceRouterTests */,
229 | );
230 | };
231 | /* End PBXProject section */
232 |
233 | /* Begin PBXResourcesBuildPhase section */
234 | 3D2FCD7C26CDF624008CD97F /* Resources */ = {
235 | isa = PBXResourcesBuildPhase;
236 | buildActionMask = 2147483647;
237 | files = (
238 | );
239 | runOnlyForDeploymentPostprocessing = 0;
240 | };
241 | /* End PBXResourcesBuildPhase section */
242 |
243 | /* Begin PBXSourcesBuildPhase section */
244 | 3D2FCD7126CDF623008CD97F /* Sources */ = {
245 | isa = PBXSourcesBuildPhase;
246 | buildActionMask = 2147483647;
247 | files = (
248 | 3D5DBF5A26D4DC49009607EA /* Extensions.swift in Sources */,
249 | 3D5DBF5E26D4DC49009607EA /* URLServiceError.swift in Sources */,
250 | 3D5DBF5626D4DC49009607EA /* URLServiceRouterProtocol.swift in Sources */,
251 | AAD0719D2A93BCFC00A6536C /* URLServiceDecision.swift in Sources */,
252 | 3D5DBF5D26D4DC49009607EA /* URLServiceNodeParserDecision.swift in Sources */,
253 | 3D5DBF5F26D4DC49009607EA /* URLServiceRequestResponse.swift in Sources */,
254 | 3D5DBF5C26D4DC49009607EA /* URLServiceNodeParser.swift in Sources */,
255 | 3D5DBF5726D4DC49009607EA /* URLServiceRequest.swift in Sources */,
256 | 3D5DBF5926D4DC49009607EA /* URLServiceRouter.swift in Sources */,
257 | 3D5DBF5B26D4DC49009607EA /* URLServiceNodel.swift in Sources */,
258 | 3D5DBF5826D4DC49009607EA /* URLServiceRouteResult.swift in Sources */,
259 | );
260 | runOnlyForDeploymentPostprocessing = 0;
261 | };
262 | 3D2FCD7A26CDF624008CD97F /* Sources */ = {
263 | isa = PBXSourcesBuildPhase;
264 | buildActionMask = 2147483647;
265 | files = (
266 | 3D2FCD8426CDF624008CD97F /* URLServiceRouterTests.swift in Sources */,
267 | );
268 | runOnlyForDeploymentPostprocessing = 0;
269 | };
270 | /* End PBXSourcesBuildPhase section */
271 |
272 | /* Begin PBXTargetDependency section */
273 | 3D2FCD8126CDF624008CD97F /* PBXTargetDependency */ = {
274 | isa = PBXTargetDependency;
275 | target = 3D2FCD7426CDF623008CD97F /* URLServiceRouter */;
276 | targetProxy = 3D2FCD8026CDF624008CD97F /* PBXContainerItemProxy */;
277 | };
278 | /* End PBXTargetDependency section */
279 |
280 | /* Begin XCBuildConfiguration section */
281 | 3D2FCD8726CDF624008CD97F /* Debug */ = {
282 | isa = XCBuildConfiguration;
283 | buildSettings = {
284 | ALWAYS_SEARCH_USER_PATHS = NO;
285 | CLANG_ANALYZER_NONNULL = YES;
286 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
287 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
288 | CLANG_CXX_LIBRARY = "libc++";
289 | CLANG_ENABLE_MODULES = YES;
290 | CLANG_ENABLE_OBJC_ARC = YES;
291 | CLANG_ENABLE_OBJC_WEAK = YES;
292 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
293 | CLANG_WARN_BOOL_CONVERSION = YES;
294 | CLANG_WARN_COMMA = YES;
295 | CLANG_WARN_CONSTANT_CONVERSION = YES;
296 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
297 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
298 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
299 | CLANG_WARN_EMPTY_BODY = YES;
300 | CLANG_WARN_ENUM_CONVERSION = YES;
301 | CLANG_WARN_INFINITE_RECURSION = YES;
302 | CLANG_WARN_INT_CONVERSION = YES;
303 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
304 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
305 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
306 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
307 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
308 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
309 | CLANG_WARN_STRICT_PROTOTYPES = YES;
310 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
311 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
312 | CLANG_WARN_UNREACHABLE_CODE = YES;
313 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
314 | COPY_PHASE_STRIP = NO;
315 | CURRENT_PROJECT_VERSION = 1;
316 | DEBUG_INFORMATION_FORMAT = dwarf;
317 | ENABLE_STRICT_OBJC_MSGSEND = YES;
318 | ENABLE_TESTABILITY = YES;
319 | GCC_C_LANGUAGE_STANDARD = gnu11;
320 | GCC_DYNAMIC_NO_PIC = NO;
321 | GCC_NO_COMMON_BLOCKS = YES;
322 | GCC_OPTIMIZATION_LEVEL = 0;
323 | GCC_PREPROCESSOR_DEFINITIONS = (
324 | "DEBUG=1",
325 | "$(inherited)",
326 | );
327 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
328 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
329 | GCC_WARN_UNDECLARED_SELECTOR = YES;
330 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
331 | GCC_WARN_UNUSED_FUNCTION = YES;
332 | GCC_WARN_UNUSED_VARIABLE = YES;
333 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
334 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
335 | MTL_FAST_MATH = YES;
336 | ONLY_ACTIVE_ARCH = YES;
337 | SDKROOT = iphoneos;
338 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
339 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
340 | VERSIONING_SYSTEM = "apple-generic";
341 | VERSION_INFO_PREFIX = "";
342 | };
343 | name = Debug;
344 | };
345 | 3D2FCD8826CDF624008CD97F /* Release */ = {
346 | isa = XCBuildConfiguration;
347 | buildSettings = {
348 | ALWAYS_SEARCH_USER_PATHS = NO;
349 | CLANG_ANALYZER_NONNULL = YES;
350 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
351 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
352 | CLANG_CXX_LIBRARY = "libc++";
353 | CLANG_ENABLE_MODULES = YES;
354 | CLANG_ENABLE_OBJC_ARC = YES;
355 | CLANG_ENABLE_OBJC_WEAK = YES;
356 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
357 | CLANG_WARN_BOOL_CONVERSION = YES;
358 | CLANG_WARN_COMMA = YES;
359 | CLANG_WARN_CONSTANT_CONVERSION = YES;
360 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
361 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
362 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
363 | CLANG_WARN_EMPTY_BODY = YES;
364 | CLANG_WARN_ENUM_CONVERSION = YES;
365 | CLANG_WARN_INFINITE_RECURSION = YES;
366 | CLANG_WARN_INT_CONVERSION = YES;
367 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
368 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
369 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
371 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
372 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
373 | CLANG_WARN_STRICT_PROTOTYPES = YES;
374 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
375 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
376 | CLANG_WARN_UNREACHABLE_CODE = YES;
377 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
378 | COPY_PHASE_STRIP = NO;
379 | CURRENT_PROJECT_VERSION = 1;
380 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
381 | ENABLE_NS_ASSERTIONS = NO;
382 | ENABLE_STRICT_OBJC_MSGSEND = YES;
383 | GCC_C_LANGUAGE_STANDARD = gnu11;
384 | GCC_NO_COMMON_BLOCKS = YES;
385 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
386 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
387 | GCC_WARN_UNDECLARED_SELECTOR = YES;
388 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
389 | GCC_WARN_UNUSED_FUNCTION = YES;
390 | GCC_WARN_UNUSED_VARIABLE = YES;
391 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
392 | MTL_ENABLE_DEBUG_INFO = NO;
393 | MTL_FAST_MATH = YES;
394 | SDKROOT = iphoneos;
395 | SWIFT_COMPILATION_MODE = wholemodule;
396 | SWIFT_OPTIMIZATION_LEVEL = "-O";
397 | VALIDATE_PRODUCT = YES;
398 | VERSIONING_SYSTEM = "apple-generic";
399 | VERSION_INFO_PREFIX = "";
400 | };
401 | name = Release;
402 | };
403 | 3D2FCD8A26CDF624008CD97F /* Debug */ = {
404 | isa = XCBuildConfiguration;
405 | buildSettings = {
406 | CLANG_ENABLE_MODULES = YES;
407 | CODE_SIGN_STYLE = Automatic;
408 | DEFINES_MODULE = YES;
409 | DYLIB_COMPATIBILITY_VERSION = 1;
410 | DYLIB_CURRENT_VERSION = 1;
411 | DYLIB_INSTALL_NAME_BASE = "@rpath";
412 | INFOPLIST_FILE = URLServiceRouter/Info.plist;
413 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
414 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
415 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 13.1;
416 | LD_RUNPATH_SEARCH_PATHS = (
417 | "$(inherited)",
418 | "@executable_path/Frameworks",
419 | "@loader_path/Frameworks",
420 | );
421 | MARKETING_VERSION = 0.0.9;
422 | OTHER_LDFLAGS = "-all_load";
423 | PRODUCT_BUNDLE_IDENTIFIER = URLServiceRouter;
424 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
425 | SKIP_INSTALL = YES;
426 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
427 | SWIFT_VERSION = 5.0;
428 | TARGETED_DEVICE_FAMILY = "1,2";
429 | };
430 | name = Debug;
431 | };
432 | 3D2FCD8B26CDF624008CD97F /* Release */ = {
433 | isa = XCBuildConfiguration;
434 | buildSettings = {
435 | CLANG_ENABLE_MODULES = YES;
436 | CODE_SIGN_STYLE = Automatic;
437 | DEFINES_MODULE = YES;
438 | DYLIB_COMPATIBILITY_VERSION = 1;
439 | DYLIB_CURRENT_VERSION = 1;
440 | DYLIB_INSTALL_NAME_BASE = "@rpath";
441 | "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
442 | INFOPLIST_FILE = URLServiceRouter/Info.plist;
443 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
444 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
445 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 13.1;
446 | LD_RUNPATH_SEARCH_PATHS = (
447 | "$(inherited)",
448 | "@executable_path/Frameworks",
449 | "@loader_path/Frameworks",
450 | );
451 | MARKETING_VERSION = 0.0.9;
452 | OTHER_LDFLAGS = "-all_load";
453 | PRODUCT_BUNDLE_IDENTIFIER = URLServiceRouter;
454 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
455 | SKIP_INSTALL = YES;
456 | SWIFT_VERSION = 5.0;
457 | TARGETED_DEVICE_FAMILY = "1,2";
458 | };
459 | name = Release;
460 | };
461 | 3D2FCD8D26CDF624008CD97F /* Debug */ = {
462 | isa = XCBuildConfiguration;
463 | buildSettings = {
464 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
465 | CODE_SIGN_STYLE = Automatic;
466 | INFOPLIST_FILE = URLServiceRouterTests/Info.plist;
467 | LD_RUNPATH_SEARCH_PATHS = (
468 | "$(inherited)",
469 | "@executable_path/Frameworks",
470 | "@loader_path/Frameworks",
471 | );
472 | PRODUCT_BUNDLE_IDENTIFIER = huanyu.li.URLServiceRouterTests;
473 | PRODUCT_NAME = "$(TARGET_NAME)";
474 | SWIFT_VERSION = 5.0;
475 | TARGETED_DEVICE_FAMILY = "1,2";
476 | };
477 | name = Debug;
478 | };
479 | 3D2FCD8E26CDF624008CD97F /* Release */ = {
480 | isa = XCBuildConfiguration;
481 | buildSettings = {
482 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
483 | CODE_SIGN_STYLE = Automatic;
484 | INFOPLIST_FILE = URLServiceRouterTests/Info.plist;
485 | LD_RUNPATH_SEARCH_PATHS = (
486 | "$(inherited)",
487 | "@executable_path/Frameworks",
488 | "@loader_path/Frameworks",
489 | );
490 | PRODUCT_BUNDLE_IDENTIFIER = huanyu.li.URLServiceRouterTests;
491 | PRODUCT_NAME = "$(TARGET_NAME)";
492 | SWIFT_VERSION = 5.0;
493 | TARGETED_DEVICE_FAMILY = "1,2";
494 | };
495 | name = Release;
496 | };
497 | /* End XCBuildConfiguration section */
498 |
499 | /* Begin XCConfigurationList section */
500 | 3D2FCD6F26CDF623008CD97F /* Build configuration list for PBXProject "URLServiceRouter" */ = {
501 | isa = XCConfigurationList;
502 | buildConfigurations = (
503 | 3D2FCD8726CDF624008CD97F /* Debug */,
504 | 3D2FCD8826CDF624008CD97F /* Release */,
505 | );
506 | defaultConfigurationIsVisible = 0;
507 | defaultConfigurationName = Release;
508 | };
509 | 3D2FCD8926CDF624008CD97F /* Build configuration list for PBXNativeTarget "URLServiceRouter" */ = {
510 | isa = XCConfigurationList;
511 | buildConfigurations = (
512 | 3D2FCD8A26CDF624008CD97F /* Debug */,
513 | 3D2FCD8B26CDF624008CD97F /* Release */,
514 | );
515 | defaultConfigurationIsVisible = 0;
516 | defaultConfigurationName = Release;
517 | };
518 | 3D2FCD8C26CDF624008CD97F /* Build configuration list for PBXNativeTarget "URLServiceRouterTests" */ = {
519 | isa = XCConfigurationList;
520 | buildConfigurations = (
521 | 3D2FCD8D26CDF624008CD97F /* Debug */,
522 | 3D2FCD8E26CDF624008CD97F /* Release */,
523 | );
524 | defaultConfigurationIsVisible = 0;
525 | defaultConfigurationName = Release;
526 | };
527 | /* End XCConfigurationList section */
528 | };
529 | rootObject = 3D2FCD6C26CDF623008CD97F /* Project object */;
530 | }
531 |
--------------------------------------------------------------------------------
/URLServiceRouter/URLServiceRouter.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/URLServiceRouter/URLServiceRouter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/URLServiceRouter/URLServiceRouter.xcodeproj/xcshareddata/xcschemes/URLServiceRouter.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
67 |
68 |
74 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/URLServiceRouter/URLServiceRouter/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/URLServiceRouter/URLServiceRouterTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/URLServiceRouter/URLServiceRouterTests/URLServiceRouterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLServiceRouterTests.swift
3 | // URLServiceRouterTests
4 | //
5 | // Created by huanyu.li on 2021/8/19.
6 | //
7 |
8 | @testable import URLServiceRouter
9 | import XCTest
10 |
11 | class URLServiceRouterTests: XCTestCase {
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 | }
15 |
16 | override func tearDownWithError() throws {
17 | // Put teardown code here. This method is called after the invocation of each test method in the class.
18 | }
19 |
20 | func testExample() throws {
21 | // This is an example of a functional test case.
22 | // Use XCTAssert and related functions to verify your tests produce the correct results.
23 | }
24 |
25 | func testPerformanceExample() throws {
26 | // This is an example of a performance test case.
27 | measure {
28 | // Put the code you want to measure the time of here.
29 | }
30 | }
31 |
32 | func testRouter() throws {
33 | let serviceName = "helloWorld"
34 | URLServiceRouter.shared.registerService(name: serviceName) {
35 | URLService(name: serviceName, executeBlock: { params, callBack in
36 | print("helloWorld 参数:\(String(describing: params))")
37 | callBack?("helloWorld", nil)
38 | })
39 | }
40 | XCTAssert(URLServiceRouter.shared.isRegisteredService(serviceName), "服务:\(serviceName)注册失败")
41 |
42 | let nodeParser = URLServiceNoramlParser(parserType: .pre) { parser, request, decision in
43 | request.merge(params: ["slogan": "just dance"], from: parser)
44 | decision.complete(parser, serviceName)
45 | }
46 | let url = "https://hello/world?name=justDance"
47 | URLServiceRouter.shared.registerNode(from: url) {
48 | [nodeParser]
49 | }
50 | XCTAssert(URLServiceRouter.shared.isRegisteredNode(url), "node\(url)注册失败")
51 |
52 | if let newUrl = URL(string: url) {
53 | URLServiceRequest(url: newUrl).start { request in
54 | if let map = request.requestParams() as? [String: Any] {
55 | if let name = map["name"] as? String {
56 | XCTAssert(name == "justDance", "返回结果不对")
57 | }
58 | if let requestUrl = map[URLServiceRequestOriginalURLKey] as? URL {
59 | XCTAssert(requestUrl.absoluteString == request.url.absoluteString, "请求URL不对")
60 | }
61 | if let slogan = map["slogan"] as? String {
62 | XCTAssert(slogan == "just dance", "node 解析器添加的参数不对")
63 | }
64 | }
65 | } failure: { _ in
66 | XCTAssert(false, "找到错误的服务了")
67 | } callback: { request in
68 | if let data = request.response?.data {
69 | if data is URLServiceErrorProtocol {
70 | // 遇到错误了
71 | } else if let newData = data as? String {
72 | XCTAssert(newData == "helloWorld", "返回结果不对")
73 | }
74 | }
75 | }
76 | }
77 |
78 | if let newUrl = URL(string: "https://see/you/again") {
79 | URLServiceRequest(url: newUrl).start(success: { _ in
80 | XCTAssert(false, "找到错误的服务了")
81 | }, failure: { request in
82 | if let data = request.response?.error {
83 | XCTAssert(data.code == "404", "找到错误的服务了")
84 | }
85 | }, callback: { _ in
86 | XCTAssert(false, "找到错误的服务了")
87 | })
88 | }
89 | }
90 | }
91 |
92 | typealias URLServiceExecuteConditionsBlock = (Any?) -> URLServiceErrorProtocol?
93 | typealias URLServiceExecuteBlock = (Any?, URLServiceExecutionCallback?) -> Void
94 | class URLService: URLServiceProtocol {
95 | func preServiceCallBack(name: String, result: Any?, error: URLServiceErrorProtocol?, decision: URLServiceDecisionProtocol) {
96 | decision.next()
97 | }
98 |
99 | var name: String
100 | var executeConditionsBlock: URLServiceExecuteConditionsBlock?
101 | var executeBlock: URLServiceExecuteBlock?
102 |
103 | public init(name: String, executeConditionsBlock: URLServiceExecuteConditionsBlock? = nil, executeBlock: URLServiceExecuteBlock? = nil) {
104 | self.name = name
105 | self.executeConditionsBlock = executeConditionsBlock
106 | self.executeBlock = executeBlock
107 | }
108 |
109 | func execute(params: Any?, callback: URLServiceExecutionCallback?) {
110 | executeBlock?(params, callback)
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 3D5DBF7A26D4DD57009607EA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF7926D4DD57009607EA /* AppDelegate.swift */; };
11 | 3D5DBF7E26D4DD57009607EA /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF7D26D4DD57009607EA /* ViewController.swift */; };
12 | 3D5DBF8126D4DD57009607EA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3D5DBF7F26D4DD57009607EA /* Main.storyboard */; };
13 | 3D5DBF8326D4DD58009607EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3D5DBF8226D4DD58009607EA /* Assets.xcassets */; };
14 | 3D5DBF8626D4DD58009607EA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3D5DBF8426D4DD58009607EA /* LaunchScreen.storyboard */; };
15 | 3D5DBF9126D4DD59009607EA /* URLServiceRouterAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF9026D4DD59009607EA /* URLServiceRouterAppTests.swift */; };
16 | 3D5DBF9C26D4DD59009607EA /* URLServiceRouterAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBF9B26D4DD59009607EA /* URLServiceRouterAppUITests.swift */; };
17 | 3D5DBFEB26D4E22E009607EA /* Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5DBFEA26D4E22E009607EA /* Demo.swift */; };
18 | AAB1580628EB3BD00036A378 /* URLServiceRouter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAB1580528EB3BD00036A378 /* URLServiceRouter.framework */; };
19 | AAB1580728EB3BD00036A378 /* URLServiceRouter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AAB1580528EB3BD00036A378 /* URLServiceRouter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXContainerItemProxy section */
23 | 3D5DBF8D26D4DD59009607EA /* PBXContainerItemProxy */ = {
24 | isa = PBXContainerItemProxy;
25 | containerPortal = 3D5DBF6E26D4DD57009607EA /* Project object */;
26 | proxyType = 1;
27 | remoteGlobalIDString = 3D5DBF7526D4DD57009607EA;
28 | remoteInfo = URLServiceRouterApp;
29 | };
30 | 3D5DBF9826D4DD59009607EA /* PBXContainerItemProxy */ = {
31 | isa = PBXContainerItemProxy;
32 | containerPortal = 3D5DBF6E26D4DD57009607EA /* Project object */;
33 | proxyType = 1;
34 | remoteGlobalIDString = 3D5DBF7526D4DD57009607EA;
35 | remoteInfo = URLServiceRouterApp;
36 | };
37 | /* End PBXContainerItemProxy section */
38 |
39 | /* Begin PBXCopyFilesBuildPhase section */
40 | AAB1580828EB3BD00036A378 /* Embed Frameworks */ = {
41 | isa = PBXCopyFilesBuildPhase;
42 | buildActionMask = 2147483647;
43 | dstPath = "";
44 | dstSubfolderSpec = 10;
45 | files = (
46 | AAB1580728EB3BD00036A378 /* URLServiceRouter.framework in Embed Frameworks */,
47 | );
48 | name = "Embed Frameworks";
49 | runOnlyForDeploymentPostprocessing = 0;
50 | };
51 | /* End PBXCopyFilesBuildPhase section */
52 |
53 | /* Begin PBXFileReference section */
54 | 3D5DBF7626D4DD57009607EA /* URLServiceRouterApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = URLServiceRouterApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
55 | 3D5DBF7926D4DD57009607EA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
56 | 3D5DBF7D26D4DD57009607EA /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
57 | 3D5DBF8026D4DD57009607EA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
58 | 3D5DBF8226D4DD58009607EA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
59 | 3D5DBF8526D4DD58009607EA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
60 | 3D5DBF8726D4DD58009607EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
61 | 3D5DBF8C26D4DD59009607EA /* URLServiceRouterAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = URLServiceRouterAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
62 | 3D5DBF9026D4DD59009607EA /* URLServiceRouterAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLServiceRouterAppTests.swift; sourceTree = ""; };
63 | 3D5DBF9226D4DD59009607EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
64 | 3D5DBF9726D4DD59009607EA /* URLServiceRouterAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = URLServiceRouterAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
65 | 3D5DBF9B26D4DD59009607EA /* URLServiceRouterAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLServiceRouterAppUITests.swift; sourceTree = ""; };
66 | 3D5DBF9D26D4DD59009607EA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
67 | 3D5DBFCA26D4E045009607EA /* URLServiceRouter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = URLServiceRouter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
68 | 3D5DBFD326D4E1C2009607EA /* URLServiceRouter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = URLServiceRouter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
69 | 3D5DBFEA26D4E22E009607EA /* Demo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Demo.swift; sourceTree = ""; };
70 | AAB1580528EB3BD00036A378 /* URLServiceRouter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = URLServiceRouter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
71 | /* End PBXFileReference section */
72 |
73 | /* Begin PBXFrameworksBuildPhase section */
74 | 3D5DBF7326D4DD57009607EA /* Frameworks */ = {
75 | isa = PBXFrameworksBuildPhase;
76 | buildActionMask = 2147483647;
77 | files = (
78 | AAB1580628EB3BD00036A378 /* URLServiceRouter.framework in Frameworks */,
79 | );
80 | runOnlyForDeploymentPostprocessing = 0;
81 | };
82 | 3D5DBF8926D4DD59009607EA /* Frameworks */ = {
83 | isa = PBXFrameworksBuildPhase;
84 | buildActionMask = 2147483647;
85 | files = (
86 | );
87 | runOnlyForDeploymentPostprocessing = 0;
88 | };
89 | 3D5DBF9426D4DD59009607EA /* Frameworks */ = {
90 | isa = PBXFrameworksBuildPhase;
91 | buildActionMask = 2147483647;
92 | files = (
93 | );
94 | runOnlyForDeploymentPostprocessing = 0;
95 | };
96 | /* End PBXFrameworksBuildPhase section */
97 |
98 | /* Begin PBXGroup section */
99 | 3D5DBF6D26D4DD57009607EA = {
100 | isa = PBXGroup;
101 | children = (
102 | 3D5DBF7826D4DD57009607EA /* URLServiceRouterApp */,
103 | 3D5DBF8F26D4DD59009607EA /* URLServiceRouterAppTests */,
104 | 3D5DBF9A26D4DD59009607EA /* URLServiceRouterAppUITests */,
105 | 3D5DBF7726D4DD57009607EA /* Products */,
106 | 3D5DBFC926D4E045009607EA /* Frameworks */,
107 | );
108 | sourceTree = "";
109 | };
110 | 3D5DBF7726D4DD57009607EA /* Products */ = {
111 | isa = PBXGroup;
112 | children = (
113 | 3D5DBF7626D4DD57009607EA /* URLServiceRouterApp.app */,
114 | 3D5DBF8C26D4DD59009607EA /* URLServiceRouterAppTests.xctest */,
115 | 3D5DBF9726D4DD59009607EA /* URLServiceRouterAppUITests.xctest */,
116 | );
117 | name = Products;
118 | sourceTree = "";
119 | };
120 | 3D5DBF7826D4DD57009607EA /* URLServiceRouterApp */ = {
121 | isa = PBXGroup;
122 | children = (
123 | 3D5DBFEA26D4E22E009607EA /* Demo.swift */,
124 | 3D5DBF7926D4DD57009607EA /* AppDelegate.swift */,
125 | 3D5DBF7D26D4DD57009607EA /* ViewController.swift */,
126 | 3D5DBF7F26D4DD57009607EA /* Main.storyboard */,
127 | 3D5DBF8226D4DD58009607EA /* Assets.xcassets */,
128 | 3D5DBF8426D4DD58009607EA /* LaunchScreen.storyboard */,
129 | 3D5DBF8726D4DD58009607EA /* Info.plist */,
130 | );
131 | path = URLServiceRouterApp;
132 | sourceTree = "";
133 | };
134 | 3D5DBF8F26D4DD59009607EA /* URLServiceRouterAppTests */ = {
135 | isa = PBXGroup;
136 | children = (
137 | 3D5DBF9026D4DD59009607EA /* URLServiceRouterAppTests.swift */,
138 | 3D5DBF9226D4DD59009607EA /* Info.plist */,
139 | );
140 | path = URLServiceRouterAppTests;
141 | sourceTree = "";
142 | };
143 | 3D5DBF9A26D4DD59009607EA /* URLServiceRouterAppUITests */ = {
144 | isa = PBXGroup;
145 | children = (
146 | 3D5DBF9B26D4DD59009607EA /* URLServiceRouterAppUITests.swift */,
147 | 3D5DBF9D26D4DD59009607EA /* Info.plist */,
148 | );
149 | path = URLServiceRouterAppUITests;
150 | sourceTree = "";
151 | };
152 | 3D5DBFC926D4E045009607EA /* Frameworks */ = {
153 | isa = PBXGroup;
154 | children = (
155 | AAB1580528EB3BD00036A378 /* URLServiceRouter.framework */,
156 | 3D5DBFD326D4E1C2009607EA /* URLServiceRouter.framework */,
157 | 3D5DBFCA26D4E045009607EA /* URLServiceRouter.framework */,
158 | );
159 | name = Frameworks;
160 | sourceTree = "";
161 | };
162 | /* End PBXGroup section */
163 |
164 | /* Begin PBXNativeTarget section */
165 | 3D5DBF7526D4DD57009607EA /* URLServiceRouterApp */ = {
166 | isa = PBXNativeTarget;
167 | buildConfigurationList = 3D5DBFA026D4DD59009607EA /* Build configuration list for PBXNativeTarget "URLServiceRouterApp" */;
168 | buildPhases = (
169 | 3D5DBF7226D4DD57009607EA /* Sources */,
170 | 3D5DBF7326D4DD57009607EA /* Frameworks */,
171 | 3D5DBF7426D4DD57009607EA /* Resources */,
172 | AAB1580828EB3BD00036A378 /* Embed Frameworks */,
173 | );
174 | buildRules = (
175 | );
176 | dependencies = (
177 | );
178 | name = URLServiceRouterApp;
179 | productName = URLServiceRouterApp;
180 | productReference = 3D5DBF7626D4DD57009607EA /* URLServiceRouterApp.app */;
181 | productType = "com.apple.product-type.application";
182 | };
183 | 3D5DBF8B26D4DD59009607EA /* URLServiceRouterAppTests */ = {
184 | isa = PBXNativeTarget;
185 | buildConfigurationList = 3D5DBFA326D4DD59009607EA /* Build configuration list for PBXNativeTarget "URLServiceRouterAppTests" */;
186 | buildPhases = (
187 | 3D5DBF8826D4DD59009607EA /* Sources */,
188 | 3D5DBF8926D4DD59009607EA /* Frameworks */,
189 | 3D5DBF8A26D4DD59009607EA /* Resources */,
190 | );
191 | buildRules = (
192 | );
193 | dependencies = (
194 | 3D5DBF8E26D4DD59009607EA /* PBXTargetDependency */,
195 | );
196 | name = URLServiceRouterAppTests;
197 | productName = URLServiceRouterAppTests;
198 | productReference = 3D5DBF8C26D4DD59009607EA /* URLServiceRouterAppTests.xctest */;
199 | productType = "com.apple.product-type.bundle.unit-test";
200 | };
201 | 3D5DBF9626D4DD59009607EA /* URLServiceRouterAppUITests */ = {
202 | isa = PBXNativeTarget;
203 | buildConfigurationList = 3D5DBFA626D4DD59009607EA /* Build configuration list for PBXNativeTarget "URLServiceRouterAppUITests" */;
204 | buildPhases = (
205 | 3D5DBF9326D4DD59009607EA /* Sources */,
206 | 3D5DBF9426D4DD59009607EA /* Frameworks */,
207 | 3D5DBF9526D4DD59009607EA /* Resources */,
208 | );
209 | buildRules = (
210 | );
211 | dependencies = (
212 | 3D5DBF9926D4DD59009607EA /* PBXTargetDependency */,
213 | );
214 | name = URLServiceRouterAppUITests;
215 | productName = URLServiceRouterAppUITests;
216 | productReference = 3D5DBF9726D4DD59009607EA /* URLServiceRouterAppUITests.xctest */;
217 | productType = "com.apple.product-type.bundle.ui-testing";
218 | };
219 | /* End PBXNativeTarget section */
220 |
221 | /* Begin PBXProject section */
222 | 3D5DBF6E26D4DD57009607EA /* Project object */ = {
223 | isa = PBXProject;
224 | attributes = {
225 | LastSwiftUpdateCheck = 1240;
226 | LastUpgradeCheck = 1240;
227 | TargetAttributes = {
228 | 3D5DBF7526D4DD57009607EA = {
229 | CreatedOnToolsVersion = 12.4;
230 | };
231 | 3D5DBF8B26D4DD59009607EA = {
232 | CreatedOnToolsVersion = 12.4;
233 | TestTargetID = 3D5DBF7526D4DD57009607EA;
234 | };
235 | 3D5DBF9626D4DD59009607EA = {
236 | CreatedOnToolsVersion = 12.4;
237 | TestTargetID = 3D5DBF7526D4DD57009607EA;
238 | };
239 | };
240 | };
241 | buildConfigurationList = 3D5DBF7126D4DD57009607EA /* Build configuration list for PBXProject "URLServiceRouterApp" */;
242 | compatibilityVersion = "Xcode 9.3";
243 | developmentRegion = en;
244 | hasScannedForEncodings = 0;
245 | knownRegions = (
246 | en,
247 | Base,
248 | );
249 | mainGroup = 3D5DBF6D26D4DD57009607EA;
250 | productRefGroup = 3D5DBF7726D4DD57009607EA /* Products */;
251 | projectDirPath = "";
252 | projectRoot = "";
253 | targets = (
254 | 3D5DBF7526D4DD57009607EA /* URLServiceRouterApp */,
255 | 3D5DBF8B26D4DD59009607EA /* URLServiceRouterAppTests */,
256 | 3D5DBF9626D4DD59009607EA /* URLServiceRouterAppUITests */,
257 | );
258 | };
259 | /* End PBXProject section */
260 |
261 | /* Begin PBXResourcesBuildPhase section */
262 | 3D5DBF7426D4DD57009607EA /* Resources */ = {
263 | isa = PBXResourcesBuildPhase;
264 | buildActionMask = 2147483647;
265 | files = (
266 | 3D5DBF8626D4DD58009607EA /* LaunchScreen.storyboard in Resources */,
267 | 3D5DBF8326D4DD58009607EA /* Assets.xcassets in Resources */,
268 | 3D5DBF8126D4DD57009607EA /* Main.storyboard in Resources */,
269 | );
270 | runOnlyForDeploymentPostprocessing = 0;
271 | };
272 | 3D5DBF8A26D4DD59009607EA /* Resources */ = {
273 | isa = PBXResourcesBuildPhase;
274 | buildActionMask = 2147483647;
275 | files = (
276 | );
277 | runOnlyForDeploymentPostprocessing = 0;
278 | };
279 | 3D5DBF9526D4DD59009607EA /* Resources */ = {
280 | isa = PBXResourcesBuildPhase;
281 | buildActionMask = 2147483647;
282 | files = (
283 | );
284 | runOnlyForDeploymentPostprocessing = 0;
285 | };
286 | /* End PBXResourcesBuildPhase section */
287 |
288 | /* Begin PBXSourcesBuildPhase section */
289 | 3D5DBF7226D4DD57009607EA /* Sources */ = {
290 | isa = PBXSourcesBuildPhase;
291 | buildActionMask = 2147483647;
292 | files = (
293 | 3D5DBF7E26D4DD57009607EA /* ViewController.swift in Sources */,
294 | 3D5DBF7A26D4DD57009607EA /* AppDelegate.swift in Sources */,
295 | 3D5DBFEB26D4E22E009607EA /* Demo.swift in Sources */,
296 | );
297 | runOnlyForDeploymentPostprocessing = 0;
298 | };
299 | 3D5DBF8826D4DD59009607EA /* Sources */ = {
300 | isa = PBXSourcesBuildPhase;
301 | buildActionMask = 2147483647;
302 | files = (
303 | 3D5DBF9126D4DD59009607EA /* URLServiceRouterAppTests.swift in Sources */,
304 | );
305 | runOnlyForDeploymentPostprocessing = 0;
306 | };
307 | 3D5DBF9326D4DD59009607EA /* Sources */ = {
308 | isa = PBXSourcesBuildPhase;
309 | buildActionMask = 2147483647;
310 | files = (
311 | 3D5DBF9C26D4DD59009607EA /* URLServiceRouterAppUITests.swift in Sources */,
312 | );
313 | runOnlyForDeploymentPostprocessing = 0;
314 | };
315 | /* End PBXSourcesBuildPhase section */
316 |
317 | /* Begin PBXTargetDependency section */
318 | 3D5DBF8E26D4DD59009607EA /* PBXTargetDependency */ = {
319 | isa = PBXTargetDependency;
320 | target = 3D5DBF7526D4DD57009607EA /* URLServiceRouterApp */;
321 | targetProxy = 3D5DBF8D26D4DD59009607EA /* PBXContainerItemProxy */;
322 | };
323 | 3D5DBF9926D4DD59009607EA /* PBXTargetDependency */ = {
324 | isa = PBXTargetDependency;
325 | target = 3D5DBF7526D4DD57009607EA /* URLServiceRouterApp */;
326 | targetProxy = 3D5DBF9826D4DD59009607EA /* PBXContainerItemProxy */;
327 | };
328 | /* End PBXTargetDependency section */
329 |
330 | /* Begin PBXVariantGroup section */
331 | 3D5DBF7F26D4DD57009607EA /* Main.storyboard */ = {
332 | isa = PBXVariantGroup;
333 | children = (
334 | 3D5DBF8026D4DD57009607EA /* Base */,
335 | );
336 | name = Main.storyboard;
337 | sourceTree = "";
338 | };
339 | 3D5DBF8426D4DD58009607EA /* LaunchScreen.storyboard */ = {
340 | isa = PBXVariantGroup;
341 | children = (
342 | 3D5DBF8526D4DD58009607EA /* Base */,
343 | );
344 | name = LaunchScreen.storyboard;
345 | sourceTree = "";
346 | };
347 | /* End PBXVariantGroup section */
348 |
349 | /* Begin XCBuildConfiguration section */
350 | 3D5DBF9E26D4DD59009607EA /* Debug */ = {
351 | isa = XCBuildConfiguration;
352 | buildSettings = {
353 | ALWAYS_SEARCH_USER_PATHS = NO;
354 | CLANG_ANALYZER_NONNULL = YES;
355 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
356 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
357 | CLANG_CXX_LIBRARY = "libc++";
358 | CLANG_ENABLE_MODULES = YES;
359 | CLANG_ENABLE_OBJC_ARC = YES;
360 | CLANG_ENABLE_OBJC_WEAK = YES;
361 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
362 | CLANG_WARN_BOOL_CONVERSION = YES;
363 | CLANG_WARN_COMMA = YES;
364 | CLANG_WARN_CONSTANT_CONVERSION = YES;
365 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
366 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
367 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
368 | CLANG_WARN_EMPTY_BODY = YES;
369 | CLANG_WARN_ENUM_CONVERSION = YES;
370 | CLANG_WARN_INFINITE_RECURSION = YES;
371 | CLANG_WARN_INT_CONVERSION = YES;
372 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
373 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
374 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
375 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
376 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
377 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
378 | CLANG_WARN_STRICT_PROTOTYPES = YES;
379 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
380 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
381 | CLANG_WARN_UNREACHABLE_CODE = YES;
382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
383 | COPY_PHASE_STRIP = NO;
384 | DEBUG_INFORMATION_FORMAT = dwarf;
385 | ENABLE_STRICT_OBJC_MSGSEND = YES;
386 | ENABLE_TESTABILITY = YES;
387 | GCC_C_LANGUAGE_STANDARD = gnu11;
388 | GCC_DYNAMIC_NO_PIC = NO;
389 | GCC_NO_COMMON_BLOCKS = YES;
390 | GCC_OPTIMIZATION_LEVEL = 0;
391 | GCC_PREPROCESSOR_DEFINITIONS = (
392 | "DEBUG=1",
393 | "$(inherited)",
394 | );
395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
397 | GCC_WARN_UNDECLARED_SELECTOR = YES;
398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
399 | GCC_WARN_UNUSED_FUNCTION = YES;
400 | GCC_WARN_UNUSED_VARIABLE = YES;
401 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
402 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
403 | MTL_FAST_MATH = YES;
404 | ONLY_ACTIVE_ARCH = YES;
405 | SDKROOT = iphoneos;
406 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
407 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
408 | };
409 | name = Debug;
410 | };
411 | 3D5DBF9F26D4DD59009607EA /* Release */ = {
412 | isa = XCBuildConfiguration;
413 | buildSettings = {
414 | ALWAYS_SEARCH_USER_PATHS = NO;
415 | CLANG_ANALYZER_NONNULL = YES;
416 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
417 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
418 | CLANG_CXX_LIBRARY = "libc++";
419 | CLANG_ENABLE_MODULES = YES;
420 | CLANG_ENABLE_OBJC_ARC = YES;
421 | CLANG_ENABLE_OBJC_WEAK = YES;
422 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
423 | CLANG_WARN_BOOL_CONVERSION = YES;
424 | CLANG_WARN_COMMA = YES;
425 | CLANG_WARN_CONSTANT_CONVERSION = YES;
426 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
427 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
428 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
429 | CLANG_WARN_EMPTY_BODY = YES;
430 | CLANG_WARN_ENUM_CONVERSION = YES;
431 | CLANG_WARN_INFINITE_RECURSION = YES;
432 | CLANG_WARN_INT_CONVERSION = YES;
433 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
434 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
435 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
436 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
437 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
438 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
439 | CLANG_WARN_STRICT_PROTOTYPES = YES;
440 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
441 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
442 | CLANG_WARN_UNREACHABLE_CODE = YES;
443 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
444 | COPY_PHASE_STRIP = NO;
445 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
446 | ENABLE_NS_ASSERTIONS = NO;
447 | ENABLE_STRICT_OBJC_MSGSEND = YES;
448 | GCC_C_LANGUAGE_STANDARD = gnu11;
449 | GCC_NO_COMMON_BLOCKS = YES;
450 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
451 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
452 | GCC_WARN_UNDECLARED_SELECTOR = YES;
453 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
454 | GCC_WARN_UNUSED_FUNCTION = YES;
455 | GCC_WARN_UNUSED_VARIABLE = YES;
456 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
457 | MTL_ENABLE_DEBUG_INFO = NO;
458 | MTL_FAST_MATH = YES;
459 | SDKROOT = iphoneos;
460 | SWIFT_COMPILATION_MODE = wholemodule;
461 | SWIFT_OPTIMIZATION_LEVEL = "-O";
462 | VALIDATE_PRODUCT = YES;
463 | };
464 | name = Release;
465 | };
466 | 3D5DBFA126D4DD59009607EA /* Debug */ = {
467 | isa = XCBuildConfiguration;
468 | buildSettings = {
469 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
470 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
471 | CODE_SIGN_STYLE = Automatic;
472 | INFOPLIST_FILE = URLServiceRouterApp/Info.plist;
473 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
474 | LD_RUNPATH_SEARCH_PATHS = (
475 | "$(inherited)",
476 | "@executable_path/Frameworks",
477 | );
478 | PRODUCT_BUNDLE_IDENTIFIER = huanyu.li.URLServiceRouterApp;
479 | PRODUCT_NAME = "$(TARGET_NAME)";
480 | SWIFT_VERSION = 5.0;
481 | TARGETED_DEVICE_FAMILY = "1,2";
482 | };
483 | name = Debug;
484 | };
485 | 3D5DBFA226D4DD59009607EA /* Release */ = {
486 | isa = XCBuildConfiguration;
487 | buildSettings = {
488 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
489 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
490 | CODE_SIGN_STYLE = Automatic;
491 | INFOPLIST_FILE = URLServiceRouterApp/Info.plist;
492 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
493 | LD_RUNPATH_SEARCH_PATHS = (
494 | "$(inherited)",
495 | "@executable_path/Frameworks",
496 | );
497 | PRODUCT_BUNDLE_IDENTIFIER = huanyu.li.URLServiceRouterApp;
498 | PRODUCT_NAME = "$(TARGET_NAME)";
499 | SWIFT_VERSION = 5.0;
500 | TARGETED_DEVICE_FAMILY = "1,2";
501 | };
502 | name = Release;
503 | };
504 | 3D5DBFA426D4DD59009607EA /* Debug */ = {
505 | isa = XCBuildConfiguration;
506 | buildSettings = {
507 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
508 | BUNDLE_LOADER = "$(TEST_HOST)";
509 | CODE_SIGN_STYLE = Automatic;
510 | INFOPLIST_FILE = URLServiceRouterAppTests/Info.plist;
511 | IPHONEOS_DEPLOYMENT_TARGET = 14.4;
512 | LD_RUNPATH_SEARCH_PATHS = (
513 | "$(inherited)",
514 | "@executable_path/Frameworks",
515 | "@loader_path/Frameworks",
516 | );
517 | PRODUCT_BUNDLE_IDENTIFIER = huanyu.li.URLServiceRouterAppTests;
518 | PRODUCT_NAME = "$(TARGET_NAME)";
519 | SWIFT_VERSION = 5.0;
520 | TARGETED_DEVICE_FAMILY = "1,2";
521 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/URLServiceRouterApp.app/URLServiceRouterApp";
522 | };
523 | name = Debug;
524 | };
525 | 3D5DBFA526D4DD59009607EA /* Release */ = {
526 | isa = XCBuildConfiguration;
527 | buildSettings = {
528 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
529 | BUNDLE_LOADER = "$(TEST_HOST)";
530 | CODE_SIGN_STYLE = Automatic;
531 | INFOPLIST_FILE = URLServiceRouterAppTests/Info.plist;
532 | IPHONEOS_DEPLOYMENT_TARGET = 14.4;
533 | LD_RUNPATH_SEARCH_PATHS = (
534 | "$(inherited)",
535 | "@executable_path/Frameworks",
536 | "@loader_path/Frameworks",
537 | );
538 | PRODUCT_BUNDLE_IDENTIFIER = huanyu.li.URLServiceRouterAppTests;
539 | PRODUCT_NAME = "$(TARGET_NAME)";
540 | SWIFT_VERSION = 5.0;
541 | TARGETED_DEVICE_FAMILY = "1,2";
542 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/URLServiceRouterApp.app/URLServiceRouterApp";
543 | };
544 | name = Release;
545 | };
546 | 3D5DBFA726D4DD59009607EA /* Debug */ = {
547 | isa = XCBuildConfiguration;
548 | buildSettings = {
549 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
550 | CODE_SIGN_STYLE = Automatic;
551 | INFOPLIST_FILE = URLServiceRouterAppUITests/Info.plist;
552 | LD_RUNPATH_SEARCH_PATHS = (
553 | "$(inherited)",
554 | "@executable_path/Frameworks",
555 | "@loader_path/Frameworks",
556 | );
557 | PRODUCT_BUNDLE_IDENTIFIER = huanyu.li.URLServiceRouterAppUITests;
558 | PRODUCT_NAME = "$(TARGET_NAME)";
559 | SWIFT_VERSION = 5.0;
560 | TARGETED_DEVICE_FAMILY = "1,2";
561 | TEST_TARGET_NAME = URLServiceRouterApp;
562 | };
563 | name = Debug;
564 | };
565 | 3D5DBFA826D4DD59009607EA /* Release */ = {
566 | isa = XCBuildConfiguration;
567 | buildSettings = {
568 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
569 | CODE_SIGN_STYLE = Automatic;
570 | INFOPLIST_FILE = URLServiceRouterAppUITests/Info.plist;
571 | LD_RUNPATH_SEARCH_PATHS = (
572 | "$(inherited)",
573 | "@executable_path/Frameworks",
574 | "@loader_path/Frameworks",
575 | );
576 | PRODUCT_BUNDLE_IDENTIFIER = huanyu.li.URLServiceRouterAppUITests;
577 | PRODUCT_NAME = "$(TARGET_NAME)";
578 | SWIFT_VERSION = 5.0;
579 | TARGETED_DEVICE_FAMILY = "1,2";
580 | TEST_TARGET_NAME = URLServiceRouterApp;
581 | };
582 | name = Release;
583 | };
584 | /* End XCBuildConfiguration section */
585 |
586 | /* Begin XCConfigurationList section */
587 | 3D5DBF7126D4DD57009607EA /* Build configuration list for PBXProject "URLServiceRouterApp" */ = {
588 | isa = XCConfigurationList;
589 | buildConfigurations = (
590 | 3D5DBF9E26D4DD59009607EA /* Debug */,
591 | 3D5DBF9F26D4DD59009607EA /* Release */,
592 | );
593 | defaultConfigurationIsVisible = 0;
594 | defaultConfigurationName = Release;
595 | };
596 | 3D5DBFA026D4DD59009607EA /* Build configuration list for PBXNativeTarget "URLServiceRouterApp" */ = {
597 | isa = XCConfigurationList;
598 | buildConfigurations = (
599 | 3D5DBFA126D4DD59009607EA /* Debug */,
600 | 3D5DBFA226D4DD59009607EA /* Release */,
601 | );
602 | defaultConfigurationIsVisible = 0;
603 | defaultConfigurationName = Release;
604 | };
605 | 3D5DBFA326D4DD59009607EA /* Build configuration list for PBXNativeTarget "URLServiceRouterAppTests" */ = {
606 | isa = XCConfigurationList;
607 | buildConfigurations = (
608 | 3D5DBFA426D4DD59009607EA /* Debug */,
609 | 3D5DBFA526D4DD59009607EA /* Release */,
610 | );
611 | defaultConfigurationIsVisible = 0;
612 | defaultConfigurationName = Release;
613 | };
614 | 3D5DBFA626D4DD59009607EA /* Build configuration list for PBXNativeTarget "URLServiceRouterAppUITests" */ = {
615 | isa = XCConfigurationList;
616 | buildConfigurations = (
617 | 3D5DBFA726D4DD59009607EA /* Debug */,
618 | 3D5DBFA826D4DD59009607EA /* Release */,
619 | );
620 | defaultConfigurationIsVisible = 0;
621 | defaultConfigurationName = Release;
622 | };
623 | /* End XCConfigurationList section */
624 | };
625 | rootObject = 3D5DBF6E26D4DD57009607EA /* Project object */;
626 | }
627 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp.xcodeproj/xcshareddata/xcschemes/URLServiceRouterApp.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // URLServiceRouterDemo
4 | //
5 | // Created by huanyu.li on 2021/8/4.
6 | //
7 |
8 | import UIKit
9 | import URLServiceRouter
10 |
11 | @main
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 | var window: UIWindow?
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | congifURLServiceRouter()
17 |
18 | window = UIWindow(frame: UIScreen.main.bounds)
19 | window?.backgroundColor = .white
20 | window?.makeKeyAndVisible()
21 |
22 | window?.rootViewController = UINavigationController(rootViewController: ViewController())
23 | return true
24 | }
25 |
26 | func congifURLServiceRouter() {
27 | URLServiceRouter.shared.delegate = URLServiceRouterDelegate()
28 | URLServiceRouter.shared.rootNodeParsersBuilder = { [URLServiceRedirectHttpParser()] }
29 | registerServices()
30 | registerNodes()
31 | }
32 |
33 | func registerServices() {
34 | URLServiceRouter.shared.registerService(name: "user://info") {
35 | URLOwnerInfoService()
36 | }
37 | URLServiceRouter.shared.registerService(name: "input_page") {
38 | InputPageService()
39 | }
40 |
41 | URLServiceRouter.shared.registerService(name: "login") {
42 | LoginPageService()
43 | }
44 |
45 | URLServiceRouter.shared.registerService(name: "user_center") {
46 | UserService()
47 | }
48 | }
49 |
50 | func registerNodes() {
51 | URLServiceRouter.shared.registerNode(from: "https") {
52 | [URLServiceRedirectTestHostParser()]
53 | }
54 |
55 | do {
56 | URLServiceRouter.shared.registerNode(from: "https://www.realword.com/owner/info") {
57 | [URLServiceNoramlParser(parserType: .post, parseBlock: { nodeParser, _, decision in
58 | decision.complete(nodeParser, "user://info")
59 | })]
60 | }
61 | }
62 |
63 | do {
64 | URLServiceRouter.shared.registerNode(from: "https://www.realword.com/owner/") {
65 | let preParser = URLServiceNoramlParser(parserType: .pre, parseBlock: { nodeParser, request, decision in
66 | var nodeNames = request.nodeNames
67 | if let first = nodeNames.first, first.isPureInt {
68 | request.merge(params: ["id": nodeNames.remove(at: 0)], from: nodeParser)
69 | request.replace(nodeNames: nodeNames, from: nodeParser)
70 | }
71 | decision.next()
72 | })
73 |
74 | let postParser = URLServiceNoramlParser(parserType: .post, parseBlock: { _, _, decision in
75 | decision.next()
76 | })
77 | return [preParser, postParser]
78 | }
79 | }
80 |
81 | URLServiceRouter.shared.registerNode(from: "https://www.realword.com/company/work")
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp/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 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp/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 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp/Demo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Demo.swift
3 | // URLServiceRouterDemo
4 | //
5 | // Created by huanyu.li on 2021/8/3.
6 | //
7 |
8 | /*
9 | 我们假设我们在一个真实世界服务器里,正式服务器是:www.realword.com,测试服务器是:*.realword.io
10 | */
11 |
12 | import Foundation
13 | import UIKit
14 | import URLServiceRouter
15 |
16 | class URLOwnerInfoService: URLServiceProtocol {
17 | func preServiceCallBack(name: String, result: Any?, error: URLServiceErrorProtocol?, decision: URLServiceDecisionProtocol) {
18 | decision.next()
19 | }
20 |
21 | func paramsForPreService(name: String) -> Any? {
22 | return nil
23 | }
24 |
25 | var preServiceNames: [String] = []
26 |
27 | let name: String = "user://info"
28 | private var ownders: [User] = [User(name: "神秘客", id: "1"), User(name: "打工人", id: "999")]
29 |
30 | func meetTheExecutionConditions(params: Any?) -> URLServiceErrorProtocol? {
31 | if getUserId(from: params) != nil {
32 | return nil
33 | } else {
34 | return URLServiceError(code: "1111", message: "no id to accsee owner info")
35 | }
36 | }
37 |
38 | func execute(params: Any?, callback: URLServiceExecutionCallback?) {
39 | if meetTheExecutionConditions(params: params) != nil {
40 | return
41 | }
42 |
43 | let userInfoCallback = {
44 | if let newId = self.getUserId(from: params) {
45 | callback?(self.findUser(with: newId), nil)
46 | }
47 | }
48 |
49 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
50 | userInfoCallback()
51 | }
52 | }
53 |
54 | func getUserId(from params: Any?) -> String? {
55 | var id: String?
56 | if params is [String: Any], let newId = (params as! [String: Any])["id"] {
57 | id = newId as? String
58 | } else if params is String? {
59 | id = params as? String
60 | }
61 | return id
62 | }
63 |
64 | func findUser(with id: String) -> User? {
65 | return ownders.first { $0.id == id }
66 | }
67 | }
68 |
69 | struct User {
70 | var name: String
71 | var id: String
72 | init(name: String, id: String) {
73 | self.name = name
74 | self.id = id
75 | }
76 | }
77 |
78 | public func screenSize() -> CGSize {
79 | return UIApplication.shared.keyWindow?.bounds.size ?? CGSize.zero
80 | }
81 |
82 | class UserService: URLServiceProtocol {
83 | var userName: String = ""
84 |
85 | func preServiceCallBack(name: String, result: Any?, error: URLServiceErrorProtocol?, decision: URLServiceDecisionProtocol) {
86 | if let userName = result as? String {
87 | self.userName = userName
88 | decision.complete()
89 | } else {
90 | decision.next()
91 | }
92 | }
93 |
94 | func paramsForPreService(name: String) -> Any? {
95 | return nil
96 | }
97 |
98 | var preServiceNames: [String] = ["login"]
99 |
100 | func execute(params: Any?, callback: URLServiceExecutionCallback?) {
101 | if let currentNavigationController = URLServiceRouter.shared.delegate?.currentNavigationController() {
102 | let UserCenterViewController = UserCenterViewController()
103 | UserCenterViewController.userInfo = userName
104 | UserCenterViewController.callBack = { result in
105 | callback?(result, nil)
106 | }
107 | currentNavigationController.pushViewController(UserCenterViewController, animated: true)
108 | }
109 | }
110 |
111 | func getPlaceholder(from params: Any?) -> String? {
112 | var placeholder: String?
113 | if params is String? {
114 | placeholder = params as? String
115 | }
116 | return placeholder
117 | }
118 | }
119 |
120 | class LoginPageService: URLServiceProtocol {
121 | func preServiceCallBack(name: String, result: Any?, error: URLServiceErrorProtocol?, decision: URLServiceDecisionProtocol) {
122 | decision.next()
123 | }
124 |
125 | func paramsForPreService(name: String) -> Any? {
126 | return nil
127 | }
128 |
129 | var preServiceNames: [String] = []
130 |
131 | var name: String = "login"
132 |
133 | func meetTheExecutionConditions(params: Any?) -> URLServiceErrorProtocol? {
134 | return nil
135 | }
136 |
137 | func execute(params: Any?, callback: URLServiceExecutionCallback?) {
138 | if let currentNavigationController = URLServiceRouter.shared.delegate?.currentNavigationController() {
139 | let loginViewController = LoginViewController()
140 | loginViewController.placeholder = getPlaceholder(from: params)
141 | loginViewController.callBack = { result in
142 | callback?(result, nil)
143 | }
144 | currentNavigationController.pushViewController(loginViewController, animated: true)
145 | }
146 | }
147 |
148 | func getPlaceholder(from params: Any?) -> String? {
149 | var placeholder: String?
150 | if params is String? {
151 | placeholder = params as? String
152 | }
153 | return placeholder
154 | }
155 | }
156 |
157 | class InputPageService: URLServiceProtocol {
158 | func preServiceCallBack(name: String, result: Any?, error: URLServiceErrorProtocol?, decision: URLServiceDecisionProtocol) {
159 | decision.next()
160 | }
161 |
162 | func paramsForPreService(name: String) -> Any? {
163 | return nil
164 | }
165 |
166 | var preServiceNames: [String] = []
167 |
168 | var name: String = "input_page"
169 |
170 | func execute(params: Any?, callback: URLServiceExecutionCallback?) {
171 | if let currentNavigationController = URLServiceRouter.shared.delegate?.currentNavigationController() {
172 | let inputViewController = InputViewController()
173 | inputViewController.placeholder = getPlaceholder(from: params)
174 | inputViewController.callBack = { result in
175 | callback?(result, nil)
176 | }
177 | currentNavigationController.pushViewController(inputViewController, animated: true)
178 | }
179 | }
180 |
181 | func getPlaceholder(from params: Any?) -> String? {
182 | var placeholder: String?
183 | if params is String? {
184 | placeholder = params as? String
185 | }
186 | return placeholder
187 | }
188 | }
189 |
190 | class UserCenterViewController: UIViewController {
191 | public var userInfo: String = ""
192 | public var callBack: ((String?) -> Void)?
193 | lazy var label: UILabel = {
194 | let label = UILabel(frame: CGRect(x: 16, y: 100, width: screenSize().width - 2 * 16, height: 44))
195 | label.text = "用户名:\(userInfo)"
196 | return label
197 | }()
198 |
199 | override func viewDidLoad() {
200 | super.viewDidLoad()
201 | view.backgroundColor = .white
202 | title = "UserCenterViewController"
203 | view.addSubview(label)
204 | }
205 | }
206 |
207 | class LoginViewController: UIViewController {
208 | public var placeholder: String?
209 | public var callBack: ((String?) -> Void)?
210 | lazy var textField: UITextField = {
211 | let textField = UITextField(frame: CGRect(x: 16, y: 100, width: screenSize().width - 2 * 16, height: 44))
212 | textField.placeholder = placeholder
213 | textField.borderStyle = .roundedRect
214 | return textField
215 | }()
216 |
217 | lazy var button: UIButton = {
218 | let button = UIButton(type: .roundedRect)
219 | button.frame = CGRect(x: 16, y: 164, width: screenSize().width - 2 * 16, height: 44)
220 | button.setTitle("点击关闭页面并回传数据", for: .normal)
221 | button.setTitleColor(.black, for: .normal)
222 | button.addTarget(self, action: #selector(didClickedButton), for: .touchUpInside)
223 | return button
224 | }()
225 |
226 | override func viewDidLoad() {
227 | super.viewDidLoad()
228 | view.backgroundColor = .white
229 | title = "登录页面"
230 | view.addSubview(textField)
231 | view.addSubview(button)
232 | textField.becomeFirstResponder()
233 | }
234 |
235 | @objc func didClickedButton() {
236 | navigationController?.popViewController(animated: true)
237 | callBack?(textField.text)
238 | }
239 | }
240 |
241 | class InputViewController: UIViewController {
242 | public var placeholder: String?
243 | public var callBack: ((String?) -> Void)?
244 | lazy var textField: UITextField = {
245 | let textField = UITextField(frame: CGRect(x: 16, y: 100, width: screenSize().width - 2 * 16, height: 44))
246 | textField.placeholder = placeholder
247 | textField.borderStyle = .roundedRect
248 | return textField
249 | }()
250 |
251 | lazy var button: UIButton = {
252 | let button = UIButton(type: .roundedRect)
253 | button.frame = CGRect(x: 16, y: 164, width: screenSize().width - 2 * 16, height: 44)
254 | button.setTitle("点击关闭页面并回传数据", for: .normal)
255 | button.setTitleColor(.black, for: .normal)
256 | button.addTarget(self, action: #selector(didClickedButton), for: .touchUpInside)
257 | return button
258 | }()
259 |
260 | override func viewDidLoad() {
261 | super.viewDidLoad()
262 | view.backgroundColor = .white
263 | title = "InputViewController"
264 | view.addSubview(textField)
265 | view.addSubview(button)
266 | textField.becomeFirstResponder()
267 | }
268 |
269 | @objc func didClickedButton() {
270 | navigationController?.popViewController(animated: true)
271 | callBack?(textField.text)
272 | }
273 | }
274 |
275 | class URLServiceRouterDelegate: URLServiceRouterDelegateProtocol {
276 | func currentViewController() -> UIViewController? {
277 | var topViewController = UIApplication.shared.delegate?.window??.rootViewController
278 | while let viewController = topViewController?.presentedViewController {
279 | topViewController = viewController
280 | }
281 | while let viewController = topViewController,
282 | viewController is UINavigationController,
283 | let navigationController = viewController as? UINavigationController,
284 | let topVC = navigationController.topViewController
285 | {
286 | topViewController = topVC
287 | }
288 | return topViewController
289 | }
290 |
291 | func currentNavigationController() -> UINavigationController? {
292 | return currentViewController()?.navigationController
293 | }
294 |
295 | func shouldRoute(request: URLServiceRequestProtocol) -> Bool {
296 | return true
297 | }
298 |
299 | func dynamicProcessingServiceRequest(_ request: URLServiceRequestProtocol) {}
300 |
301 | func logError(_ message: String) {
302 | print("❌URLServiceRouter log error start: \n\(message)\nURLServiceRouter log error end❌")
303 | }
304 |
305 | func logInfo(_ message: String) {
306 | print("‼️ URLServiceRouter log info start: \n\(message)\nURLServiceRouter log info end‼️")
307 | }
308 | }
309 |
310 | struct URLServiceRedirectTestHostParser: URLServiceNodeParserProtocol {
311 | let priority: Int = URLServiceNodeParserPriorityDefault
312 | var parserType: URLServiceNodeParserType = .pre
313 |
314 | func parse(request: URLServiceRequestProtocol, decision: URLServiceNodeParserDecisionProtocol) {
315 | if let host = request.url.host, request.nodeNames.contains(host), host.isTestHost {
316 | var nodeNames = request.nodeNames
317 | nodeNames.remove(at: 0)
318 | nodeNames.insert(String.productionHost, at: 0)
319 | request.replace(nodeNames: nodeNames, from: self)
320 | }
321 | decision.next()
322 | }
323 | }
324 |
325 | extension String {
326 | var isTestHost: Bool {
327 | return hasSuffix(".realword.io")
328 | }
329 |
330 | static var productionHost: String {
331 | return "www.realword.com"
332 | }
333 |
334 | var isPureInt: Bool {
335 | let scanner = Scanner(string: self)
336 | var value: UnsafeMutablePointer?
337 | return scanner.scanInt(value) && scanner.isAtEnd
338 | }
339 | }
340 |
341 | public struct URLServiceRedirectHttpParser: URLServiceNodeParserProtocol {
342 | public let priority: Int = URLServiceNodeParserPriorityDefault
343 | public var parserType: URLServiceNodeParserType = .pre
344 |
345 | public func parse(request: URLServiceRequestProtocol, decision: URLServiceNodeParserDecisionProtocol) {
346 | if let scheme = request.url.scheme, scheme == "http", request.nodeNames.contains(scheme) {
347 | var nodeNames = request.nodeNames
348 | nodeNames.remove(at: 0)
349 | nodeNames.insert("https", at: 0)
350 | request.replace(nodeNames: nodeNames, from: self)
351 | }
352 | decision.next()
353 | }
354 | }
355 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSupportsIndirectInputEvents
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterApp/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // URLServiceRouterDemo
4 | //
5 | // Created by huanyu.li on 2021/8/4.
6 | //
7 |
8 | import UIKit
9 | import URLServiceRouter
10 |
11 | class ViewController: UIViewController {
12 | var cellItems: [TableViewCellItem] = []
13 | lazy var tableView: UITableView = {
14 | let tableView = UITableView(frame: self.view.bounds, style: .plain)
15 | tableView.delegate = self
16 | tableView.dataSource = self
17 | return tableView
18 | }()
19 |
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 | title = "URLServiceRouter"
23 | view.addSubview(tableView)
24 |
25 | addCellItems()
26 | registerCellClass()
27 | tableView.reloadData()
28 |
29 | URLServiceRouter.shared.unitTestRequest(url: "http://china.realword.io/owner/1/info") { request, _ in
30 | URLServiceRouter.shared.logInfo("\(String(describing: request.response?.data))")
31 | }
32 | }
33 |
34 | func addCellItems() {
35 | cellItems.append(TableViewCellItem(cellClass: UITableViewCell.classForCoder(), cellSettingBlock: { _, _, tableViewCell, _ in
36 | tableViewCell.textLabel?.text = "页面跳转"
37 | tableViewCell.selectionStyle = .none
38 | }, cellSelectedBlock: { _, _, _, _ in
39 |
40 | }))
41 |
42 | cellItems.append(TableViewCellItem(cellClass: UITableViewCell.classForCoder(), cellSettingBlock: { _, _, tableViewCell, _ in
43 | tableViewCell.textLabel?.text = "push 页面,并接受回传的数据"
44 | tableViewCell.accessoryType = .disclosureIndicator
45 | }, cellSelectedBlock: { _, _, _, _ in
46 | URLServiceRouter.shared.callService(name: "input_page", params: "请在此输入信息,以便回调回去(上个页面传过来的)") { _, _ in
47 | } callback: { result, _ in
48 | if result is String? {
49 | self.showAlertMessge(title: "页面回传来的数据", message: result as! String?)
50 | }
51 | }
52 | }))
53 |
54 | addTitleCellItem()
55 | addTitleCellItem("服务调用")
56 |
57 | cellItems.append(TableViewCellItem(cellClass: UITableViewCell.classForCoder(), cellSettingBlock: { _, _, tableViewCell, _ in
58 | tableViewCell.textLabel?.numberOfLines = 0
59 | tableViewCell.textLabel?.text = "外部请求服务,获取业务数据。\n点击后3秒后将有数据回调"
60 | tableViewCell.accessoryType = .disclosureIndicator
61 | }, cellSelectedBlock: { _, _, _, _ in
62 | if let url = URL(string: "http://china.realword.io/owner/1/info") {
63 | URLServiceRequest(url: url).start(callback: { request in
64 | if let data = request.response?.data {
65 | // 正确的数据
66 | self.showAlertMessge(title: "回调的业务数据", message: String(describing: data))
67 | }
68 | URLServiceRouter.shared.logInfo("\(String(describing: request.response?.data))")
69 | })
70 | }
71 | }))
72 |
73 | cellItems.append(TableViewCellItem(cellClass: UITableViewCell.classForCoder(), cellSettingBlock: { _, _, tableViewCell, _ in
74 | tableViewCell.textLabel?.numberOfLines = 0
75 | tableViewCell.textLabel?.text = "内部直接调用服务,获取业务数据。\n点击后3秒后将有数据回调"
76 | tableViewCell.accessoryType = .disclosureIndicator
77 | }, cellSelectedBlock: { _, _, _, _ in
78 | URLServiceRouter.shared.callService(name: "user://info", params: "1") { _, _ in
79 | } callback: { result, _ in
80 | self.showAlertMessge(title: "回调的业务数据", message: String(describing: result))
81 | URLServiceRouter.shared.logInfo("\(String(describing: result))")
82 | }
83 | }))
84 |
85 | cellItems.append(TableViewCellItem(cellClass: UITableViewCell.classForCoder(), cellSettingBlock: { _, _, tableViewCell, _ in
86 | tableViewCell.textLabel?.numberOfLines = 0
87 | tableViewCell.textLabel?.text = "打开用户中心(会首先打开登录页)"
88 | tableViewCell.accessoryType = .disclosureIndicator
89 | }, cellSelectedBlock: { _, _, _, _ in
90 | URLServiceRouter.shared.callService(name: "user_center", params: "1") { _, _ in
91 | } callback: { result, _ in
92 | self.showAlertMessge(title: "回调的业务数据", message: String(describing: result))
93 | URLServiceRouter.shared.logInfo("\(String(describing: result))")
94 | }
95 | }))
96 |
97 | addTitleCellItem()
98 | addTitleCellItem("业务相关")
99 |
100 | cellItems.append(TableViewCellItem(cellClass: UITableViewCell.classForCoder(), cellSettingBlock: { _, _, tableViewCell, _ in
101 | tableViewCell.textLabel?.numberOfLines = 0
102 | tableViewCell.textLabel?.text = "将测试环境替换为生产环境。 \n详情请查看这部分代码"
103 | tableViewCell.selectionStyle = .none
104 | }, cellSelectedBlock: { _, _, _, _ in
105 | // 由于存在重复注册,这里注释掉重复代码
106 | // URLServiceRouter.shared.registerNode(from: "https") {
107 | // [URLServiceRedirectTestHostParser()]
108 | // }
109 | }))
110 | }
111 |
112 | func registerCellClass() {
113 | cellItems.forEach { cellItem in
114 | tableView.register(cellItem.cellClass, forCellReuseIdentifier: cellItem.cellReuseIdentifier)
115 | }
116 | }
117 |
118 | func showAlertMessge(title: String? = nil, message: String?) {
119 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
120 | let cancelAction = UIAlertAction(title: "取消", style: .cancel, handler: nil)
121 | alertController.addAction(cancelAction)
122 | present(alertController, animated: true, completion: nil)
123 | }
124 |
125 | func addTitleCellItem(_ title: String = "") {
126 | cellItems.append(TableViewCellItem(cellClass: UITableViewCell.classForCoder(), cellSettingBlock: { _, _, tableViewCell, _ in
127 | tableViewCell.textLabel?.text = title
128 | tableViewCell.selectionStyle = .none
129 | }, cellSelectedBlock: { _, _, _, _ in
130 |
131 | }))
132 | }
133 | }
134 |
135 | extension ViewController: UITableViewDataSource, UITableViewDelegate {
136 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
137 | return cellItems.count
138 | }
139 |
140 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
141 | let cellItem = cellItems[indexPath.row]
142 | let tableViewCell = tableView.dequeueReusableCell(withIdentifier: cellItem.cellReuseIdentifier, for: indexPath)
143 | cellItem.cellSettingBlock(cellItem, tableView, tableViewCell, indexPath)
144 | return tableViewCell
145 | }
146 |
147 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
148 | let cellItem = cellItems[indexPath.row]
149 | let tableViewCell = tableView.dequeueReusableCell(withIdentifier: cellItem.cellReuseIdentifier, for: indexPath)
150 | cellItem.cellSelectedBlock(cellItem, tableView, tableViewCell, indexPath)
151 | tableView.deselectRow(at: indexPath, animated: true)
152 | }
153 | }
154 |
155 | typealias TableViewCellItemBlock = (TableViewCellItem, UITableView, UITableViewCell, IndexPath) -> Void
156 | class TableViewCellItem: NSObject {
157 | let cellClass: AnyClass
158 | let cellHeight: Double
159 | let cellSettingBlock: TableViewCellItemBlock
160 | let cellSelectedBlock: TableViewCellItemBlock
161 |
162 | var cellReuseIdentifier: String {
163 | NSStringFromClass(cellClass)
164 | }
165 |
166 | init(cellClass: AnyClass, cellHeight: Double = -1, cellSettingBlock: @escaping TableViewCellItemBlock, cellSelectedBlock: @escaping TableViewCellItemBlock) {
167 | self.cellClass = cellClass
168 | self.cellHeight = cellHeight
169 | self.cellSettingBlock = cellSettingBlock
170 | self.cellSelectedBlock = cellSelectedBlock
171 | super.init()
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterAppTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterAppTests/URLServiceRouterAppTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLServiceRouterAppTests.swift
3 | // URLServiceRouterAppTests
4 | //
5 | // Created by huanyu.li on 2021/8/24.
6 | //
7 |
8 | import XCTest
9 | @testable import URLServiceRouterApp
10 |
11 | class URLServiceRouterAppTests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDownWithError() throws {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() throws {
22 | // This is an example of a functional test case.
23 | // Use XCTAssert and related functions to verify your tests produce the correct results.
24 | }
25 |
26 | func testPerformanceExample() throws {
27 | // This is an example of a performance test case.
28 | self.measure {
29 | // Put the code you want to measure the time of here.
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterAppUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/URLServiceRouterApp/URLServiceRouterAppUITests/URLServiceRouterAppUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLServiceRouterAppUITests.swift
3 | // URLServiceRouterAppUITests
4 | //
5 | // Created by huanyu.li on 2021/8/24.
6 | //
7 |
8 | import XCTest
9 |
10 | class URLServiceRouterAppUITests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | func testExample() throws {
26 | // UI tests must launch the application that they test.
27 | let app = XCUIApplication()
28 | app.launch()
29 |
30 | // Use recording to get started writing UI tests.
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | func testLaunchPerformance() throws {
35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
36 | // This measures how long it takes to launch your application.
37 | measure(metrics: [XCTApplicationLaunchMetric()]) {
38 | XCUIApplication().launch()
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/service_router.drawio:
--------------------------------------------------------------------------------
1 | 7Vxbc9o4FP41zLQPyfhueEwg292dNJNJOt3tvuwIW4AaY7G2uPXXr2xLvkmAKQbktnkJOpYl+zvnfDqSjtwzh/PNhwgsZh+xD4OeofmbnjnqGYauaw79l0i2mWQwcDPBNEI+q1QIXtE3yIQaky6RD+NKRYJxQNCiKvRwGEKPVGQgivC6Wm2Cg2qvCzCFguDVA4Eo/Qv5ZMakjm0VF36HaDrjXevOILsyBt7bNMLLkHXYM8xJ+pddngPeGHvTeAZ8vC6JzIeeOYwwJtmv+WYIgwRcjlt23287ruYPHsGQNLnhYfXFGXyYg69fPn7uu/HTzfBBuzGZ+lYgWDJEPsOIIAoQlX6KIKT/HsEWLwl7C7Ll0MVrNA9ASEv3MzIPqFCnP2MCIsL0TJ+IXsMR+oZDAngNb4YCn7VpjgjtgxdKddOuzVHSQARj9A2MA172kgoohNGn7QKyFqhYxINBtKIvBDclEcPnA8RzSKItrcKvDpiumDFbNiuvC9NwNKb/WckqLIdVBMwcp3nbeXcv1HxBOA1g0Z9b60/Xxf50/gzl/oxadyAgMAoBgfeJPcZlO6A/Sq9aiFLrOMZSXMFSUts1tJCSgmAc6xki8HUBvKS8ptxRtRJRWfvNs7EKDdu6tatKlGCqSTC19qiwAubRyPUF5AK4SnlUVw6+mkGaVwdvIIAngAV9SumsCIMxXj8UgvtUQC+sOK1VgEz5G/qMWY6ENcbLyIMNfIZS4hTubZGZSPIme9UUwQAQtKoOXq2DzrXZCYt1qu5+fZO1dAG9H9BkOXyqmKzRZdD7TUE32wad3fqMUUgKp7J191Yr/RlVFxvUXCd7QdZG+6EHf2sJHRnK0VF1AM3L12Mjq8OOwVE57Bj2T+kYdnccQ3eU8wxx7tsdz2g+Trs/pWeIs1VlI1irP6hidf0IVpyxdsczOCke9ozBZTzDsfd5htG/sGeIE2ruGaZqnlEfM0zZ+thFPcMWZ8Yd8oym0ZSt/4yeYYvzdmWjKdNVLZqyuzwB56R42DMuNAHnu2ic+OpqO7criDPu2/RPGQeoDw2WdnUH6PJ0gnPfYQe40HRCd6oeYNgX9oDdEwjlwiRTUy5M6vIEorkvXGgCYZhXHg06NGWwLNV8wRUDIylipXQKEZ2jUxn6WnVRQZbKYMhwyIXHpTL09SrutiP258i6M6u9XSCTwRWpvWc4QZJ/Ei9ASH9P09/L8RNOcpPYNdpX+fKZLD63luaRUBV4Pm4esnfzbPYu434R3kUEn0EUw6hrAOtNp1rnQ1iWEyFBGMekoxBLyOPCEIvBfPbaT+cb7E6Grem6cRuo/aGPJ/CfNzT+c27rn/wBed6Qo5N1cERmeIpDEJRjv2p0V9R5xHjBEP0KCdmyJEOwJPgQ3mz4LId+0hewJKGfvCLbVb189oP8ccRlyTQPU4r+IxjDWkANAjQN6W+PIgYjKuBx9x27MEe+nymnloy5SILO9HXs+549kqK/31bq1p7n+rJeeuV0WZkXaLc8Cfl742xeBU8mMTw1hN5rWCUFvcJohTz4Av9bwljUVNULjmaY43Nf+zUOlpCJISGTfZmvp9m0GDd3jEyspmTS+prKacCLi38URcoLu0xVHVLJbeZkUrmhrOIO+lWXUJ5lxFwfzjKpAq9OMqZyJCNm0HSMZCR5INKKfP6vCsmIAXZnSIbbTBuRi87ZtjMcI66eJMtZ2VGhK9OLo6lGL0eugytHL5w1DtNL6ykDpwEvzkQ9PF8EkCAcqswt3GDaCGB0V7e6RS6S9JoXNirEy+D6k6R+/XzUtQlGkrOi2rxSM1XDrOsxH8/OOEzKaq1SSTIkspjvme8FKMzMdmtRX8rM1UNrN4byzCxbFCgd/832F3y0KvYW8i2HivQUOhLU3gI/CUdeJRulF+YnMXZp4ibq0lXTdTD7aqdg9z63sHGfGfY4qpt6B63fcmrTz6tbvyNGNN22fsnZTvmLq7VAI0nh4imgP5D9O/WzN9e3f3EKBEP/3x+OeQbKjbtOk5OyoX+XfHUoASMAcYy8KlJwg8jfCai3Nit9KV0ZbRjeaWHLCyF9+tJNSTG/KykUt6Ulft9OJRxkkBLEsq/LcNmpOex9a9/pDr2mxx1JjEKzrm3ubyhjWqEhqjewLVVjM4F9j1/th+1dNH6uan36I3uCVsNyR/ZVnnS0zJN/pMOlGnOq3OPaWEk3+AkUdSdRTpOVh18M05hhXEs/B8PYtbyqczGMy7NCGjKM8FwXYRgxDuwSw7S5V2fznCWFGabJIaRfDNP8qwaadg6GMZzB/oZaYhhb28sYh5/rIgyzc72rEwyz64jKdzCMrtXwv1F+x84Vp6tJinSmthH0UCzbcC2ddZngkPDFm/TrnpWvg8YEeG+Sz4P2JB8bdZLWUBAMcYCp7kchzr5Nmt/0mjTWK31UNHnMTLt6TfQRbCoVH0FM+APiIACLGGVaTW6cU2dF4T0mBM9ZpbOsVNePNknyvfPc7jIX8hMZrc+YXXGtbrIMPSoJ4Ya8ey/onb5pekqBRPgN1vQkUR1HMYATInFmkizeJUcePBROH9M6I6uQvDAEEhGm906CdMybUQqAYbooSADZSQH3FMhhMppRMjCGtKwX5ZQfFjgiQxzSdwEo1SmkNrJOtlwbc0fuPIdtgBOy00zlvF77Khf3gVKVD43encZSLKj8jlZ5F2c70U9gziSvJKJ6eZ9V/mUcbRsHnwCfwThosfhSdTZ2FN8DNx/+Bw==
--------------------------------------------------------------------------------