Skip to content

Instantly share code, notes, and snippets.

@rebo
Created September 1, 2009 22:18
Show Gist options
  • Save rebo/179428 to your computer and use it in GitHub Desktop.
Save rebo/179428 to your computer and use it in GitHub Desktop.
//
// NSObject+AssociatedObjects.m
//
// Created by Andy Matuschak on 8/27/09.
// Public domain
//
// edited by rebo to add message forwarding, so extended objects can call zero-argument void
// blocks using [instance method] format.
//
// Not really recommended as this produces "may not respond" warnings in compiler.
#import "NSObject+AssociatedObjects.h"
#import <objc/runtime.h>
#import "typedefs.h"
@implementation NSObject (AMAssociatedObjects)
- (void)associateValue:(id)value withKey:(void *)key
{
objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN);
}
- (id)associatedValueForKey:(void *)key
{
return objc_getAssociatedObject(self, key);
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
SEL sel = @selector(extendedMethodCall:);
NSMethodSignature * sig ;
if ( aSelector != sel){
sig = [[self class] instanceMethodSignatureForSelector:sel];
} else {
sig = nil;
}
return sig;
}
- (void)forwardInvocation: (NSInvocation *) invocation
{
SEL sel = [invocation selector];
[invocation setArgument: &sel atIndex:2];
[invocation setSelector: @selector(extendedMethodCall:)];
return [invocation invokeWithTarget:self];
}
- (void) extendedMethodCall:(SEL)aSelector;
{
WorkBlk_t extendedMethod = [self associatedValueForKey:aSelector];
if (extendedMethod){
extendedMethod();
}
}
@end
//
// NSObject+StateMachine.m
#import "NSObject+StateMachine.h"
#import "NSObject+AssociatedObjects.h"
#import <objc/runtime.h>
#import "typedefs.h"
@implementation NSObject (StateMachine)
- (void)stateMachineExtendInstanceMethods {
//Mix-in Instance Variables
//
//
NSMutableArray * transitions = [[NSMutableArray alloc] init];
NSMutableArray * callbacks = [[NSMutableArray alloc] init];
NSString * state = nil;
objc_setAssociatedObject(self, @selector(transitions), transitions, OBJC_ASSOCIATION_RETAIN);
objc_setAssociatedObject(self, @selector(callbacks), callbacks, OBJC_ASSOCIATION_RETAIN);
objc_setAssociatedObject(self, @selector(state), state, OBJC_ASSOCIATION_RETAIN);
//Mix-in Methods
//
//
WorkBlk_t initializeState = ^{
NSLog(@"Initiailizing State ...");
objc_setAssociatedObject(self, @selector(state), @"INACTIVE", OBJC_ASSOCIATION_RETAIN);
};
objc_setAssociatedObject(self, @selector(initializeState), [initializeState copy], OBJC_ASSOCIATION_RETAIN);
TransBlk_t addTransition = ^(NSString * transitionName, NSString * startState, NSString * endState){
NSLog(@"Adding Transition ...");
SEL transitionSelector = NSSelectorFromString(transitionName);
[transitions addObject:[NSDictionary dictionaryWithObjectsAndKeys: transitionName,@"transitionName", startState,@"startState", endState, @"endState",nil]];
WorkBlk_t callTransition = ^{
NSLog(@"Applying Transition...");
NSString *state = objc_getAssociatedObject(self, @selector(state));
if (state == startState){
objc_setAssociatedObject(self, @selector(state), endState, OBJC_ASSOCIATION_RETAIN);
//call backs
NSMutableArray * callbacks = objc_getAssociatedObject(self, @selector(callbacks));
NSUInteger i = [callbacks indexOfObjectPassingTest:^BOOL(id obj, NSUInteger index, BOOL * stop){ if ([obj valueForKey:@"transitionName"]== transitionName){ return YES; }else{return NO;} }];
if ( i != NSNotFound ){
WorkBlk_t callback = [[callbacks objectAtIndex:i] valueForKey:@"callback"];
callback();
}
}
};
objc_setAssociatedObject(self, transitionSelector , [callTransition copy], OBJC_ASSOCIATION_RETAIN);
return transitionSelector;
};
objc_setAssociatedObject(self, @selector(addTransition), [addTransition copy], OBJC_ASSOCIATION_RETAIN);
CallbackBlk_t addCallbackForTransition = ^(NSString * transitionName, WorkBlk_t callback){
NSLog(@"Adding CallBack ...");
NSMutableArray * callbacks = objc_getAssociatedObject(self, @selector(callbacks));
// [transitions addObject:[NSDictionary dictionaryWithObjectsAndKeys: transitionName,@"transitionName", startState,@"startState", endState, @"endState",nil]];
[callbacks addObject:[NSDictionary dictionaryWithObjectsAndKeys:transitionName,@"transitionName", callback, @"callback",nil]];
};
objc_setAssociatedObject(self, @selector(addCallbackForTransition), [addCallbackForTransition copy], OBJC_ASSOCIATION_RETAIN);
}
+(void)stateMachineExtendClassMethods{
// OK even more stupid stuff
// By adding a category class method you can add class 'methods' and 'ivars'
//
// You can even add pseudo-instance methods on arbitrary classes at run time
// as shown in the third objc_setAssociatedObject call below
NSMutableArray * transitions = [[NSMutableArray alloc] init];
[transitions addObject:@"foo"];
objc_setAssociatedObject(self, @selector(transitions), transitions, OBJC_ASSOCIATION_RETAIN);
WorkBlk_t longDescription = ^{
NSLog(@"Here is a long description of the class %@", [self description]);
};
objc_setAssociatedObject(self, @selector(longDescription), [longDescription copy], OBJC_ASSOCIATION_RETAIN);
InstanceBlk_t describeInstance = ^(id obj){
//check obj is right kind of class
if ([obj isKindOfClass:self]){
NSLog(@" This is an pseudo-instance method, called on %@", [obj description]);
} else { NSLog(@"Warning instance is not a %@", [self description]);}
};
objc_setAssociatedObject(self, @selector(describeInstance), [describeInstance copy], OBJC_ASSOCIATION_RETAIN);
}
@end
//
// StateMachineAppDelegate.m
// StateMachine
//
//
// CRAZY ATTEMPT TO SEE HOW MUCH I CAN MANGLE objc_setAssociatedObject
// DO NOT USE THIS CODE:P
// The idea is to add functionality to arbitrary objects
// so one can have ruby extend style behaviour through Objective-c categories
// It works by adding a single category method, within which instance variables and 'method' blocks
// are set up by making use of objc_setAssociatedObject.
// I wanted to see how easy it would be to use, well it is possible but it is really unwieldy
// and makes code very hard to read.
// see NSObject+StateMachine.m for the core of the work, it is a dumb partially implemented state machine
// that in theory can be used for any instance of any NSObject.
#import "StateMachineAppDelegate.h"
#import "NSObject+StateMachine.h"
#import "NSObject+AssociatedObjects.h"
#import "typedefs.h"
@implementation StateMachineAppDelegate
@synthesize window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
// StateMachine * sm = [[StateMachine alloc] init];
NSDictionary * dictStateMachine = [[NSDictionary alloc] init];
[dictStateMachine stateMachineExtendInstanceMethods];
// Initialize State
WorkBlk_t initializeState = [dictStateMachine associatedValueForKey:@selector(initializeState)];
initializeState();
//Check State
NSLog(@"Object State is :%@", [dictStateMachine associatedValueForKey:@selector(state)]);
//Add a transition from Inactive to Active
TransBlk_t selectorByAddingTransition = [dictStateMachine associatedValueForKey:@selector(addTransition)];
SEL activate = selectorByAddingTransition( @"ACTIVATE", @"INACTIVE", @"ACTIVE");
// Activate
WorkBlk_t activateState = [dictStateMachine associatedValueForKey:activate];
activateState();
//Check Final State
NSLog(@"Object State is :%@", [dictStateMachine associatedValueForKey:@selector(state)]);
// Reset and Run Again
NSLog(@"\n\nReseting and Running Again:");
initializeState();
activateState();
NSLog(@"\n\nAdding A Callback to Activate State:");
// How about a callback when activating
CallbackBlk_t addCallbackToTransition = [dictStateMachine associatedValueForKey:@selector(addCallbackForTransition)];
addCallbackToTransition(@"ACTIVATE", ^{ NSLog(@"Warning State Has Been Activated!") ;});
// Reset and Run Again This time With Call Back In Effect
NSLog(@"\n\nReseting and Running Again, after callback has been added:");
initializeState();
activateState();
NSLog(@"Object State is :%@", [dictStateMachine associatedValueForKey:@selector(state)]);
NSLog(@"Object With additional methods and state is a : %@", [dictStateMachine className]);
NSLog(@"\n\n\n Creating New Object");
NSString * sm = [[NSString alloc] init];
[sm stateMachineExtendInstanceMethods];
// Initialize State
WorkBlk_t initializeState_b = [sm associatedValueForKey:@selector(initializeState)];
initializeState_b();
//Check State
NSLog(@"Object State is :%@", [sm associatedValueForKey:@selector(state)]);
//Add a transition from Inactive to Active
TransBlk_t selectorByAddingTransition_b = [sm associatedValueForKey:@selector(addTransition)];
SEL activate_b = selectorByAddingTransition_b( @"PAINT", @"INACTIVE", @"PAINTED");
// Activate
WorkBlk_t activateState_b = [sm associatedValueForKey:activate_b];
activateState_b();
//Check Final State
NSLog(@"Object State is :%@", [sm associatedValueForKey:@selector(state)]);
// Reset and Run Again
NSLog(@"\n\nReseting and Running Again:");
initializeState_b();
activateState_b();
NSLog(@"Object State is :%@", [sm associatedValueForKey:@selector(state)]);
NSLog(@"\n\nAdding A Callback to Activate State:");
// How about a callback when activating
CallbackBlk_t addCallbackToTransition_b = [sm associatedValueForKey:@selector(addCallbackForTransition)];
addCallbackToTransition_b(@"PAINT", ^{ NSLog(@"Hey This Object Has Been Painted!") ;});
// Reset and Run Again This time With Call Back In Effect
NSLog(@"\n\nReseting and Running Again, after callback has been added:");
initializeState_b();
activateState_b();
NSLog(@"Object With additional methods and state is a : %@", [sm className]);
//added....
// Can add class instance variables, class methods, and also pseudo-instance methods on arbitrary classes:
[NSArray stateMachineExtendClassMethods];
NSArray * array = [NSArray associatedValueForKey:@selector(transitions)];
NSLog( @"Class instance array = %@" , array);
WorkBlk_t longDescription = [NSArray associatedValueForKey:@selector(longDescription)];
longDescription();
NSArray * arr = [NSArray arrayWithObjects:@"bar",nil];
InstanceBlk_t describeInstance = [NSArray associatedValueForKey:@selector(describeInstance)];
describeInstance(arr);
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment