├── .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 | [![Cocoapods](https://img.shields.io/cocoapods/v/URLServiceRouter.svg)](https://cocoapods.org/pods/URLServiceRouter) 4 | [![Carthage compatible](https://img.shields.io/badge/Carthage-Compatible-brightgreen.svg?style=flat)](https://github.com/Carthage/Carthage) 5 | [![SPM compatible](https://img.shields.io/badge/SPM-Compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager/) 6 | [![Swift](https://img.shields.io/badge/Swift-5.3-orange.svg)](https://swift.org) 7 | [![Xcode](https://img.shields.io/badge/Xcode-12.4-blue.svg)](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== --------------------------------------------------------------------------------