-
-
Save jameswomack/8b74f7aa14ef7f7e9142 to your computer and use it in GitHub Desktop.
Two modes of Aspect-Oriented-Programming (AOP) for Objective-C (on the end-user end I think this will become the simplest solution out there)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This code represents a duo of classes meant to be used together and subclassed | |
so that your method calls can all have identical code run at their beginning | |
or end as needed while staying DRY. I've provided two ways of doing this, | |
both accessible in the same class. One way is to insert code above and below | |
a method you'll create that calls a magic piece of code that will get injected | |
into the runtime for you. Another way is assigning enter and exit blocks to an | |
NSProxy subclass. Either method allows you to write a piece of code once that | |
gets injected into every instance method in a class. | |
Thanks to Blake Watters, cocoadev.com, GeorgFritzsche @ StackOverflow.com... | |
...& Dash for the quick documentation browsing | |
Brought to you in part by NobleGesture/jameswomack | |
Intended to be run in CodeRunner on Mac OS X Lion (at least in this form) | |
*/ | |
#import <Foundation/Foundation.h> | |
#import </Developer/SDKs/MacOSX10.7.sdk/usr/include/objc/runtime.h> | |
#import </Developer/SDKs/MacOSX10.7.sdk/usr/include/objc/message.h> | |
@class NGObject; | |
typedef void (^NGBlock)(SEL aSelector); | |
@interface NGProxy : NSProxy | |
@property(nonatomic,retain) NGObject *object; | |
@property(readwrite,copy) NGBlock enterCode; | |
@property(readwrite,copy) NGBlock exitCode; | |
- (void)registerObject:(id)anObject withSelector:(SEL)aSelector; | |
- (NSArray*)propertiesForClass:(Class)currentClass; | |
@end | |
@implementation NGProxy | |
@synthesize object, enterCode, exitCode; | |
- (void)dealloc | |
{ | |
[exitCode release]; | |
[object release]; | |
[super dealloc]; | |
} | |
// MethodSwizzle used from here: http://www.cocoadev.com/index.pl?MethodSwizzle | |
BOOL MethodSwizzle(Class klass, SEL origSel, SEL altSel, BOOL forInstance) { | |
// Make sure the class isn't nil | |
if (klass == nil) | |
return NO; | |
// Look for the methods in the implementation of the immediate class | |
Class iterKlass = (forInstance ? klass : klass->isa); | |
Method origMethod = NULL, altMethod = NULL; | |
unsigned int methodCount = 0; | |
Method *mlist = class_copyMethodList(iterKlass, &methodCount); | |
if(mlist != NULL) { | |
int i; | |
for (i = 0; i < methodCount; ++i) { | |
if(method_getName(mlist[i]) == origSel ) | |
origMethod = mlist[i]; | |
if (method_getName(mlist[i]) == altSel) | |
altMethod = mlist[i]; | |
} | |
} | |
// if origMethod was not found, that means it is not in the immediate class | |
// try searching the entire class hierarchy with class_getInstanceMethod | |
// if not found or not added, bail out | |
if(origMethod == NULL) { | |
NSLog(@"%@: %@",@"Orig method not found",NSStringFromSelector(origSel)); | |
origMethod = class_getInstanceMethod(iterKlass, origSel); | |
if(origMethod == NULL) { | |
return NO; | |
} | |
if(class_addMethod(iterKlass, method_getName(origMethod), method_getImplementation(origMethod), method_getTypeEncoding(origMethod)) == NO) { | |
return NO; | |
} | |
} | |
// same thing with altMethod | |
if(altMethod == NULL) { | |
NSLog(@"%@: %@",@"Alt method not found",NSStringFromSelector(altSel)); | |
altMethod = class_getInstanceMethod(iterKlass, altSel); | |
if(altMethod == NULL ) | |
return NO; | |
if(class_addMethod(iterKlass, method_getName(altMethod), method_getImplementation(altMethod), method_getTypeEncoding(altMethod)) == NO ) | |
return NO; | |
} | |
//clean up | |
free(mlist); | |
// we now have to look up again for the methods in case they were not in the class implementation, | |
//but in one of the superclasses. In the latter, that means we added the method to the class, | |
//but the Leopard APIs is only 'class_addMethod', in which case we need to have the pointer | |
//to the Method objects actually stored in the Class structure (in the Tiger implementation, | |
//a new mlist was explicitely created with the added methods and directly added to the class; | |
//thus we were able to add a new Method AND get the pointer to it) | |
// for simplicity, just use the same code as in the first step | |
origMethod = NULL; | |
altMethod = NULL; | |
methodCount = 0; | |
mlist = class_copyMethodList(iterKlass, &methodCount); | |
if(mlist != NULL) { | |
int i; | |
for (i = 0; i < methodCount; ++i) { | |
if(method_getName(mlist[i]) == origSel ) | |
origMethod = mlist[i]; | |
if (method_getName(mlist[i]) == altSel) | |
altMethod = mlist[i]; | |
} | |
} | |
// bail if one of the methods doesn't exist anywhere | |
// with all we did, this should not happen, though | |
if (origMethod == NULL || altMethod == NULL) | |
return NO; | |
// now swizzle | |
method_exchangeImplementations(origMethod, altMethod); | |
//clean up | |
free(mlist); | |
return YES; | |
} | |
/* MODIFIED FROM: | |
// | |
// RKObjectPropertyInspector.m | |
// RestKit | |
// | |
// Created by Blake Watters on 3/4/10. | |
// Copyright 2010 Two Toasters. All rights reserved. | |
// | |
*/ | |
- (NSArray*)propertiesForClass:(Class)currentClass | |
{ | |
unsigned int outCount; | |
objc_property_t *propList = class_copyPropertyList(currentClass, &outCount); | |
NSMutableArray *array = [[NSMutableArray new] autorelease]; | |
// Collect the property names | |
int i; | |
NSString *propName; | |
for (i = 0; i < outCount; i++) { | |
// property_getAttributes() returns everything we need to implement this... | |
// See: http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW5 | |
objc_property_t* prop = propList + i; | |
NSString* attributeString = [NSString stringWithCString:property_getAttributes(*prop) encoding:NSUTF8StringEncoding]; | |
propName = [NSString stringWithCString:property_getName(*prop) encoding:NSUTF8StringEncoding]; | |
[array addObject:propName]; | |
} | |
free(propList); | |
return [NSArray arrayWithArray:array]; | |
} | |
- (void)registerObject:(id)anObject withSelector:(SEL)aopSelector { | |
self.object = anObject; | |
Method *instanceMethods; | |
unsigned int methodCount; | |
Class aClass = [self.object class]; | |
NSArray *properties = [self propertiesForClass:aClass]; | |
if ((instanceMethods = class_copyMethodList([self.object class], &methodCount))) | |
{ | |
while (methodCount--) | |
{ | |
SEL aSelector = method_getName(instanceMethods[methodCount]); | |
if(sel_isEqual(aSelector,aopSelector)) | |
continue; | |
/* properties get turned into methods, check to make sure properties are swizzled */ | |
NSString *selString = NSStringFromSelector(aSelector); | |
if([properties containsObject:selString]) | |
continue; | |
/* clear setter syntax from selector string and check that as well */ | |
NSError *error = NULL; | |
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^set" | |
options:NSCaseInsensitiveSearch | |
error:&error]; | |
NSString *modifiedString = [regex stringByReplacingMatchesInString:selString | |
options:0 | |
range:NSMakeRange(0, 3) | |
withTemplate:@""]; | |
//account for capitalization after "set" | |
modifiedString = [modifiedString stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[modifiedString substringToIndex:1] lowercaseString]]; | |
//remove colon | |
if([properties containsObject:[modifiedString stringByReplacingOccurrencesOfString:@":" withString:@""]]) | |
continue; | |
NSError *initError = NULL; | |
NSRegularExpression *initRegex = [NSRegularExpression regularExpressionWithPattern:@"^init" | |
options:NSCaseInsensitiveSearch | |
error:&initError]; | |
BOOL isInit = ([initRegex numberOfMatchesInString:selString options:0 range:NSMakeRange(0, selString.length)] > 0); | |
if(isInit) | |
continue; | |
/* --- */ | |
NSParameterAssert(aClass); | |
NSParameterAssert(aSelector); | |
// Get the instance method | |
Method method = instanceMethods[methodCount]; | |
NSAssert(method, @"No instance method found for the given selector. Only instance methods can be intercepted."); | |
IMP implementation; | |
NSMethodSignature *methodSignature = [aClass instanceMethodSignatureForSelector:aSelector]; | |
// Get the original method implementation | |
if ([methodSignature methodReturnLength] > sizeof(double)) { | |
implementation = class_getMethodImplementation_stret(aClass, aSelector); | |
} | |
else { | |
implementation = class_getMethodImplementation(aClass, aSelector); | |
} | |
Method testMethod = class_getInstanceMethod(aClass, aopSelector); | |
SEL newSel = NSSelectorFromString([@"_" stringByAppendingString:NSStringFromSelector(aSelector)]); | |
class_addMethod(aClass, newSel, class_getMethodImplementation(aClass, aopSelector), method_getTypeEncoding(testMethod)); | |
MethodSwizzle(aClass, aSelector, newSel, YES); | |
} | |
free(instanceMethods); | |
} | |
} | |
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel | |
{ | |
NSMethodSignature *sig; | |
sig = [self.object methodSignatureForSelector:sel]; | |
return sig; | |
} | |
- (BOOL)respondsToSelector:(SEL)aSelector | |
{ | |
return [self.object respondsToSelector:aSelector]; | |
} | |
- (void)forwardInvocation:(NSInvocation *)invocation | |
{ | |
self.enterCode(invocation.selector); | |
[invocation invokeWithTarget:self.object]; | |
self.exitCode(invocation.selector); | |
} | |
@end | |
@class NGProxy; | |
@interface NGObject : NSObject | |
SEL NGSelectorForSelector(SEL aSelector); | |
- (id)initWithProxy:(id)aProxy; | |
- (void)test; | |
- (void)swizzled; | |
- (void)swizzled2; | |
@property(nonatomic,retain) NGProxy *$; | |
@property(nonatomic,assign) BOOL useRuntime; | |
@end | |
@implementation NGObject | |
@synthesize $,useRuntime; | |
- (id)initWithProxy:(id)aProxy; | |
{ | |
self = [super init]; | |
self.$ = aProxy; | |
self.$.object = self; | |
self.$.enterCode = ^(SEL aSelector){ | |
NSLog(@"%@: %@",@"Proxy execute on entrance",NSStringFromSelector(aSelector)); }; //enter code method 2 | |
self.$.exitCode = ^(SEL aSelector){ | |
NSLog(@"%@: %@",@"Proxy execute on exit",NSStringFromSelector(aSelector)); }; //exit code method 2 | |
return self; | |
} | |
- (id)init | |
{ | |
return [self initWithProxy:[NGProxy alloc]]; | |
} | |
- (void)dealloc | |
{ | |
[$ release]; | |
[super dealloc]; | |
} | |
SEL NGSelectorForSelector(SEL aSelector) | |
{ | |
return NSSelectorFromString([@"_" stringByAppendingString:NSStringFromSelector(aSelector)]); | |
} | |
- (void)test; | |
{ | |
useRuntime?NSLog(@"Runtime execute on entrance: %@",NSStringFromSelector(_cmd)):0; //enter code method 1 | |
[self performSelector:NGSelectorForSelector(_cmd)]; | |
useRuntime?NSLog(@"Runtime execute on exit: %@",NSStringFromSelector(_cmd)):0; //exit code method 1 | |
} | |
- (void)swizzled; | |
{ | |
NSLog(@"%@",@"Original code"); | |
} | |
- (void)swizzled2; | |
{ | |
NSLog(@"%@",@"Original code"); | |
} | |
@end | |
int main(int argc, char *argv[]) { | |
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init]; | |
/* Proxy is automatically set up by NGObject */ | |
NGObject *obj = [NGObject new]; | |
obj.useRuntime = YES; //enable runtime method | |
[obj.$ registerObject:obj withSelector:@selector(test)]; | |
[obj swizzled]; //call methods as usual | |
[obj swizzled2]; | |
NGObject *obj2 = [NGObject new]; | |
[obj.$ registerObject:obj withSelector:@selector(test)]; | |
[(id)obj2.$ swizzled]; //call methods through proxy to use proxy method | |
[(id)obj2.$ swizzled2]; | |
[p release]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment