├── .swift-version ├── .travis.yml ├── Demo ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcuserdata │ │ └── maksim.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── Info.plist ├── LNAppDelegate.swift ├── LNApplication.swift ├── LNViewController.swift └── main.swift ├── LICENSE ├── Linker.podspec ├── Linker.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ ├── Linker.xcscheme │ │ └── LinkerTests.xcscheme └── xcuserdata │ └── maksim.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Linker ├── Configs │ ├── Linker.plist │ ├── LinkerTests.plist │ └── logo.png ├── LinkerTests │ ├── LinkerMocks.swift │ ├── LinkerTests.swift │ └── Spectre │ │ ├── Case.swift │ │ ├── Context.swift │ │ ├── Expectation.swift │ │ ├── Failure.swift │ │ ├── Global.swift │ │ ├── GlobalContext.swift │ │ ├── Reporter.swift │ │ └── Reporters.swift └── Sources │ └── Linker │ ├── Linker.swift │ ├── LinkerExtensions.swift │ ├── LinkerModel.swift │ └── LinkerTypes.swift └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - osx 3 | language: swift 4 | osx_image: xcode9.2 5 | -------------------------------------------------------------------------------- /Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Demo/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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B55918732035CE2200AFB31A /* Linker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559186F2035CE2200AFB31A /* Linker.swift */; }; 11 | B55918742035CE2200AFB31A /* LinkerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918702035CE2200AFB31A /* LinkerModel.swift */; }; 12 | B55918752035CE2200AFB31A /* LinkerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918712035CE2200AFB31A /* LinkerExtensions.swift */; }; 13 | B55918762035CE2200AFB31A /* LinkerTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918722035CE2200AFB31A /* LinkerTypes.swift */; }; 14 | B5A43E65203489B9004D27CA /* LNViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A43E4A203489B9004D27CA /* LNViewController.swift */; }; 15 | B5A43E66203489B9004D27CA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5A43E4B203489B9004D27CA /* Assets.xcassets */; }; 16 | B5A43E67203489B9004D27CA /* LNAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A43E4C203489B9004D27CA /* LNAppDelegate.swift */; }; 17 | B5A43E68203489B9004D27CA /* LNApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A43E4D203489B9004D27CA /* LNApplication.swift */; }; 18 | B5A43E69203489B9004D27CA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5A43E4E203489B9004D27CA /* LaunchScreen.storyboard */; }; 19 | B5A43E6A203489B9004D27CA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5A43E50203489B9004D27CA /* Main.storyboard */; }; 20 | B5A43E6B203489B9004D27CA /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A43E52203489B9004D27CA /* main.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | B559186F2035CE2200AFB31A /* Linker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Linker.swift; sourceTree = ""; }; 25 | B55918702035CE2200AFB31A /* LinkerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerModel.swift; sourceTree = ""; }; 26 | B55918712035CE2200AFB31A /* LinkerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerExtensions.swift; sourceTree = ""; }; 27 | B55918722035CE2200AFB31A /* LinkerTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerTypes.swift; sourceTree = ""; }; 28 | B58BF1872032133C006921B2 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | B5A43E4A203489B9004D27CA /* LNViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LNViewController.swift; sourceTree = ""; }; 30 | B5A43E4B203489B9004D27CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | B5A43E4C203489B9004D27CA /* LNAppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LNAppDelegate.swift; sourceTree = ""; }; 32 | B5A43E4D203489B9004D27CA /* LNApplication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LNApplication.swift; sourceTree = ""; }; 33 | B5A43E4F203489B9004D27CA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 34 | B5A43E51203489B9004D27CA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 35 | B5A43E52203489B9004D27CA /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 36 | B5A43E53203489B9004D27CA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | B58BF1842032133C006921B2 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | B559186D2035CE2200AFB31A /* Sources */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | B559186E2035CE2200AFB31A /* Linker */, 54 | ); 55 | name = Sources; 56 | path = ../Linker/Sources; 57 | sourceTree = ""; 58 | }; 59 | B559186E2035CE2200AFB31A /* Linker */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | B559186F2035CE2200AFB31A /* Linker.swift */, 63 | B55918702035CE2200AFB31A /* LinkerModel.swift */, 64 | B55918712035CE2200AFB31A /* LinkerExtensions.swift */, 65 | B55918722035CE2200AFB31A /* LinkerTypes.swift */, 66 | ); 67 | path = Linker; 68 | sourceTree = ""; 69 | }; 70 | B58BF17E2032133C006921B2 = { 71 | isa = PBXGroup; 72 | children = ( 73 | B559186D2035CE2200AFB31A /* Sources */, 74 | B5A43E46203489B9004D27CA /* Demo */, 75 | B58BF1882032133C006921B2 /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | B58BF1882032133C006921B2 /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | B58BF1872032133C006921B2 /* Demo.app */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | B5A43E46203489B9004D27CA /* Demo */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | B5A43E4B203489B9004D27CA /* Assets.xcassets */, 91 | B5A43E4A203489B9004D27CA /* LNViewController.swift */, 92 | B5A43E4C203489B9004D27CA /* LNAppDelegate.swift */, 93 | B5A43E4D203489B9004D27CA /* LNApplication.swift */, 94 | B5A43E4E203489B9004D27CA /* LaunchScreen.storyboard */, 95 | B5A43E50203489B9004D27CA /* Main.storyboard */, 96 | B5A43E52203489B9004D27CA /* main.swift */, 97 | B5A43E53203489B9004D27CA /* Info.plist */, 98 | ); 99 | name = Demo; 100 | sourceTree = ""; 101 | }; 102 | /* End PBXGroup section */ 103 | 104 | /* Begin PBXNativeTarget section */ 105 | B58BF1862032133C006921B2 /* Demo */ = { 106 | isa = PBXNativeTarget; 107 | buildConfigurationList = B58BF1A42032133C006921B2 /* Build configuration list for PBXNativeTarget "Demo" */; 108 | buildPhases = ( 109 | B58BF1832032133C006921B2 /* Sources */, 110 | B58BF1842032133C006921B2 /* Frameworks */, 111 | B58BF1852032133C006921B2 /* Resources */, 112 | ); 113 | buildRules = ( 114 | ); 115 | dependencies = ( 116 | ); 117 | name = Demo; 118 | productName = Linker; 119 | productReference = B58BF1872032133C006921B2 /* Demo.app */; 120 | productType = "com.apple.product-type.application"; 121 | }; 122 | /* End PBXNativeTarget section */ 123 | 124 | /* Begin PBXProject section */ 125 | B58BF17F2032133C006921B2 /* Project object */ = { 126 | isa = PBXProject; 127 | attributes = { 128 | LastSwiftUpdateCheck = 0920; 129 | LastUpgradeCheck = 0920; 130 | ORGANIZATIONNAME = "Maksim Kurpa"; 131 | TargetAttributes = { 132 | B58BF1862032133C006921B2 = { 133 | CreatedOnToolsVersion = 9.2; 134 | LastSwiftMigration = 0920; 135 | ProvisioningStyle = Automatic; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = B58BF1822032133C006921B2 /* Build configuration list for PBXProject "Demo" */; 140 | compatibilityVersion = "Xcode 8.0"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = B58BF17E2032133C006921B2; 148 | productRefGroup = B58BF1882032133C006921B2 /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | B58BF1862032133C006921B2 /* Demo */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | B58BF1852032133C006921B2 /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | B5A43E69203489B9004D27CA /* LaunchScreen.storyboard in Resources */, 163 | B5A43E66203489B9004D27CA /* Assets.xcassets in Resources */, 164 | B5A43E6A203489B9004D27CA /* Main.storyboard in Resources */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXResourcesBuildPhase section */ 169 | 170 | /* Begin PBXSourcesBuildPhase section */ 171 | B58BF1832032133C006921B2 /* Sources */ = { 172 | isa = PBXSourcesBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | B5A43E68203489B9004D27CA /* LNApplication.swift in Sources */, 176 | B5A43E67203489B9004D27CA /* LNAppDelegate.swift in Sources */, 177 | B55918732035CE2200AFB31A /* Linker.swift in Sources */, 178 | B5A43E65203489B9004D27CA /* LNViewController.swift in Sources */, 179 | B55918752035CE2200AFB31A /* LinkerExtensions.swift in Sources */, 180 | B55918742035CE2200AFB31A /* LinkerModel.swift in Sources */, 181 | B55918762035CE2200AFB31A /* LinkerTypes.swift in Sources */, 182 | B5A43E6B203489B9004D27CA /* main.swift in Sources */, 183 | ); 184 | runOnlyForDeploymentPostprocessing = 0; 185 | }; 186 | /* End PBXSourcesBuildPhase section */ 187 | 188 | /* Begin PBXVariantGroup section */ 189 | B5A43E4E203489B9004D27CA /* LaunchScreen.storyboard */ = { 190 | isa = PBXVariantGroup; 191 | children = ( 192 | B5A43E4F203489B9004D27CA /* Base */, 193 | ); 194 | name = LaunchScreen.storyboard; 195 | sourceTree = ""; 196 | }; 197 | B5A43E50203489B9004D27CA /* Main.storyboard */ = { 198 | isa = PBXVariantGroup; 199 | children = ( 200 | B5A43E51203489B9004D27CA /* Base */, 201 | ); 202 | name = Main.storyboard; 203 | sourceTree = ""; 204 | }; 205 | /* End PBXVariantGroup section */ 206 | 207 | /* Begin XCBuildConfiguration section */ 208 | B58BF1A22032133C006921B2 /* Debug */ = { 209 | isa = XCBuildConfiguration; 210 | buildSettings = { 211 | ALWAYS_SEARCH_USER_PATHS = NO; 212 | CLANG_ANALYZER_NONNULL = YES; 213 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 214 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 215 | CLANG_CXX_LIBRARY = "libc++"; 216 | CLANG_ENABLE_MODULES = YES; 217 | CLANG_ENABLE_OBJC_ARC = YES; 218 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 219 | CLANG_WARN_BOOL_CONVERSION = YES; 220 | CLANG_WARN_COMMA = YES; 221 | CLANG_WARN_CONSTANT_CONVERSION = YES; 222 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 223 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 224 | CLANG_WARN_EMPTY_BODY = YES; 225 | CLANG_WARN_ENUM_CONVERSION = YES; 226 | CLANG_WARN_INFINITE_RECURSION = YES; 227 | CLANG_WARN_INT_CONVERSION = YES; 228 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 229 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 230 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 231 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 232 | CLANG_WARN_STRICT_PROTOTYPES = YES; 233 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 234 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 235 | CLANG_WARN_UNREACHABLE_CODE = YES; 236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 237 | CODE_SIGN_IDENTITY = "iPhone Developer"; 238 | COPY_PHASE_STRIP = NO; 239 | DEBUG_INFORMATION_FORMAT = dwarf; 240 | ENABLE_STRICT_OBJC_MSGSEND = YES; 241 | ENABLE_TESTABILITY = YES; 242 | GCC_C_LANGUAGE_STANDARD = gnu11; 243 | GCC_DYNAMIC_NO_PIC = NO; 244 | GCC_NO_COMMON_BLOCKS = YES; 245 | GCC_OPTIMIZATION_LEVEL = 0; 246 | GCC_PREPROCESSOR_DEFINITIONS = ( 247 | "DEBUG=1", 248 | "$(inherited)", 249 | ); 250 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 251 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 252 | GCC_WARN_UNDECLARED_SELECTOR = YES; 253 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 254 | GCC_WARN_UNUSED_FUNCTION = YES; 255 | GCC_WARN_UNUSED_VARIABLE = YES; 256 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 257 | MTL_ENABLE_DEBUG_INFO = YES; 258 | ONLY_ACTIVE_ARCH = YES; 259 | SDKROOT = iphoneos; 260 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 261 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 262 | }; 263 | name = Debug; 264 | }; 265 | B58BF1A32032133C006921B2 /* Release */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | ALWAYS_SEARCH_USER_PATHS = NO; 269 | CLANG_ANALYZER_NONNULL = YES; 270 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 271 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 272 | CLANG_CXX_LIBRARY = "libc++"; 273 | CLANG_ENABLE_MODULES = YES; 274 | CLANG_ENABLE_OBJC_ARC = YES; 275 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 276 | CLANG_WARN_BOOL_CONVERSION = YES; 277 | CLANG_WARN_COMMA = YES; 278 | CLANG_WARN_CONSTANT_CONVERSION = YES; 279 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 280 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 281 | CLANG_WARN_EMPTY_BODY = YES; 282 | CLANG_WARN_ENUM_CONVERSION = YES; 283 | CLANG_WARN_INFINITE_RECURSION = YES; 284 | CLANG_WARN_INT_CONVERSION = YES; 285 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 287 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 288 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 289 | CLANG_WARN_STRICT_PROTOTYPES = YES; 290 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 291 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 292 | CLANG_WARN_UNREACHABLE_CODE = YES; 293 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 294 | CODE_SIGN_IDENTITY = "iPhone Developer"; 295 | COPY_PHASE_STRIP = NO; 296 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 297 | ENABLE_NS_ASSERTIONS = NO; 298 | ENABLE_STRICT_OBJC_MSGSEND = YES; 299 | GCC_C_LANGUAGE_STANDARD = gnu11; 300 | GCC_NO_COMMON_BLOCKS = YES; 301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 302 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 303 | GCC_WARN_UNDECLARED_SELECTOR = YES; 304 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 305 | GCC_WARN_UNUSED_FUNCTION = YES; 306 | GCC_WARN_UNUSED_VARIABLE = YES; 307 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 308 | MTL_ENABLE_DEBUG_INFO = NO; 309 | SDKROOT = iphoneos; 310 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 311 | VALIDATE_PRODUCT = YES; 312 | }; 313 | name = Release; 314 | }; 315 | B58BF1A52032133C006921B2 /* Debug */ = { 316 | isa = XCBuildConfiguration; 317 | buildSettings = { 318 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 319 | CLANG_ENABLE_MODULES = YES; 320 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 321 | CODE_SIGN_STYLE = Automatic; 322 | DEVELOPMENT_TEAM = NX84NCXAJ3; 323 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; 324 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 325 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 326 | PRODUCT_BUNDLE_IDENTIFIER = com.maksimkurpa.Linker; 327 | PRODUCT_NAME = "$(TARGET_NAME)"; 328 | PROVISIONING_PROFILE_SPECIFIER = ""; 329 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 330 | SWIFT_VERSION = 4.0; 331 | TARGETED_DEVICE_FAMILY = "1,2"; 332 | }; 333 | name = Debug; 334 | }; 335 | B58BF1A62032133C006921B2 /* Release */ = { 336 | isa = XCBuildConfiguration; 337 | buildSettings = { 338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 339 | CLANG_ENABLE_MODULES = YES; 340 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 341 | CODE_SIGN_STYLE = Automatic; 342 | DEVELOPMENT_TEAM = NX84NCXAJ3; 343 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; 344 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 345 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 346 | PRODUCT_BUNDLE_IDENTIFIER = com.maksimkurpa.Linker; 347 | PRODUCT_NAME = "$(TARGET_NAME)"; 348 | PROVISIONING_PROFILE_SPECIFIER = ""; 349 | SWIFT_VERSION = 4.0; 350 | TARGETED_DEVICE_FAMILY = "1,2"; 351 | }; 352 | name = Release; 353 | }; 354 | /* End XCBuildConfiguration section */ 355 | 356 | /* Begin XCConfigurationList section */ 357 | B58BF1822032133C006921B2 /* Build configuration list for PBXProject "Demo" */ = { 358 | isa = XCConfigurationList; 359 | buildConfigurations = ( 360 | B58BF1A22032133C006921B2 /* Debug */, 361 | B58BF1A32032133C006921B2 /* Release */, 362 | ); 363 | defaultConfigurationIsVisible = 0; 364 | defaultConfigurationName = Release; 365 | }; 366 | B58BF1A42032133C006921B2 /* Build configuration list for PBXNativeTarget "Demo" */ = { 367 | isa = XCConfigurationList; 368 | buildConfigurations = ( 369 | B58BF1A52032133C006921B2 /* Debug */, 370 | B58BF1A62032133C006921B2 /* Release */, 371 | ); 372 | defaultConfigurationIsVisible = 0; 373 | defaultConfigurationName = Release; 374 | }; 375 | /* End XCConfigurationList section */ 376 | }; 377 | rootObject = B58BF17F2032133C006921B2 /* Project object */; 378 | } 379 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/xcuserdata/maksim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/xcuserdata/maksim.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Demo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | Linker.xcscheme 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Demo/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CFBundleURLTypes 45 | 46 | 47 | CFBundleTypeRole 48 | Editor 49 | CFBundleURLSchemes 50 | 51 | linker 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Demo/LNAppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LNAppDelegate.swift 3 | // Linker 4 | // 5 | // Created by Maksim Kurpa on 1/16/18. 6 | // Copyright © 2018 Maksim Kurpa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LNAppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | 18 | let launchURL = URL(string: "linker://launchURL")! 19 | Linker.handle(launchURL, closure: { url in 20 | print("Your URL has been handle!") 21 | }) 22 | 23 | return true 24 | } 25 | 26 | func applicationWillResignActive(_ application: UIApplication) { 27 | } 28 | 29 | func applicationDidEnterBackground(_ application: UIApplication) { 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | } 37 | 38 | func applicationWillTerminate(_ application: UIApplication) { 39 | } 40 | 41 | func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { 42 | print("original IMP") 43 | return true 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Demo/LNApplication.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LNApplication.swift 3 | // Linker 4 | // 5 | // Created by Maksim Kurpa on 1/26/18. 6 | // Copyright © 2018 Maksim Kurpa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LNApplication: UIApplication { 12 | 13 | override func openURL(_ url: URL) -> Bool { 14 | print("openURL original method") 15 | return true 16 | } 17 | 18 | override func open(_ url: URL, options: [String : Any] = [:], completionHandler completion: ((Bool) -> Void)? = nil) { 19 | print("openURL original method") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Demo/LNViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LNViewController.swift 3 | // Linker 4 | // 5 | // Created by Maksim Kurpa on 1/16/18. 6 | // Copyright © 2018 Maksim Kurpa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LNViewController: UIViewController { 12 | 13 | let sourceURL = URL(string: "linker://viewcontroller?title=ExampleAlert&description=ExampleDescriptionAlert")! 14 | 15 | @IBAction func action(_ sender: Any) { 16 | UIApplication.shared.open(sourceURL, options: [:], completionHandler: nil) 17 | } 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | Linker.handle(sourceURL) { url in 22 | 23 | guard let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems! else { 24 | return } 25 | var title : String? = nil 26 | var description: String? = nil 27 | 28 | for item in queryItems { 29 | if item.name == "title" { 30 | title = item.value 31 | } 32 | if item.name == "description" { 33 | description = item.value; 34 | } 35 | } 36 | 37 | if let name = title, let message = description { 38 | let alertVC = UIAlertController(title: name, message: message, preferredStyle: UIAlertControllerStyle.alert) 39 | alertVC.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: {action in 40 | alertVC.dismiss(animated: true, completion: nil) 41 | })) 42 | self.present(alertVC, animated: false, completion: nil) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Demo/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // Linker 4 | // 5 | // Created by Maksim Kurpa on 1/26/18. 6 | // Copyright © 2018 Maksim Kurpa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | UIApplicationMain( 13 | CommandLine.argc, 14 | UnsafeMutableRawPointer(CommandLine.unsafeArgv) 15 | .bindMemory( 16 | to: UnsafeMutablePointer.self, 17 | capacity: Int(CommandLine.argc)), 18 | NSStringFromClass(LNApplication.self), 19 | NSStringFromClass(LNAppDelegate.self) 20 | ) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Maksim Kurpa 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 | -------------------------------------------------------------------------------- /Linker.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = 'Linker' 4 | s.version = '1.0.0' 5 | s.summary = 'Lightweight way to handle internal and external URLs in Swift for iOS.' 6 | s.ios.deployment_target = '8.0' 7 | s.license = { :type => 'MIT', :file => 'LICENSE' } 8 | s.author = { 'Maksim Kurpa' => 'maksim.kurpa@gmail.com' } 9 | s.description = 'Linker is the easiest way to handle internal and external URLs in your project!' 10 | s.homepage = 'https://github.com/MaksimKurpa/Linker' 11 | s.source = { :git => 'https://github.com/MaksimKurpa/Linker.git', :branch => 'master',:tag => s.version.to_s } 12 | s.social_media_url = 'https://www.facebook.com/maksim.kurpa' 13 | s.source_files = 'Linker/Sources/Linker/*.{swift}' 14 | s.requires_arc = true 15 | end 16 | -------------------------------------------------------------------------------- /Linker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B559188F2035D00B00AFB31A /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = B559187A2035D00B00AFB31A /* logo.png */; }; 11 | B55918902035D00B00AFB31A /* Linker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559187D2035D00B00AFB31A /* Linker.swift */; }; 12 | B55918912035D00C00AFB31A /* LinkerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559187E2035D00B00AFB31A /* LinkerModel.swift */; }; 13 | B55918922035D00C00AFB31A /* LinkerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559187F2035D00B00AFB31A /* LinkerExtensions.swift */; }; 14 | B55918932035D00C00AFB31A /* LinkerTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918802035D00B00AFB31A /* LinkerTypes.swift */; }; 15 | B559189F2035E27900AFB31A /* LinkerMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559188C2035D00B00AFB31A /* LinkerMocks.swift */; }; 16 | B55918A02035E28300AFB31A /* LinkerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918822035D00B00AFB31A /* LinkerTests.swift */; }; 17 | B55918A12035E33700AFB31A /* Expectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918842035D00B00AFB31A /* Expectation.swift */; }; 18 | B55918A22035E33700AFB31A /* Failure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918852035D00B00AFB31A /* Failure.swift */; }; 19 | B55918A32035E33700AFB31A /* Global.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918862035D00B00AFB31A /* Global.swift */; }; 20 | B55918A42035E33700AFB31A /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918872035D00B00AFB31A /* Context.swift */; }; 21 | B55918A52035E33700AFB31A /* Case.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918882035D00B00AFB31A /* Case.swift */; }; 22 | B55918A62035E33700AFB31A /* GlobalContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918892035D00B00AFB31A /* GlobalContext.swift */; }; 23 | B55918A72035E33700AFB31A /* Reporters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559188A2035D00B00AFB31A /* Reporters.swift */; }; 24 | B55918A82035E33700AFB31A /* Reporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559188B2035D00B00AFB31A /* Reporter.swift */; }; 25 | B55918A92035E35600AFB31A /* LinkerTests.plist in Resources */ = {isa = PBXBuildFile; fileRef = B55918782035D00B00AFB31A /* LinkerTests.plist */; }; 26 | B55918AA2035E35B00AFB31A /* Linker.plist in Resources */ = {isa = PBXBuildFile; fileRef = B55918792035D00B00AFB31A /* Linker.plist */; }; 27 | B55918AB2035E5AF00AFB31A /* LinkerTests.plist in Resources */ = {isa = PBXBuildFile; fileRef = B55918782035D00B00AFB31A /* LinkerTests.plist */; }; 28 | B5A43E8520358F62004D27CA /* Linker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A43E7B20358F62004D27CA /* Linker.framework */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXContainerItemProxy section */ 32 | B5A43E8620358F62004D27CA /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = B5A43E7220358F62004D27CA /* Project object */; 35 | proxyType = 1; 36 | remoteGlobalIDString = B5A43E7A20358F62004D27CA; 37 | remoteInfo = Linker; 38 | }; 39 | /* End PBXContainerItemProxy section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | B55918782035D00B00AFB31A /* LinkerTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = LinkerTests.plist; sourceTree = ""; }; 43 | B55918792035D00B00AFB31A /* Linker.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Linker.plist; sourceTree = ""; }; 44 | B559187A2035D00B00AFB31A /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = ""; }; 45 | B559187D2035D00B00AFB31A /* Linker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Linker.swift; sourceTree = ""; }; 46 | B559187E2035D00B00AFB31A /* LinkerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerModel.swift; sourceTree = ""; }; 47 | B559187F2035D00B00AFB31A /* LinkerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerExtensions.swift; sourceTree = ""; }; 48 | B55918802035D00B00AFB31A /* LinkerTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerTypes.swift; sourceTree = ""; }; 49 | B55918822035D00B00AFB31A /* LinkerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerTests.swift; sourceTree = ""; }; 50 | B55918842035D00B00AFB31A /* Expectation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expectation.swift; sourceTree = ""; }; 51 | B55918852035D00B00AFB31A /* Failure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Failure.swift; sourceTree = ""; }; 52 | B55918862035D00B00AFB31A /* Global.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Global.swift; sourceTree = ""; }; 53 | B55918872035D00B00AFB31A /* Context.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = ""; }; 54 | B55918882035D00B00AFB31A /* Case.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Case.swift; sourceTree = ""; }; 55 | B55918892035D00B00AFB31A /* GlobalContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalContext.swift; sourceTree = ""; }; 56 | B559188A2035D00B00AFB31A /* Reporters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reporters.swift; sourceTree = ""; }; 57 | B559188B2035D00B00AFB31A /* Reporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reporter.swift; sourceTree = ""; }; 58 | B559188C2035D00B00AFB31A /* LinkerMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerMocks.swift; sourceTree = ""; }; 59 | B5A43E7B20358F62004D27CA /* Linker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Linker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | B5A43E8420358F62004D27CA /* LinkerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LinkerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | /* End PBXFileReference section */ 62 | 63 | /* Begin PBXFrameworksBuildPhase section */ 64 | B5A43E7720358F62004D27CA /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | B5A43E8120358F62004D27CA /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | B5A43E8520358F62004D27CA /* Linker.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | /* End PBXFrameworksBuildPhase section */ 80 | 81 | /* Begin PBXGroup section */ 82 | B55918772035D00B00AFB31A /* Configs */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | B55918782035D00B00AFB31A /* LinkerTests.plist */, 86 | B55918792035D00B00AFB31A /* Linker.plist */, 87 | B559187A2035D00B00AFB31A /* logo.png */, 88 | ); 89 | name = Configs; 90 | path = Linker/Configs; 91 | sourceTree = ""; 92 | }; 93 | B559187B2035D00B00AFB31A /* Sources */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | B559187C2035D00B00AFB31A /* Linker */, 97 | ); 98 | name = Sources; 99 | path = Linker/Sources; 100 | sourceTree = ""; 101 | }; 102 | B559187C2035D00B00AFB31A /* Linker */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | B559187D2035D00B00AFB31A /* Linker.swift */, 106 | B559187E2035D00B00AFB31A /* LinkerModel.swift */, 107 | B559187F2035D00B00AFB31A /* LinkerExtensions.swift */, 108 | B55918802035D00B00AFB31A /* LinkerTypes.swift */, 109 | ); 110 | path = Linker; 111 | sourceTree = ""; 112 | }; 113 | B55918812035D00B00AFB31A /* LinkerTests */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | B55918832035D00B00AFB31A /* Spectre */, 117 | B55918822035D00B00AFB31A /* LinkerTests.swift */, 118 | B559188C2035D00B00AFB31A /* LinkerMocks.swift */, 119 | ); 120 | name = LinkerTests; 121 | path = Linker/LinkerTests; 122 | sourceTree = ""; 123 | }; 124 | B55918832035D00B00AFB31A /* Spectre */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | B55918842035D00B00AFB31A /* Expectation.swift */, 128 | B55918852035D00B00AFB31A /* Failure.swift */, 129 | B55918862035D00B00AFB31A /* Global.swift */, 130 | B55918872035D00B00AFB31A /* Context.swift */, 131 | B55918882035D00B00AFB31A /* Case.swift */, 132 | B55918892035D00B00AFB31A /* GlobalContext.swift */, 133 | B559188A2035D00B00AFB31A /* Reporters.swift */, 134 | B559188B2035D00B00AFB31A /* Reporter.swift */, 135 | ); 136 | path = Spectre; 137 | sourceTree = ""; 138 | }; 139 | B5A43E7120358F62004D27CA = { 140 | isa = PBXGroup; 141 | children = ( 142 | B55918772035D00B00AFB31A /* Configs */, 143 | B55918812035D00B00AFB31A /* LinkerTests */, 144 | B559187B2035D00B00AFB31A /* Sources */, 145 | B5A43E7C20358F62004D27CA /* Products */, 146 | ); 147 | sourceTree = ""; 148 | }; 149 | B5A43E7C20358F62004D27CA /* Products */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | B5A43E7B20358F62004D27CA /* Linker.framework */, 153 | B5A43E8420358F62004D27CA /* LinkerTests.xctest */, 154 | ); 155 | name = Products; 156 | sourceTree = ""; 157 | }; 158 | /* End PBXGroup section */ 159 | 160 | /* Begin PBXHeadersBuildPhase section */ 161 | B5A43E7820358F62004D27CA /* Headers */ = { 162 | isa = PBXHeadersBuildPhase; 163 | buildActionMask = 2147483647; 164 | files = ( 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXHeadersBuildPhase section */ 169 | 170 | /* Begin PBXNativeTarget section */ 171 | B5A43E7A20358F62004D27CA /* Linker */ = { 172 | isa = PBXNativeTarget; 173 | buildConfigurationList = B5A43E8F20358F62004D27CA /* Build configuration list for PBXNativeTarget "Linker" */; 174 | buildPhases = ( 175 | B5A43E7620358F62004D27CA /* Sources */, 176 | B5A43E7720358F62004D27CA /* Frameworks */, 177 | B5A43E7820358F62004D27CA /* Headers */, 178 | B5A43E7920358F62004D27CA /* Resources */, 179 | ); 180 | buildRules = ( 181 | ); 182 | dependencies = ( 183 | ); 184 | name = Linker; 185 | productName = Linker; 186 | productReference = B5A43E7B20358F62004D27CA /* Linker.framework */; 187 | productType = "com.apple.product-type.framework"; 188 | }; 189 | B5A43E8320358F62004D27CA /* LinkerTests */ = { 190 | isa = PBXNativeTarget; 191 | buildConfigurationList = B5A43E9220358F62004D27CA /* Build configuration list for PBXNativeTarget "LinkerTests" */; 192 | buildPhases = ( 193 | B5A43E8020358F62004D27CA /* Sources */, 194 | B5A43E8120358F62004D27CA /* Frameworks */, 195 | B5A43E8220358F62004D27CA /* Resources */, 196 | ); 197 | buildRules = ( 198 | ); 199 | dependencies = ( 200 | B5A43E8720358F62004D27CA /* PBXTargetDependency */, 201 | ); 202 | name = LinkerTests; 203 | productName = LinkerTests; 204 | productReference = B5A43E8420358F62004D27CA /* LinkerTests.xctest */; 205 | productType = "com.apple.product-type.bundle.unit-test"; 206 | }; 207 | /* End PBXNativeTarget section */ 208 | 209 | /* Begin PBXProject section */ 210 | B5A43E7220358F62004D27CA /* Project object */ = { 211 | isa = PBXProject; 212 | attributes = { 213 | LastSwiftUpdateCheck = 0920; 214 | LastUpgradeCheck = 0920; 215 | ORGANIZATIONNAME = "Maksim Kurpa"; 216 | TargetAttributes = { 217 | B5A43E7A20358F62004D27CA = { 218 | CreatedOnToolsVersion = 9.2; 219 | ProvisioningStyle = Manual; 220 | }; 221 | B5A43E8320358F62004D27CA = { 222 | CreatedOnToolsVersion = 9.2; 223 | LastSwiftMigration = 0920; 224 | ProvisioningStyle = Automatic; 225 | }; 226 | }; 227 | }; 228 | buildConfigurationList = B5A43E7520358F62004D27CA /* Build configuration list for PBXProject "Linker" */; 229 | compatibilityVersion = "Xcode 8.0"; 230 | developmentRegion = en; 231 | hasScannedForEncodings = 0; 232 | knownRegions = ( 233 | en, 234 | ); 235 | mainGroup = B5A43E7120358F62004D27CA; 236 | productRefGroup = B5A43E7C20358F62004D27CA /* Products */; 237 | projectDirPath = ""; 238 | projectRoot = ""; 239 | targets = ( 240 | B5A43E7A20358F62004D27CA /* Linker */, 241 | B5A43E8320358F62004D27CA /* LinkerTests */, 242 | ); 243 | }; 244 | /* End PBXProject section */ 245 | 246 | /* Begin PBXResourcesBuildPhase section */ 247 | B5A43E7920358F62004D27CA /* Resources */ = { 248 | isa = PBXResourcesBuildPhase; 249 | buildActionMask = 2147483647; 250 | files = ( 251 | B559188F2035D00B00AFB31A /* logo.png in Resources */, 252 | B55918AB2035E5AF00AFB31A /* LinkerTests.plist in Resources */, 253 | B55918AA2035E35B00AFB31A /* Linker.plist in Resources */, 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | B5A43E8220358F62004D27CA /* Resources */ = { 258 | isa = PBXResourcesBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | B55918A92035E35600AFB31A /* LinkerTests.plist in Resources */, 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | }; 265 | /* End PBXResourcesBuildPhase section */ 266 | 267 | /* Begin PBXSourcesBuildPhase section */ 268 | B5A43E7620358F62004D27CA /* Sources */ = { 269 | isa = PBXSourcesBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | B55918912035D00C00AFB31A /* LinkerModel.swift in Sources */, 273 | B55918922035D00C00AFB31A /* LinkerExtensions.swift in Sources */, 274 | B55918932035D00C00AFB31A /* LinkerTypes.swift in Sources */, 275 | B55918902035D00B00AFB31A /* Linker.swift in Sources */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | B5A43E8020358F62004D27CA /* Sources */ = { 280 | isa = PBXSourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | B55918A22035E33700AFB31A /* Failure.swift in Sources */, 284 | B55918A02035E28300AFB31A /* LinkerTests.swift in Sources */, 285 | B55918A12035E33700AFB31A /* Expectation.swift in Sources */, 286 | B55918A42035E33700AFB31A /* Context.swift in Sources */, 287 | B55918A62035E33700AFB31A /* GlobalContext.swift in Sources */, 288 | B55918A32035E33700AFB31A /* Global.swift in Sources */, 289 | B55918A82035E33700AFB31A /* Reporter.swift in Sources */, 290 | B55918A52035E33700AFB31A /* Case.swift in Sources */, 291 | B559189F2035E27900AFB31A /* LinkerMocks.swift in Sources */, 292 | B55918A72035E33700AFB31A /* Reporters.swift in Sources */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | /* End PBXSourcesBuildPhase section */ 297 | 298 | /* Begin PBXTargetDependency section */ 299 | B5A43E8720358F62004D27CA /* PBXTargetDependency */ = { 300 | isa = PBXTargetDependency; 301 | target = B5A43E7A20358F62004D27CA /* Linker */; 302 | targetProxy = B5A43E8620358F62004D27CA /* PBXContainerItemProxy */; 303 | }; 304 | /* End PBXTargetDependency section */ 305 | 306 | /* Begin XCBuildConfiguration section */ 307 | B5A43E8D20358F62004D27CA /* Debug */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ALWAYS_SEARCH_USER_PATHS = NO; 311 | CLANG_ANALYZER_NONNULL = YES; 312 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 313 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 314 | CLANG_CXX_LIBRARY = "libc++"; 315 | CLANG_ENABLE_MODULES = YES; 316 | CLANG_ENABLE_OBJC_ARC = YES; 317 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 318 | CLANG_WARN_BOOL_CONVERSION = YES; 319 | CLANG_WARN_COMMA = YES; 320 | CLANG_WARN_CONSTANT_CONVERSION = YES; 321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 322 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 323 | CLANG_WARN_EMPTY_BODY = YES; 324 | CLANG_WARN_ENUM_CONVERSION = YES; 325 | CLANG_WARN_INFINITE_RECURSION = YES; 326 | CLANG_WARN_INT_CONVERSION = YES; 327 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 331 | CLANG_WARN_STRICT_PROTOTYPES = YES; 332 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 333 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 334 | CLANG_WARN_UNREACHABLE_CODE = YES; 335 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 336 | CODE_SIGN_IDENTITY = "iPhone Developer"; 337 | COPY_PHASE_STRIP = NO; 338 | CURRENT_PROJECT_VERSION = 1; 339 | DEBUG_INFORMATION_FORMAT = dwarf; 340 | ENABLE_STRICT_OBJC_MSGSEND = YES; 341 | ENABLE_TESTABILITY = YES; 342 | GCC_C_LANGUAGE_STANDARD = gnu11; 343 | GCC_DYNAMIC_NO_PIC = NO; 344 | GCC_NO_COMMON_BLOCKS = YES; 345 | GCC_OPTIMIZATION_LEVEL = 0; 346 | GCC_PREPROCESSOR_DEFINITIONS = ( 347 | "DEBUG=1", 348 | "$(inherited)", 349 | ); 350 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 351 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 352 | GCC_WARN_UNDECLARED_SELECTOR = YES; 353 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 354 | GCC_WARN_UNUSED_FUNCTION = YES; 355 | GCC_WARN_UNUSED_VARIABLE = YES; 356 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 357 | MTL_ENABLE_DEBUG_INFO = YES; 358 | ONLY_ACTIVE_ARCH = YES; 359 | SDKROOT = iphoneos; 360 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 361 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 362 | VERSIONING_SYSTEM = "apple-generic"; 363 | VERSION_INFO_PREFIX = ""; 364 | }; 365 | name = Debug; 366 | }; 367 | B5A43E8E20358F62004D27CA /* Release */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | ALWAYS_SEARCH_USER_PATHS = NO; 371 | CLANG_ANALYZER_NONNULL = YES; 372 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 373 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 374 | CLANG_CXX_LIBRARY = "libc++"; 375 | CLANG_ENABLE_MODULES = YES; 376 | CLANG_ENABLE_OBJC_ARC = YES; 377 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 378 | CLANG_WARN_BOOL_CONVERSION = YES; 379 | CLANG_WARN_COMMA = YES; 380 | CLANG_WARN_CONSTANT_CONVERSION = YES; 381 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 382 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 383 | CLANG_WARN_EMPTY_BODY = YES; 384 | CLANG_WARN_ENUM_CONVERSION = YES; 385 | CLANG_WARN_INFINITE_RECURSION = YES; 386 | CLANG_WARN_INT_CONVERSION = YES; 387 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 388 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 389 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 390 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 391 | CLANG_WARN_STRICT_PROTOTYPES = YES; 392 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 393 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 394 | CLANG_WARN_UNREACHABLE_CODE = YES; 395 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 396 | CODE_SIGN_IDENTITY = "iPhone Developer"; 397 | COPY_PHASE_STRIP = NO; 398 | CURRENT_PROJECT_VERSION = 1; 399 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 400 | ENABLE_NS_ASSERTIONS = NO; 401 | ENABLE_STRICT_OBJC_MSGSEND = YES; 402 | GCC_C_LANGUAGE_STANDARD = gnu11; 403 | GCC_NO_COMMON_BLOCKS = YES; 404 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 405 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 406 | GCC_WARN_UNDECLARED_SELECTOR = YES; 407 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 408 | GCC_WARN_UNUSED_FUNCTION = YES; 409 | GCC_WARN_UNUSED_VARIABLE = YES; 410 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 411 | MTL_ENABLE_DEBUG_INFO = NO; 412 | SDKROOT = iphoneos; 413 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 414 | VALIDATE_PRODUCT = YES; 415 | VERSIONING_SYSTEM = "apple-generic"; 416 | VERSION_INFO_PREFIX = ""; 417 | }; 418 | name = Release; 419 | }; 420 | B5A43E9020358F62004D27CA /* Debug */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | APPLICATION_EXTENSION_API_ONLY = NO; 424 | CODE_SIGN_IDENTITY = ""; 425 | CODE_SIGN_STYLE = Manual; 426 | DEFINES_MODULE = YES; 427 | DEVELOPMENT_TEAM = ""; 428 | DYLIB_COMPATIBILITY_VERSION = 1; 429 | DYLIB_CURRENT_VERSION = 1; 430 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 431 | INFOPLIST_FILE = "$(SRCROOT)/Linker/Configs/Linker.plist"; 432 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 433 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 434 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 435 | PRODUCT_BUNDLE_IDENTIFIER = "com.maksimkurpa.Linker-iOS"; 436 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 437 | PROVISIONING_PROFILE_SPECIFIER = ""; 438 | SKIP_INSTALL = YES; 439 | SWIFT_VERSION = 4.0; 440 | TARGETED_DEVICE_FAMILY = "1,2"; 441 | }; 442 | name = Debug; 443 | }; 444 | B5A43E9120358F62004D27CA /* Release */ = { 445 | isa = XCBuildConfiguration; 446 | buildSettings = { 447 | APPLICATION_EXTENSION_API_ONLY = NO; 448 | CODE_SIGN_IDENTITY = ""; 449 | CODE_SIGN_STYLE = Manual; 450 | DEFINES_MODULE = YES; 451 | DEVELOPMENT_TEAM = ""; 452 | DYLIB_COMPATIBILITY_VERSION = 1; 453 | DYLIB_CURRENT_VERSION = 1; 454 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 455 | INFOPLIST_FILE = "$(SRCROOT)/Linker/Configs/Linker.plist"; 456 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 457 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 458 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 459 | PRODUCT_BUNDLE_IDENTIFIER = "com.maksimkurpa.Linker-iOS"; 460 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 461 | PROVISIONING_PROFILE_SPECIFIER = ""; 462 | SKIP_INSTALL = YES; 463 | SWIFT_VERSION = 4.0; 464 | TARGETED_DEVICE_FAMILY = "1,2"; 465 | }; 466 | name = Release; 467 | }; 468 | B5A43E9320358F62004D27CA /* Debug */ = { 469 | isa = XCBuildConfiguration; 470 | buildSettings = { 471 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 472 | CLANG_ENABLE_MODULES = YES; 473 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 474 | CODE_SIGN_STYLE = Automatic; 475 | DEVELOPMENT_TEAM = ""; 476 | INFOPLIST_FILE = "$(PROJECT_DIR)/Linker/Configs/LinkerTests.plist"; 477 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 478 | PRODUCT_BUNDLE_IDENTIFIER = com.maksimkurpa.LinkerTests; 479 | PRODUCT_NAME = "$(TARGET_NAME)"; 480 | PROVISIONING_PROFILE_SPECIFIER = ""; 481 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 482 | SWIFT_VERSION = 4.0; 483 | TARGETED_DEVICE_FAMILY = "1,2"; 484 | }; 485 | name = Debug; 486 | }; 487 | B5A43E9420358F62004D27CA /* Release */ = { 488 | isa = XCBuildConfiguration; 489 | buildSettings = { 490 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 491 | CLANG_ENABLE_MODULES = YES; 492 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 493 | CODE_SIGN_STYLE = Automatic; 494 | DEVELOPMENT_TEAM = ""; 495 | INFOPLIST_FILE = "$(PROJECT_DIR)/Linker/Configs/LinkerTests.plist"; 496 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 497 | PRODUCT_BUNDLE_IDENTIFIER = com.maksimkurpa.LinkerTests; 498 | PRODUCT_NAME = "$(TARGET_NAME)"; 499 | PROVISIONING_PROFILE_SPECIFIER = ""; 500 | SWIFT_VERSION = 4.0; 501 | TARGETED_DEVICE_FAMILY = "1,2"; 502 | }; 503 | name = Release; 504 | }; 505 | /* End XCBuildConfiguration section */ 506 | 507 | /* Begin XCConfigurationList section */ 508 | B5A43E7520358F62004D27CA /* Build configuration list for PBXProject "Linker" */ = { 509 | isa = XCConfigurationList; 510 | buildConfigurations = ( 511 | B5A43E8D20358F62004D27CA /* Debug */, 512 | B5A43E8E20358F62004D27CA /* Release */, 513 | ); 514 | defaultConfigurationIsVisible = 0; 515 | defaultConfigurationName = Release; 516 | }; 517 | B5A43E8F20358F62004D27CA /* Build configuration list for PBXNativeTarget "Linker" */ = { 518 | isa = XCConfigurationList; 519 | buildConfigurations = ( 520 | B5A43E9020358F62004D27CA /* Debug */, 521 | B5A43E9120358F62004D27CA /* Release */, 522 | ); 523 | defaultConfigurationIsVisible = 0; 524 | defaultConfigurationName = Release; 525 | }; 526 | B5A43E9220358F62004D27CA /* Build configuration list for PBXNativeTarget "LinkerTests" */ = { 527 | isa = XCConfigurationList; 528 | buildConfigurations = ( 529 | B5A43E9320358F62004D27CA /* Debug */, 530 | B5A43E9420358F62004D27CA /* Release */, 531 | ); 532 | defaultConfigurationIsVisible = 0; 533 | defaultConfigurationName = Release; 534 | }; 535 | /* End XCConfigurationList section */ 536 | }; 537 | rootObject = B5A43E7220358F62004D27CA /* Project object */; 538 | } 539 | -------------------------------------------------------------------------------- /Linker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Linker.xcodeproj/xcshareddata/xcschemes/Linker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Linker.xcodeproj/xcshareddata/xcschemes/LinkerTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 67 | 68 | 69 | 75 | 76 | 78 | 79 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /Linker.xcodeproj/xcuserdata/maksim.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Linker.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | LinkerTests.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | B5A43E7A20358F62004D27CA 21 | 22 | primary 23 | 24 | 25 | B5A43E8320358F62004D27CA 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Linker/Configs/Linker.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Linker/Configs/LinkerTests.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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Linker/Configs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaksimKurpa/Linker/50b28ce84fe8e6b1e73c11ab896f433a8f7a865a/Linker/Configs/logo.png -------------------------------------------------------------------------------- /Linker/LinkerTests/LinkerMocks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mocks.swift 3 | // Tests 4 | // 5 | // Created by Maksim Kurpa on 2/2/18. 6 | // Copyright © 2018 Maksim Kurpa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | protocol AppDelegateURLProtocol { 13 | func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] ) -> Bool 14 | func application(_ application: UIApplication, handleOpen url: URL) -> Bool 15 | func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool 16 | } 17 | 18 | class AppDelegateMock: NSObject, AppDelegateURLProtocol { 19 | 20 | func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { 21 | return true 22 | } 23 | 24 | func application(_ application: UIApplication, handleOpen url: URL) -> Bool { 25 | return true 26 | } 27 | 28 | func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { 29 | return true 30 | } 31 | } 32 | 33 | protocol ApplicationURLProtocol { 34 | func openURL(_ url: URL) -> Bool 35 | func open(_ url: URL, options: [String : Any], completionHandler completion: ((Bool) -> Void)?) 36 | } 37 | 38 | class ApplicationMock: NSObject, ApplicationURLProtocol { 39 | 40 | func openURL(_ url: URL) -> Bool { 41 | return true 42 | } 43 | 44 | func open(_ url: URL, options: [String : Any] = [:], completionHandler completion: ((Bool) -> Void)? = nil) { 45 | } 46 | } 47 | 48 | 49 | class SwizzleMockObject: NSObject { 50 | } 51 | 52 | -------------------------------------------------------------------------------- /Linker/LinkerTests/LinkerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tests.swift 3 | // Tests 4 | // 5 | // Created by Maksim Kurpa on 1/26/18. 6 | // Copyright © 2018 Maksim Kurpa. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Foundation 11 | @testable import Linker 12 | 13 | extension SwizzleMockObject { 14 | @objc dynamic func test1(URL url: URL) -> Void { 15 | describe("testURL1", closure: { 16 | $0.it("original function with one parameter", closure: { 17 | throw failure("Called") 18 | }) 19 | }) 20 | } 21 | @objc dynamic func test2(_ val0: String?, URL url: URL) -> Void { 22 | describe("testURL2", closure: { 23 | $0.it("original function with two parameters", closure: { 24 | throw failure("Called") 25 | }) 26 | }) 27 | } 28 | @objc dynamic func test3(_ val0: String?, URL url: URL, _ val1: String?) -> Void { 29 | describe("testURL3", closure: { 30 | $0.it("original function with three parameters", closure: { 31 | throw failure("Called") 32 | }) 33 | }) 34 | } 35 | @objc dynamic func test4(_ val0: String?, _ val1: String?,URL url: URL, _ val2: String?) -> Void { 36 | describe("testURL4", closure: { 37 | $0.it("original function with four parameters", closure: { 38 | throw failure("Called") 39 | }) 40 | }) 41 | } 42 | @objc dynamic func succesTest4(_ val0: String?, _ val1: String?,URL url: URL, _ val2: String?) -> Void { 43 | describe("succesTest4", closure: { 44 | $0.it("original function with four parameters", closure: { 45 | try expect(true).beTrue() 46 | }) 47 | }) 48 | } 49 | } 50 | 51 | class Tests: XCTestCase { 52 | 53 | func test() { 54 | utilsTest() 55 | modelTest() 56 | businessLogicTest() 57 | } 58 | 59 | 60 | func businessLogicTest() { 61 | let urlToHandleOriginalIMP: URL = URL(string:"successURL://")! 62 | let objectToSwizzle = SwizzleMockObject() 63 | 64 | let execution: ExecutionClosureType = {url in 65 | describe("Swizzled closure called", closure: { 66 | $0.it(url.absoluteString, closure: { 67 | try expect(true).to.beTrue() 68 | }) 69 | }) 70 | if url == urlToHandleOriginalIMP { 71 | return url 72 | } 73 | return nil 74 | } 75 | func testModels(execution: @escaping ExecutionClosureType) -> [LNModel] { 76 | let clss = SwizzleMockObject.self 77 | return Array(arrayLiteral: 78 | LNModel(clss:clss, selector: #selector(SwizzleMockObject.test1(URL:)), 79 | execution: execution), 80 | LNModel(clss:clss, 81 | selector:#selector(SwizzleMockObject.test2(_:URL:)), 82 | execution: execution), 83 | LNModel(clss:clss,selector: #selector(SwizzleMockObject.test3(_:URL:_:)), 84 | execution: execution), 85 | LNModel(clss:clss, selector:#selector(SwizzleMockObject.test4(_:_:URL:_:)), 86 | execution: execution), 87 | LNModel(clss:clss, selector:#selector(SwizzleMockObject.succesTest4(_:_:URL:_:)), 88 | execution: execution) 89 | )} 90 | 91 | let models = testModels(execution: execution) 92 | for model in models { 93 | Linker.swizzleFunc(clss: model.clss.self, selector: model.selector, execution: model.execution as Any) 94 | } 95 | 96 | objectToSwizzle.test1(URL: URL(string: "swizzleWith1parameter://")!) 97 | objectToSwizzle.test2(nil, URL: URL(string: "swizzleWith2parameters://")!) 98 | objectToSwizzle.test3(nil,URL: URL(string: "swizzleWith3parameters://")!, nil); 99 | objectToSwizzle.test4(nil, nil, URL: URL(string: "swizzleWith4parameters://")!, nil) 100 | objectToSwizzle.succesTest4(nil, nil, URL: urlToHandleOriginalIMP, nil) 101 | } 102 | 103 | func utilsTest() { 104 | describe("Utils") { 105 | $0.it("func parse bad selector", closure: { 106 | let selector = NSSelectorFromString("badSelector") 107 | try expect(selector.argumentsCount()) == 0 108 | try expect(selector.indexOfArgument(withName: "URL")).beNil() 109 | }) 110 | $0.it("func parse good selector", closure: { 111 | let selector = NSSelectorFromString("application:openURL:sourceApplication:annotation:") 112 | try expect(selector.argumentsCount()) == 4 113 | try expect(selector.indexOfArgument(withName: "annotation")) == 3 114 | }) 115 | } 116 | } 117 | 118 | func modelTest() { 119 | 120 | describe("Application Model Test") { 121 | $0.it("UIApplication openURL:", closure: { 122 | let clss = ApplicationMock.self 123 | 124 | let selector = NSSelectorFromString("openURL:") 125 | let execution: ExecutionClosureType = {url in 126 | return url 127 | } 128 | let model = LNModel(clss: clss, selector: selector, execution: execution) 129 | 130 | try expect(model.selector) == selector 131 | try expect(model.clss).to.beOfType(type(of: clss)) 132 | try expect(expression: { () -> Bool in 133 | model.execution != nil ? true : false 134 | }) == true 135 | }) 136 | 137 | $0.it("UIApplication openURL:options:completionHandler:", closure: { 138 | let clss = ApplicationMock.self 139 | let selector = NSSelectorFromString("openURL:options:completionHandler:") 140 | let execution: ExecutionClosureType = {url in 141 | return url 142 | } 143 | let model = LNModel(clss: clss, selector: selector, execution: execution) 144 | 145 | try expect(model.selector) == selector 146 | try expect(model.clss).to.beOfType(type(of: clss)) 147 | try expect(expression: { () -> Bool in 148 | model.execution != nil ? true : false 149 | }) == true 150 | }) 151 | } 152 | 153 | describe("Application Delegate Model Test", closure: { 154 | $0.it("UIApplicationDelegate application:openURL:options:", closure: { 155 | 156 | let clss = AppDelegateMock.self 157 | let selector = NSSelectorFromString("application:openURL:options:") 158 | let execution: ExecutionClosureType = {url in 159 | return url 160 | } 161 | let model = LNModel(clss: clss, selector: selector, execution: execution) 162 | 163 | try expect(model.selector) == selector 164 | try expect(model.clss).to.beOfType(type(of: clss)) 165 | try expect(expression: { () -> Bool in 166 | model.execution != nil ? true : false 167 | }) == true 168 | }) 169 | 170 | $0.it("UIApplicationDelegate application:openURL:sourceApplication:annotation:", closure: { 171 | 172 | let clss = AppDelegateMock.self 173 | let selector = NSSelectorFromString("application:openURL:sourceApplication:annotation:") 174 | let execution: ExecutionClosureType = {url in 175 | return url 176 | } 177 | let model = LNModel(clss: clss, selector: selector, execution: execution) 178 | 179 | try expect(model.selector) == selector 180 | try expect(model.clss).to.beOfType(type(of: clss)) 181 | try expect(expression: { () -> Bool in 182 | model.execution != nil ? true : false 183 | }) == true 184 | }) 185 | $0.it("UIApplicationDelegate application:handleOpenURL:", closure: { 186 | 187 | let clss = AppDelegateMock.self 188 | let selector = NSSelectorFromString("application:handleOpenURL:") 189 | let execution: ExecutionClosureType = {url in 190 | return url 191 | } 192 | let model = LNModel(clss: clss, selector: selector, execution: execution) 193 | 194 | try expect(model.selector) == selector 195 | try expect(model.clss).to.beOfType(type(of: clss)) 196 | try expect(expression: { () -> Bool in 197 | model.execution != nil ? true : false 198 | }) == true 199 | }) 200 | }) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /Linker/LinkerTests/Spectre/Case.swift: -------------------------------------------------------------------------------- 1 | protocol CaseType { 2 | /// Run a test case in the given reporter 3 | func run(reporter: ContextReporter) 4 | } 5 | 6 | class Case : CaseType { 7 | let name:String 8 | let disabled: Bool 9 | let closure:() throws -> () 10 | 11 | let function: String 12 | let file: String 13 | let line: Int 14 | 15 | init(name: String, disabled: Bool = false, closure: @escaping () throws -> (), function: String = #function, file: String = #file, line: Int = #line) { 16 | self.name = name 17 | self.disabled = disabled 18 | self.closure = closure 19 | 20 | self.function = function 21 | self.file = file 22 | self.line = line 23 | } 24 | 25 | func run(reporter: ContextReporter) { 26 | if disabled { 27 | reporter.addDisabled(name) 28 | return 29 | } 30 | 31 | do { 32 | try closure() 33 | reporter.addSuccess(name) 34 | } catch _ as Skip { 35 | reporter.addDisabled(name) 36 | } catch let error as FailureType { 37 | reporter.addFailure(name, failure: error) 38 | } catch { 39 | reporter.addFailure(name, failure: Failure(reason: "Unhandled error: \(error)", function: function, file: file, line: line)) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Linker/LinkerTests/Spectre/Context.swift: -------------------------------------------------------------------------------- 1 | public protocol ContextType { 2 | /// Creates a new sub-context 3 | func context(_ name: String, closure: (ContextType) -> Void) 4 | 5 | /// Creates a new sub-context 6 | func describe(_ name: String, closure: (ContextType) -> Void) 7 | 8 | /// Creates a new disabled sub-context 9 | func xcontext(_ name: String, closure: (ContextType) -> Void) 10 | 11 | /// Creates a new disabled sub-context 12 | func xdescribe(_ name: String, closure: (ContextType) -> Void) 13 | 14 | func before(_ closure: @escaping () -> Void) 15 | func after(_ closure: @escaping () -> Void) 16 | 17 | /// Adds a new test case 18 | func it(_ name: String, closure: @escaping () throws -> Void) 19 | 20 | /// Adds a disabled test case 21 | func xit(_ name: String, closure: @escaping () throws -> Void) 22 | } 23 | 24 | class Context : ContextType, CaseType { 25 | let name: String 26 | let disabled: Bool 27 | fileprivate weak var parent: Context? 28 | var cases = [CaseType]() 29 | 30 | typealias Before = (() -> Void) 31 | typealias After = (() -> Void) 32 | 33 | var befores = [Before]() 34 | var afters = [After]() 35 | 36 | init(name: String, disabled: Bool = false, parent: Context? = nil) { 37 | self.name = name 38 | self.disabled = disabled 39 | self.parent = parent 40 | } 41 | 42 | func context(_ name: String, closure: (ContextType) -> Void) { 43 | let context = Context(name: name, parent: self) 44 | closure(context) 45 | cases.append(context) 46 | } 47 | 48 | func describe(_ name: String, closure: (ContextType) -> Void) { 49 | let context = Context(name: name, parent: self) 50 | closure(context) 51 | cases.append(context) 52 | } 53 | 54 | func xcontext(_ name: String, closure: (ContextType) -> Void) { 55 | let context = Context(name: name, disabled: true, parent: self) 56 | closure(context) 57 | cases.append(context) 58 | } 59 | 60 | func xdescribe(_ name: String, closure: (ContextType) -> Void) { 61 | let context = Context(name: name, disabled: true, parent: self) 62 | closure(context) 63 | cases.append(context) 64 | } 65 | 66 | func before(_ closure: @escaping () -> Void) { 67 | befores.append(closure) 68 | } 69 | 70 | func after(_ closure: @escaping () -> Void) { 71 | afters.append(closure) 72 | } 73 | 74 | func it(_ name: String, closure: @escaping () throws -> Void) { 75 | cases.append(Case(name: name, closure: closure)) 76 | } 77 | 78 | func xit(_ name: String, closure: @escaping () throws -> Void) { 79 | cases.append(Case(name: name, disabled: true, closure: closure)) 80 | } 81 | 82 | func runBefores() { 83 | parent?.runBefores() 84 | befores.forEach { $0() } 85 | } 86 | 87 | func runAfters() { 88 | afters.forEach { $0() } 89 | parent?.runAfters() 90 | } 91 | 92 | func run(reporter: ContextReporter) { 93 | if disabled { 94 | reporter.addDisabled(name) 95 | return 96 | } 97 | 98 | reporter.report(name) { reporter in 99 | cases.forEach { 100 | runBefores() 101 | $0.run(reporter: reporter) 102 | runAfters() 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Linker/LinkerTests/Spectre/Expectation.swift: -------------------------------------------------------------------------------- 1 | public protocol ExpectationType { 2 | associatedtype ValueType 3 | var expression: () throws -> ValueType? { get } 4 | func failure(_ reason: String) -> FailureType 5 | } 6 | 7 | 8 | struct ExpectationFailure : FailureType { 9 | let file: String 10 | let line: Int 11 | let function: String 12 | 13 | let reason: String 14 | 15 | init(reason: String, file: String, line: Int, function: String) { 16 | self.reason = reason 17 | self.file = file 18 | self.line = line 19 | self.function = function 20 | } 21 | } 22 | 23 | open class Expectation : ExpectationType { 24 | public typealias ValueType = T 25 | open let expression: () throws -> ValueType? 26 | 27 | let file: String 28 | let line: Int 29 | let function: String 30 | 31 | open var to: Expectation { 32 | return self 33 | } 34 | 35 | init(file: String, line: Int, function: String, expression: @escaping () throws -> ValueType?) { 36 | self.file = file 37 | self.line = line 38 | self.function = function 39 | self.expression = expression 40 | } 41 | 42 | open func failure(_ reason: String) -> FailureType { 43 | return ExpectationFailure(reason: reason, file: file, line: line, function: function) 44 | } 45 | } 46 | 47 | public func expect( _ expression: @autoclosure @escaping () throws -> T?, file: String = #file, line: Int = #line, function: String = #function) -> Expectation { 48 | return Expectation(file: file, line: line, function: function, expression: expression) 49 | } 50 | 51 | public func expect(_ file: String = #file, line: Int = #line, function: String = #function, expression: @escaping () throws -> T?) -> Expectation { 52 | return Expectation(file: file, line: line, function: function, expression: expression) 53 | } 54 | 55 | // MARK: Equatability 56 | 57 | public func == (lhs: E, rhs: E.ValueType) throws where E.ValueType: Equatable { 58 | if let value = try lhs.expression() { 59 | if value != rhs { 60 | throw lhs.failure("\(String(describing: value)) is not equal to \(rhs)") 61 | } 62 | } else { 63 | throw lhs.failure("given value is nil") 64 | } 65 | } 66 | 67 | public func != (lhs: E, rhs: E.ValueType) throws where E.ValueType: Equatable { 68 | let value = try lhs.expression() 69 | if value == rhs { 70 | throw lhs.failure("\(String(describing: value)) is equal to \(rhs)") 71 | } 72 | } 73 | 74 | // MARK: Array Equatability 75 | 76 | public func == (lhs: Expectation<[Element]>, rhs: [Element]) throws { 77 | if let value = try lhs.expression() { 78 | if value != rhs { 79 | throw lhs.failure("\(String(describing: value)) is not equal to \(rhs)") 80 | } 81 | } else { 82 | throw lhs.failure("given value is nil") 83 | } 84 | } 85 | 86 | public func != (lhs: Expectation<[Element]>, rhs: [Element]) throws { 87 | if let value = try lhs.expression() { 88 | if value == rhs { 89 | throw lhs.failure("\(String(describing: value)) is equal to \(rhs)") 90 | } 91 | } else { 92 | throw lhs.failure("given value is nil") 93 | } 94 | } 95 | 96 | // MARK: Dictionary Equatability 97 | 98 | public func == (lhs: Expectation<[Key: Value]>, rhs: [Key: Value]) throws { 99 | if let value = try lhs.expression() { 100 | if value != rhs { 101 | throw lhs.failure("\(String(describing: value)) is not equal to \(rhs)") 102 | } 103 | } else { 104 | throw lhs.failure("given value is nil") 105 | } 106 | } 107 | 108 | public func != (lhs: Expectation<[Key: Value]>, rhs: [Key: Value]) throws { 109 | if let value = try lhs.expression() { 110 | if value == rhs { 111 | throw lhs.failure("\(String(describing: value)) is equal to \(rhs)") 112 | } 113 | } else { 114 | throw lhs.failure("given value is nil") 115 | } 116 | } 117 | 118 | // MARK: Nil 119 | 120 | extension ExpectationType { 121 | public func beNil() throws { 122 | let value = try expression() 123 | if value != nil { 124 | throw failure("value is not nil") 125 | } 126 | } 127 | } 128 | 129 | // MARK: Boolean 130 | 131 | extension ExpectationType where ValueType == Bool { 132 | public func beTrue() throws { 133 | let value = try expression() 134 | if value != true { 135 | throw failure("value is not true") 136 | } 137 | } 138 | 139 | public func beFalse() throws { 140 | let value = try expression() 141 | if value != false { 142 | throw failure("value is not false") 143 | } 144 | } 145 | } 146 | 147 | // Mark: Types 148 | 149 | extension ExpectationType { 150 | public func beOfType(_ expectedType: Any.Type) throws { 151 | guard let value = try expression() else { throw failure("cannot determine type: expression threw an error or value is nil") } 152 | let valueType = Mirror(reflecting: value).subjectType 153 | if valueType != expectedType { 154 | throw failure("'\(valueType)' is not the expected type '\(expectedType)'") 155 | } 156 | } 157 | } 158 | 159 | // MARK: Error Handling 160 | 161 | extension ExpectationType { 162 | public func toThrow() throws { 163 | var didThrow = false 164 | 165 | do { 166 | _ = try expression() 167 | } catch { 168 | didThrow = true 169 | } 170 | 171 | if !didThrow { 172 | throw failure("expression did not throw an error") 173 | } 174 | } 175 | 176 | public func toThrow(_ error: T) throws { 177 | var thrownError: Error? = nil 178 | 179 | do { 180 | _ = try expression() 181 | } catch { 182 | thrownError = error 183 | } 184 | 185 | if let thrownError = thrownError { 186 | if let thrownError = thrownError as? T { 187 | if error != thrownError { 188 | throw failure("\(thrownError) is not \(error)") 189 | } 190 | } else { 191 | throw failure("\(thrownError) is not \(error)") 192 | } 193 | } else { 194 | throw failure("expression did not throw an error") 195 | } 196 | } 197 | } 198 | 199 | // MARK: Comparable 200 | 201 | public func > (lhs: E, rhs: E.ValueType) throws where E.ValueType: Comparable { 202 | let value = try lhs.expression() 203 | guard value! > rhs else { 204 | throw lhs.failure("\(String(describing: value)) is not more than \(rhs)") 205 | } 206 | } 207 | 208 | public func >= (lhs: E, rhs: E.ValueType) throws where E.ValueType: Comparable { 209 | let value = try lhs.expression() 210 | guard value! >= rhs else { 211 | throw lhs.failure("\(String(describing: value)) is not more than or equal to \(rhs)") 212 | } 213 | } 214 | 215 | public func < (lhs: E, rhs: E.ValueType) throws where E.ValueType: Comparable { 216 | let value = try lhs.expression() 217 | guard value! < rhs else { 218 | throw lhs.failure("\(String(describing: value)) is not less than \(rhs)") 219 | } 220 | } 221 | 222 | public func <= (lhs: E, rhs: E.ValueType) throws where E.ValueType: Comparable { 223 | let value = try lhs.expression() 224 | guard value! <= rhs else { 225 | throw lhs.failure("\(String(describing: value)) is not less than or equal to \(rhs)") 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /Linker/LinkerTests/Spectre/Failure.swift: -------------------------------------------------------------------------------- 1 | public protocol FailureType : Error { 2 | var function: String { get } 3 | var file: String { get } 4 | var line: Int { get } 5 | 6 | var reason: String { get } 7 | } 8 | 9 | struct Failure : FailureType { 10 | let reason: String 11 | 12 | let function: String 13 | let file: String 14 | let line: Int 15 | 16 | init(reason: String, function: String = #function, file: String = #file, line: Int = #line) { 17 | self.reason = reason 18 | self.function = function 19 | self.file = file 20 | self.line = line 21 | } 22 | } 23 | 24 | struct Skip: Error { 25 | let reason: String? 26 | } 27 | 28 | public func skip(_ reason: String? = nil) -> Error { 29 | return Skip(reason: reason) 30 | } 31 | 32 | 33 | public func failure(_ reason: String? = nil, function: String = #function, file: String = #file, line: Int = #line) -> FailureType { 34 | return Failure(reason: reason ?? "-", function: function, file: file, line: line) 35 | } 36 | -------------------------------------------------------------------------------- /Linker/LinkerTests/Spectre/Global.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | #else 4 | import Darwin 5 | #endif 6 | 7 | 8 | let globalContext: GlobalContext = { 9 | atexit { run() } 10 | return GlobalContext() 11 | }() 12 | 13 | public func describe(_ name: String, closure: (ContextType) -> Void) { 14 | globalContext.describe(name, closure: closure) 15 | } 16 | 17 | public func it(_ name: String, closure: @escaping () throws -> Void) { 18 | globalContext.it(name, closure: closure) 19 | } 20 | 21 | public func run() -> Never { 22 | let reporter: Reporter 23 | 24 | if CommandLine.arguments.contains("--tap") { 25 | reporter = TapReporter() 26 | } else if CommandLine.arguments.contains("-t") { 27 | reporter = DotReporter() 28 | } else { 29 | reporter = StandardReporter() 30 | } 31 | 32 | run(reporter: reporter) 33 | } 34 | 35 | public func run(reporter: Reporter) -> Never { 36 | if globalContext.run(reporter: reporter) { 37 | exit(0) 38 | } 39 | exit(1) 40 | } 41 | -------------------------------------------------------------------------------- /Linker/LinkerTests/Spectre/GlobalContext.swift: -------------------------------------------------------------------------------- 1 | class GlobalContext { 2 | var cases = [CaseType]() 3 | 4 | func describe(_ name: String, closure: (ContextType) -> Void) { 5 | let context = Context(name: name) 6 | closure(context) 7 | cases.append(context) 8 | } 9 | 10 | func it(_ name: String, closure: @escaping () throws -> Void) { 11 | cases.append(Case(name: name, closure: closure)) 12 | } 13 | 14 | func run(reporter: Reporter) -> Bool { 15 | return reporter.report { reporter in 16 | for `case` in cases { 17 | `case`.run(reporter: reporter) 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Linker/LinkerTests/Spectre/Reporter.swift: -------------------------------------------------------------------------------- 1 | public protocol Reporter { 2 | /// Create a new report 3 | func report(closure: (ContextReporter) -> Void) -> Bool 4 | } 5 | 6 | public protocol ContextReporter { 7 | func report(_ name: String, closure: (ContextReporter) -> Void) 8 | 9 | /// Add a passing test case 10 | func addSuccess(_ name: String) 11 | 12 | /// Add a disabled test case 13 | func addDisabled(_ name: String) 14 | 15 | /// Adds a failing test case 16 | func addFailure(_ name: String, failure: FailureType) 17 | } 18 | -------------------------------------------------------------------------------- /Linker/LinkerTests/Spectre/Reporters.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if os(Linux) 3 | import Glibc 4 | #else 5 | import Darwin.C 6 | #endif 7 | 8 | 9 | enum ANSI : String, CustomStringConvertible { 10 | case Red = "\u{001B}[0;31m" 11 | case Green = "\u{001B}[0;32m" 12 | case Yellow = "\u{001B}[0;33m" 13 | 14 | case Bold = "\u{001B}[0;1m" 15 | case Reset = "\u{001B}[0;0m" 16 | 17 | static var supportsANSI: Bool { 18 | guard isatty(STDOUT_FILENO) != 0 else { 19 | return false 20 | } 21 | 22 | guard let termType = getenv("TERM") else { 23 | return false 24 | } 25 | 26 | guard String(cString: termType).lowercased() != "dumb" else { 27 | return false 28 | } 29 | 30 | return true 31 | } 32 | 33 | var description: String { 34 | if ANSI.supportsANSI { 35 | return rawValue 36 | } 37 | 38 | return "" 39 | } 40 | } 41 | 42 | 43 | struct CaseFailure { 44 | let position: [String] 45 | let failure: FailureType 46 | 47 | init(position: [String], failure: FailureType) { 48 | self.position = position 49 | self.failure = failure 50 | } 51 | } 52 | 53 | 54 | fileprivate func stripCurrentDirectory(_ file: String) -> String { 55 | var currentPath = FileManager.`default`.currentDirectoryPath 56 | if !currentPath.hasSuffix("/") { 57 | currentPath += "/" 58 | } 59 | 60 | if file.hasPrefix(currentPath) { 61 | return String(file.suffix(from: currentPath.endIndex)) 62 | } 63 | 64 | return file 65 | } 66 | 67 | 68 | func printFailures(_ failures: [CaseFailure]) { 69 | for failure in failures { 70 | let name = failure.position.joined(separator: " ") 71 | Swift.print(ANSI.Red, name) 72 | 73 | let file = "\(stripCurrentDirectory(failure.failure.file)):\(failure.failure.line)" 74 | Swift.print(" \(ANSI.Bold)\(file)\(ANSI.Reset) \(ANSI.Yellow)\(failure.failure.reason)\(ANSI.Reset)\n") 75 | 76 | if let contents = try? String(contentsOfFile: failure.failure.file, encoding: String.Encoding.utf8) as String { 77 | let lines = contents.components(separatedBy: CharacterSet.newlines) 78 | let line = lines[failure.failure.line - 1] 79 | let trimmedLine = line.trimmingCharacters(in: CharacterSet.whitespaces) 80 | Swift.print(" ```") 81 | Swift.print(" \(trimmedLine)") 82 | Swift.print(" ```") 83 | } 84 | } 85 | } 86 | 87 | 88 | class CountReporter : Reporter, ContextReporter { 89 | var depth = 0 90 | var successes = 0 91 | var disabled = 0 92 | var position = [String]() 93 | var failures = [CaseFailure]() 94 | 95 | func printStatus() { 96 | printFailures(failures) 97 | 98 | let disabledMessage: String 99 | if disabled > 0 { 100 | disabledMessage = " \(disabled) skipped," 101 | } else { 102 | disabledMessage = "" 103 | } 104 | 105 | if failures.count == 1 { 106 | print("\(successes) passes\(disabledMessage) and \(failures.count) failure") 107 | } else { 108 | print("\(successes) passes\(disabledMessage) and \(failures.count) failures") 109 | } 110 | } 111 | 112 | #if swift(>=3.0) 113 | func report(closure: (ContextReporter) -> Void) -> Bool { 114 | closure(self) 115 | printStatus() 116 | return failures.isEmpty 117 | } 118 | #else 119 | func report(@noescape _ closure: (ContextReporter) -> Void) -> Bool { 120 | closure(self) 121 | printStatus() 122 | return failures.isEmpty 123 | } 124 | #endif 125 | 126 | #if swift(>=3.0) 127 | func report(_ name: String, closure: (ContextReporter) -> Void) { 128 | depth += 1 129 | position.append(name) 130 | closure(self) 131 | depth -= 1 132 | position.removeLast() 133 | } 134 | #else 135 | func report(_ name: String, @noescape closure: (ContextReporter) -> Void) { 136 | depth += 1 137 | position.append(name) 138 | closure(self) 139 | depth -= 1 140 | position.removeLast() 141 | } 142 | #endif 143 | 144 | func addSuccess(_ name: String) { 145 | successes += 1 146 | } 147 | 148 | func addDisabled(_ name: String) { 149 | disabled += 1 150 | } 151 | 152 | func addFailure(_ name: String, failure: FailureType) { 153 | failures.append(CaseFailure(position: position + [name], failure: failure)) 154 | } 155 | } 156 | 157 | 158 | /// Standard reporter 159 | class StandardReporter : CountReporter { 160 | override func report(_ name: String, closure: (ContextReporter) -> Void) { 161 | colour(.Bold, "-> \(name)") 162 | super.report(name, closure: closure) 163 | print("") 164 | } 165 | 166 | override func addSuccess(_ name: String) { 167 | super.addSuccess(name) 168 | colour(.Green, "-> \(name)") 169 | } 170 | 171 | override func addDisabled(_ name: String) { 172 | super.addDisabled(name) 173 | colour(.Yellow, "-> \(name)") 174 | } 175 | 176 | override func addFailure(_ name: String, failure: FailureType) { 177 | super.addFailure(name, failure: failure) 178 | colour(.Red, "-> \(name)") 179 | } 180 | 181 | func colour(_ colour: ANSI, _ message: String) { 182 | let indentation = String(repeating: " ", count: depth * 2) 183 | print("\(indentation)\(colour)\(message)\(ANSI.Reset)") 184 | } 185 | } 186 | 187 | 188 | /// Simple reporter that outputs minimal . F and S. 189 | class DotReporter : CountReporter { 190 | override func addSuccess(_ name: String) { 191 | super.addSuccess(name) 192 | print(ANSI.Green, ".", ANSI.Reset, separator: "", terminator: "") 193 | } 194 | 195 | override func addDisabled(_ name: String) { 196 | super.addDisabled(name) 197 | print(ANSI.Yellow, "S", ANSI.Reset, separator: "", terminator: "") 198 | } 199 | 200 | override func addFailure(_ name: String, failure: FailureType) { 201 | super.addFailure(name, failure: failure) 202 | print(ANSI.Red, "F", ANSI.Reset, separator: "", terminator: "") 203 | } 204 | 205 | override func printStatus() { 206 | print("\n") 207 | super.printStatus() 208 | } 209 | } 210 | 211 | 212 | /// Test Anything Protocol compatible reporter 213 | /// http://testanything.org 214 | class TapReporter : CountReporter { 215 | var count = 0 216 | 217 | override func addSuccess(_ name: String) { 218 | count += 1 219 | super.addSuccess(name) 220 | 221 | let message = (position + [name]).joined(separator: " ") 222 | print("ok \(count) - \(message)") 223 | } 224 | 225 | override func addDisabled(_ name: String) { 226 | count += 1 227 | super.addDisabled(name) 228 | 229 | let message = (position + [name]).joined(separator: " ") 230 | print("ok \(count) - # skip \(message)") 231 | } 232 | 233 | override func addFailure(_ name: String, failure: FailureType) { 234 | count += 1 235 | super.addFailure(name, failure: failure) 236 | 237 | let message = (position + [name]).joined(separator: " ") 238 | print("not ok \(count) - \(message)") 239 | print("# \(failure.reason) from \(stripCurrentDirectory(failure.file)):\(failure.line)") 240 | } 241 | 242 | override func printStatus() { 243 | print("\(min(1, count))..\(count)") 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /Linker/Sources/Linker/Linker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Linker.swift 3 | // Linker 4 | // 5 | // Created by Maksim Kurpa on 1/16/18. 6 | // Copyright © 2018 Maksim Kurpa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreFoundation 11 | 12 | open class Linker: NSObject { 13 | 14 | open class func handle(_ url: URL, closure: @escaping (_ url: URL) -> Void) { 15 | _ = swizzleOnce 16 | let key = url.key() 17 | closuresDict[key] = closure 18 | } 19 | 20 | fileprivate static var handlingURL : URL? 21 | 22 | fileprivate static func models(execution: @escaping ExecutionClosureType) -> [LNModel] { 23 | return Array(arrayLiteral: 24 | LNModel(clss:type(of: UIApplication.shared), 25 | selector:NSSelectorFromString("openURL:options:completionHandler:"), 26 | execution: execution), 27 | LNModel(clss:type(of: UIApplication.shared), 28 | selector:NSSelectorFromString("openURL:"), 29 | execution: execution), 30 | LNModel(clss:type(of: UIApplication.shared.delegate!), 31 | selector:NSSelectorFromString("application:openURL:options:"), 32 | execution: execution), 33 | LNModel(clss:type(of: UIApplication.shared.delegate!), 34 | selector:NSSelectorFromString("application:openURL:sourceApplication:annotation:"), 35 | execution: execution), 36 | LNModel(clss:type(of: UIApplication.shared.delegate!), 37 | selector:NSSelectorFromString("application:handleOpenURL:"), 38 | execution: execution) 39 | )} 40 | 41 | internal static func swizzleFunc(clss: AnyClass, selector: Selector, execution: Any) -> Void{ 42 | 43 | let replaceMethod = class_getInstanceMethod(Linker.self, #selector(Linker.fakeMethod)) 44 | var method: Method? = class_getInstanceMethod(clss, selector) 45 | if method == nil { 46 | class_addMethod(clss, selector, method_getImplementation(replaceMethod!), nil) 47 | method = class_getInstanceMethod(clss, selector) 48 | } 49 | 50 | let executionIMP : IMP? 51 | let argumentsCount = selector.argumentsCount() + 1//- because val0 always object which receive a message 52 | 53 | switch argumentsCount { 54 | case 2: 55 | let closure: ObjcBlockType_2 = {val0, val1 in 56 | (execution as! (AnyObject, AnyObject) -> Void)(val0, val1) 57 | } 58 | executionIMP = imp_implementationWithBlock(closure) 59 | case 3: 60 | let closure: ObjcBlockType_3 = {val0, val1, val2 in 61 | (execution as! (AnyObject, AnyObject, AnyObject) -> Void)(val0, val1, val2) 62 | } 63 | executionIMP = imp_implementationWithBlock(closure) 64 | case 4: 65 | let closure: ObjcBlockType_4 = {val0, val1, val2, val4 in 66 | (execution as! (AnyObject, AnyObject, AnyObject, AnyObject) -> Void)(val0, val1, val2, val4) 67 | } 68 | executionIMP = imp_implementationWithBlock(closure) 69 | case 5: 70 | let closure: ObjcBlockType_5 = {val0, val1, val2, val4, val5 in 71 | (execution as! (AnyObject, AnyObject, AnyObject, AnyObject, AnyObject) -> Void)(val0, val1, val2, val4, val5) 72 | } 73 | executionIMP = imp_implementationWithBlock(closure) 74 | default: 75 | assert(true, "method has more then 4 arguments") 76 | return 77 | } 78 | 79 | method_setImplementation(replaceMethod!, executionIMP!) 80 | method_exchangeImplementations(method!, replaceMethod!) 81 | } 82 | 83 | fileprivate static var swizzleOnce: Void = { 84 | 85 | let execution: (URL) -> (URL?) = {url in 86 | return Linker.handle(url) 87 | } 88 | for swizzleClosure in models(execution: execution) { 89 | swizzleFunc(clss: swizzleClosure.clss, selector: swizzleClosure.selector, execution: swizzleClosure.execution as Any) 90 | } 91 | }() 92 | 93 | @objc fileprivate func fakeMethod() -> Void { 94 | } 95 | 96 | static fileprivate var closuresDict: [String : LinkerClosureType] = { 97 | return [:] }() 98 | 99 | 100 | fileprivate class func handle(_ url: URL) -> URL? { 101 | 102 | let urlStringKey: String = url.key() 103 | let closure: LinkerClosureType? = closuresDict[urlStringKey] 104 | 105 | guard let execution = closure else { 106 | return url 107 | } 108 | 109 | if self.handlingURL != nil { 110 | if url == self.handlingURL! { 111 | return nil 112 | } 113 | } 114 | self.handlingURL = url 115 | self.handleIfAppIsLoaded(withConmplitionBlock: {() -> Void in 116 | execution(url) 117 | }) 118 | return nil 119 | } 120 | 121 | fileprivate class func handleIfAppIsLoaded(withConmplitionBlock complitionBlock: @escaping () -> ()) { 122 | if UIApplication.shared.applicationState == .active { 123 | self.handlingURL = nil 124 | complitionBlock() 125 | } else { 126 | var observer : Any? = nil 127 | observer = NotificationCenter.default.addObserver(forName: .UIApplicationDidBecomeActive, object: nil, queue: OperationQueue.main, using: {(_ note: Notification) -> Void in 128 | self.handlingURL = nil 129 | complitionBlock() 130 | NotificationCenter.default.removeObserver(observer!) 131 | observer = nil 132 | }) 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Linker/Sources/Linker/LinkerExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LinkerUtils.swift 3 | // Linker 4 | // 5 | // Created by Maksim Kurpa on 2/2/18. 6 | // Copyright © 2018 Maksim Kurpa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Selector { 12 | public func argumentsCount()-> Int { 13 | let stringSelector = NSStringFromSelector(self); 14 | if !stringSelector.contains(":") { 15 | return 0 16 | } 17 | let colons: String = stringSelector.filter({ 18 | return (":" == $0) 19 | }) 20 | return colons.count 21 | } 22 | 23 | public func indexOfArgument(withName name: String) -> Int? { 24 | let subStrings = NSStringFromSelector(self).split(separator: ":", maxSplits: NSIntegerMax, omittingEmptySubsequences: false) 25 | for subString in subStrings { 26 | if (subString.contains(name)) { 27 | return subStrings.index(of: subString) 28 | } 29 | } 30 | return nil 31 | } 32 | } 33 | 34 | extension URL { 35 | public func key() -> String { 36 | let components = URLComponents(url: self, resolvingAgainstBaseURL: true) 37 | return "\(components?.scheme ?? "")://\(components?.host ?? "")" 38 | } 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /Linker/Sources/Linker/LinkerModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LNModel.swift 3 | // Linker 4 | // 5 | // Created by Maksim Kurpa on 1/23/18. 6 | // Copyright © 2018 Maksim Kurpa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal struct LNModel { 12 | 13 | init(clss: AnyClass, selector: Selector, execution:@escaping ExecutionClosureType ) { 14 | 15 | let method: Method? = class_getInstanceMethod(clss, selector); 16 | var originalIMP : IMP? = nil 17 | if let originalMethod = method { 18 | originalIMP = method_getImplementation(originalMethod); 19 | } 20 | 21 | let argumentsCount = selector.argumentsCount() + 1 //because first parament is always self object 22 | let sourceParameterIndex = selector.indexOfArgument(withName: "URL") 23 | 24 | switch argumentsCount { 25 | case 2: 26 | let closure: ClosureType_2 = {val0, val1 in 27 | 28 | let urlDidntHandle = execution(val1 as! URL) 29 | if let _ = urlDidntHandle, let _ = originalIMP{ 30 | let curriedImplementation = unsafeBitCast(originalIMP, to: FuncCType_2.self) 31 | curriedImplementation(val0, selector,val1) 32 | } 33 | } 34 | self._execution = closure as AnyObject 35 | case 3: 36 | let closure: ClosureType_3 = {val0, val1, val2 in 37 | guard let sourceIndex = sourceParameterIndex else { 38 | return 39 | } 40 | let urlDidntHandle = execution(sourceIndex == 0 ? val1 as! URL : val2 as! URL) 41 | if let _ = urlDidntHandle, let _ = originalIMP{ 42 | let curriedImplementation = unsafeBitCast(originalIMP, to: FuncCType_3.self) 43 | curriedImplementation(val0, selector,val1, val2) 44 | } 45 | } 46 | self._execution = closure as AnyObject 47 | case 4: 48 | let closure: ClosureType_4 = {val0, val1, val2, val3 in 49 | guard let sourceIndex = sourceParameterIndex else { 50 | return 51 | } 52 | let urlDidntHandle = execution(sourceIndex == 0 ? val1 as! URL : sourceIndex == 1 ? val2 as! URL : val3 as! URL) 53 | if let _ = urlDidntHandle, let _ = originalIMP{ 54 | let curriedImplementation = unsafeBitCast(originalIMP, to: FuncCType_4.self) 55 | curriedImplementation(val0, selector,val1, val2, val3) 56 | } 57 | } 58 | self._execution = closure as AnyObject 59 | case 5: 60 | let closure: ClosureType_5 = {val0, val1, val2, val3, val4 in 61 | guard let sourceIndex = sourceParameterIndex else { 62 | return 63 | } 64 | let urlDidntHandle = execution(sourceIndex == 0 ? val1 as! URL : sourceIndex == 1 ? val2 as! URL : sourceIndex == 2 ? val3 as! URL : val4 as! URL) 65 | if let _ = urlDidntHandle, let _ = originalIMP{ 66 | let curriedImplementation = unsafeBitCast(originalIMP, to: FuncCType_5.self) 67 | curriedImplementation(val0, selector,val1, val2, val3, val4) 68 | } 69 | } 70 | self._execution = closure as AnyObject 71 | default: 72 | self._execution = nil 73 | } 74 | _clss = clss 75 | _selector = selector 76 | } 77 | 78 | //MARK: - Properties 79 | 80 | public var selector: Selector { 81 | return _selector 82 | } 83 | public var clss: AnyClass { 84 | return _clss 85 | } 86 | public var execution: AnyObject? { 87 | return _execution 88 | } 89 | 90 | fileprivate var _selector: Selector; 91 | fileprivate var _clss: AnyClass 92 | fileprivate var _execution: AnyObject?; 93 | } 94 | -------------------------------------------------------------------------------- /Linker/Sources/Linker/LinkerTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LinkerTypes.swift 3 | // Linker 4 | // 5 | // Created by Maksim Kurpa on 1/26/18. 6 | // Copyright © 2018 Maksim Kurpa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | typealias LinkerClosureType = (_ url: URL) -> Void 12 | typealias ExecutionClosureType = (URL) -> (URL?)? 13 | 14 | typealias ClosureType_2 = (AnyObject, AnyObject) -> Void 15 | typealias ClosureType_3 = (AnyObject, AnyObject, AnyObject) -> Void 16 | typealias ClosureType_4 = (AnyObject, AnyObject, AnyObject, AnyObject) -> Void 17 | typealias ClosureType_5 = (AnyObject, AnyObject, AnyObject, AnyObject, AnyObject) -> Void 18 | 19 | typealias FuncCType_2 = @convention(c) (AnyObject, Selector ,AnyObject) -> Void 20 | typealias FuncCType_3 = @convention(c) (AnyObject, Selector ,AnyObject, AnyObject) -> Void 21 | typealias FuncCType_4 = @convention(c) (AnyObject, Selector ,AnyObject, AnyObject, AnyObject) -> Void 22 | typealias FuncCType_5 = @convention(c) (AnyObject, Selector ,AnyObject, AnyObject, AnyObject, AnyObject) -> Void 23 | 24 | typealias ObjcBlockType_2 = @convention(block) (AnyObject, AnyObject) -> Void 25 | typealias ObjcBlockType_3 = @convention(block) (AnyObject, AnyObject, AnyObject) -> Void 26 | typealias ObjcBlockType_4 = @convention(block) (AnyObject, AnyObject, AnyObject, AnyObject) -> Void 27 | typealias ObjcBlockType_5 = @convention(block) (AnyObject, AnyObject, AnyObject, AnyObject, AnyObject) -> Void 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

Linker

4 |

Lightweight way to handle internal and external deeplinks in Swift for iOS.

5 |

6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 |

14 | 15 | 16 | --- 17 | 18 | 19 | ## Installation 20 | 21 | ### Dependency Managers 22 |
23 | CocoaPods 24 | 25 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command: 26 | 27 | ```bash 28 | $ gem install cocoapods 29 | ``` 30 | 31 | To integrate Linker into your Xcode project using CocoaPods, specify it in your `Podfile`: 32 | 33 | ```ruby 34 | source 'https://github.com/CocoaPods/Specs.git' 35 | platform :ios, '8.0' 36 | use_frameworks! 37 | 38 | pod 'Linker' 39 | ``` 40 | 41 | Then, run the following command: 42 | 43 | ```bash 44 | $ pod install 45 | ``` 46 | 47 |
48 | 49 |
50 | Carthage 51 | 52 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application. 53 | 54 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 55 | 56 | ```bash 57 | $ brew update 58 | $ brew install carthage 59 | ``` 60 | 61 | To integrate Linker into your Xcode project using Carthage, specify it in your `Cartfile`: 62 | 63 | ```ogdl 64 | github "Linker" 65 | ``` 66 | 67 |
68 | 69 | ## Usage 70 | 71 | (see sample Xcode project Demo) 72 | 73 | The main thought of this framework is useful and convenient handling of external and internal URLs in your iOS application. Linker provides only one function to install your own handler to specific URL. A dependency between specific URL and your closure is based on `scheme` and `host` of each URL. That is you can configure miscellaneous behavior for different components of specific URL. You can split handling by `query` with different parameters and/or by `path`, `fragment`. 74 | 75 |
76 | Realization details 77 | On start of your application occurs swizzling methods in `UIApplication` and `UIApplicationDelegate` of your application. Original implementation exchanged on Linker's implementation, where occur handle process. If Linker can't handle specific URL, original implementation of this method will be called. 78 | 79 | Swizzled functions: 80 | 81 | `UIApplication.shared - openURL:options:completionHandler:` 82 | 83 | `UIApplication.shared - openURL:` (deprecated since iOS 10.0) 84 | 85 | `UIApplication.shared.delegate - application:openURL:options:` 86 | 87 | `UIApplication.shared.delegate - application:openURL:sourceApplication:annotation:` (deprecated since iOS 9.0) 88 | 89 | `UIApplication.shared.delegate - application:handleOpenURL:` (deprecated since iOS 9.0)
90 | 91 | 92 | For complience with URL style, use format: 93 | 94 | `your_app_url_scheme://inapp_am/buy_subscription?type=subscription&productID=com.yourapp.7days_trial#test` 95 | 96 | where: 97 | 98 | scheme - `your_app_url_scheme`, 99 | 100 | host - `inapp_am`, 101 | 102 | path - `buy_subscription` 103 | 104 | query - `type=subscription&productID=com.yourapp.7days_trial` 105 | 106 | fragment - `test` 107 | 108 | If you don't need configuration with complexed behavior, you can use URL just with `host`: 109 | 110 | `your_app_url_scheme://some_host_from_your_app` 111 | 112 | One special case - handle external URLs when app isn't launched. You should install closure for specific URL and if this url will be in pair with `UIApplicationLaunchOptionsKey` in `launchOptions` - this url will be handled. 113 | 114 | ```Swift 115 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool 116 | { 117 | let launchURL = URL(string: "linker://launchURL")! 118 | 119 | //if launchUrl equal launchOptions?[UIApplicationLaunchOptionsKey.url] -> this closure will be handled after app launch 120 | Linker.handle(launchURL, closure: { url in 121 | print("Your URL has been handle!") 122 | }) 123 | return true 124 | } 125 | ``` 126 | In other cases of usage you should set your handle closure for special URL before calling its from somewhere. If you have a few places where you need handle one specific url, you should reassign closure where latest set closure should have right `context`. 127 | 128 | 129 |
(!) Notice: Only the last sent closure for a unique URL (scheme + host) will be executed.
130 | 131 | ```Swift 132 | class ViewController: UIViewController { 133 | 134 | let sourceURL = URL(string: "linker://viewcontroller?title=ExampleAlert&description=ExampleDescriptionAlert")! 135 | 136 | @IBAction func action(_ sender: Any) { 137 | UIApplication.shared.open(sourceURL, options: [:], completionHandler: nil) 138 | } 139 | 140 | override func viewDidLoad() { 141 | super.viewDidLoad() 142 | Linker.handle(sourceURL) { url in 143 | 144 | guard let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems! else { 145 | return } 146 | var title : String? = nil 147 | var description: String? = nil 148 | 149 | for item in queryItems { 150 | if item.name == "title" { 151 | title = item.value 152 | } 153 | if item.name == "description" { 154 | description = item.value; 155 | } 156 | } 157 | 158 | if let name = title, let message = description { 159 | let alertVC = UIAlertController(title: name, message: message, preferredStyle: UIAlertControllerStyle.alert) 160 | alertVC.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: {action in 161 | alertVC.dismiss(animated: true, completion: nil) 162 | })) 163 | self.present(alertVC, animated: false, completion: nil) 164 | } 165 | } 166 | } 167 | } 168 | ``` 169 | You can also find Objective-C version of this [here](https://github.com/MaksimKurpa/DeepLinksHandler). 170 | 171 | ## Contributing 172 | 173 | Issues and pull requests are welcome! 174 | 175 | ## Author 176 | 177 | Maksim Kurpa - [@maksim_kurpa](https://twitter.com/maksim_kurpa) 178 | 179 | ## License 180 | 181 | This code is distributed under the terms and conditions of the [MIT license](https://raw.githubusercontent.com/MaksimKurpa/Linker/master/LICENSE). 182 | --------------------------------------------------------------------------------