Skip to content

Instantly share code, notes, and snippets.

@jameswomack
Created February 15, 2012 09:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jameswomack/8b74f7aa14ef7f7e9142 to your computer and use it in GitHub Desktop.
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 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