secret
Last active

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)

  • Download Gist
objective-c-aop.m
Objective-C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
/*
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];
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.