├── AOPAspect ├── AOPAspect.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── AOPAspect │ ├── AOPAspect-Prefix.pch │ ├── AOPAspect.h │ └── AOPAspect.m └── README.md /AOPAspect/AOPAspect.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3A04D7B914CABE1D00A2852F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A04D7B814CABE1D00A2852F /* Foundation.framework */; }; 11 | 3A04D7BF14CABE1D00A2852F /* AOPAspect.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A04D7BE14CABE1D00A2852F /* AOPAspect.m */; }; 12 | 3AF77A2414D716F200F5D77E /* AOPAspect.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A04D7BD14CABE1D00A2852F /* AOPAspect.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXFileReference section */ 16 | 3A04D7B514CABE1D00A2852F /* libAOPAspect.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAOPAspect.a; sourceTree = BUILT_PRODUCTS_DIR; }; 17 | 3A04D7B814CABE1D00A2852F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 18 | 3A04D7BC14CABE1D00A2852F /* AOPAspect-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AOPAspect-Prefix.pch"; sourceTree = ""; }; 19 | 3A04D7BD14CABE1D00A2852F /* AOPAspect.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AOPAspect.h; sourceTree = ""; }; 20 | 3A04D7BE14CABE1D00A2852F /* AOPAspect.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AOPAspect.m; sourceTree = ""; }; 21 | /* End PBXFileReference section */ 22 | 23 | /* Begin PBXFrameworksBuildPhase section */ 24 | 3A04D7B214CABE1D00A2852F /* Frameworks */ = { 25 | isa = PBXFrameworksBuildPhase; 26 | buildActionMask = 2147483647; 27 | files = ( 28 | 3A04D7B914CABE1D00A2852F /* Foundation.framework in Frameworks */, 29 | ); 30 | runOnlyForDeploymentPostprocessing = 0; 31 | }; 32 | /* End PBXFrameworksBuildPhase section */ 33 | 34 | /* Begin PBXGroup section */ 35 | 3A04D7AA14CABE1D00A2852F = { 36 | isa = PBXGroup; 37 | children = ( 38 | 3A04D7BA14CABE1D00A2852F /* AOPAspect */, 39 | 3A04D7B714CABE1D00A2852F /* Frameworks */, 40 | 3A04D7B614CABE1D00A2852F /* Products */, 41 | ); 42 | sourceTree = ""; 43 | }; 44 | 3A04D7B614CABE1D00A2852F /* Products */ = { 45 | isa = PBXGroup; 46 | children = ( 47 | 3A04D7B514CABE1D00A2852F /* libAOPAspect.a */, 48 | ); 49 | name = Products; 50 | sourceTree = ""; 51 | }; 52 | 3A04D7B714CABE1D00A2852F /* Frameworks */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 3A04D7B814CABE1D00A2852F /* Foundation.framework */, 56 | ); 57 | name = Frameworks; 58 | sourceTree = ""; 59 | }; 60 | 3A04D7BA14CABE1D00A2852F /* AOPAspect */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 3A04D7BD14CABE1D00A2852F /* AOPAspect.h */, 64 | 3A04D7BE14CABE1D00A2852F /* AOPAspect.m */, 65 | 3A04D7BB14CABE1D00A2852F /* Supporting Files */, 66 | ); 67 | path = AOPAspect; 68 | sourceTree = ""; 69 | }; 70 | 3A04D7BB14CABE1D00A2852F /* Supporting Files */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 3A04D7BC14CABE1D00A2852F /* AOPAspect-Prefix.pch */, 74 | ); 75 | name = "Supporting Files"; 76 | sourceTree = ""; 77 | }; 78 | /* End PBXGroup section */ 79 | 80 | /* Begin PBXHeadersBuildPhase section */ 81 | 3A04D7B314CABE1D00A2852F /* Headers */ = { 82 | isa = PBXHeadersBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | 3AF77A2414D716F200F5D77E /* AOPAspect.h in Headers */, 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | /* End PBXHeadersBuildPhase section */ 90 | 91 | /* Begin PBXNativeTarget section */ 92 | 3A04D7B414CABE1D00A2852F /* AOPAspect */ = { 93 | isa = PBXNativeTarget; 94 | buildConfigurationList = 3A04D7C214CABE1D00A2852F /* Build configuration list for PBXNativeTarget "AOPAspect" */; 95 | buildPhases = ( 96 | 3A04D7B114CABE1D00A2852F /* Sources */, 97 | 3A04D7B214CABE1D00A2852F /* Frameworks */, 98 | 3A04D7B314CABE1D00A2852F /* Headers */, 99 | ); 100 | buildRules = ( 101 | ); 102 | dependencies = ( 103 | ); 104 | name = AOPAspect; 105 | productName = AOPAspect; 106 | productReference = 3A04D7B514CABE1D00A2852F /* libAOPAspect.a */; 107 | productType = "com.apple.product-type.library.static"; 108 | }; 109 | /* End PBXNativeTarget section */ 110 | 111 | /* Begin PBXProject section */ 112 | 3A04D7AC14CABE1D00A2852F /* Project object */ = { 113 | isa = PBXProject; 114 | attributes = { 115 | LastUpgradeCheck = 0420; 116 | }; 117 | buildConfigurationList = 3A04D7AF14CABE1D00A2852F /* Build configuration list for PBXProject "AOPAspect" */; 118 | compatibilityVersion = "Xcode 3.2"; 119 | developmentRegion = English; 120 | hasScannedForEncodings = 0; 121 | knownRegions = ( 122 | en, 123 | ); 124 | mainGroup = 3A04D7AA14CABE1D00A2852F; 125 | productRefGroup = 3A04D7B614CABE1D00A2852F /* Products */; 126 | projectDirPath = ""; 127 | projectRoot = ""; 128 | targets = ( 129 | 3A04D7B414CABE1D00A2852F /* AOPAspect */, 130 | ); 131 | }; 132 | /* End PBXProject section */ 133 | 134 | /* Begin PBXSourcesBuildPhase section */ 135 | 3A04D7B114CABE1D00A2852F /* Sources */ = { 136 | isa = PBXSourcesBuildPhase; 137 | buildActionMask = 2147483647; 138 | files = ( 139 | 3A04D7BF14CABE1D00A2852F /* AOPAspect.m in Sources */, 140 | ); 141 | runOnlyForDeploymentPostprocessing = 0; 142 | }; 143 | /* End PBXSourcesBuildPhase section */ 144 | 145 | /* Begin XCBuildConfiguration section */ 146 | 3A04D7C014CABE1D00A2852F /* Debug */ = { 147 | isa = XCBuildConfiguration; 148 | buildSettings = { 149 | ALWAYS_SEARCH_USER_PATHS = NO; 150 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 151 | CLANG_ENABLE_OBJC_ARC = YES; 152 | COPY_PHASE_STRIP = NO; 153 | GCC_C_LANGUAGE_STANDARD = gnu99; 154 | GCC_DYNAMIC_NO_PIC = NO; 155 | GCC_OPTIMIZATION_LEVEL = 0; 156 | GCC_PREPROCESSOR_DEFINITIONS = ( 157 | "DEBUG=1", 158 | "$(inherited)", 159 | ); 160 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 161 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 162 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 163 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 164 | GCC_WARN_UNUSED_VARIABLE = YES; 165 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 166 | SDKROOT = iphoneos; 167 | }; 168 | name = Debug; 169 | }; 170 | 3A04D7C114CABE1D00A2852F /* Release */ = { 171 | isa = XCBuildConfiguration; 172 | buildSettings = { 173 | ALWAYS_SEARCH_USER_PATHS = NO; 174 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 175 | CLANG_ENABLE_OBJC_ARC = YES; 176 | COPY_PHASE_STRIP = YES; 177 | GCC_C_LANGUAGE_STANDARD = gnu99; 178 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 179 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 180 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 181 | GCC_WARN_UNUSED_VARIABLE = YES; 182 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 183 | SDKROOT = iphoneos; 184 | VALIDATE_PRODUCT = YES; 185 | }; 186 | name = Release; 187 | }; 188 | 3A04D7C314CABE1D00A2852F /* Debug */ = { 189 | isa = XCBuildConfiguration; 190 | buildSettings = { 191 | DSTROOT = /tmp/AOPAspect.dst; 192 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 193 | GCC_PREFIX_HEADER = "AOPAspect/AOPAspect-Prefix.pch"; 194 | OTHER_LDFLAGS = "-ObjC"; 195 | PRODUCT_NAME = "$(TARGET_NAME)"; 196 | SKIP_INSTALL = YES; 197 | }; 198 | name = Debug; 199 | }; 200 | 3A04D7C414CABE1D00A2852F /* Release */ = { 201 | isa = XCBuildConfiguration; 202 | buildSettings = { 203 | DSTROOT = /tmp/AOPAspect.dst; 204 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 205 | GCC_PREFIX_HEADER = "AOPAspect/AOPAspect-Prefix.pch"; 206 | OTHER_LDFLAGS = "-ObjC"; 207 | PRODUCT_NAME = "$(TARGET_NAME)"; 208 | SKIP_INSTALL = YES; 209 | }; 210 | name = Release; 211 | }; 212 | /* End XCBuildConfiguration section */ 213 | 214 | /* Begin XCConfigurationList section */ 215 | 3A04D7AF14CABE1D00A2852F /* Build configuration list for PBXProject "AOPAspect" */ = { 216 | isa = XCConfigurationList; 217 | buildConfigurations = ( 218 | 3A04D7C014CABE1D00A2852F /* Debug */, 219 | 3A04D7C114CABE1D00A2852F /* Release */, 220 | ); 221 | defaultConfigurationIsVisible = 0; 222 | defaultConfigurationName = Release; 223 | }; 224 | 3A04D7C214CABE1D00A2852F /* Build configuration list for PBXNativeTarget "AOPAspect" */ = { 225 | isa = XCConfigurationList; 226 | buildConfigurations = ( 227 | 3A04D7C314CABE1D00A2852F /* Debug */, 228 | 3A04D7C414CABE1D00A2852F /* Release */, 229 | ); 230 | defaultConfigurationIsVisible = 0; 231 | defaultConfigurationName = Release; 232 | }; 233 | /* End XCConfigurationList section */ 234 | }; 235 | rootObject = 3A04D7AC14CABE1D00A2852F /* Project object */; 236 | } 237 | -------------------------------------------------------------------------------- /AOPAspect/AOPAspect.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AOPAspect/AOPAspect/AOPAspect-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'AOPAspect' target in the 'AOPAspect' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /AOPAspect/AOPAspect/AOPAspect.h: -------------------------------------------------------------------------------- 1 | // 2 | // AOPAspect.h 3 | // AOPAspect 4 | // 5 | // Created by Andras Koczka on 1/21/12. 6 | // Copyright (c) 2012 Andras Koczka 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 furnished 13 | // to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included 16 | // in 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, FITNESS 20 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 21 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 22 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | 26 | #import 27 | 28 | typedef void (^aspect_block_t)(NSInvocation *invocation); 29 | 30 | @interface AOPAspect : NSObject 31 | 32 | // Intercept methods return an identifier that can be used for deregistration with the removeInterceptorWithIdentifier method 33 | // Only instance methods can be intercepted 34 | - (NSString *)interceptClass:(Class)aClass beforeExecutingSelector:(SEL)selector usingBlock:(aspect_block_t)block; 35 | - (NSString *)interceptClass:(Class)aClass afterExecutingSelector:(SEL)selector usingBlock:(aspect_block_t)block; 36 | - (NSString *)interceptClass:(Class)aClass insteadExecutingSelector:(SEL)selector usingBlock:(aspect_block_t)block; 37 | 38 | // Removes an interceptor block 39 | - (void)removeInterceptorWithIdentifier:(NSString *)identifier; 40 | 41 | // Use this method to get the AOPAspect instance. Don't use alloc/init. 42 | + (AOPAspect *)instance; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /AOPAspect/AOPAspect/AOPAspect.m: -------------------------------------------------------------------------------- 1 | // 2 | // AOPAspect.m 3 | // AOPAspect 4 | // 5 | // Created by Andras Koczka on 1/21/12. 6 | // Copyright (c) 2012 Andras Koczka 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 furnished 13 | // to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included 16 | // in 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, FITNESS 20 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 21 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 22 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | // 25 | 26 | #import "AOPAspect.h" 27 | #import 28 | #import 29 | 30 | 31 | #pragma mark - Type definitions and keys 32 | 33 | 34 | typedef enum { 35 | AOPAspectInspectorTypeBefore = 0, 36 | AOPAspectInspectorTypeInstead = 1, 37 | AOPAspectInspectorTypeAfter = 2 38 | }AOPAspectInspectorType; 39 | 40 | static NSString *const AOPAspectCurrentObjectKey = @"AOPAspectCurrentObjectKey"; 41 | 42 | 43 | #pragma mark - Shared instance 44 | 45 | 46 | static AOPAspect *aspectManager = NULL; 47 | 48 | 49 | #pragma mark - Implementation 50 | 51 | 52 | @implementation AOPAspect { 53 | 54 | // interceptorStorage (dict) -> interceptorTypes (dict) -> interceptors (array) -> interceptor (dict) -> block 55 | NSMutableDictionary *interceptorStorage; // Ok, this is ugly 56 | 57 | aspect_block_t methodInvoker; 58 | dispatch_queue_t synchronizerQueue; 59 | } 60 | 61 | 62 | #pragma mark - Object lifecycle 63 | 64 | 65 | + (void)initialize { 66 | static dispatch_once_t onceToken; 67 | dispatch_once(&onceToken, ^{ 68 | aspectManager = [[AOPAspect alloc] init]; 69 | aspectManager->interceptorStorage = [[NSMutableDictionary alloc] init]; 70 | 71 | // Create queue for synchronization 72 | aspectManager->synchronizerQueue = dispatch_queue_create("Synchronizer queue - AOPAspect", DISPATCH_QUEUE_SERIAL); 73 | 74 | // Store the default method invoker block 75 | aspectManager->methodInvoker = ^(NSInvocation *invocation) { 76 | // Invoke the original method 77 | [invocation invoke]; 78 | }; 79 | }); 80 | } 81 | 82 | + (AOPAspect *)instance { 83 | return aspectManager; 84 | } 85 | 86 | - (void)dealloc { 87 | dispatch_release(synchronizerQueue); 88 | } 89 | 90 | 91 | #pragma mark - Helper methods 92 | 93 | 94 | - (NSString *)keyWithClass:(Class)aClass selector:(SEL)selector { 95 | return [NSString stringWithFormat:@"%@%@", NSStringFromClass(aClass), NSStringFromSelector(selector)]; 96 | } 97 | 98 | - (SEL)extendedSelectorWithClass:(Class)aClass selector:(SEL)selector { 99 | return NSSelectorFromString([self keyWithClass:aClass selector:selector]); 100 | } 101 | 102 | // Stores the current class in the thread dictionary. 103 | - (void)setCurrentObject:(id)anObject { 104 | [[[NSThread currentThread] threadDictionary] setObject:anObject forKey:AOPAspectCurrentObjectKey]; 105 | } 106 | 107 | - (id)currentObject { 108 | return [[[NSThread currentThread] threadDictionary] objectForKey:AOPAspectCurrentObjectKey]; 109 | } 110 | 111 | - (Class)currentClass { 112 | return [[[[NSThread currentThread] threadDictionary] objectForKey:AOPAspectCurrentObjectKey] class]; 113 | } 114 | 115 | - (NSString *)identifierWithClass:(Class)aClass selector:(SEL)aSelector dictionary:(NSDictionary *)dictionary { 116 | return [NSString stringWithFormat:@"%@ | %@ | %p", NSStringFromClass(aClass), NSStringFromSelector(aSelector), dictionary]; 117 | } 118 | 119 | #pragma mark - Interceptor registration 120 | 121 | - (void)restoreOriginalMethodWithClass:(Class)aClass selector:(SEL)aSelector { 122 | 123 | Method method = class_getInstanceMethod(aClass, aSelector); 124 | IMP implementation; 125 | 126 | if ([[aClass instanceMethodSignatureForSelector:aSelector] methodReturnLength] > sizeof(double)) { 127 | implementation = class_getMethodImplementation_stret([self class], [self extendedSelectorWithClass:aClass selector:aSelector]); 128 | } 129 | else { 130 | implementation = class_getMethodImplementation([self class], [self extendedSelectorWithClass:aClass selector:aSelector]); 131 | } 132 | 133 | method_setImplementation(method, implementation); 134 | } 135 | 136 | - (void)interceptMethodWithClass:(Class)aClass selector:(SEL)aSelector { 137 | 138 | Method method = class_getInstanceMethod(aClass, aSelector); 139 | IMP implementation; 140 | 141 | // Check method return type 142 | if ([[aClass instanceMethodSignatureForSelector:aSelector] methodReturnLength] > sizeof(double)) { 143 | implementation = (IMP)_objc_msgForward_stret; 144 | } 145 | else { 146 | implementation = (IMP)_objc_msgForward; 147 | } 148 | 149 | // Change the implementation 150 | method_setImplementation(method, implementation); 151 | } 152 | 153 | - (NSString *)storeInterceptorBlock:(aspect_block_t)block withClass:(Class)aClass selector:(SEL)aSelector type:(AOPAspectInspectorType)type { 154 | 155 | NSString *key = [self keyWithClass:aClass selector:aSelector]; 156 | 157 | // Get the type dictionary 158 | NSMutableDictionary *interceptorTypeDictionary = [interceptorStorage objectForKey:key]; 159 | 160 | // Create a type dictionary if needed 161 | if (!interceptorTypeDictionary) { 162 | interceptorTypeDictionary = [[NSMutableDictionary alloc] init]; 163 | [interceptorStorage setObject:interceptorTypeDictionary forKey:key]; 164 | } 165 | 166 | // Get the interceptors array 167 | NSMutableArray *interceptors = [interceptorTypeDictionary objectForKey:[NSNumber numberWithInt:type]]; 168 | 169 | // Initialize a new array (if needed) for storing interceptors. One array for each type: before, instead, after 170 | if (!interceptors) { 171 | interceptors = [[NSMutableArray alloc] init]; 172 | [interceptorTypeDictionary setObject:interceptors forKey:[NSNumber numberWithInt:type]]; 173 | } 174 | 175 | // Wrap the interceptor into an NSDictionary so its address will be unique 176 | NSDictionary *interceptor = [NSDictionary dictionaryWithObject:block forKey:[NSDate date]]; 177 | 178 | // Remove the default methodinvoker in case of a new "instead" type interceptor 179 | if (type == AOPAspectInspectorTypeInstead && interceptors.count == 1) { 180 | if ([[[interceptors lastObject] allValues] lastObject] == (id)methodInvoker) { 181 | [interceptors removeLastObject]; 182 | } 183 | } 184 | 185 | [interceptors addObject:interceptor]; 186 | 187 | // Return a unique identifier that can be used to identify a certain interceptor 188 | return [self identifierWithClass:aClass selector:aSelector dictionary:interceptor]; 189 | } 190 | 191 | - (NSString *)registerClass:(Class)aClass withSelector:(SEL)aSelector type:(AOPAspectInspectorType)type usingBlock:(aspect_block_t)block { 192 | NSParameterAssert(aClass); 193 | NSParameterAssert(aSelector); 194 | NSParameterAssert(block); 195 | 196 | // Hook a new method 197 | if (![self respondsToSelector:[self extendedSelectorWithClass:aClass selector:aSelector]]) { 198 | 199 | // Get the instance method 200 | Method method = class_getInstanceMethod(aClass, aSelector); 201 | NSAssert(method, @"No instance method found for the given selector. Only instance methods can be intercepted."); 202 | 203 | IMP implementation; 204 | NSMethodSignature *methodSignature = [aClass instanceMethodSignatureForSelector:aSelector]; 205 | 206 | // Get the original method implementation 207 | if ([methodSignature methodReturnLength] > sizeof(double)) { 208 | implementation = class_getMethodImplementation_stret(aClass, aSelector); 209 | } 210 | else { 211 | implementation = class_getMethodImplementation(aClass, aSelector); 212 | } 213 | 214 | [self interceptMethodWithClass:aClass selector:aSelector]; 215 | 216 | // Get the forwarding method properties 217 | SEL forwardingMethodSelector = @selector(forwardingTargetForSelector:); 218 | IMP forwardingMethodImplementation = class_getMethodImplementation([self class], @selector(baseClassForwardingTargetForSelector:)); 219 | Method forwardingMethod = class_getInstanceMethod([self class], @selector(baseClassForwardingTargetForSelector:)); 220 | const char *forwardingMethodTypeEncoding = method_getTypeEncoding(forwardingMethod); 221 | 222 | // Add the original forwarding method with the extended selector to self 223 | IMP originalForwardingMethodImp = class_getMethodImplementation(aClass, forwardingMethodSelector); 224 | SEL extendedForwardingSelector = [self extendedSelectorWithClass:aClass selector:forwardingMethodSelector]; 225 | class_addMethod([self class], extendedForwardingSelector, originalForwardingMethodImp, forwardingMethodTypeEncoding); 226 | 227 | // Initiate hook to self on the base object 228 | class_replaceMethod(aClass, forwardingMethodSelector, forwardingMethodImplementation, forwardingMethodTypeEncoding); 229 | 230 | SEL extendedSelector = [self extendedSelectorWithClass:aClass selector:aSelector]; 231 | const char *typeEncoding = method_getTypeEncoding(method); 232 | 233 | // Add the original method with the extended selector to self 234 | class_addMethod([self class], extendedSelector, implementation, typeEncoding); 235 | 236 | // Add the default method invoker block 237 | dispatch_sync(synchronizerQueue, ^{ 238 | [self storeInterceptorBlock:methodInvoker withClass:aClass selector:aSelector type:AOPAspectInspectorTypeInstead]; 239 | }); 240 | } 241 | 242 | // Store the interceptor block 243 | __block NSString *identifier; 244 | dispatch_sync(synchronizerQueue, ^{ 245 | identifier = [self storeInterceptorBlock:block withClass:aClass selector:aSelector type:type]; 246 | }); 247 | 248 | return identifier; 249 | } 250 | 251 | - (NSString *)interceptClass:(Class)aClass beforeExecutingSelector:(SEL)selector usingBlock:(aspect_block_t)block { 252 | return [self registerClass:aClass withSelector:selector type:AOPAspectInspectorTypeBefore usingBlock:block]; 253 | } 254 | 255 | - (NSString *)interceptClass:(Class)aClass afterExecutingSelector:(SEL)selector usingBlock:(aspect_block_t)block { 256 | return [self registerClass:aClass withSelector:selector type:AOPAspectInspectorTypeAfter usingBlock:block]; 257 | } 258 | 259 | - (NSString *)interceptClass:(Class)aClass insteadExecutingSelector:(SEL)selector usingBlock:(aspect_block_t)block { 260 | return [self registerClass:aClass withSelector:selector type:AOPAspectInspectorTypeInstead usingBlock:block]; 261 | } 262 | 263 | - (void)deregisterMethodWithClass:(Class)aClass selector:(SEL)aSelector { 264 | 265 | [self restoreOriginalMethodWithClass:aClass selector:aSelector]; 266 | [interceptorStorage removeObjectForKey:[self keyWithClass:aClass selector:aSelector]]; 267 | } 268 | 269 | - (void)removeInterceptorWithIdentifier:(NSString *)identifier { 270 | 271 | // Get the class and the selector from the identifier 272 | NSArray *components = [identifier componentsSeparatedByString:@" | "]; 273 | Class aClass = NSClassFromString([components objectAtIndex:0]); 274 | SEL selector = NSSelectorFromString([components objectAtIndex:1]); 275 | 276 | dispatch_sync(synchronizerQueue, ^{ 277 | 278 | // Search for the interceptor that belongs to the given identifier 279 | for (NSDictionary *interceptorTypeDictionary in [interceptorStorage allValues]) { 280 | NSInteger interceptorCount = 0; 281 | 282 | for (int i = 0; i < 3; i++) { 283 | NSMutableArray *interceptors = [interceptorTypeDictionary objectForKey:[NSNumber numberWithInt:i]]; 284 | 285 | for (NSDictionary *dictionary in [NSArray arrayWithArray:interceptors]) { 286 | 287 | // If found remove the interceptor 288 | if ([[self identifierWithClass:aClass selector:selector dictionary:dictionary] isEqualToString:identifier]) { 289 | [interceptors removeObject:dictionary]; 290 | 291 | // Add back the default method invoker block in case of no more "instead" type blocks 292 | if (i == AOPAspectInspectorTypeInstead && interceptors.count == 0) { 293 | [self storeInterceptorBlock:methodInvoker withClass:aClass selector:selector type:i]; 294 | } 295 | } 296 | } 297 | 298 | interceptorCount += interceptors.count; 299 | } 300 | 301 | // If only the default methodinvoker interceptor remained than deregister the method to improve performance 302 | if (interceptorCount == 1 && [[[[interceptorTypeDictionary objectForKey:[NSNumber numberWithInt:AOPAspectInspectorTypeInstead]] lastObject] allValues] lastObject] == (id)methodInvoker) { 303 | [self deregisterMethodWithClass:aClass selector:selector]; 304 | } 305 | } 306 | }); 307 | } 308 | 309 | 310 | #pragma mark - Hook 311 | 312 | 313 | - (id)baseClassForwardingTargetForSelector:(SEL)aSelector { 314 | 315 | // In case the selector is not implemented on the base class 316 | if (![self respondsToSelector:aSelector]) { 317 | SEL extendedForwardingMethodSelector = [[AOPAspect instance] extendedSelectorWithClass:[self class] selector:@selector(forwardingTargetForSelector:)]; 318 | 319 | // Invoke the original forwardingTargetForSelector method 320 | return method_invoke([AOPAspect instance], class_getInstanceMethod([AOPAspect class], extendedForwardingMethodSelector), aSelector); 321 | } 322 | 323 | // Store the current class 324 | [[AOPAspect instance] setCurrentObject:self]; 325 | 326 | return [AOPAspect instance]; 327 | } 328 | 329 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { 330 | return [[self currentClass] instanceMethodSignatureForSelector:aSelector]; 331 | } 332 | 333 | - (void)executeInterceptorsWithClass:(Class)aClass selector:(SEL)aSelector invocation:(NSInvocation *)anInvocation { 334 | 335 | NSString *key = [self keyWithClass:aClass selector:aSelector]; 336 | __block NSMutableDictionary *interceptorTypeDictionary; 337 | 338 | dispatch_sync(synchronizerQueue, ^{ 339 | interceptorTypeDictionary = [interceptorStorage objectForKey:key]; 340 | }); 341 | 342 | // Restore original state - this is needed for self and _cmd to be valid 343 | // FIXME: this could cause issues between threads 344 | [aspectManager restoreOriginalMethodWithClass:aClass selector:aSelector]; 345 | 346 | // Executes interceptors before, instead and after 347 | for (int i = 0; i < 3; i++) { 348 | __block NSArray *interceptors; 349 | 350 | dispatch_sync(synchronizerQueue, ^{ 351 | interceptors = [NSArray arrayWithArray:[interceptorTypeDictionary objectForKey:[NSNumber numberWithInt:i]]]; 352 | }); 353 | 354 | for (NSDictionary *interceptor in interceptors) { 355 | aspect_block_t block = [[interceptor allValues] lastObject]; 356 | block(anInvocation); 357 | } 358 | } 359 | 360 | // Restore interception 361 | [aspectManager interceptMethodWithClass:aClass selector:aSelector]; 362 | } 363 | 364 | - (void)forwardInvocation:(NSInvocation *)anInvocation { 365 | 366 | anInvocation.target = [self currentObject]; 367 | [self executeInterceptorsWithClass:[self currentClass] selector:anInvocation.selector invocation:anInvocation]; 368 | } 369 | 370 | @end 371 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AOPAspect is a small aspect oriented programming library for iOS. Licensed under the MIT license. 2 | 3 | **Note:** Current implementation is **not thread safe**. Also there is a **bug** when intercepting the same method in subclasses when it is only implemented on the superclass. If all the subclasses override the method than it works OK. I will try to fix these in the next few days. 4 | 5 | For more information on how it works, please check out the my article: 6 | 7 | 8 | --------------------------------------------------------------------------------