├── .gitignore ├── .travis.yml ├── Example ├── FWRouter.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── FWRouter-Example.xcscheme ├── FWRouter │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── FWRouter.playground │ │ ├── Contents.swift │ │ └── contents.xcplayground │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ └── Info.plist ├── Podfile ├── Podfile.lock └── Tests │ ├── FWRouterSpec.swift │ └── Info.plist ├── FWRouter.podspec ├── FWRouter ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── Action.swift │ ├── CoreRouter.swift │ ├── FWRouter.swift │ ├── Parameter.swift │ ├── PathComponent.swift │ ├── RoutableComponent.swift │ ├── Route.swift │ ├── RouteNode.swift │ ├── Router+Method.swift │ ├── Router.swift │ ├── Target.swift │ ├── TrieRouter.swift │ └── URLComponentsRepresentable.swift ├── LICENSE ├── Makefile ├── README.md ├── README_CN.md └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | 70 | *.xcworkspace -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode10 6 | language: swift 7 | cache: cocoapods 8 | podfile: Example/Podfile 9 | before_install: 10 | - gem install cocoapods # Since Travis is not always on latest version 11 | - pod install --project-directory=Example 12 | script: 13 | - make test 14 | after_success: 15 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /Example/FWRouter.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3CE96B412545556D4B4E17E5 /* Pods_FWRouter_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03738AB53A2F0F753A5CBDBC /* Pods_FWRouter_Tests.framework */; }; 11 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 12 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 13 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 14 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 15 | 607FACEC1AFB9204008FA782 /* FWRouterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* FWRouterSpec.swift */; }; 16 | A85F9484D9C920E12F8BFB62 /* Pods_FWRouter_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4397EB22E7B0165DB9BA4EA /* Pods_FWRouter_Example.framework */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = 607FACCF1AFB9204008FA782; 25 | remoteInfo = FWRouter; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 03738AB53A2F0F753A5CBDBC /* Pods_FWRouter_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FWRouter_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 08F284DBF4C033C451A49788 /* Pods-FWRouter_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FWRouter_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FWRouter_Example/Pods-FWRouter_Example.debug.xcconfig"; sourceTree = ""; }; 32 | 18BEF763E2325BC3C8ABB4CB /* FWRouter.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FWRouter.podspec; path = ../FWRouter.podspec; sourceTree = ""; }; 33 | 2ABD9C1CC4F70E26E536EAF9 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 34 | 3BA129F8E2959EDA6A16018D /* Pods-FWRouter_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FWRouter_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FWRouter_Tests/Pods-FWRouter_Tests.debug.xcconfig"; sourceTree = ""; }; 35 | 607FACD01AFB9204008FA782 /* FWRouter_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FWRouter_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 38 | 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 40 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 41 | 607FACE51AFB9204008FA782 /* FWRouter_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FWRouter_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | 607FACEB1AFB9204008FA782 /* FWRouterSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FWRouterSpec.swift; sourceTree = ""; }; 44 | 6A5081ED8B14C63966D50902 /* Pods-FWRouter_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FWRouter_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FWRouter_Tests/Pods-FWRouter_Tests.release.xcconfig"; sourceTree = ""; }; 45 | 7FA90238216FA9DE00817D37 /* FWRouter.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = FWRouter.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 46 | B50CBAE9DF4B8219716C3303 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 47 | E4397EB22E7B0165DB9BA4EA /* Pods_FWRouter_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FWRouter_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | E62958F39400BB19D23D68E2 /* Pods-FWRouter_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FWRouter_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-FWRouter_Example/Pods-FWRouter_Example.release.xcconfig"; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | A85F9484D9C920E12F8BFB62 /* Pods_FWRouter_Example.framework in Frameworks */, 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | 3CE96B412545556D4B4E17E5 /* Pods_FWRouter_Tests.framework in Frameworks */, 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 2BD046088E26EACEB921D321 /* Pods */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 08F284DBF4C033C451A49788 /* Pods-FWRouter_Example.debug.xcconfig */, 75 | E62958F39400BB19D23D68E2 /* Pods-FWRouter_Example.release.xcconfig */, 76 | 3BA129F8E2959EDA6A16018D /* Pods-FWRouter_Tests.debug.xcconfig */, 77 | 6A5081ED8B14C63966D50902 /* Pods-FWRouter_Tests.release.xcconfig */, 78 | ); 79 | name = Pods; 80 | sourceTree = ""; 81 | }; 82 | 607FACC71AFB9204008FA782 = { 83 | isa = PBXGroup; 84 | children = ( 85 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 86 | 607FACD21AFB9204008FA782 /* Example for FWRouter */, 87 | 607FACE81AFB9204008FA782 /* Tests */, 88 | 607FACD11AFB9204008FA782 /* Products */, 89 | 2BD046088E26EACEB921D321 /* Pods */, 90 | 777839D9723BA5B5B6851AA2 /* Frameworks */, 91 | ); 92 | sourceTree = ""; 93 | }; 94 | 607FACD11AFB9204008FA782 /* Products */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 607FACD01AFB9204008FA782 /* FWRouter_Example.app */, 98 | 607FACE51AFB9204008FA782 /* FWRouter_Tests.xctest */, 99 | ); 100 | name = Products; 101 | sourceTree = ""; 102 | }; 103 | 607FACD21AFB9204008FA782 /* Example for FWRouter */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 7FA90238216FA9DE00817D37 /* FWRouter.playground */, 107 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 108 | 607FACD91AFB9204008FA782 /* Main.storyboard */, 109 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 110 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 111 | 607FACD31AFB9204008FA782 /* Supporting Files */, 112 | ); 113 | name = "Example for FWRouter"; 114 | path = FWRouter; 115 | sourceTree = ""; 116 | }; 117 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 607FACD41AFB9204008FA782 /* Info.plist */, 121 | ); 122 | name = "Supporting Files"; 123 | sourceTree = ""; 124 | }; 125 | 607FACE81AFB9204008FA782 /* Tests */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 607FACEB1AFB9204008FA782 /* FWRouterSpec.swift */, 129 | 607FACE91AFB9204008FA782 /* Supporting Files */, 130 | ); 131 | path = Tests; 132 | sourceTree = ""; 133 | }; 134 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 607FACEA1AFB9204008FA782 /* Info.plist */, 138 | ); 139 | name = "Supporting Files"; 140 | sourceTree = ""; 141 | }; 142 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 18BEF763E2325BC3C8ABB4CB /* FWRouter.podspec */, 146 | 2ABD9C1CC4F70E26E536EAF9 /* README.md */, 147 | B50CBAE9DF4B8219716C3303 /* LICENSE */, 148 | ); 149 | name = "Podspec Metadata"; 150 | sourceTree = ""; 151 | }; 152 | 777839D9723BA5B5B6851AA2 /* Frameworks */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | E4397EB22E7B0165DB9BA4EA /* Pods_FWRouter_Example.framework */, 156 | 03738AB53A2F0F753A5CBDBC /* Pods_FWRouter_Tests.framework */, 157 | ); 158 | name = Frameworks; 159 | sourceTree = ""; 160 | }; 161 | /* End PBXGroup section */ 162 | 163 | /* Begin PBXNativeTarget section */ 164 | 607FACCF1AFB9204008FA782 /* FWRouter_Example */ = { 165 | isa = PBXNativeTarget; 166 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FWRouter_Example" */; 167 | buildPhases = ( 168 | A816A93C742C108514F10592 /* [CP] Check Pods Manifest.lock */, 169 | 607FACCC1AFB9204008FA782 /* Sources */, 170 | 607FACCD1AFB9204008FA782 /* Frameworks */, 171 | 607FACCE1AFB9204008FA782 /* Resources */, 172 | 2AA6336AA1BBBC8073410CB6 /* [CP] Embed Pods Frameworks */, 173 | ); 174 | buildRules = ( 175 | ); 176 | dependencies = ( 177 | ); 178 | name = FWRouter_Example; 179 | productName = FWRouter; 180 | productReference = 607FACD01AFB9204008FA782 /* FWRouter_Example.app */; 181 | productType = "com.apple.product-type.application"; 182 | }; 183 | 607FACE41AFB9204008FA782 /* FWRouter_Tests */ = { 184 | isa = PBXNativeTarget; 185 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FWRouter_Tests" */; 186 | buildPhases = ( 187 | 4B559A56FCBEFB731096027F /* [CP] Check Pods Manifest.lock */, 188 | 607FACE11AFB9204008FA782 /* Sources */, 189 | 607FACE21AFB9204008FA782 /* Frameworks */, 190 | 607FACE31AFB9204008FA782 /* Resources */, 191 | 79427EA5BBB85E578E5E2238 /* [CP] Embed Pods Frameworks */, 192 | ); 193 | buildRules = ( 194 | ); 195 | dependencies = ( 196 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */, 197 | ); 198 | name = FWRouter_Tests; 199 | productName = Tests; 200 | productReference = 607FACE51AFB9204008FA782 /* FWRouter_Tests.xctest */; 201 | productType = "com.apple.product-type.bundle.unit-test"; 202 | }; 203 | /* End PBXNativeTarget section */ 204 | 205 | /* Begin PBXProject section */ 206 | 607FACC81AFB9204008FA782 /* Project object */ = { 207 | isa = PBXProject; 208 | attributes = { 209 | LastSwiftUpdateCheck = 0830; 210 | LastUpgradeCheck = 1000; 211 | ORGANIZATIONNAME = CocoaPods; 212 | TargetAttributes = { 213 | 607FACCF1AFB9204008FA782 = { 214 | CreatedOnToolsVersion = 6.3.1; 215 | LastSwiftMigration = 1000; 216 | }; 217 | 607FACE41AFB9204008FA782 = { 218 | CreatedOnToolsVersion = 6.3.1; 219 | LastSwiftMigration = 1000; 220 | TestTargetID = 607FACCF1AFB9204008FA782; 221 | }; 222 | }; 223 | }; 224 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "FWRouter" */; 225 | compatibilityVersion = "Xcode 3.2"; 226 | developmentRegion = English; 227 | hasScannedForEncodings = 0; 228 | knownRegions = ( 229 | en, 230 | Base, 231 | ); 232 | mainGroup = 607FACC71AFB9204008FA782; 233 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 234 | projectDirPath = ""; 235 | projectRoot = ""; 236 | targets = ( 237 | 607FACCF1AFB9204008FA782 /* FWRouter_Example */, 238 | 607FACE41AFB9204008FA782 /* FWRouter_Tests */, 239 | ); 240 | }; 241 | /* End PBXProject section */ 242 | 243 | /* Begin PBXResourcesBuildPhase section */ 244 | 607FACCE1AFB9204008FA782 /* Resources */ = { 245 | isa = PBXResourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, 249 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 250 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | }; 254 | 607FACE31AFB9204008FA782 /* Resources */ = { 255 | isa = PBXResourcesBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | /* End PBXResourcesBuildPhase section */ 262 | 263 | /* Begin PBXShellScriptBuildPhase section */ 264 | 2AA6336AA1BBBC8073410CB6 /* [CP] Embed Pods Frameworks */ = { 265 | isa = PBXShellScriptBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | ); 269 | inputPaths = ( 270 | "${SRCROOT}/Pods/Target Support Files/Pods-FWRouter_Example/Pods-FWRouter_Example-frameworks.sh", 271 | "${BUILT_PRODUCTS_DIR}/FWRouter/FWRouter.framework", 272 | ); 273 | name = "[CP] Embed Pods Frameworks"; 274 | outputPaths = ( 275 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FWRouter.framework", 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | shellPath = /bin/sh; 279 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FWRouter_Example/Pods-FWRouter_Example-frameworks.sh\"\n"; 280 | showEnvVarsInLog = 0; 281 | }; 282 | 4B559A56FCBEFB731096027F /* [CP] Check Pods Manifest.lock */ = { 283 | isa = PBXShellScriptBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | ); 287 | inputPaths = ( 288 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 289 | "${PODS_ROOT}/Manifest.lock", 290 | ); 291 | name = "[CP] Check Pods Manifest.lock"; 292 | outputPaths = ( 293 | "$(DERIVED_FILE_DIR)/Pods-FWRouter_Tests-checkManifestLockResult.txt", 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | shellPath = /bin/sh; 297 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 298 | showEnvVarsInLog = 0; 299 | }; 300 | 79427EA5BBB85E578E5E2238 /* [CP] Embed Pods Frameworks */ = { 301 | isa = PBXShellScriptBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | ); 305 | inputPaths = ( 306 | "${SRCROOT}/Pods/Target Support Files/Pods-FWRouter_Tests/Pods-FWRouter_Tests-frameworks.sh", 307 | "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", 308 | "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", 309 | ); 310 | name = "[CP] Embed Pods Frameworks"; 311 | outputPaths = ( 312 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", 313 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", 314 | ); 315 | runOnlyForDeploymentPostprocessing = 0; 316 | shellPath = /bin/sh; 317 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FWRouter_Tests/Pods-FWRouter_Tests-frameworks.sh\"\n"; 318 | showEnvVarsInLog = 0; 319 | }; 320 | A816A93C742C108514F10592 /* [CP] Check Pods Manifest.lock */ = { 321 | isa = PBXShellScriptBuildPhase; 322 | buildActionMask = 2147483647; 323 | files = ( 324 | ); 325 | inputPaths = ( 326 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 327 | "${PODS_ROOT}/Manifest.lock", 328 | ); 329 | name = "[CP] Check Pods Manifest.lock"; 330 | outputPaths = ( 331 | "$(DERIVED_FILE_DIR)/Pods-FWRouter_Example-checkManifestLockResult.txt", 332 | ); 333 | runOnlyForDeploymentPostprocessing = 0; 334 | shellPath = /bin/sh; 335 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 336 | showEnvVarsInLog = 0; 337 | }; 338 | /* End PBXShellScriptBuildPhase section */ 339 | 340 | /* Begin PBXSourcesBuildPhase section */ 341 | 607FACCC1AFB9204008FA782 /* Sources */ = { 342 | isa = PBXSourcesBuildPhase; 343 | buildActionMask = 2147483647; 344 | files = ( 345 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 346 | ); 347 | runOnlyForDeploymentPostprocessing = 0; 348 | }; 349 | 607FACE11AFB9204008FA782 /* Sources */ = { 350 | isa = PBXSourcesBuildPhase; 351 | buildActionMask = 2147483647; 352 | files = ( 353 | 607FACEC1AFB9204008FA782 /* FWRouterSpec.swift in Sources */, 354 | ); 355 | runOnlyForDeploymentPostprocessing = 0; 356 | }; 357 | /* End PBXSourcesBuildPhase section */ 358 | 359 | /* Begin PBXTargetDependency section */ 360 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { 361 | isa = PBXTargetDependency; 362 | target = 607FACCF1AFB9204008FA782 /* FWRouter_Example */; 363 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; 364 | }; 365 | /* End PBXTargetDependency section */ 366 | 367 | /* Begin PBXVariantGroup section */ 368 | 607FACD91AFB9204008FA782 /* Main.storyboard */ = { 369 | isa = PBXVariantGroup; 370 | children = ( 371 | 607FACDA1AFB9204008FA782 /* Base */, 372 | ); 373 | name = Main.storyboard; 374 | sourceTree = ""; 375 | }; 376 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 377 | isa = PBXVariantGroup; 378 | children = ( 379 | 607FACDF1AFB9204008FA782 /* Base */, 380 | ); 381 | name = LaunchScreen.xib; 382 | sourceTree = ""; 383 | }; 384 | /* End PBXVariantGroup section */ 385 | 386 | /* Begin XCBuildConfiguration section */ 387 | 607FACED1AFB9204008FA782 /* Debug */ = { 388 | isa = XCBuildConfiguration; 389 | buildSettings = { 390 | ALWAYS_SEARCH_USER_PATHS = NO; 391 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 392 | CLANG_CXX_LIBRARY = "libc++"; 393 | CLANG_ENABLE_MODULES = YES; 394 | CLANG_ENABLE_OBJC_ARC = YES; 395 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 396 | CLANG_WARN_BOOL_CONVERSION = YES; 397 | CLANG_WARN_COMMA = YES; 398 | CLANG_WARN_CONSTANT_CONVERSION = YES; 399 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 400 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 401 | CLANG_WARN_EMPTY_BODY = YES; 402 | CLANG_WARN_ENUM_CONVERSION = YES; 403 | CLANG_WARN_INFINITE_RECURSION = YES; 404 | CLANG_WARN_INT_CONVERSION = YES; 405 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 406 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 407 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 408 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 409 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 410 | CLANG_WARN_STRICT_PROTOTYPES = YES; 411 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 412 | CLANG_WARN_UNREACHABLE_CODE = YES; 413 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 414 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 415 | COPY_PHASE_STRIP = NO; 416 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 417 | ENABLE_STRICT_OBJC_MSGSEND = YES; 418 | ENABLE_TESTABILITY = YES; 419 | GCC_C_LANGUAGE_STANDARD = gnu99; 420 | GCC_DYNAMIC_NO_PIC = NO; 421 | GCC_NO_COMMON_BLOCKS = YES; 422 | GCC_OPTIMIZATION_LEVEL = 0; 423 | GCC_PREPROCESSOR_DEFINITIONS = ( 424 | "DEBUG=1", 425 | "$(inherited)", 426 | ); 427 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 428 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 429 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 430 | GCC_WARN_UNDECLARED_SELECTOR = YES; 431 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 432 | GCC_WARN_UNUSED_FUNCTION = YES; 433 | GCC_WARN_UNUSED_VARIABLE = YES; 434 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 435 | MTL_ENABLE_DEBUG_INFO = YES; 436 | ONLY_ACTIVE_ARCH = YES; 437 | SDKROOT = iphoneos; 438 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 439 | }; 440 | name = Debug; 441 | }; 442 | 607FACEE1AFB9204008FA782 /* Release */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | ALWAYS_SEARCH_USER_PATHS = NO; 446 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 447 | CLANG_CXX_LIBRARY = "libc++"; 448 | CLANG_ENABLE_MODULES = YES; 449 | CLANG_ENABLE_OBJC_ARC = YES; 450 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 451 | CLANG_WARN_BOOL_CONVERSION = YES; 452 | CLANG_WARN_COMMA = YES; 453 | CLANG_WARN_CONSTANT_CONVERSION = YES; 454 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 455 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 456 | CLANG_WARN_EMPTY_BODY = YES; 457 | CLANG_WARN_ENUM_CONVERSION = YES; 458 | CLANG_WARN_INFINITE_RECURSION = YES; 459 | CLANG_WARN_INT_CONVERSION = YES; 460 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 461 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 462 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 463 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 464 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 465 | CLANG_WARN_STRICT_PROTOTYPES = YES; 466 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 467 | CLANG_WARN_UNREACHABLE_CODE = YES; 468 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 469 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 470 | COPY_PHASE_STRIP = NO; 471 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 472 | ENABLE_NS_ASSERTIONS = NO; 473 | ENABLE_STRICT_OBJC_MSGSEND = YES; 474 | GCC_C_LANGUAGE_STANDARD = gnu99; 475 | GCC_NO_COMMON_BLOCKS = YES; 476 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 477 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 478 | GCC_WARN_UNDECLARED_SELECTOR = YES; 479 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 480 | GCC_WARN_UNUSED_FUNCTION = YES; 481 | GCC_WARN_UNUSED_VARIABLE = YES; 482 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 483 | MTL_ENABLE_DEBUG_INFO = NO; 484 | SDKROOT = iphoneos; 485 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 486 | VALIDATE_PRODUCT = YES; 487 | }; 488 | name = Release; 489 | }; 490 | 607FACF01AFB9204008FA782 /* Debug */ = { 491 | isa = XCBuildConfiguration; 492 | baseConfigurationReference = 08F284DBF4C033C451A49788 /* Pods-FWRouter_Example.debug.xcconfig */; 493 | buildSettings = { 494 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 495 | INFOPLIST_FILE = FWRouter/Info.plist; 496 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 497 | MODULE_NAME = ExampleApp; 498 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 499 | PRODUCT_NAME = "$(TARGET_NAME)"; 500 | SWIFT_VERSION = 4.2; 501 | }; 502 | name = Debug; 503 | }; 504 | 607FACF11AFB9204008FA782 /* Release */ = { 505 | isa = XCBuildConfiguration; 506 | baseConfigurationReference = E62958F39400BB19D23D68E2 /* Pods-FWRouter_Example.release.xcconfig */; 507 | buildSettings = { 508 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 509 | INFOPLIST_FILE = FWRouter/Info.plist; 510 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 511 | MODULE_NAME = ExampleApp; 512 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 513 | PRODUCT_NAME = "$(TARGET_NAME)"; 514 | SWIFT_VERSION = 4.2; 515 | }; 516 | name = Release; 517 | }; 518 | 607FACF31AFB9204008FA782 /* Debug */ = { 519 | isa = XCBuildConfiguration; 520 | baseConfigurationReference = 3BA129F8E2959EDA6A16018D /* Pods-FWRouter_Tests.debug.xcconfig */; 521 | buildSettings = { 522 | BUNDLE_LOADER = "$(TEST_HOST)"; 523 | FRAMEWORK_SEARCH_PATHS = ( 524 | "$(SDKROOT)/Developer/Library/Frameworks", 525 | "$(inherited)", 526 | ); 527 | GCC_PREPROCESSOR_DEFINITIONS = ( 528 | "DEBUG=1", 529 | "$(inherited)", 530 | ); 531 | INFOPLIST_FILE = Tests/Info.plist; 532 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 533 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 534 | PRODUCT_NAME = "$(TARGET_NAME)"; 535 | SWIFT_VERSION = 4.2; 536 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FWRouter_Example.app/FWRouter_Example"; 537 | }; 538 | name = Debug; 539 | }; 540 | 607FACF41AFB9204008FA782 /* Release */ = { 541 | isa = XCBuildConfiguration; 542 | baseConfigurationReference = 6A5081ED8B14C63966D50902 /* Pods-FWRouter_Tests.release.xcconfig */; 543 | buildSettings = { 544 | BUNDLE_LOADER = "$(TEST_HOST)"; 545 | FRAMEWORK_SEARCH_PATHS = ( 546 | "$(SDKROOT)/Developer/Library/Frameworks", 547 | "$(inherited)", 548 | ); 549 | INFOPLIST_FILE = Tests/Info.plist; 550 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 551 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 552 | PRODUCT_NAME = "$(TARGET_NAME)"; 553 | SWIFT_VERSION = 4.2; 554 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FWRouter_Example.app/FWRouter_Example"; 555 | }; 556 | name = Release; 557 | }; 558 | /* End XCBuildConfiguration section */ 559 | 560 | /* Begin XCConfigurationList section */ 561 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "FWRouter" */ = { 562 | isa = XCConfigurationList; 563 | buildConfigurations = ( 564 | 607FACED1AFB9204008FA782 /* Debug */, 565 | 607FACEE1AFB9204008FA782 /* Release */, 566 | ); 567 | defaultConfigurationIsVisible = 0; 568 | defaultConfigurationName = Release; 569 | }; 570 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FWRouter_Example" */ = { 571 | isa = XCConfigurationList; 572 | buildConfigurations = ( 573 | 607FACF01AFB9204008FA782 /* Debug */, 574 | 607FACF11AFB9204008FA782 /* Release */, 575 | ); 576 | defaultConfigurationIsVisible = 0; 577 | defaultConfigurationName = Release; 578 | }; 579 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FWRouter_Tests" */ = { 580 | isa = XCConfigurationList; 581 | buildConfigurations = ( 582 | 607FACF31AFB9204008FA782 /* Debug */, 583 | 607FACF41AFB9204008FA782 /* Release */, 584 | ); 585 | defaultConfigurationIsVisible = 0; 586 | defaultConfigurationName = Release; 587 | }; 588 | /* End XCConfigurationList section */ 589 | }; 590 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 591 | } 592 | -------------------------------------------------------------------------------- /Example/FWRouter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/FWRouter.xcodeproj/xcshareddata/xcschemes/FWRouter-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 46 | 47 | 53 | 54 | 55 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 74 | 75 | 76 | 77 | 78 | 79 | 89 | 91 | 97 | 98 | 99 | 100 | 101 | 102 | 108 | 110 | 116 | 117 | 118 | 119 | 121 | 122 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /Example/FWRouter/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 09/20/2018. 6 | // Copyright (c) 2018 Wang Jianwei. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import FWRouter 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | 19 | let vc = UIStoryboard(name: "Main", bundle: .main).instantiateInitialViewController() 20 | let nav = UINavigationController(rootViewController: vc!) 21 | 22 | window = UIWindow(frame: UIScreen.main.bounds) 23 | window?.makeKeyAndVisible() 24 | window?.backgroundColor = .white 25 | 26 | // nav controller 27 | // window?.rootViewController = nav 28 | 29 | 30 | // tab controller 31 | let tab = UITabBarController() 32 | tab.viewControllers = [nav] 33 | window?.rootViewController = tab 34 | 35 | 36 | let route = FWRouterManager.shared.router 37 | 38 | route.match("https://www.wangjianwei.com/dd", "demo", String.parameter, "subtitle", String.parameter) { (target) -> Bool in 39 | let demoVC = DemoViewController() 40 | let title = target.pathParameters.next(String.self)! + "+" + target.pathParameters.next(String.self)! 41 | demoVC.title = title 42 | target.navigation?.pushViewController(demoVC, animated: true) 43 | print(target.url) 44 | print(target.applicationDelegate) 45 | print(target.navigation as Any) 46 | print(target.window as Any) 47 | print(target) 48 | print(target.parameters["name"] as Any) 49 | return true 50 | } 51 | 52 | route.route(url: "https://www.wangjianwei.com/dd/demo/second/subtitle/title") 53 | 54 | return true 55 | } 56 | 57 | } 58 | 59 | class ViewController: UIViewController { 60 | 61 | let router = FWRouterManager.shared.router 62 | 63 | @IBAction func onClick(_ sender: Any) { 64 | guard router.route(url: "https://www.wangjianwei.com/dd/demo/second/subtitle/title", parameters: ["name": "wangjianwei"]) else { 65 | print("route failed") 66 | return 67 | } 68 | print("route success") 69 | } 70 | 71 | @IBAction func unmatchOnClick(_ sender: Any) { 72 | router.route(url: "http://www.xxx/xxx") 73 | } 74 | } 75 | 76 | class DemoViewController: UIViewController { 77 | 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /Example/FWRouter/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Example/FWRouter/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 | 32 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Example/FWRouter/FWRouter.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | import FWRouter 4 | 5 | 6 | -------------------------------------------------------------------------------- /Example/FWRouter/FWRouter.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Example/FWRouter/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/FWRouter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'FWRouter_Example' do 4 | pod 'FWRouter', :path => '../' 5 | 6 | target 'FWRouter_Tests' do 7 | inherit! :search_paths 8 | 9 | pod 'Quick', '1.3.1' 10 | pod 'Nimble', '7.3.0' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - FWRouter (0.1.0-rc2) 3 | - Nimble (7.3.0) 4 | - Quick (1.3.1) 5 | 6 | DEPENDENCIES: 7 | - FWRouter (from `../`) 8 | - Nimble (= 7.3.0) 9 | - Quick (= 1.3.1) 10 | 11 | SPEC REPOS: 12 | https://github.com/CocoaPods/Specs.git: 13 | - Nimble 14 | - Quick 15 | 16 | EXTERNAL SOURCES: 17 | FWRouter: 18 | :path: "../" 19 | 20 | SPEC CHECKSUMS: 21 | FWRouter: 911219d2b7fe156ced887dcd0eced5d27ccb9bf4 22 | Nimble: c8d6a1f463701921c46de965c673e13cafa09196 23 | Quick: d17304d58d0d169dd0bd1c6e5c28e3318de32a1a 24 | 25 | PODFILE CHECKSUM: 8c0e961c6e00c886d98dbca59b4c1c13ecc54212 26 | 27 | COCOAPODS: 1.5.0 28 | -------------------------------------------------------------------------------- /Example/Tests/FWRouterSpec.swift: -------------------------------------------------------------------------------- 1 | // https://github.com/Quick/Quick 2 | 3 | import Quick 4 | import Nimble 5 | import FWRouter 6 | 7 | class FWRouterSpec: QuickSpec { 8 | override func spec() { 9 | let router = FWRouterManager.shared.router 10 | describe("FWRouter Test") { 11 | var testTimes = 0 12 | afterSuite { 13 | // advoid project running effect, could use fake application instead of it. 14 | expect(router.routes.count) >= testTimes 15 | } 16 | afterEach { 17 | testTimes += 1 18 | } 19 | context("Host + Path Router Match", { 20 | it("Host/Path + Path", closure: { 21 | router.match("scheme://host/path", "test1", use: { (target) -> Bool in 22 | expect(target.url) == "scheme://host/path/test1" 23 | return true 24 | }) 25 | expect(router.route(url: "scheme://host/path/test1")) == true 26 | }) 27 | 28 | it("Host + Path", closure: { 29 | router.match("scheme://host", "test2", use: { (target) -> Bool in 30 | expect(target.url) == "scheme://host/test2" 31 | expect(target.url.origin) == "scheme://host" 32 | return true 33 | }) 34 | expect(router.route(url: "scheme://host/test2")) == true 35 | }) 36 | }) 37 | context("PathParameter", { 38 | it("Host + Path + Parameter", closure: { 39 | router.match("scheme://host", "parameter", String.parameter, use: { (target) -> Bool in 40 | expect(target.url) == "scheme://host/parameter/param0" 41 | expect(target.pathParameters.next(String.self)) == "param0" 42 | return true 43 | }) 44 | expect(router.route(url: "scheme://host/parameter/param0")) == true 45 | }) 46 | it("Host + Path + Parameter + Path", closure: { 47 | router.match("scheme://host", "parameter", String.parameter, "test1", use: { (target) -> Bool in 48 | expect(target.url) == "scheme://host/parameter/param0/test1" 49 | expect(target.pathParameters.next(String.self)) == "param0" 50 | return true 51 | }) 52 | expect(router.route(url: "scheme://host/parameter/param0/test1")) == true 53 | }) 54 | it("Host + Path + Int.Parameter", closure: { 55 | router.match("scheme://host", "/parameter", "/int", Int.parameter, use: { (target) -> Bool in 56 | expect(target.url) == "scheme://host/parameter/int/50" 57 | expect(target.pathParameters.next(Int.self)) == 50 58 | return true 59 | }) 60 | expect(router.route(url: "scheme://host/parameter/int/50")) == true 61 | }) 62 | it("Host + Path + Float.Parameter", closure: { 63 | router.match("scheme://host", "/parameter", "/float", Float.parameter, use: { (target) -> Bool in 64 | expect(target.url) == "scheme://host/parameter/float/50.3" 65 | expect(target.pathParameters.next(Float.self)) == 50.3 66 | return true 67 | }) 68 | expect(router.route(url: "scheme://host/parameter/float/50.3")) == true 69 | }) 70 | }) 71 | 72 | context("Any & All", { 73 | it("Host + Path + Any", closure: { 74 | router.match("scheme://host/path", path: [any, PathComponent(stringLiteral: "path2"), Int.parameter, String.parameter], use: { (target) -> Bool in 75 | expect(target.url) == "scheme://host/path/testAny/path2/65/asd" 76 | print(target) 77 | return true 78 | }) 79 | expect(router.route(url: "scheme://host/path/testAny/path2/65/asd")) == true 80 | }) 81 | it("All", closure: { 82 | router.match("all", all, use: { (target) -> Bool in 83 | expect(target.url) == "/all/wangfei" 84 | return true 85 | }) 86 | expect(router.route(url: "/all/wangfei")) == true 87 | expect(router.route(url: "/all1/wangfei")) == false 88 | }) 89 | }) 90 | 91 | context("Parameter", { 92 | it("route with parameter", closure: { 93 | router.match("scheme://host/path", "/thridParameter", use: { (target) -> Bool in 94 | expect(target.parameters["name"] as? String) == "wangjianwei" 95 | return true 96 | }) 97 | expect(router.route(url: "scheme://host/path/thridParameter", parameters: ["name": "wangjianwei"])) == true 98 | }) 99 | }) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /FWRouter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint FWRouter.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'FWRouter' 11 | s.version = '0.1.0' 12 | s.summary = '🚍 High-performance trie-node router.' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = <<-DESC 21 | FWRouter is a High-performance trie-node router. 22 | DESC 23 | 24 | s.homepage = 'https://github.com/JianweiWangs/FWRouter' 25 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 26 | s.license = { :type => 'MIT', :file => 'LICENSE' } 27 | s.author = { 'JianweiWangs' => 'wangjianwei.sir@gmail.com' } 28 | s.source = { :git => 'https://github.com/JianweiWangs/FWRouter.git', :tag => s.version.to_s } 29 | 30 | s.ios.deployment_target = '8.0' 31 | s.swift_version = '4.2' 32 | s.source_files = 'FWRouter/Classes/**/*' 33 | end 34 | -------------------------------------------------------------------------------- /FWRouter/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JianweiWangs/FWRouter/f470f67e97ef1769a2b360ba5b0bc4e6c1318d7e/FWRouter/Assets/.gitkeep -------------------------------------------------------------------------------- /FWRouter/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JianweiWangs/FWRouter/f470f67e97ef1769a2b360ba5b0bc4e6c1318d7e/FWRouter/Classes/.gitkeep -------------------------------------------------------------------------------- /FWRouter/Classes/Action.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Resolution.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/10/13. 6 | // 7 | 8 | public protocol Action { 9 | func resolve(to int: Target) -> Bool 10 | } 11 | 12 | public struct BasicAction: Action { 13 | private let closure: (Target) -> Bool 14 | public init(closure: @escaping (Target) -> Bool) { 15 | self.closure = closure 16 | } 17 | 18 | public func resolve(to tar: Target) -> Bool { 19 | return closure(tar) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FWRouter/Classes/CoreRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreRouter.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/10/13. 6 | // 7 | 8 | public final class CoreRouter: Router { 9 | 10 | private let router: TrieRouter = .init() 11 | 12 | public var routes: [Route] { 13 | return router.routes 14 | } 15 | 16 | public func register(route: Route) { 17 | router.register(route: route) 18 | } 19 | 20 | public func route(tar: Target) -> Action? { 21 | let path = tar.urlMatch 22 | return router.route(path: path, parameters: &tar.pathParameters) 23 | } 24 | 25 | public static func `default`() -> CoreRouter { 26 | return CoreRouter() 27 | } 28 | 29 | private init() {} 30 | } 31 | 32 | extension Target { 33 | var urlMatch: [String] { 34 | let ret = url.path.split(separator: "/").map { String($0) } 35 | if url.host == "" { 36 | return ret 37 | } 38 | return [url.host] + ret 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FWRouter/Classes/FWRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FWRouter.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/10/13. 6 | // 7 | 8 | public protocol Navigation: class { 9 | var navigation: UINavigationController? { get } 10 | } 11 | 12 | 13 | public protocol UIKit { 14 | var applicationDelegate: UIApplicationDelegate { get } 15 | var window: UIWindow? { get } 16 | } 17 | 18 | public typealias UIRouter = Navigation & UIKit 19 | 20 | extension UIKit { 21 | public var window: UIWindow? { 22 | guard let w = applicationDelegate.window else { 23 | return nil 24 | } 25 | return w 26 | } 27 | 28 | public var applicationDelegate: UIApplicationDelegate { 29 | return UIApplication.shared.delegate! 30 | } 31 | } 32 | 33 | extension Navigation where Self: UIKit { 34 | public var navigation: UINavigationController? { 35 | guard let w = applicationDelegate.window as? UIWindow else { 36 | return nil 37 | } 38 | 39 | guard let root = w.rootViewController else { 40 | return nil 41 | } 42 | 43 | 44 | var tempNav: UINavigationController? 45 | 46 | if let nav = root as? UINavigationController { 47 | tempNav = nav 48 | } 49 | 50 | if let tab = root as? Navigation, let n = tab.navigation { 51 | tempNav = n 52 | } 53 | 54 | guard let vc = tempNav else { 55 | return nil 56 | } 57 | 58 | var topVC: UIViewController = vc 59 | 60 | while topVC.presentedViewController != nil { 61 | topVC = topVC.presentedViewController! 62 | } 63 | 64 | guard let currentNavigation = topVC as? UINavigationController else { 65 | return nil 66 | } 67 | 68 | return currentNavigation 69 | } 70 | } 71 | 72 | extension UITabBarController: Navigation { 73 | public var navigation: UINavigationController? { 74 | guard let nav = selectedViewController as? UINavigationController else { 75 | return nil 76 | } 77 | return nav 78 | } 79 | } 80 | 81 | 82 | public final class FWRouterManager { 83 | public static let shared = FWRouterManager() 84 | public let router: CoreRouter = CoreRouter.default() 85 | private init() { } 86 | } 87 | 88 | extension Target: UIRouter { } 89 | -------------------------------------------------------------------------------- /FWRouter/Classes/Parameter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parameter.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/10/13. 6 | // 7 | 8 | public protocol Parameter { 9 | 10 | associatedtype ResolvedParameter 11 | 12 | static var routingSlug: String { get } 13 | 14 | static func resolveParameter(_ parameter: String) -> ResolvedParameter 15 | } 16 | 17 | extension Parameter { 18 | public static var parameter: PathComponent { 19 | return .parameter(routingSlug) 20 | } 21 | } 22 | 23 | extension Parameter { 24 | public static var routingSlug: String { 25 | return "\(Self.self)".lowercased() 26 | } 27 | } 28 | 29 | extension String: Parameter { 30 | public static func resolveParameter(_ parameter: String) -> String { 31 | return parameter 32 | } 33 | } 34 | 35 | extension FixedWidthInteger { 36 | public static func resolveParameter(_ parameter: String) -> Self? { 37 | guard let number = Self(parameter) else { 38 | return nil 39 | } 40 | return number 41 | } 42 | } 43 | 44 | extension Int: Parameter { } 45 | extension Int8: Parameter { } 46 | extension Int16: Parameter { } 47 | extension Int32: Parameter { } 48 | extension Int64: Parameter { } 49 | extension UInt: Parameter { } 50 | extension UInt8: Parameter { } 51 | extension UInt16: Parameter { } 52 | extension UInt32: Parameter { } 53 | extension UInt64: Parameter { } 54 | 55 | extension BinaryFloatingPoint { 56 | public static func resolveParameter(_ parameter: String) -> Self? { 57 | guard let number = Double(parameter) else { 58 | return nil 59 | } 60 | return Self(number) 61 | } 62 | } 63 | 64 | extension Float: Parameter { } 65 | extension Double: Parameter { } 66 | 67 | 68 | public struct ParameterValue { 69 | public let slug: String 70 | public let value: String 71 | } 72 | 73 | public struct Parameters: CustomStringConvertible { 74 | 75 | public var values: [ParameterValue] = [] 76 | 77 | public mutating func next

(_ parameter: P.Type) -> P.ResolvedParameter? where P: Parameter { 78 | 79 | guard values.count > 0 else { 80 | return nil 81 | } 82 | 83 | let current = values[0] 84 | 85 | guard current.slug == P.routingSlug else { 86 | return nil 87 | } 88 | 89 | let item = P.resolveParameter(current.value) 90 | values = Array(values.dropFirst()) 91 | return item 92 | } 93 | public var description: String { 94 | return values 95 | .map { "\t" + $0.slug + " = " + $0.value } 96 | .joined(separator: "\n") 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /FWRouter/Classes/PathComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PathComponent.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/10/13. 6 | // 7 | public enum PathComponent: ExpressibleByStringLiteral { 8 | case constant(String) 9 | case parameter(String) 10 | case anything 11 | case catchall 12 | public init(stringLiteral value: String) { 13 | self = .constant(value) 14 | } 15 | } 16 | 17 | public let any: PathComponent = .anything 18 | public let all: PathComponent = .catchall 19 | 20 | public protocol PathComponentsRepresentable { 21 | func convertToPathComponents() -> [PathComponent] 22 | } 23 | 24 | extension PathComponent: PathComponentsRepresentable { 25 | public func convertToPathComponents() -> [PathComponent] { 26 | return [self] 27 | } 28 | } 29 | 30 | extension String: PathComponentsRepresentable { 31 | public func convertToPathComponents() -> [PathComponent] { 32 | return split(separator: "/").map { .constant(.init($0)) } 33 | } 34 | } 35 | 36 | extension Array: PathComponentsRepresentable where Element == PathComponentsRepresentable { 37 | public func convertToPathComponents() -> [PathComponent] { 38 | return flatMap { $0.convertToPathComponents() } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FWRouter/Classes/RoutableComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoutableComponent.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/10/13. 6 | // 7 | 8 | public protocol RoutableComponent { 9 | 10 | var routerParameterValue: String { get } 11 | 12 | func routerCompare(to value: String) -> Bool 13 | } 14 | 15 | extension String: RoutableComponent { 16 | public var routerParameterValue: String { return self} 17 | public func routerCompare(to value: String) -> Bool { 18 | return self == value 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /FWRouter/Classes/Route.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Route.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/10/13. 6 | // 7 | 8 | public final class Route { 9 | public var path: [PathComponent] 10 | public var output: Output 11 | public init(path: [PathComponent], output: Output) { 12 | self.path = path 13 | self.output = output 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FWRouter/Classes/RouteNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RouteNode.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/10/13. 6 | // 7 | 8 | final class RouteNode { 9 | 10 | var value: String 11 | var constants: [RouteNode] 12 | var parameter: RouteNode? 13 | var catchall: RouteNode? 14 | var anything: RouteNode? 15 | 16 | var output: Output? 17 | 18 | init(value: String, output: Output? = nil) { 19 | self.value = value 20 | self.output = output 21 | self.constants = [] 22 | } 23 | 24 | func buildOrFetchChild(for component: PathComponent) -> RouteNode { 25 | switch component { 26 | case .constant(let value): 27 | for constant in constants { 28 | if constant.value == value { 29 | return constant 30 | } 31 | } 32 | let node = RouteNode(value: value) 33 | constants.append(node) 34 | return node 35 | case .parameter(let value): 36 | let node: RouteNode 37 | if let parameter = self.parameter { 38 | node = parameter 39 | } else { 40 | node = RouteNode(value: value) 41 | self.parameter = node 42 | } 43 | return node 44 | case .catchall: 45 | let node: RouteNode 46 | if let fallback = self.catchall { 47 | node = fallback 48 | } else { 49 | // asterisk * 50 | node = RouteNode(value: "*") 51 | self.catchall = node 52 | } 53 | return node 54 | case .anything: 55 | let node: RouteNode 56 | if let anything = self.anything { 57 | node = anything 58 | } else { 59 | node = RouteNode(value: ":") 60 | self.anything = node 61 | } 62 | return node 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /FWRouter/Classes/Router+Method.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Router+Method.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/10/13. 6 | // 7 | 8 | extension Router { 9 | @discardableResult 10 | public func route(url: URLComponentsRepresentable, parameters: [String: AnyHashable] = [:]) -> Bool { 11 | let target = Target(url: url, parameters: parameters) 12 | guard let resolution = route(tar: target) else { 13 | return false 14 | } 15 | return resolution.resolve(to: target) 16 | } 17 | 18 | public func match(_ url: URLComponentsRepresentable = "", path: [PathComponentsRepresentable], use closure: @escaping (Target) -> Bool) { 19 | match(url, path: path.convertToPathComponents(), use: closure) 20 | } 21 | 22 | public func match(_ url: URLComponentsRepresentable = "",_ path: PathComponentsRepresentable..., use closure: @escaping (Target) -> Bool) { 23 | match(url, path: path.convertToPathComponents(), use: closure) 24 | } 25 | } 26 | 27 | extension Router { 28 | 29 | private func match(_ url: URLComponentsRepresentable, path: [PathComponent], use closure: @escaping (Target) -> Bool) { 30 | let resolution = BasicAction { 31 | return closure($0) 32 | } 33 | let allPath = url.convertURLToPathComponents() + path 34 | let route = Route(path: allPath, output: resolution) 35 | register(route: route) 36 | } 37 | } 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /FWRouter/Classes/Router.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Router.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/9/20. 6 | // 7 | 8 | public protocol Router: class { 9 | var routes: [Route] { get } 10 | func register(route: Route) 11 | func route(tar: Target) -> Action? 12 | } 13 | 14 | -------------------------------------------------------------------------------- /FWRouter/Classes/Target.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Intent.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/10/13. 6 | // 7 | 8 | public final class Target: CustomStringConvertible { 9 | public var description: String { 10 | let des: [String] = [ 11 | "url = " + url.url, 12 | "parameters = {\n" + pathParameters.description + "\n}", 13 | ] 14 | return des.joined(separator: "\n") 15 | } 16 | public let url: String 17 | public var pathParameters: Parameters = .init() 18 | public let parameters: [String: AnyHashable] 19 | public init(url: URLComponentsRepresentable, parameters: [String: AnyHashable] = [:]) { 20 | self.url = url.url 21 | self.parameters = parameters 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FWRouter/Classes/TrieRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrieRouter.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/10/13. 6 | // 7 | 8 | public final class TrieRouter { 9 | public private(set) var routes: [Route] 10 | private var root: RouteNode 11 | 12 | public init(_ type: Output.Type = Output.self) { 13 | self.root = RouteNode(value: "/") 14 | self.routes = [] 15 | } 16 | 17 | public func register(route: Route) { 18 | routes.append(route) 19 | 20 | var current = root 21 | 22 | for component in route.path { 23 | current = current.buildOrFetchChild(for: component) 24 | } 25 | 26 | current.output = route.output 27 | } 28 | 29 | public func route(path: [C], parameters: inout Parameters) -> Output? where C: RoutableComponent { 30 | var currentNode: RouteNode = root 31 | 32 | search: for path in path { 33 | for constant in currentNode.constants { 34 | // TODO: path match 35 | if constant.value.routerCompare(to: path.routerParameterValue) { 36 | currentNode = constant 37 | continue search 38 | } 39 | } 40 | 41 | if let parameter = currentNode.parameter { 42 | let value = ParameterValue(slug: parameter.value, value: path.routerParameterValue) 43 | parameters.values.append(value) 44 | currentNode = parameter 45 | continue search 46 | } 47 | 48 | if let anything = currentNode.anything { 49 | currentNode = anything 50 | continue search 51 | } 52 | 53 | if let catchall = currentNode.catchall { 54 | return catchall.output 55 | } 56 | 57 | return nil 58 | } 59 | 60 | return currentNode.output 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /FWRouter/Classes/URLComponentsRepresentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLComponentsRepresentable.swift 3 | // FWRouter 4 | // 5 | // Created by Wang Jianwei on 2018/10/14. 6 | // 7 | 8 | public protocol URLComponentsRepresentable { 9 | var url: String { get } 10 | var origin: String { get } 11 | var scheme: String { get } 12 | var host: String { get } 13 | var path: String { get } 14 | func convertURLToPathComponents() -> [PathComponent] 15 | } 16 | 17 | 18 | extension URLComponentsRepresentable { 19 | public func convertURLToPathComponents() -> [PathComponent] { 20 | guard let component = URLComponents(string: url) else { 21 | return [] 22 | } 23 | return host.convertToPathComponents() + component.path.convertToPathComponents() 24 | } 25 | public var scheme: String { 26 | guard let components = URLComponents(string: url) else { 27 | return "" 28 | } 29 | guard let scheme = components.scheme else { 30 | return "" 31 | } 32 | return scheme 33 | } 34 | 35 | public var host: String { 36 | guard let components = URLComponents(string: url) else { 37 | return "" 38 | } 39 | 40 | guard let host = components.host else { 41 | return "" 42 | } 43 | return host 44 | } 45 | 46 | public var path: String { 47 | guard let components = URLComponents(string: url) else { 48 | return "" 49 | } 50 | return components.path 51 | } 52 | 53 | public var origin: String { 54 | return scheme + "://" + host 55 | } 56 | } 57 | 58 | 59 | extension String: URLComponentsRepresentable { 60 | public var url: String { 61 | return self 62 | } 63 | } 64 | 65 | 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 JianweiWangs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: install open 2 | open: 3 | open ./Example/FWRouter.xcworkspace 4 | install: 5 | pod install --project-directory=Example 6 | clean: 7 | pod cache clean --all 8 | quit: 9 | osascript -e 'quit app "Xcode"' 10 | test: 11 | - xcodebuild -scheme FWRouter-Example -workspace Example/FWRouter.xcworkspace -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone XR,OS=12.0' build test 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FWRouter 2 | 3 | [![Contact Us](https://img.shields.io/discord/490015137181466645.svg)](https://discordapp.com/invite/RF2rWZ) 4 | [![CI Status](https://img.shields.io/travis/JianweiWangs/FWRouter.svg?style=flat)](https://travis-ci.org/JianweiWangs/FWRouter) 5 | [![Version](https://img.shields.io/cocoapods/v/FWRouter.svg?style=flat)](https://cocoapods.org/pods/FWRouter) 6 | [![License](https://img.shields.io/cocoapods/l/FWRouter.svg?style=flat)](https://cocoapods.org/pods/FWRouter) 7 | [![Platform](https://img.shields.io/cocoapods/p/FWRouter.svg?style=flat)](https://cocoapods.org/pods/FWRouter) 8 | [![codecov](https://codecov.io/gh/JianweiWangs/FWRouter/branch/master/graph/badge.svg)](https://codecov.io/gh/JianweiWangs/FWRouter) 9 | 10 | 中文版本请查看[这里](./README_CN.md) 11 | 12 | FWRouter is a High-performance trie-node router, idea from [Vapor/routing](https://github.com/vapor/routing) 13 | 14 | This is not a Server router, but an iOS UI Router, You can build your own iOS app from it. 15 | 16 | ## Example 17 | 18 | use 19 | ``` 20 | git clone https://github.com/JianweiWangs/FWRouter.git 21 | cd FWRouter 22 | make 23 | ``` 24 | to fetch and build source code quickly. 25 | 26 | There are also some script help you develop and PR. 27 | 28 | ```make 29 | # install dependence and open project 30 | make 31 | 32 | # install dependence 33 | make install 34 | 35 | # build test 36 | make test 37 | 38 | # open project 39 | make open 40 | 41 | # quit Xcode 42 | make quit 43 | 44 | ``` 45 | 46 | Before you pull request, make sure test success. 47 | 48 | ## Usage 49 | 50 | ### URL Match 51 | 52 | ```Swift 53 | let router = FWRouterManager.shared.router 54 | // url match 55 | route.match("scheme://host/path") { (target) -> Bool in 56 | let demoVC = DemoViewController() 57 | target.navigation?.pushViewController(demoVC, animated: true) 58 | return true 59 | } 60 | route.route(url: "scheme://host/path") 61 | ``` 62 | 63 | ### Path Parameter 64 | 65 | ```Swift 66 | router.match("scheme://host", "parameter", String.parameter, use: { (target) -> Bool in 67 | print(target.url) // scheme://host/parameter/param0 68 | print(target.pathParamters.next(String.self)) // "param0" 69 | return true 70 | }) 71 | router.route(url: "scheme://host/parameter/param0") 72 | ``` 73 | 74 | ### Parameter 75 | 76 | ```Swift 77 | router.match("scheme://host/path", "/thridParameter", use: { (target) -> Bool in 78 | print(target.parameters["name"] as? String) //"wangjianwei" 79 | return true 80 | }) 81 | router.route( 82 | url: "scheme://host/path/thridParameter", 83 | parameters: ["name": "wangjianwei"] 84 | ) 85 | ``` 86 | 87 | ## Requirements 88 | 89 | This library requires `iOS 8.0+`, `Xcode 9.0+` and `Swift 4.0+`. 90 | 91 | ## Installation 92 | 93 | FWRouter is available through [CocoaPods](https://cocoapods.org). To install 94 | it, simply add the following line to your Podfile: 95 | 96 | ```ruby 97 | pod 'FWRouter' 98 | ``` 99 | 100 | ## Author 101 | 102 | JianweiWangs, wangjianwei.sir@gmail.com 103 | 104 | ## License 105 | 106 | FWRouter is available under the MIT license. See the LICENSE file for more info. 107 | 108 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # FWRouter 2 | 3 | [![CI Status](https://img.shields.io/travis/JianweiWangs/FWRouter.svg?style=flat)](https://travis-ci.org/JianweiWangs/FWRouter) 4 | [![Version](https://img.shields.io/cocoapods/v/FWRouter.svg?style=flat)](https://cocoapods.org/pods/FWRouter) 5 | [![License](https://img.shields.io/cocoapods/l/FWRouter.svg?style=flat)](https://cocoapods.org/pods/FWRouter) 6 | [![Platform](https://img.shields.io/cocoapods/p/FWRouter.svg?style=flat)](https://cocoapods.org/pods/FWRouter) 7 | [![codecov](https://codecov.io/gh/JianweiWangs/FWRouter/branch/master/graph/badge.svg)](https://codecov.io/gh/JianweiWangs/FWRouter) 8 | 9 | 10 | FWRouter 是一个高性能的字典树路由,灵感来自于 [Vapor/routing](https://github.com/vapor/routing)。 11 | 12 | 这并非是一个服务端路由,你可以通过它简单的构建自定义的 iOS 应用。 13 | 14 | ## 示例 15 | 16 | 通过 17 | ``` 18 | git clone https://github.com/JianweiWangs/FWRouter.git 19 | cd FWRouter 20 | make 21 | ``` 22 | 命令,快速开始查看源码及使用样例代码。 23 | 24 | 这里有一些脚本能够辅助开发以及向仓库发起 PR。 25 | 26 | ```make 27 | # 拉取全部依赖并自动打开工程 28 | make 29 | 30 | # 拉取依赖 31 | make install 32 | 33 | # 单元测试 34 | make test 35 | 36 | # 打开工程 37 | make open 38 | 39 | # 退出 Xcode 40 | make quit 41 | 42 | ``` 43 | 44 | 请在 PR 发起前确保单元测试能够通过,并保证单元测试的覆盖率不会降低。 45 | 46 | ## 使用 47 | 48 | ### URL 匹配 49 | 50 | ```Swift 51 | let router = FWRouterManager.shared.router 52 | // url match 53 | route.match("scheme://host/path") { (target) -> Bool in 54 | let demoVC = DemoViewController() 55 | target.navigation?.pushViewController(demoVC, animated: true) 56 | return true 57 | } 58 | route.route(url: "scheme://host/path") 59 | ``` 60 | 61 | ### 路径参数获取 62 | 63 | ```Swift 64 | router.match("scheme://host", "parameter", String.parameter, use: { (target) -> Bool in 65 | print(target.url) // scheme://host/parameter/param0 66 | print(target.pathParamters.next(String.self)) // "param0" 67 | return true 68 | }) 69 | router.route(url: "scheme://host/parameter/param0") 70 | ``` 71 | 72 | ### 传递额外的参数 73 | 74 | ```Swift 75 | router.match("scheme://host/path", "/thridParameter", use: { (target) -> Bool in 76 | print(target.parameters["name"] as? String) //"wangjianwei" 77 | return true 78 | }) 79 | router.route( 80 | url: "scheme://host/path/thridParameter", 81 | parameters: ["name": "wangjianwei"] 82 | ) 83 | ``` 84 | 85 | ## 环境 86 | 87 | 该仓库需要 `iOS 8.0+`, `Xcode 9.0+` 以及 `Swift 4.0+`. 88 | 89 | ## 依赖 90 | 91 | FWRouter 支持 [CocoaPods](https://cocoapods.org). 92 | 在 Podfile 中添加如下代码进行依赖 93 | 94 | ```ruby 95 | pod 'FWRouter' 96 | ``` 97 | 98 | ## 作者 99 | 100 | JianweiWangs, wangjianwei.sir@gmail.com 101 | 102 | ## 证书 103 | 104 | FWRouter 使用 MIT 证书. 查看 LICENSE 文件获取更多详情. 105 | 106 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------