├── .gitignore ├── .swiftlint.yml ├── Aspect.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Aspect ├── Aspect.Arguments.swift ├── Aspect.h ├── Aspect.swift ├── AspectBlock.swift ├── AspectError.swift ├── AspectIdentifier.swift ├── Info.plist ├── Lock.swift ├── NSObject.Association.swift ├── Objc │ ├── ObjCRuntimeAliases.h │ └── ObjCRuntimeAliases.m └── Runtime.swift ├── AspectTests ├── AspectTests.swift └── Info.plist ├── Images └── aspect.png ├── LICENSE ├── Package.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | IDEWorkspaceChecks.plist 20 | 21 | ## Other 22 | *.moved-aside 23 | *.xccheckout 24 | *.xcscmblueprint 25 | .DS_Store 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | .build/ 44 | 45 | # CocoaPods 46 | # 47 | # We recommend against adding the Pods directory to your .gitignore. However 48 | # you should judge for yourself, the pros and cons are mentioned at: 49 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 50 | Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | Carthage/Checkouts 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: # rule identifiers to exclude from running 2 | - force_cast 3 | - trailing_comma 4 | - opening_brace 5 | 6 | opt_in_rules: # Find all the available rules by running: swiftlint rules 7 | - anyobject_protocol 8 | - array_init 9 | - closure_spacing 10 | - discouraged_object_literal 11 | - empty_count 12 | - empty_string 13 | - explicit_init 14 | - fallthrough 15 | - first_where 16 | - identical_operands 17 | - implicit_return 18 | - let_var_whitespace 19 | - multiline_parameters 20 | - overridden_super_call 21 | - override_in_extension 22 | - pattern_matching_keywords 23 | - private_action 24 | - private_outlet 25 | - prohibited_super_call 26 | - sorted_first_last 27 | - unused_import 28 | - yoda_condition 29 | 30 | included: # paths to include during linting. `--path` is ignored if present. 31 | - ./ 32 | excluded: # paths to ignore during linting. Takes precedence over `included`. 33 | - Carthage 34 | - Pods 35 | 36 | force_try: 37 | severity: warning # explicitly 38 | 39 | trailing_whitespace: 40 | ignores_empty_lines: true 41 | vertical_whitespace: 42 | max_empty_lines: 2 43 | 44 | line_length: 200 45 | 46 | type_body_length: 47 | - 300 # warning 48 | - 400 # error 49 | 50 | file_length: 51 | warning: 500 52 | error: 1200 53 | 54 | function_parameter_count: 55 | warning: 8 56 | error: 10 57 | 58 | function_body_length: 59 | warning: 80 60 | error: 100 61 | 62 | cyclomatic_complexity: 63 | warning: 15 64 | warning: 20 65 | 66 | type_name: 67 | min_length: 3 # only warning 68 | max_length: 69 | warning: 40 70 | error: 50 71 | excluded: 72 | - R 73 | - ID 74 | - Key 75 | 76 | identifier_name: 77 | min_length: 3 78 | max_length: 79 | warning: 40 80 | error: 50 81 | allowed_symbols: "_" 82 | excluded: # excluded via string array 83 | - id 84 | - ip 85 | - in 86 | - vc 87 | - URL 88 | 89 | large_tuple: 90 | warning: 4 91 | error: 5 92 | 93 | nesting: 94 | type_level: 95 | warning: 2 96 | 97 | custom_rules: 98 | line_break_after_curly_brackets: 99 | name: 'Line break after curly brackets' 100 | regex: '^[^\n]*[ \t](?!class|struct|enum|protocol|extension)[ \t][^\n]+\{$\n\s*$' 101 | message: "Blank line should be removed after left curly brackets({)." 102 | severity: warning 103 | 104 | attributes_with_name: 105 | name: 'Attribute with Name' 106 | regex: '(@objc|@nonobjc){1}\({1}[A-Za-z0-9]+\){1}[ A-Za-z0-9]+' 107 | message: "Attributes should be on their own lines if has name in parentheses." 108 | severity: warning 109 | 110 | attributes_without_name: 111 | name: 'Attribute without Name' 112 | regex: '(@objc|@nonobjc){1}[()]{0}\n+' 113 | message: "Attributes should be on the same line if hasn't name in parentheses." 114 | severity: warning 115 | 116 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji) 117 | -------------------------------------------------------------------------------- /Aspect.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9C11F064229A87B400CAED25 /* AspectBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C11F063229A87B400CAED25 /* AspectBlock.swift */; }; 11 | 9C11F06C229AB2B700CAED25 /* AspectIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C11F06B229AB2B700CAED25 /* AspectIdentifier.swift */; }; 12 | 9C11F06E229AB40B00CAED25 /* Aspect.Arguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C11F06D229AB40B00CAED25 /* Aspect.Arguments.swift */; }; 13 | 9C11F070229AB4D000CAED25 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C11F06F229AB4D000CAED25 /* Lock.swift */; }; 14 | 9C92B044229017B4009A2C5D /* Aspect.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C92B042229017B4009A2C5D /* Aspect.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | 9C92B04B229017C3009A2C5D /* Aspect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C92B04A229017C3009A2C5D /* Aspect.swift */; }; 16 | 9CC84E3222A5101D007EFBEB /* Runtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CC84E3122A5101D007EFBEB /* Runtime.swift */; }; 17 | 9CE515A4229D80EB0063FE00 /* AspectError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE515A3229D80EB0063FE00 /* AspectError.swift */; }; 18 | 9CE515AF22A02DB20063FE00 /* ObjCRuntimeAliases.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CE515AD22A02DB10063FE00 /* ObjCRuntimeAliases.m */; }; 19 | 9CE515B022A02DB20063FE00 /* ObjCRuntimeAliases.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CE515AE22A02DB20063FE00 /* ObjCRuntimeAliases.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20 | 9CE515B222A27D3D0063FE00 /* NSObject.Association.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE515B122A27D3D0063FE00 /* NSObject.Association.swift */; }; 21 | 9CE515BA22A27E9D0063FE00 /* AspectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE515B922A27E9D0063FE00 /* AspectTests.swift */; }; 22 | 9CE515BC22A27E9D0063FE00 /* Aspect.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C92B03F229017B4009A2C5D /* Aspect.framework */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 9CE515BD22A27E9D0063FE00 /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 9C92B036229017B4009A2C5D /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 9C92B03E229017B4009A2C5D; 31 | remoteInfo = Aspect; 32 | }; 33 | /* End PBXContainerItemProxy section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | 9C11F063229A87B400CAED25 /* AspectBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AspectBlock.swift; sourceTree = ""; }; 37 | 9C11F06B229AB2B700CAED25 /* AspectIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AspectIdentifier.swift; sourceTree = ""; }; 38 | 9C11F06D229AB40B00CAED25 /* Aspect.Arguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Aspect.Arguments.swift; sourceTree = ""; }; 39 | 9C11F06F229AB4D000CAED25 /* Lock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lock.swift; sourceTree = ""; }; 40 | 9C92B03F229017B4009A2C5D /* Aspect.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Aspect.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 9C92B042229017B4009A2C5D /* Aspect.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Aspect.h; sourceTree = ""; }; 42 | 9C92B043229017B4009A2C5D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | 9C92B04A229017C3009A2C5D /* Aspect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Aspect.swift; sourceTree = ""; }; 44 | 9CC84E3122A5101D007EFBEB /* Runtime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Runtime.swift; sourceTree = ""; }; 45 | 9CE515A3229D80EB0063FE00 /* AspectError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AspectError.swift; sourceTree = ""; }; 46 | 9CE515AD22A02DB10063FE00 /* ObjCRuntimeAliases.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjCRuntimeAliases.m; sourceTree = ""; }; 47 | 9CE515AE22A02DB20063FE00 /* ObjCRuntimeAliases.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjCRuntimeAliases.h; sourceTree = ""; }; 48 | 9CE515B122A27D3D0063FE00 /* NSObject.Association.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSObject.Association.swift; sourceTree = ""; }; 49 | 9CE515B722A27E9D0063FE00 /* AspectTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AspectTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 9CE515B922A27E9D0063FE00 /* AspectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AspectTests.swift; sourceTree = ""; }; 51 | 9CE515BB22A27E9D0063FE00 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | 9C92B03C229017B4009A2C5D /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | 9CE515B422A27E9D0063FE00 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | 9CE515BC22A27E9D0063FE00 /* Aspect.framework in Frameworks */, 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | 9C92B035229017B4009A2C5D = { 74 | isa = PBXGroup; 75 | children = ( 76 | 9C92B041229017B4009A2C5D /* Aspect */, 77 | 9CE515B822A27E9D0063FE00 /* AspectTests */, 78 | 9C92B040229017B4009A2C5D /* Products */, 79 | ); 80 | sourceTree = ""; 81 | }; 82 | 9C92B040229017B4009A2C5D /* Products */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 9C92B03F229017B4009A2C5D /* Aspect.framework */, 86 | 9CE515B722A27E9D0063FE00 /* AspectTests.xctest */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | 9C92B041229017B4009A2C5D /* Aspect */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 9C92B042229017B4009A2C5D /* Aspect.h */, 95 | 9C92B04A229017C3009A2C5D /* Aspect.swift */, 96 | 9C11F06D229AB40B00CAED25 /* Aspect.Arguments.swift */, 97 | 9C11F063229A87B400CAED25 /* AspectBlock.swift */, 98 | 9CE515A3229D80EB0063FE00 /* AspectError.swift */, 99 | 9C11F06B229AB2B700CAED25 /* AspectIdentifier.swift */, 100 | 9C92B043229017B4009A2C5D /* Info.plist */, 101 | 9C11F06F229AB4D000CAED25 /* Lock.swift */, 102 | 9CE515B122A27D3D0063FE00 /* NSObject.Association.swift */, 103 | 9CE515AC22A02DA70063FE00 /* Objc */, 104 | 9CC84E3122A5101D007EFBEB /* Runtime.swift */, 105 | ); 106 | path = Aspect; 107 | sourceTree = ""; 108 | }; 109 | 9CE515AC22A02DA70063FE00 /* Objc */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 9CE515AE22A02DB20063FE00 /* ObjCRuntimeAliases.h */, 113 | 9CE515AD22A02DB10063FE00 /* ObjCRuntimeAliases.m */, 114 | ); 115 | path = Objc; 116 | sourceTree = ""; 117 | }; 118 | 9CE515B822A27E9D0063FE00 /* AspectTests */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 9CE515B922A27E9D0063FE00 /* AspectTests.swift */, 122 | 9CE515BB22A27E9D0063FE00 /* Info.plist */, 123 | ); 124 | path = AspectTests; 125 | sourceTree = ""; 126 | }; 127 | /* End PBXGroup section */ 128 | 129 | /* Begin PBXHeadersBuildPhase section */ 130 | 9C92B03A229017B4009A2C5D /* Headers */ = { 131 | isa = PBXHeadersBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | 9C92B044229017B4009A2C5D /* Aspect.h in Headers */, 135 | 9CE515B022A02DB20063FE00 /* ObjCRuntimeAliases.h in Headers */, 136 | ); 137 | runOnlyForDeploymentPostprocessing = 0; 138 | }; 139 | /* End PBXHeadersBuildPhase section */ 140 | 141 | /* Begin PBXNativeTarget section */ 142 | 9C92B03E229017B4009A2C5D /* Aspect */ = { 143 | isa = PBXNativeTarget; 144 | buildConfigurationList = 9C92B047229017B4009A2C5D /* Build configuration list for PBXNativeTarget "Aspect" */; 145 | buildPhases = ( 146 | 9C92B03A229017B4009A2C5D /* Headers */, 147 | 9C92B03B229017B4009A2C5D /* Sources */, 148 | 9C92B03C229017B4009A2C5D /* Frameworks */, 149 | 9C92B03D229017B4009A2C5D /* Resources */, 150 | 9CC84E3922A68357007EFBEB /* Swift Lint */, 151 | ); 152 | buildRules = ( 153 | ); 154 | dependencies = ( 155 | ); 156 | name = Aspect; 157 | productName = Aspect; 158 | productReference = 9C92B03F229017B4009A2C5D /* Aspect.framework */; 159 | productType = "com.apple.product-type.framework"; 160 | }; 161 | 9CE515B622A27E9D0063FE00 /* AspectTests */ = { 162 | isa = PBXNativeTarget; 163 | buildConfigurationList = 9CE515BF22A27E9D0063FE00 /* Build configuration list for PBXNativeTarget "AspectTests" */; 164 | buildPhases = ( 165 | 9CE515B322A27E9D0063FE00 /* Sources */, 166 | 9CE515B422A27E9D0063FE00 /* Frameworks */, 167 | 9CE515B522A27E9D0063FE00 /* Resources */, 168 | ); 169 | buildRules = ( 170 | ); 171 | dependencies = ( 172 | 9CE515BE22A27E9D0063FE00 /* PBXTargetDependency */, 173 | ); 174 | name = AspectTests; 175 | productName = AspectTests; 176 | productReference = 9CE515B722A27E9D0063FE00 /* AspectTests.xctest */; 177 | productType = "com.apple.product-type.bundle.unit-test"; 178 | }; 179 | /* End PBXNativeTarget section */ 180 | 181 | /* Begin PBXProject section */ 182 | 9C92B036229017B4009A2C5D /* Project object */ = { 183 | isa = PBXProject; 184 | attributes = { 185 | LastSwiftUpdateCheck = 1000; 186 | LastUpgradeCheck = 1000; 187 | ORGANIZATIONNAME = roy; 188 | TargetAttributes = { 189 | 9C92B03E229017B4009A2C5D = { 190 | CreatedOnToolsVersion = 10.0; 191 | LastSwiftMigration = 1000; 192 | }; 193 | 9CE515B622A27E9D0063FE00 = { 194 | CreatedOnToolsVersion = 10.0; 195 | }; 196 | }; 197 | }; 198 | buildConfigurationList = 9C92B039229017B4009A2C5D /* Build configuration list for PBXProject "Aspect" */; 199 | compatibilityVersion = "Xcode 9.3"; 200 | developmentRegion = en; 201 | hasScannedForEncodings = 0; 202 | knownRegions = ( 203 | en, 204 | Base, 205 | ); 206 | mainGroup = 9C92B035229017B4009A2C5D; 207 | productRefGroup = 9C92B040229017B4009A2C5D /* Products */; 208 | projectDirPath = ""; 209 | projectRoot = ""; 210 | targets = ( 211 | 9C92B03E229017B4009A2C5D /* Aspect */, 212 | 9CE515B622A27E9D0063FE00 /* AspectTests */, 213 | ); 214 | }; 215 | /* End PBXProject section */ 216 | 217 | /* Begin PBXResourcesBuildPhase section */ 218 | 9C92B03D229017B4009A2C5D /* Resources */ = { 219 | isa = PBXResourcesBuildPhase; 220 | buildActionMask = 2147483647; 221 | files = ( 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | 9CE515B522A27E9D0063FE00 /* Resources */ = { 226 | isa = PBXResourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | }; 232 | /* End PBXResourcesBuildPhase section */ 233 | 234 | /* Begin PBXShellScriptBuildPhase section */ 235 | 9CC84E3922A68357007EFBEB /* Swift Lint */ = { 236 | isa = PBXShellScriptBuildPhase; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | ); 240 | inputFileListPaths = ( 241 | ); 242 | inputPaths = ( 243 | ); 244 | name = "Swift Lint"; 245 | outputFileListPaths = ( 246 | ); 247 | outputPaths = ( 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | shellPath = /bin/sh; 251 | shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 252 | }; 253 | /* End PBXShellScriptBuildPhase section */ 254 | 255 | /* Begin PBXSourcesBuildPhase section */ 256 | 9C92B03B229017B4009A2C5D /* Sources */ = { 257 | isa = PBXSourcesBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | 9C11F06E229AB40B00CAED25 /* Aspect.Arguments.swift in Sources */, 261 | 9C92B04B229017C3009A2C5D /* Aspect.swift in Sources */, 262 | 9C11F064229A87B400CAED25 /* AspectBlock.swift in Sources */, 263 | 9CE515A4229D80EB0063FE00 /* AspectError.swift in Sources */, 264 | 9C11F06C229AB2B700CAED25 /* AspectIdentifier.swift in Sources */, 265 | 9C11F070229AB4D000CAED25 /* Lock.swift in Sources */, 266 | 9CE515B222A27D3D0063FE00 /* NSObject.Association.swift in Sources */, 267 | 9CE515AF22A02DB20063FE00 /* ObjCRuntimeAliases.m in Sources */, 268 | 9CC84E3222A5101D007EFBEB /* Runtime.swift in Sources */, 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | }; 272 | 9CE515B322A27E9D0063FE00 /* Sources */ = { 273 | isa = PBXSourcesBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | 9CE515BA22A27E9D0063FE00 /* AspectTests.swift in Sources */, 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | /* End PBXSourcesBuildPhase section */ 281 | 282 | /* Begin PBXTargetDependency section */ 283 | 9CE515BE22A27E9D0063FE00 /* PBXTargetDependency */ = { 284 | isa = PBXTargetDependency; 285 | target = 9C92B03E229017B4009A2C5D /* Aspect */; 286 | targetProxy = 9CE515BD22A27E9D0063FE00 /* PBXContainerItemProxy */; 287 | }; 288 | /* End PBXTargetDependency section */ 289 | 290 | /* Begin XCBuildConfiguration section */ 291 | 9C92B045229017B4009A2C5D /* Debug */ = { 292 | isa = XCBuildConfiguration; 293 | buildSettings = { 294 | ALWAYS_SEARCH_USER_PATHS = NO; 295 | CLANG_ANALYZER_NONNULL = YES; 296 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 297 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 298 | CLANG_CXX_LIBRARY = "libc++"; 299 | CLANG_ENABLE_MODULES = YES; 300 | CLANG_ENABLE_OBJC_ARC = YES; 301 | CLANG_ENABLE_OBJC_WEAK = YES; 302 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 303 | CLANG_WARN_BOOL_CONVERSION = YES; 304 | CLANG_WARN_COMMA = YES; 305 | CLANG_WARN_CONSTANT_CONVERSION = YES; 306 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 307 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 308 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 309 | CLANG_WARN_EMPTY_BODY = YES; 310 | CLANG_WARN_ENUM_CONVERSION = YES; 311 | CLANG_WARN_INFINITE_RECURSION = YES; 312 | CLANG_WARN_INT_CONVERSION = YES; 313 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 314 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 315 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 316 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 317 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 318 | CLANG_WARN_STRICT_PROTOTYPES = YES; 319 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 320 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 321 | CLANG_WARN_UNREACHABLE_CODE = YES; 322 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 323 | CODE_SIGN_IDENTITY = "iPhone Developer"; 324 | COPY_PHASE_STRIP = NO; 325 | CURRENT_PROJECT_VERSION = 1; 326 | DEBUG_INFORMATION_FORMAT = dwarf; 327 | ENABLE_STRICT_OBJC_MSGSEND = YES; 328 | ENABLE_TESTABILITY = YES; 329 | GCC_C_LANGUAGE_STANDARD = gnu11; 330 | GCC_DYNAMIC_NO_PIC = NO; 331 | GCC_NO_COMMON_BLOCKS = YES; 332 | GCC_OPTIMIZATION_LEVEL = 0; 333 | GCC_PREPROCESSOR_DEFINITIONS = ( 334 | "DEBUG=1", 335 | "$(inherited)", 336 | ); 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 344 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 345 | MTL_FAST_MATH = YES; 346 | ONLY_ACTIVE_ARCH = YES; 347 | SDKROOT = iphoneos; 348 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 349 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 350 | VERSIONING_SYSTEM = "apple-generic"; 351 | VERSION_INFO_PREFIX = ""; 352 | }; 353 | name = Debug; 354 | }; 355 | 9C92B046229017B4009A2C5D /* Release */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | ALWAYS_SEARCH_USER_PATHS = NO; 359 | CLANG_ANALYZER_NONNULL = YES; 360 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 361 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 362 | CLANG_CXX_LIBRARY = "libc++"; 363 | CLANG_ENABLE_MODULES = YES; 364 | CLANG_ENABLE_OBJC_ARC = YES; 365 | CLANG_ENABLE_OBJC_WEAK = YES; 366 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 367 | CLANG_WARN_BOOL_CONVERSION = YES; 368 | CLANG_WARN_COMMA = YES; 369 | CLANG_WARN_CONSTANT_CONVERSION = YES; 370 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 371 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 372 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 373 | CLANG_WARN_EMPTY_BODY = YES; 374 | CLANG_WARN_ENUM_CONVERSION = YES; 375 | CLANG_WARN_INFINITE_RECURSION = YES; 376 | CLANG_WARN_INT_CONVERSION = YES; 377 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 378 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 379 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 381 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 382 | CLANG_WARN_STRICT_PROTOTYPES = YES; 383 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 384 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 385 | CLANG_WARN_UNREACHABLE_CODE = YES; 386 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 387 | CODE_SIGN_IDENTITY = "iPhone Developer"; 388 | COPY_PHASE_STRIP = NO; 389 | CURRENT_PROJECT_VERSION = 1; 390 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 391 | ENABLE_NS_ASSERTIONS = NO; 392 | ENABLE_STRICT_OBJC_MSGSEND = YES; 393 | GCC_C_LANGUAGE_STANDARD = gnu11; 394 | GCC_NO_COMMON_BLOCKS = YES; 395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 397 | GCC_WARN_UNDECLARED_SELECTOR = YES; 398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 399 | GCC_WARN_UNUSED_FUNCTION = YES; 400 | GCC_WARN_UNUSED_VARIABLE = YES; 401 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 402 | MTL_ENABLE_DEBUG_INFO = NO; 403 | MTL_FAST_MATH = YES; 404 | SDKROOT = iphoneos; 405 | SWIFT_COMPILATION_MODE = wholemodule; 406 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 407 | VALIDATE_PRODUCT = YES; 408 | VERSIONING_SYSTEM = "apple-generic"; 409 | VERSION_INFO_PREFIX = ""; 410 | }; 411 | name = Release; 412 | }; 413 | 9C92B048229017B4009A2C5D /* Debug */ = { 414 | isa = XCBuildConfiguration; 415 | buildSettings = { 416 | CLANG_ENABLE_MODULES = YES; 417 | CODE_SIGN_IDENTITY = ""; 418 | CODE_SIGN_STYLE = Automatic; 419 | DEFINES_MODULE = YES; 420 | DYLIB_COMPATIBILITY_VERSION = 1; 421 | DYLIB_CURRENT_VERSION = 1; 422 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 423 | INFOPLIST_FILE = Aspect/Info.plist; 424 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 425 | LD_RUNPATH_SEARCH_PATHS = ( 426 | "$(inherited)", 427 | "@executable_path/Frameworks", 428 | "@loader_path/Frameworks", 429 | ); 430 | PRODUCT_BUNDLE_IDENTIFIER = com.roy.Aspect; 431 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 432 | SKIP_INSTALL = YES; 433 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 434 | SWIFT_VERSION = 4.2; 435 | TARGETED_DEVICE_FAMILY = "1,2"; 436 | }; 437 | name = Debug; 438 | }; 439 | 9C92B049229017B4009A2C5D /* Release */ = { 440 | isa = XCBuildConfiguration; 441 | buildSettings = { 442 | CLANG_ENABLE_MODULES = YES; 443 | CODE_SIGN_IDENTITY = ""; 444 | CODE_SIGN_STYLE = Automatic; 445 | DEFINES_MODULE = YES; 446 | DYLIB_COMPATIBILITY_VERSION = 1; 447 | DYLIB_CURRENT_VERSION = 1; 448 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 449 | INFOPLIST_FILE = Aspect/Info.plist; 450 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 451 | LD_RUNPATH_SEARCH_PATHS = ( 452 | "$(inherited)", 453 | "@executable_path/Frameworks", 454 | "@loader_path/Frameworks", 455 | ); 456 | PRODUCT_BUNDLE_IDENTIFIER = com.roy.Aspect; 457 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 458 | SKIP_INSTALL = YES; 459 | SWIFT_VERSION = 4.2; 460 | TARGETED_DEVICE_FAMILY = "1,2"; 461 | }; 462 | name = Release; 463 | }; 464 | 9CE515C022A27E9D0063FE00 /* Debug */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | CODE_SIGN_STYLE = Automatic; 468 | INFOPLIST_FILE = AspectTests/Info.plist; 469 | LD_RUNPATH_SEARCH_PATHS = ( 470 | "$(inherited)", 471 | "@executable_path/Frameworks", 472 | "@loader_path/Frameworks", 473 | ); 474 | PRODUCT_BUNDLE_IDENTIFIER = com.roy.AspectTests; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | SWIFT_VERSION = 4.2; 477 | TARGETED_DEVICE_FAMILY = "1,2"; 478 | }; 479 | name = Debug; 480 | }; 481 | 9CE515C122A27E9D0063FE00 /* Release */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | CODE_SIGN_STYLE = Automatic; 485 | INFOPLIST_FILE = AspectTests/Info.plist; 486 | LD_RUNPATH_SEARCH_PATHS = ( 487 | "$(inherited)", 488 | "@executable_path/Frameworks", 489 | "@loader_path/Frameworks", 490 | ); 491 | PRODUCT_BUNDLE_IDENTIFIER = com.roy.AspectTests; 492 | PRODUCT_NAME = "$(TARGET_NAME)"; 493 | SWIFT_VERSION = 4.2; 494 | TARGETED_DEVICE_FAMILY = "1,2"; 495 | }; 496 | name = Release; 497 | }; 498 | /* End XCBuildConfiguration section */ 499 | 500 | /* Begin XCConfigurationList section */ 501 | 9C92B039229017B4009A2C5D /* Build configuration list for PBXProject "Aspect" */ = { 502 | isa = XCConfigurationList; 503 | buildConfigurations = ( 504 | 9C92B045229017B4009A2C5D /* Debug */, 505 | 9C92B046229017B4009A2C5D /* Release */, 506 | ); 507 | defaultConfigurationIsVisible = 0; 508 | defaultConfigurationName = Release; 509 | }; 510 | 9C92B047229017B4009A2C5D /* Build configuration list for PBXNativeTarget "Aspect" */ = { 511 | isa = XCConfigurationList; 512 | buildConfigurations = ( 513 | 9C92B048229017B4009A2C5D /* Debug */, 514 | 9C92B049229017B4009A2C5D /* Release */, 515 | ); 516 | defaultConfigurationIsVisible = 0; 517 | defaultConfigurationName = Release; 518 | }; 519 | 9CE515BF22A27E9D0063FE00 /* Build configuration list for PBXNativeTarget "AspectTests" */ = { 520 | isa = XCConfigurationList; 521 | buildConfigurations = ( 522 | 9CE515C022A27E9D0063FE00 /* Debug */, 523 | 9CE515C122A27E9D0063FE00 /* Release */, 524 | ); 525 | defaultConfigurationIsVisible = 0; 526 | defaultConfigurationName = Release; 527 | }; 528 | /* End XCConfigurationList section */ 529 | }; 530 | rootObject = 9C92B036229017B4009A2C5D /* Project object */; 531 | } 532 | -------------------------------------------------------------------------------- /Aspect.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Aspect/Aspect.Arguments.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Aspect.Arguments.swift 3 | // Aspect 4 | // 5 | // Created by roy.cao on 2019/5/26. 6 | // Copyright © 2019 roy. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import Foundation 27 | 28 | public extension NSObject { 29 | 30 | @discardableResult 31 | func hook( 32 | selector: Selector, 33 | strategy: AspectStrategy = .before, 34 | block: @escaping(AspectInfo) -> Void) throws -> AspectToken 35 | { 36 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 37 | block(aspectInfo) 38 | } 39 | 40 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 41 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 42 | } 43 | 44 | @discardableResult 45 | func hook( 46 | selector: Selector, 47 | strategy: AspectStrategy = .before, 48 | block: @escaping(AspectInfo, Arg1) -> Void) throws -> AspectToken 49 | { 50 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 51 | guard aspectInfo.arguments.count == 1, 52 | let arg1 = aspectInfo.arguments[0] as? Arg1 else { return } 53 | block(aspectInfo, arg1) 54 | } 55 | return try hook(selector: selector, strategy: strategy, block: wrappedBlock) 56 | } 57 | 58 | @discardableResult 59 | func hook( 60 | selector: Selector, 61 | strategy: AspectStrategy = .before, 62 | block: @escaping(AspectInfo, Arg1, Arg2) -> Void) throws -> AspectToken 63 | { 64 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 65 | guard aspectInfo.arguments.count == 2, 66 | let arg1 = aspectInfo.arguments[0] as? Arg1, 67 | let arg2 = aspectInfo.arguments[1] as? Arg2 else { return } 68 | block(aspectInfo, arg1, arg2) 69 | } 70 | 71 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 72 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 73 | } 74 | 75 | @discardableResult 76 | func hook( 77 | selector: Selector, 78 | strategy: AspectStrategy = .before, 79 | block: @escaping(AspectInfo, Arg1, Arg2, Arg3) -> Void) throws -> AspectToken 80 | { 81 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 82 | guard aspectInfo.arguments.count == 3, 83 | let arg1 = aspectInfo.arguments[0] as? Arg1, 84 | let arg2 = aspectInfo.arguments[1] as? Arg2, 85 | let arg3 = aspectInfo.arguments[2] as? Arg3 else { return } 86 | block(aspectInfo, arg1, arg2, arg3) 87 | } 88 | 89 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 90 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 91 | } 92 | 93 | @discardableResult 94 | func hook( 95 | selector: Selector, 96 | strategy: AspectStrategy = .before, 97 | block: @escaping(AspectInfo, Arg1, Arg2, Arg3, Arg4) -> Void) throws -> AspectToken 98 | { 99 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 100 | guard aspectInfo.arguments.count == 4, 101 | let arg1 = aspectInfo.arguments[0] as? Arg1, 102 | let arg2 = aspectInfo.arguments[1] as? Arg2, 103 | let arg3 = aspectInfo.arguments[2] as? Arg3, 104 | let arg4 = aspectInfo.arguments[3] as? Arg4 else { return } 105 | block(aspectInfo, arg1, arg2, arg3, arg4) 106 | } 107 | 108 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 109 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 110 | } 111 | 112 | @discardableResult 113 | func hook( 114 | selector: Selector, 115 | strategy: AspectStrategy = .before, 116 | block: @escaping(AspectInfo, Arg1, Arg2, Arg3, Arg4, Arg5) -> Void) throws -> AspectToken 117 | { 118 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 119 | guard aspectInfo.arguments.count == 5, 120 | let arg1 = aspectInfo.arguments[0] as? Arg1, 121 | let arg2 = aspectInfo.arguments[1] as? Arg2, 122 | let arg3 = aspectInfo.arguments[2] as? Arg3, 123 | let arg4 = aspectInfo.arguments[3] as? Arg4, 124 | let arg5 = aspectInfo.arguments[4] as? Arg5 else { 125 | return 126 | } 127 | block(aspectInfo, arg1, arg2, arg3, arg4, arg5) 128 | } 129 | 130 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 131 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 132 | } 133 | 134 | @discardableResult 135 | func hook( 136 | selector: Selector, 137 | strategy: AspectStrategy = .before, 138 | block: @escaping(AspectInfo, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> Void) throws -> AspectToken 139 | { 140 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 141 | guard aspectInfo.arguments.count == 6, 142 | let arg1 = aspectInfo.arguments[0] as? Arg1, 143 | let arg2 = aspectInfo.arguments[1] as? Arg2, 144 | let arg3 = aspectInfo.arguments[2] as? Arg3, 145 | let arg4 = aspectInfo.arguments[3] as? Arg4, 146 | let arg5 = aspectInfo.arguments[4] as? Arg5, 147 | let arg6 = aspectInfo.arguments[5] as? Arg6 else { 148 | return 149 | } 150 | block(aspectInfo, arg1, arg2, arg3, arg4, arg5, arg6) 151 | } 152 | 153 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 154 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 155 | } 156 | 157 | @discardableResult 158 | func hook( 159 | selector: Selector, 160 | strategy: AspectStrategy = .before, 161 | block: @escaping(AspectInfo, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> Void) throws -> AspectToken 162 | { 163 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 164 | guard aspectInfo.arguments.count == 7, 165 | let arg1 = aspectInfo.arguments[0] as? Arg1, 166 | let arg2 = aspectInfo.arguments[1] as? Arg2, 167 | let arg3 = aspectInfo.arguments[2] as? Arg3, 168 | let arg4 = aspectInfo.arguments[3] as? Arg4, 169 | let arg5 = aspectInfo.arguments[4] as? Arg5, 170 | let arg6 = aspectInfo.arguments[5] as? Arg6, 171 | let arg7 = aspectInfo.arguments[6] as? Arg7 else { 172 | return 173 | } 174 | block(aspectInfo, arg1, arg2, arg3, arg4, arg5, arg6, arg7) 175 | } 176 | 177 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 178 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 179 | } 180 | } 181 | 182 | public extension NSObject { 183 | @discardableResult 184 | class func hook( 185 | selector: Selector, 186 | strategy: AspectStrategy = .before, 187 | block: @escaping(AspectInfo) -> Void) throws -> AspectToken 188 | { 189 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 190 | block(aspectInfo) 191 | } 192 | 193 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 194 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 195 | } 196 | 197 | @discardableResult 198 | class func hook( 199 | selector: Selector, 200 | strategy: AspectStrategy = .before, 201 | block: @escaping(AspectInfo, Arg1) -> Void) throws -> AspectToken 202 | { 203 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 204 | guard aspectInfo.arguments.count == 1, 205 | let arg1 = aspectInfo.arguments[0] as? Arg1 else { return } 206 | block(aspectInfo, arg1) 207 | } 208 | 209 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 210 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 211 | } 212 | 213 | @discardableResult 214 | class func hook( 215 | selector: Selector, 216 | strategy: AspectStrategy = .before, 217 | block: @escaping(AspectInfo, Arg1, Arg2) -> Void) throws -> AspectToken 218 | { 219 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 220 | guard aspectInfo.arguments.count == 2, 221 | let arg1 = aspectInfo.arguments[0] as? Arg1, 222 | let arg2 = aspectInfo.arguments[1] as? Arg2 else { return } 223 | block(aspectInfo, arg1, arg2) 224 | } 225 | 226 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 227 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 228 | } 229 | 230 | @discardableResult 231 | class func hook( 232 | selector: Selector, 233 | strategy: AspectStrategy = .before, 234 | block: @escaping(AspectInfo, Arg1, Arg2, Arg3) -> Void) throws -> AspectToken 235 | { 236 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 237 | guard aspectInfo.arguments.count == 3, 238 | let arg1 = aspectInfo.arguments[0] as? Arg1, 239 | let arg2 = aspectInfo.arguments[1] as? Arg2, 240 | let arg3 = aspectInfo.arguments[2] as? Arg3 else { return } 241 | block(aspectInfo, arg1, arg2, arg3) 242 | } 243 | 244 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 245 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 246 | } 247 | 248 | @discardableResult 249 | class func hook( 250 | selector: Selector, 251 | strategy: AspectStrategy = .before, 252 | block: @escaping(AspectInfo, Arg1, Arg2, Arg3, Arg4) -> Void) throws -> AspectToken 253 | { 254 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 255 | guard aspectInfo.arguments.count == 4, 256 | let arg1 = aspectInfo.arguments[0] as? Arg1, 257 | let arg2 = aspectInfo.arguments[1] as? Arg2, 258 | let arg3 = aspectInfo.arguments[2] as? Arg3, 259 | let arg4 = aspectInfo.arguments[3] as? Arg4 else { return } 260 | block(aspectInfo, arg1, arg2, arg3, arg4) 261 | } 262 | 263 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 264 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 265 | } 266 | 267 | @discardableResult 268 | class func hook( 269 | selector: Selector, 270 | strategy: AspectStrategy = .before, 271 | block: @escaping(AspectInfo, Arg1, Arg2, Arg3, Arg4, Arg5) -> Void) throws -> AspectToken 272 | { 273 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 274 | guard aspectInfo.arguments.count == 5, 275 | let arg1 = aspectInfo.arguments[0] as? Arg1, 276 | let arg2 = aspectInfo.arguments[1] as? Arg2, 277 | let arg3 = aspectInfo.arguments[2] as? Arg3, 278 | let arg4 = aspectInfo.arguments[3] as? Arg4, 279 | let arg5 = aspectInfo.arguments[4] as? Arg5 else { 280 | return 281 | } 282 | block(aspectInfo, arg1, arg2, arg3, arg4, arg5) 283 | } 284 | 285 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 286 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 287 | } 288 | 289 | @discardableResult 290 | class func hook( 291 | selector: Selector, 292 | strategy: AspectStrategy = .before, 293 | block: @escaping(AspectInfo, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> Void) throws -> AspectToken 294 | { 295 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 296 | guard aspectInfo.arguments.count == 6, 297 | let arg1 = aspectInfo.arguments[0] as? Arg1, 298 | let arg2 = aspectInfo.arguments[1] as? Arg2, 299 | let arg3 = aspectInfo.arguments[2] as? Arg3, 300 | let arg4 = aspectInfo.arguments[3] as? Arg4, 301 | let arg5 = aspectInfo.arguments[4] as? Arg5, 302 | let arg6 = aspectInfo.arguments[5] as? Arg6 else { 303 | return 304 | } 305 | block(aspectInfo, arg1, arg2, arg3, arg4, arg5, arg6) 306 | } 307 | 308 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 309 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 310 | } 311 | 312 | @discardableResult 313 | class func hook( 314 | selector: Selector, 315 | strategy: AspectStrategy = .before, 316 | block: @escaping(AspectInfo, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> Void) throws -> AspectToken 317 | { 318 | let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in 319 | guard aspectInfo.arguments.count == 7, 320 | let arg1 = aspectInfo.arguments[0] as? Arg1, 321 | let arg2 = aspectInfo.arguments[1] as? Arg2, 322 | let arg3 = aspectInfo.arguments[2] as? Arg3, 323 | let arg4 = aspectInfo.arguments[3] as? Arg4, 324 | let arg5 = aspectInfo.arguments[4] as? Arg5, 325 | let arg6 = aspectInfo.arguments[5] as? Arg6, 326 | let arg7 = aspectInfo.arguments[6] as? Arg7 else { 327 | return 328 | } 329 | block(aspectInfo, arg1, arg2, arg3, arg4, arg5, arg6, arg7) 330 | } 331 | 332 | let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 333 | return try hook(selector: selector, strategy: strategy, block: wrappedObject) 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /Aspect/Aspect.h: -------------------------------------------------------------------------------- 1 | // 2 | // Aspect.h 3 | // Aspect 4 | // 5 | // Created by roy.cao on 2019/5/18. 6 | // Copyright © 2019 roy. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Aspect. 12 | FOUNDATION_EXPORT double AspectVersionNumber; 13 | 14 | //! Project version string for Aspect. 15 | FOUNDATION_EXPORT const unsigned char AspectVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | #import 20 | -------------------------------------------------------------------------------- /Aspect/Aspect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Aspect.swift 3 | // Aspect 4 | // 5 | // Created by roy.cao on 2019/5/18. 6 | // Copyright © 2019 roy. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import Foundation 27 | 28 | let lock = SpinLock() 29 | 30 | //@_silgen_name ("_objc_msgForward") 31 | //public func _as_objc_msgForward() -> IMP 32 | 33 | private enum Constants { 34 | static let subclassSuffix = "_Aspect_" 35 | static let forwardInvocationSelectorName = "__aspect_forwardInvocation:" 36 | } 37 | 38 | public enum AspectStrategy { 39 | case after /// Called after the original implementation (default) 40 | case instead /// Will replace the original implementation. 41 | case before /// Called before the original implementation. 42 | } 43 | 44 | public class AspectInfo: NSObject { 45 | 46 | let instance: AnyObject 47 | let originalInvocation: AnyObject 48 | var arguments: [Any?] { return unpackInvocation(originalInvocation) } 49 | 50 | init(instance: AnyObject, invocation: AnyObject) { 51 | self.instance = instance 52 | self.originalInvocation = invocation 53 | } 54 | } 55 | 56 | internal class AspectsCache { 57 | 58 | var beforeAspects: [AspectIdentifier] = [] 59 | var insteadAspects: [AspectIdentifier] = [] 60 | var afterAspects: [AspectIdentifier] = [] 61 | 62 | func add(_ aspect: AspectIdentifier, option: AspectStrategy) { 63 | switch option { 64 | case .before: 65 | beforeAspects.append(aspect) 66 | case .instead: 67 | insteadAspects.append(aspect) 68 | case .after: 69 | afterAspects.append(aspect) 70 | } 71 | } 72 | 73 | // TODO: remove aspect. 74 | func remove(_ aspect: AspectIdentifier) { 75 | 76 | } 77 | 78 | func hasAspects() -> Bool { 79 | return !(beforeAspects.isEmpty && insteadAspects.isEmpty && afterAspects.isEmpty) 80 | } 81 | } 82 | 83 | extension NSObject { 84 | 85 | /// Hook an object selector. 86 | /// 87 | /// - Parameters: 88 | /// - selector: The selector need to hook. 89 | /// - strategy: The hook strategy, `.before` by default. 90 | /// - block: The hook block. 91 | /// 92 | /// - returns: AspectToken, you can use it to remove hook. 93 | public func hook(selector: Selector, strategy: AspectStrategy = .before, block: AnyObject) throws -> AspectToken { 94 | return try ahook(object: self, selector: selector, strategy: strategy, block: block) 95 | } 96 | } 97 | 98 | extension NSObject { 99 | 100 | /// Hook an object selector. 101 | /// 102 | /// - Parameters: 103 | /// - selector: The selector need to hook. 104 | /// - strategy: The hook strategy, `.before` by default. 105 | /// - block: The hook block. 106 | /// - returns: AspectToken, you can use it to remove hook. 107 | public class func hook(selector: Selector, strategy: AspectStrategy, block: AnyObject) throws -> AspectToken { 108 | return try ahook(object: self, selector: selector, strategy: strategy, block: block) 109 | } 110 | } 111 | 112 | func ahook(object: AnyObject, selector: Selector, strategy: AspectStrategy, block: AnyObject) throws -> AspectToken { 113 | return try lock.performLocked { 114 | let identifier = try AspectIdentifier.identifier(with: selector, object: object, strategy: strategy, block: block) 115 | 116 | let cache = getAspectCache(for: object, selector: selector) 117 | cache.add(identifier, option: strategy) 118 | 119 | let subclass: AnyClass = try hookClass(object: object, selector: selector) 120 | let method = class_getInstanceMethod(subclass, selector) ?? class_getClassMethod(subclass, selector) 121 | 122 | guard let impl = method.map(method_getImplementation), let typeEncoding = method.flatMap(method_getTypeEncoding) else { 123 | throw AspectError.unrecognizedSelector 124 | } 125 | 126 | assert(checkTypeEncoding(typeEncoding)) 127 | 128 | let aliasSelector = selector.alias 129 | 130 | if !subclass.instancesRespond(to: aliasSelector) { 131 | let succeeds = class_addMethod(subclass, aliasSelector, impl, typeEncoding) 132 | precondition(succeeds, "Aspect attempts to swizzle a selector that has message forwarding enabled with a runtime injected implementation. This is unsupported in the current version.") 133 | } 134 | 135 | class_addMethod(subclass, aliasSelector, impl, typeEncoding) 136 | class_replaceMethod(subclass, selector, _aspect_objc_msgForward, typeEncoding) 137 | return identifier 138 | } 139 | } 140 | 141 | private func hookClass(object: AnyObject, selector: Selector) throws -> AnyClass { 142 | let perceivedClass: AnyClass = object.objcClass 143 | let realClass: AnyClass = object_getClass(object)! 144 | let className = String(cString: class_getName(realClass)) 145 | 146 | if className.hasPrefix(Constants.subclassSuffix) { 147 | return realClass 148 | } else if class_isMetaClass(realClass) { 149 | if class_getInstanceMethod(perceivedClass, selector) != nil { 150 | swizzleForwardInvocation(perceivedClass) 151 | return perceivedClass 152 | } else { 153 | swizzleForwardInvocation(realClass) 154 | return realClass 155 | } 156 | } 157 | 158 | let subclassName = Constants.subclassSuffix+className 159 | let subclass: AnyClass? = subclassName.withCString { cString in 160 | if let existingClass = objc_getClass(cString) as! AnyClass? { 161 | return existingClass 162 | } else { 163 | if let subclass: AnyClass = objc_allocateClassPair(perceivedClass, cString, 0) { 164 | swizzleForwardInvocation(subclass) 165 | replaceGetClass(in: subclass, decoy: perceivedClass) 166 | objc_registerClassPair(subclass) 167 | return subclass 168 | } else { 169 | return nil 170 | } 171 | } 172 | } 173 | 174 | guard let nonnullSubclass = subclass else { 175 | throw AspectError.failedToAllocateClassPair 176 | } 177 | 178 | object_setClass(object, nonnullSubclass) 179 | return nonnullSubclass 180 | } 181 | 182 | private func swizzleForwardInvocation(_ realClass: AnyClass) { 183 | guard let originalImplementation = class_replaceMethod(realClass, 184 | ObjCSelector.forwardInvocation, 185 | imp_implementationWithBlock(aspectForwardInvocation as Any), 186 | ObjCMethodEncoding.forwardInvocation) else { 187 | return 188 | } 189 | class_addMethod(realClass, NSSelectorFromString(Constants.forwardInvocationSelectorName), originalImplementation, ObjCMethodEncoding.forwardInvocation) 190 | } 191 | 192 | private let aspectForwardInvocation: @convention(block) (Unmanaged, AnyObject) -> Void = { objectRef, invocation in 193 | let object = objectRef.takeUnretainedValue() as AnyObject 194 | let selector = invocation.selector! 195 | 196 | var aliasSelector = selector.alias 197 | var aliasSelectorKey = AssociationKey(aliasSelector) 198 | 199 | let selectorKey = AssociationKey(selector) 200 | let associations = Associations(object.objcClass as AnyObject) 201 | 202 | let aspectCache: AspectsCache 203 | 204 | if let cache = associations.value(forKey: selectorKey) { 205 | aspectCache = cache 206 | } else if let cache = (objectRef.takeUnretainedValue() as NSObject).associations.value(forKey: aliasSelectorKey) { 207 | aspectCache = cache 208 | } else { 209 | return 210 | } 211 | 212 | var info = AspectInfo(instance: object, invocation: invocation) 213 | 214 | // Before hooks. 215 | aspectCache.beforeAspects.invoke(with: &info) 216 | 217 | if !aspectCache.insteadAspects.isEmpty { 218 | // Instead hooks 219 | aspectCache.insteadAspects.invoke(with: &info) 220 | } else { 221 | invocation.setSelector(aliasSelector) 222 | invocation.invoke() 223 | } 224 | 225 | // After hooks. 226 | aspectCache.afterAspects.invoke(with: &info) 227 | } 228 | 229 | private func replaceGetClass(in class: AnyClass, decoy perceivedClass: AnyClass) { 230 | let getClass: @convention(block) (UnsafeRawPointer?) -> AnyClass = { _ in 231 | perceivedClass 232 | } 233 | 234 | let impl = imp_implementationWithBlock(getClass as Any) 235 | 236 | _ = class_replaceMethod(`class`, 237 | ObjCSelector.getClass, 238 | impl, 239 | ObjCMethodEncoding.getClass) 240 | 241 | _ = class_replaceMethod(object_getClass(`class`), 242 | ObjCSelector.getClass, 243 | impl, 244 | ObjCMethodEncoding.getClass) 245 | } 246 | 247 | func getAspectCache(for object: AnyObject, selector: Selector) -> AspectsCache { 248 | let realClass: AnyClass = object_getClass(object)! 249 | let selectorKey: AssociationKey 250 | 251 | if class_isMetaClass(realClass) { 252 | selectorKey = AssociationKey(selector) 253 | } else { 254 | let aliasSelector = selector.alias 255 | selectorKey = AssociationKey(aliasSelector) 256 | } 257 | var aspectCache = (object as! NSObject).associations.value(forKey: selectorKey) 258 | 259 | if aspectCache == nil { 260 | aspectCache = AspectsCache() 261 | (object as! NSObject).associations.setValue(aspectCache, forKey: selectorKey) 262 | } 263 | 264 | return aspectCache! 265 | } 266 | 267 | // TODO: remove aspect. 268 | func remove(_ aspect: AspectIdentifier) { 269 | return lock.performLocked { 270 | guard let object = aspect.object else { return } 271 | 272 | let aspectCache = getAspectCache(for: object, selector: aspect.selector) 273 | aspectCache.remove(aspect) 274 | } 275 | } 276 | 277 | extension Collection where Iterator.Element == AspectIdentifier { 278 | 279 | func invoke(with info: inout AspectInfo) { 280 | self.forEach { $0.invoke(with: &info) } 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /Aspect/AspectBlock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AspectBlock.swift 3 | // Aspect 4 | // 5 | // This is the original copyright notice: 6 | // Copyright © 2017 Brandon. All rights reserved. 7 | 8 | import Foundation 9 | 10 | // Block internals. 11 | internal class AspectBlock { 12 | 13 | private let block: AnyObject 14 | 15 | init(_ block: AnyObject) { 16 | self.block = block 17 | } 18 | 19 | var blockSignature: AnyObject? { 20 | return self.signature != nil ? NSMethodSignature.signature(objCTypes: self.signature!) : nil 21 | } 22 | 23 | private var signature: String? { 24 | let block = unsafeBitCast(self.block, to: UnsafePointer.self).pointee 25 | let descriptor = block.descriptor.pointee 26 | 27 | let signatureFlag: UInt32 = 1 << 30 28 | 29 | if block.flags & signatureFlag != 0 { 30 | let signature = String(cString: descriptor.signature) 31 | return signature 32 | } 33 | return nil 34 | } 35 | 36 | private struct BlockInfo { 37 | var isa: UnsafeRawPointer 38 | var flags: UInt32 39 | var reserved: UInt32 40 | var invoke: UnsafeRawPointer 41 | var descriptor: UnsafePointer 42 | } 43 | 44 | private struct BlockDescriptor { 45 | var reserved: UInt 46 | var size: UInt 47 | 48 | var copy_helper: UnsafeRawPointer 49 | var dispose_helper: UnsafeRawPointer 50 | var signature: UnsafePointer 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Aspect/AspectError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AspectError.swift 3 | // Aspect 4 | // 5 | // Created by roy.cao on 2019/5/28. 6 | // Copyright © 2019 roy. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import Foundation 27 | 28 | /// `AspectError` is the error type returned by Aspect. It encompasses a few different types of errors. 29 | public enum AspectError: Error { 30 | case unrecognizedSelector 31 | case blockSignatureNotMatch 32 | case missingBlockSignature 33 | case failedToAllocateClassPair 34 | } 35 | -------------------------------------------------------------------------------- /Aspect/AspectIdentifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AspectIdentifier.swift 3 | // Aspect 4 | // 5 | // Created by roy.cao on 2019/5/26. 6 | // Copyright © 2019 roy. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import Foundation 27 | 28 | public protocol AspectToken { 29 | func remove() 30 | } 31 | 32 | extension AspectIdentifier: AspectToken { 33 | 34 | public func remove() { 35 | Aspect.remove(self) 36 | } 37 | } 38 | 39 | internal struct AspectIdentifier { 40 | 41 | let selector: Selector 42 | weak var object: AnyObject? 43 | let strategy: AspectStrategy 44 | let block: AnyObject 45 | var blockSignature: AnyObject? 46 | 47 | init(selector: Selector, object: AnyObject, strategy: AspectStrategy, block: AnyObject) { 48 | self.selector = selector 49 | self.object = object 50 | self.strategy = strategy 51 | self.block = block 52 | } 53 | 54 | func invoke(with info: inout AspectInfo) { 55 | guard let signature = blockSignature, let numberOfArguments = signature.objcNumberOfArguments else { return } 56 | let invocation = NSInvocation.invocation(methodSignature: signature) 57 | 58 | if numberOfArguments > 1 { 59 | invocation.setArgument(&info, at: 1) 60 | } 61 | invocation.invoke(withTarget: self.block) 62 | } 63 | 64 | /// Create a AspectIdentifier 65 | /// 66 | /// - Parameters: 67 | /// - selector: The selector need to hook. 68 | /// - object: The object/class. 69 | /// - strategy: The hook strategy. 70 | /// - block: The hook strategy. 71 | static func identifier(with selector: Selector, object: AnyObject, strategy: AspectStrategy, block: AnyObject) throws -> AspectIdentifier { 72 | guard let blockSignature = AspectBlock(block).blockSignature else { 73 | throw AspectError.missingBlockSignature 74 | } 75 | 76 | do { 77 | try isCompatibleBlockSignature(blockSignature: blockSignature, object: object, selector: selector) 78 | var aspectIdentifier = AspectIdentifier(selector: selector, object: object, strategy: strategy, block: block) 79 | aspectIdentifier.blockSignature = blockSignature 80 | return aspectIdentifier 81 | } catch { 82 | throw error 83 | } 84 | } 85 | 86 | /// Compare block Signature and object method Signature are the same 87 | /// 88 | /// - Parameters: 89 | /// - blockSignature: The block Signature. 90 | /// - object: The object/class. 91 | /// - selector: The selector need to hook. 92 | static func isCompatibleBlockSignature(blockSignature: AnyObject, object: AnyObject, selector: Selector) throws { 93 | let perceivedClass: AnyClass = object.objcClass 94 | let realClass: AnyClass = object_getClass(object)! 95 | let method = class_getInstanceMethod(perceivedClass, selector) ?? class_getClassMethod(realClass, selector) 96 | 97 | guard let nonnullMethod = method, let typeEncoding = method_getTypeEncoding(nonnullMethod) else { 98 | object.doesNotRecognizeSelector?(selector) 99 | throw AspectError.unrecognizedSelector 100 | } 101 | 102 | let signature = NSMethodSignature.signature(objCTypes: typeEncoding) 103 | var signaturesMatch = true 104 | 105 | if blockSignature.objcNumberOfArguments > signature.objcNumberOfArguments { 106 | signaturesMatch = false 107 | } else { 108 | if blockSignature.objcNumberOfArguments > 1 { 109 | let rawEncoding = blockSignature.getArgumentType(at: UInt(1)) 110 | let encoding = ObjCTypeEncoding(rawValue: rawEncoding.pointee) ?? .undefined 111 | if encoding != .object { 112 | signaturesMatch = false 113 | } 114 | } 115 | 116 | if signaturesMatch { 117 | for index in 2 ..< blockSignature.objcNumberOfArguments { 118 | let methodRawEncoding = signature.getArgumentType(at: index) 119 | let blockRawEncoding = blockSignature.getArgumentType(at: index) 120 | 121 | let methodEncoding = ObjCTypeEncoding(rawValue: methodRawEncoding.pointee) ?? .undefined 122 | let blockEncoding = ObjCTypeEncoding(rawValue: blockRawEncoding.pointee) ?? .undefined 123 | 124 | if methodEncoding != blockEncoding { 125 | signaturesMatch = false 126 | break 127 | } 128 | } 129 | } 130 | } 131 | 132 | if !signaturesMatch { 133 | throw AspectError.blockSignatureNotMatch 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Aspect/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Aspect/Lock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Lock.swift 3 | // Aspect 4 | // 5 | // Created by roy.cao on 2019/5/26. 6 | // Copyright © 2019 roy. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import Foundation 27 | 28 | internal protocol Lock { 29 | func lock() 30 | func unlock() 31 | } 32 | 33 | @available(iOS 10.0, OSX 10.12, watchOS 3.0, tvOS 10.0, *) 34 | internal class UnfairLock: Lock { 35 | private var unfairLock = os_unfair_lock_s() 36 | 37 | func lock() { 38 | os_unfair_lock_lock(&unfairLock) 39 | } 40 | 41 | func unlock() { 42 | os_unfair_lock_unlock(&unfairLock) 43 | } 44 | } 45 | 46 | internal class MutexLock: Lock { 47 | private var mutex = NSLock() 48 | 49 | func lock() { 50 | mutex.lock() 51 | } 52 | 53 | func unlock() { 54 | mutex.unlock() 55 | } 56 | } 57 | 58 | internal class SpinLock: Lock { 59 | private let locker: Lock 60 | 61 | init() { 62 | if #available(iOS 10.0, macOS 10.12, watchOS 3.0, tvOS 10.0, *) { 63 | locker = UnfairLock() 64 | } else { 65 | locker = MutexLock() 66 | } 67 | } 68 | 69 | func lock() { 70 | locker.lock() 71 | } 72 | 73 | func unlock() { 74 | locker.unlock() 75 | } 76 | } 77 | 78 | internal extension SpinLock { 79 | 80 | @inline(__always) 81 | final func performLocked(_ action: () throws -> Result) rethrows -> Result { 82 | lock(); defer { unlock() } 83 | return try action() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Aspect/NSObject.Association.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject.Association.swift 3 | // Aspect 4 | // 5 | // This is the original copyright notice: 6 | // Copyright ReactiveCocoa. All rights reserved. 7 | 8 | import Foundation 9 | 10 | internal struct AssociationKey { 11 | fileprivate let address: UnsafeRawPointer 12 | fileprivate let `default`: Value! 13 | 14 | /// Create an ObjC association key. 15 | /// 16 | /// - warning: The key must be uniqued. 17 | /// 18 | /// - parameters: 19 | /// - default: The default value, or `nil` to trap on undefined value. It is 20 | /// ignored if `Value` is an optional. 21 | init(default: Value? = nil) { 22 | self.address = UnsafeRawPointer(UnsafeMutablePointer.allocate(capacity: 1)) 23 | self.default = `default` 24 | } 25 | 26 | /// Create an ObjC association key from a `StaticString`. 27 | /// 28 | /// - precondition: `key` has a pointer representation. 29 | /// 30 | /// - parameters: 31 | /// - default: The default value, or `nil` to trap on undefined value. It is 32 | /// ignored if `Value` is an optional. 33 | init(_ key: StaticString, default: Value? = nil) { 34 | assert(key.hasPointerRepresentation) 35 | self.address = UnsafeRawPointer(key.utf8Start) 36 | self.default = `default` 37 | } 38 | 39 | /// Create an ObjC association key from a `Selector`. 40 | /// 41 | /// - parameters: 42 | /// - default: The default value, or `nil` to trap on undefined value. It is 43 | /// ignored if `Value` is an optional. 44 | init(_ key: Selector, default: Value? = nil) { 45 | self.address = UnsafeRawPointer(key.utf8Start) 46 | self.default = `default` 47 | } 48 | } 49 | 50 | internal struct Associations { 51 | fileprivate let base: Base 52 | 53 | init(_ base: Base) { 54 | self.base = base 55 | } 56 | } 57 | 58 | extension NSObjectProtocol { 59 | @nonobjc internal var associations: Associations { 60 | return Associations(self) 61 | } 62 | } 63 | 64 | extension Associations { 65 | /// Retrieve the associated value for the specified key. 66 | /// 67 | /// - parameters: 68 | /// - key: The key. 69 | /// 70 | /// - returns: The associated value, or the default value if no value has been 71 | /// associated with the key. 72 | internal func value(forKey key: AssociationKey) -> Value { 73 | return (objc_getAssociatedObject(base, key.address) as! Value?) ?? key.default 74 | } 75 | 76 | /// Retrieve the associated value for the specified key. 77 | /// 78 | /// - parameters: 79 | /// - key: The key. 80 | /// 81 | /// - returns: The associated value, or `nil` if no value is associated with 82 | /// the key. 83 | internal func value(forKey key: AssociationKey) -> Value? { 84 | return objc_getAssociatedObject(base, key.address) as! Value? 85 | } 86 | 87 | /// Set the associated value for the specified key. 88 | /// 89 | /// - parameters: 90 | /// - value: The value to be associated. 91 | /// - key: The key. 92 | internal func setValue(_ value: Value, forKey key: AssociationKey) { 93 | objc_setAssociatedObject(base, key.address, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 94 | } 95 | 96 | /// Set the associated value for the specified key. 97 | /// 98 | /// - parameters: 99 | /// - value: The value to be associated. 100 | /// - key: The key. 101 | internal func setValue(_ value: Value?, forKey key: AssociationKey) { 102 | objc_setAssociatedObject(base, key.address, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Aspect/Objc/ObjCRuntimeAliases.h: -------------------------------------------------------------------------------- 1 | // 2 | // ObjCRuntimeAliases.h 3 | // Aspect 4 | // 5 | // Created by roy.cao on 2019/4/14. 6 | // Copyright © 2019 roy. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | extern const IMP _aspect_objc_msgForward; 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /Aspect/Objc/ObjCRuntimeAliases.m: -------------------------------------------------------------------------------- 1 | // 2 | // ObjCRuntimeAliases.m 3 | // Aspect 4 | // 5 | // This is the original copyright notice: 6 | // Copyright ReactiveCocoa. All rights reserved. 7 | 8 | #import 9 | #import 10 | 11 | const IMP _aspect_objc_msgForward = _objc_msgForward; 12 | 13 | -------------------------------------------------------------------------------- /Aspect/Runtime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Runtime.swift 3 | // Aspect 4 | // 5 | // This is the original copyright notice: 6 | // Copyright ReactiveCocoa. All rights reserved. 7 | 8 | import Foundation 9 | 10 | // Unavailable selectors in Swift. 11 | internal enum ObjCSelector { 12 | static let forwardInvocation = Selector((("forwardInvocation:"))) 13 | static let methodSignatureForSelector = Selector((("methodSignatureForSelector:"))) 14 | static let getClass = Selector((("class"))) 15 | } 16 | 17 | // Method encoding of the unavailable selectors. 18 | internal enum ObjCMethodEncoding { 19 | static let forwardInvocation = extract("v@:@") 20 | static let methodSignatureForSelector = extract("v@::") 21 | static let getClass = extract("#@:") 22 | 23 | private static func extract(_ string: StaticString) -> UnsafePointer { 24 | return UnsafeRawPointer(string.utf8Start).assumingMemoryBound(to: CChar.self) 25 | } 26 | } 27 | 28 | internal let NSInvocation: AnyClass = NSClassFromString("NSInvocation")! 29 | internal let NSMethodSignature: AnyClass = NSClassFromString("NSMethodSignature")! 30 | 31 | // Signatures defined in `@objc` protocols would be available for ObjC message, sending via `AnyObject`. 32 | @objc internal protocol ObjCClassReporting { 33 | // An alias for `-class`, which is unavailable in Swift. 34 | @objc(class) 35 | var objcClass: AnyClass! { get } 36 | 37 | @objc(methodSignatureForSelector:) 38 | func objcMethodSignature(for selector: Selector) -> AnyObject 39 | } 40 | 41 | // Methods of `NSInvocation`. 42 | @objc internal protocol ObjCInvocation { 43 | @objc(setSelector:) 44 | func setSelector(_ selector: Selector) 45 | 46 | @objc(target) 47 | var objcTarget: AnyObject { get } 48 | 49 | @objc(methodSignature) 50 | var objcMethodSignature: AnyObject { get } 51 | 52 | @objc(getArgument:atIndex:) 53 | func getArgument(_ argumentLocation: UnsafeMutableRawPointer, atIndex idx: Int) 54 | 55 | @objc(setArgument:atIndex:) 56 | func setArgument(_ argumentLocation: UnsafeMutableRawPointer, atIndex idx: Int) 57 | 58 | @objc(invoke) 59 | func invoke() 60 | 61 | @objc(invokeWithTarget:) 62 | func invoke(target: AnyObject) 63 | 64 | @objc(invocationWithMethodSignature:) 65 | static func invocation(methodSignature: AnyObject) -> AnyObject 66 | } 67 | 68 | // Methods of `NSMethodSignature`. 69 | @objc internal protocol ObjCMethodSignature { 70 | @objc(numberOfArguments) 71 | var objcNumberOfArguments: UInt { get } 72 | 73 | @objc(getArgumentTypeAtIndex:) 74 | func getArgumentType(at index: UInt) -> UnsafePointer 75 | 76 | @objc(signatureWithObjCTypes:) 77 | static func signature(objCTypes: UnsafePointer) -> AnyObject 78 | } 79 | 80 | /// Objective-C type encoding. 81 | /// 82 | /// The enum does not cover all options, but only those that are expressive in 83 | /// Swift. 84 | internal enum ObjCTypeEncoding: Int8 { 85 | case char = 99 86 | case int = 105 87 | case short = 115 88 | case long = 108 89 | case longLong = 113 90 | 91 | case unsignedChar = 67 92 | case unsignedInt = 73 93 | case unsignedShort = 83 94 | case unsignedLong = 76 95 | case unsignedLongLong = 81 96 | 97 | case float = 102 98 | case double = 100 99 | 100 | case bool = 66 101 | 102 | case object = 64 103 | case type = 35 104 | case selector = 58 105 | 106 | case undefined = -1 107 | } 108 | 109 | internal func unpackInvocation(_ invocation: AnyObject) -> [Any?] { 110 | let invocation = invocation as AnyObject 111 | let methodSignature = invocation.objcMethodSignature! 112 | let count = methodSignature.objcNumberOfArguments! 113 | 114 | var bridged = [Any?]() 115 | bridged.reserveCapacity(Int(count - 2)) 116 | 117 | // Ignore `self` and `_cmd` at index 0 and 1. 118 | for position in 2 ..< count { 119 | let rawEncoding = methodSignature.getArgumentType(at: position) 120 | let encoding = ObjCTypeEncoding(rawValue: rawEncoding.pointee) ?? .undefined 121 | 122 | func extract(_ type: U.Type) -> U { 123 | let pointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout.size, 124 | alignment: MemoryLayout.alignment) 125 | defer { 126 | pointer.deallocate() 127 | } 128 | 129 | invocation.getArgument(pointer, atIndex: Int(position)) 130 | return pointer.assumingMemoryBound(to: type).pointee 131 | } 132 | 133 | let value: Any? 134 | 135 | switch encoding { 136 | case .char: 137 | value = NSNumber(value: extract(CChar.self)) 138 | case .int: 139 | value = NSNumber(value: extract(CInt.self)) 140 | case .short: 141 | value = NSNumber(value: extract(CShort.self)) 142 | case .long: 143 | value = NSNumber(value: extract(CLong.self)) 144 | case .longLong: 145 | value = NSNumber(value: extract(CLongLong.self)) 146 | case .unsignedChar: 147 | value = NSNumber(value: extract(CUnsignedChar.self)) 148 | case .unsignedInt: 149 | value = NSNumber(value: extract(CUnsignedInt.self)) 150 | case .unsignedShort: 151 | value = NSNumber(value: extract(CUnsignedShort.self)) 152 | case .unsignedLong: 153 | value = NSNumber(value: extract(CUnsignedLong.self)) 154 | case .unsignedLongLong: 155 | value = NSNumber(value: extract(CUnsignedLongLong.self)) 156 | case .float: 157 | value = NSNumber(value: extract(CFloat.self)) 158 | case .double: 159 | value = NSNumber(value: extract(CDouble.self)) 160 | case .bool: 161 | value = NSNumber(value: extract(CBool.self)) 162 | case .object: 163 | value = extract((AnyObject?).self) 164 | case .type: 165 | value = extract((AnyClass?).self) 166 | case .selector: 167 | value = extract((Selector?).self) 168 | case .undefined: 169 | var size = 0, alignment = 0 170 | NSGetSizeAndAlignment(rawEncoding, &size, &alignment) 171 | let buffer = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment) 172 | defer { buffer.deallocate() } 173 | 174 | invocation.getArgument(buffer, atIndex: Int(position)) 175 | value = NSValue(bytes: buffer, objCType: rawEncoding) 176 | } 177 | 178 | bridged.append(value) 179 | } 180 | 181 | return bridged 182 | } 183 | 184 | extension Selector { 185 | 186 | internal var utf8Start: UnsafePointer { 187 | return unsafeBitCast(self, to: UnsafePointer.self) 188 | } 189 | 190 | /// An alias of `self`, used in method interception. 191 | internal var alias: Selector { 192 | return prefixing("aspect_") 193 | } 194 | 195 | internal func prefixing(_ prefix: StaticString) -> Selector { 196 | let length = Int(strlen(utf8Start)) 197 | let prefixedLength = length + prefix.utf8CodeUnitCount 198 | 199 | let asciiPrefix = UnsafeRawPointer(prefix.utf8Start).assumingMemoryBound(to: Int8.self) 200 | 201 | let cString = UnsafeMutablePointer.allocate(capacity: prefixedLength + 1) 202 | defer { 203 | cString.deinitialize(count: prefixedLength + 1) 204 | cString.deallocate() 205 | } 206 | 207 | cString.initialize(from: asciiPrefix, count: prefix.utf8CodeUnitCount) 208 | (cString + prefix.utf8CodeUnitCount).initialize(from: utf8Start, count: length) 209 | (cString + prefixedLength).initialize(to: Int8(UInt8(ascii: "\0"))) 210 | 211 | return sel_registerName(cString) 212 | } 213 | } 214 | 215 | internal func checkTypeEncoding(_ types: UnsafePointer) -> Bool { 216 | // Some types, including vector types, are not encoded. In these cases the 217 | // signature starts with the size of the argument frame. 218 | assert(types.pointee < Int8(UInt8(ascii: "1")) || types.pointee > Int8(UInt8(ascii: "9")), 219 | "unknown method return type not supported in type encoding: \(String(cString: types))") 220 | 221 | assert(types.pointee != Int8(UInt8(ascii: "(")), "union method return type not supported") 222 | assert(types.pointee != Int8(UInt8(ascii: "{")), "struct method return type not supported") 223 | assert(types.pointee != Int8(UInt8(ascii: "[")), "array method return type not supported") 224 | 225 | assert(types.pointee != Int8(UInt8(ascii: "j")), "complex method return type not supported") 226 | 227 | return true 228 | } 229 | -------------------------------------------------------------------------------- /AspectTests/AspectTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AspectTests.swift 3 | // AspectTests 4 | // 5 | // Created by roy.cao on 2019/6/1. 6 | // Copyright © 2019 roy. All rights reserved. 7 | 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import XCTest 27 | @testable import Aspect 28 | 29 | public class Test: NSObject { 30 | 31 | @objc dynamic func test(id: Int, name: String) { 32 | print(id) 33 | print(name) 34 | } 35 | 36 | @objc dynamic static func classSelector(id: Int, name: String) { 37 | print(id) 38 | print(name) 39 | } 40 | } 41 | 42 | class TestA: Test { 43 | 44 | override dynamic func test(id: Int, name: String) { 45 | print(id) 46 | print(name) 47 | } 48 | } 49 | 50 | class TestB: Test { 51 | 52 | override dynamic func test(id: Int, name: String) { 53 | print(id) 54 | print(name) 55 | } 56 | } 57 | 58 | class AspectTests: XCTestCase { 59 | 60 | func testHookObjectSelectorByInstance() { 61 | let test = Test() 62 | 63 | let expectation = self.expectation(description: "Hook the selector test of Test instance ok") 64 | _ = try? test.hook(selector: #selector(Test.test(id:name:)), strategy: .before) { (_, id: Int, name: String) in 65 | XCTAssertEqual(id, 1) 66 | XCTAssertEqual(name, "roy") 67 | expectation.fulfill() 68 | } 69 | 70 | test.test(id: 1, name: "roy") 71 | 72 | self.waitForExpectations(timeout: 2) { error in 73 | XCTAssertNil(error) 74 | } 75 | } 76 | 77 | func testHookObjectSelectorByClasse() { 78 | let test = Test() 79 | 80 | let expectation = self.expectation(description: "Hook the selector test of Test class ok") 81 | _ = try? Test.hook(selector: #selector(Test.test(id:name:)), strategy: .before) { (_, id: Int, name: String) in 82 | XCTAssertEqual(id, 1) 83 | XCTAssertEqual(name, "roy") 84 | expectation.fulfill() 85 | } 86 | 87 | test.test(id: 1, name: "roy") 88 | 89 | self.waitForExpectations(timeout: 2) { error in 90 | XCTAssertNil(error) 91 | } 92 | } 93 | 94 | func testHookObjectSelectorByClasseAndInstance() { 95 | let test = Test() 96 | 97 | let expectation = self.expectation(description: "Hook the selector test of Test instance and class ok") 98 | _ = try? Test.hook(selector: #selector(Test.test(id:name:)), strategy: .before) { (_, id: Int, name: String) in 99 | XCTAssertEqual(id, 1) 100 | XCTAssertEqual(name, "roy") 101 | expectation.fulfill() 102 | } 103 | 104 | _ = try? test.hook(selector: #selector(Test.test(id:name:)), strategy: .before) { (_, id: Int, name: String) in 105 | XCTAssertEqual(id, 1) 106 | XCTAssertEqual(name, "roy") 107 | expectation.fulfill() 108 | } 109 | 110 | test.test(id: 1, name: "roy") 111 | 112 | self.waitForExpectations(timeout: 2) { error in 113 | XCTAssertNil(error) 114 | } 115 | } 116 | 117 | func testHookClassSelector() { 118 | let expectation = self.expectation(description: "Hook the selector test of Test class ok") 119 | _ = try? Test.hook(selector: #selector(Test.classSelector(id:name:)), strategy: .before) { (_, id: Int, name: String) in 120 | XCTAssertEqual(id, 1) 121 | XCTAssertEqual(name, "roy") 122 | expectation.fulfill() 123 | } 124 | 125 | Test.classSelector(id: 1, name: "roy") 126 | 127 | self.waitForExpectations(timeout: 2) { error in 128 | XCTAssertNil(error) 129 | } 130 | } 131 | 132 | func testHookInTheSameHierarchy() { 133 | let testA = TestA() 134 | 135 | let expectationA = self.expectation(description: "Hook the selector test of Test class ok") 136 | _ = try? TestA.hook(selector: #selector(TestA.test(id:name:)), strategy: .before) { (_, id: Int, name: String) in 137 | XCTAssertEqual(id, 1) 138 | XCTAssertEqual(name, "roy") 139 | expectationA.fulfill() 140 | } 141 | 142 | testA.test(id: 1, name: "roy") 143 | 144 | let testB = TestB() 145 | 146 | let expectationB = self.expectation(description: "Hook the selector test of Test class ok") 147 | _ = try? TestB.hook(selector: #selector(TestB.test(id:name:)), strategy: .before) { (_, id: Int, name: String) in 148 | XCTAssertEqual(id, 1) 149 | XCTAssertEqual(name, "roy") 150 | expectationB.fulfill() 151 | } 152 | 153 | testB.test(id: 1, name: "roy") 154 | 155 | self.waitForExpectations(timeout: 2) { error in 156 | XCTAssertNil(error) 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /AspectTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Images/aspect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woshiccm/Aspect/0e8e398119df6f3ba5b23074e137f8193731f7ff/Images/aspect.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2019 roy 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 | 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Aspect", 8 | products: [ 9 | .library( 10 | name: "Aspect", 11 | targets: ["Aspect"]) 12 | ], 13 | targets: [ 14 | .target( 15 | name: "Aspect", 16 | path: "Aspect") 17 | ], 18 | swiftLanguageVersions: [.v4, .v5] 19 | ) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | ![badge-pms](https://img.shields.io/badge/languages-Swift-orange.svg) 7 | [![Swift Version](https://img.shields.io/badge/Swift-4.0--5.0.x-F16D39.svg?style=flat)](https://developer.apple.com/swift) 8 | 9 | 10 | Aspect is a lightweight, pure-Swift library for for aspect oriented programming. This project is heavily inspired by the popular [Aspects](https://github.com/steipete/Aspects). It provides you a chance to use a pure-Swift alternative in your next app. 11 | 12 | ## Features 13 | 14 | - [x] Hook object selector 15 | - [x] Hook different classes selector in the same hierarchy 16 | - [x] Hook class and static selector 17 | - [x] Provide more friendly Swift interfaces 18 | 19 | ## Usage 20 | ### Hook object selector with OC block 21 | 22 | ``` 23 | public class Test: NSObject { 24 | 25 | @objc dynamic func test(id: Int, name: String) { 26 | 27 | } 28 | 29 | @objc dynamic static func classSelector(id: Int, name: String) { 30 | 31 | } 32 | } 33 | 34 | let test = Test() 35 | 36 | let wrappedBlock: @convention(block) (AspectInfo, Int, String) -> Void = { aspectInfo, id, name in 37 | 38 | } 39 | let block: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self) 40 | test.hook(selector: #selector(Test.test(id:name:)), strategy: .before, block: ) 41 | ``` 42 | 43 | ### Hook object selector with Swift block 44 | 45 | ``` 46 | let test = Test() 47 | 48 | _ = try? test.hook(selector: #selector(Test.test(id:name:)), strategy: .before) { (_, id: Int, name: String) in 49 | 50 | } 51 | 52 | ``` 53 | 54 | ### Hook class instance selector with Swift block 55 | 56 | ``` 57 | _ = try? Test.hook(selector: #selector(Test.test(id:name:)), strategy: .before) { (_, id: Int, name: String) in 58 | 59 | } 60 | 61 | ``` 62 | 63 | ### Hook class class and static selector with Swift block 64 | ``` 65 | 66 | _ = try? Test.hook(selector: #selector(Test.classSelector(id:name:)), strategy: .before) { (_, id: Int, name: String) in 67 | 68 | } 69 | ``` 70 | 71 | 72 | 73 | ## Requirements 74 | 75 | - iOS 8.0+ 76 | - Swift 4.0-5.x 77 | 78 | 79 | ## Next Steps 80 | 81 | 82 | * Support remove aspect 83 | * Improve detail 84 | * Support Cocopods install 85 | 86 | 87 | ## Installation 88 | 89 | #### Carthage 90 | Add the following line to your [Cartfile](https://github.com/carthage/carthage) 91 | 92 | ``` 93 | git "https://github.com/woshiccm/Aspect.git" "master" 94 | ``` 95 | 96 | 97 | 98 | ## Contributors 99 | 100 | 101 | 102 | 103 | ## Backers 104 | 105 | 106 | 107 | 108 | ### License 109 | 110 | Aspect is released under the MIT license. See LICENSE for details. 111 | 112 | 113 | --------------------------------------------------------------------------------