Skip to content

Instantly share code, notes, and snippets.

@BenedictC
Created August 5, 2012 10:19
Show Gist options
  • Save BenedictC/3263712 to your computer and use it in GitHub Desktop.
Save BenedictC/3263712 to your computer and use it in GitHub Desktop.
Prototype inheritance and posing for Objective-C
//
// proto+posing.m
//
// Created by Benedict Cohen on 04/08/2012.
// Copyright (c) 2012 Benedict Cohen. All rights reserved.
//
/*
The motivation for this code was to find a way to create subclasses which only differ slightly to their super classes
without having to create a new file. The solution was inspired by prototype inheritance found in Javascript (and IO).
The code I created for this is EMK_subclassByReplacingInstanceMethod:withBlock:. (I'm not completely happy with the
naming).
A problem I discovered while implementing EMK_subclassByReplacingInstanceMethod:withBlock: was that 'super'
within the block was not the super you'd most likely want. EMKPosingProxy is the solution to this problem.
EMKPosingProxy is similar to the posing functionality from Objective-C < 2.0. It allows methods from any class to be
invoked on the proxies target.
I wrote this code to satisfy my own curioisty. It hasn't been tested in any meaningful way and is certainly not safe
for production use.
*/
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#pragma mark - NSObject (EMKProtoSubClass)
//#import "NSObject+EMKProtoSubClass.h"
typedef id(^methodBlock)(id bself, ...);
@implementation NSObject (EMKProtoSubClass)
+(Class)EMK_subclassByReplacingInstanceMethod:(SEL)selector withBlock:(methodBlock)methodBlock {
//create a new subclass of self with a unique name
int subClassCounter = 0;
NSString *protoSubclassName = nil;
do {
protoSubclassName = [NSString stringWithFormat:@"%@-proto%i", NSStringFromClass([self class]), subClassCounter];
subClassCounter++;
} while (NSClassFromString(protoSubclassName) != nil);
Class protoClass = objc_allocateClassPair(self, [protoSubclassName UTF8String], 0);
objc_registerClassPair(protoClass);
//Add the block as a method. We know that protoClass does not implement the method
//so we don't need to check for its existance and then replace it.
Method method = class_getInstanceMethod(protoClass, selector);
IMP implementation = imp_implementationWithBlock(methodBlock);
const char *encoding = method_getTypeEncoding(method);
class_addMethod(protoClass, selector, implementation, encoding);
return protoClass;
}
@end
#pragma mark - EMKPosingProxy
//#import "NSObject+EMKPosingProxy.h"
/**
*
* This class mimics the 'poseAs' functionality from Objective-C < 2.0.
* It fails when:
* - the method being called on the proxy is implemented by NSProxy
* - the called method relies on _cmd
* - other unknown unknowns
*/
@interface EMKPosingProxy : NSProxy
@end
@implementation EMKPosingProxy
{
id _target;
Class _posingClass;
}
+(id)posingProxyWithTarget:(id)target poseAs:(Class)posingClass{
EMKPosingProxy *result = [self alloc];
if (result != nil) {
//we set the ivars directly to avoid implementing any unnecessary instance methods
result->_target = target;
result->_posingClass = posingClass;
}
return result;
}
-(void)forwardInvocation:(NSInvocation *)invocation {
//1. create a new selector for the method by appending the posingClass name
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"%@_implementedByPosingProxy_%@", NSStringFromClass(_posingClass), NSStringFromSelector(invocation.selector)]);
//do we need to implement the method on target?
if (![_target respondsToSelector:selector]) {
//2. get the imp and types from _posingClass that relates to invokation.selector
Method method = class_getInstanceMethod(_posingClass, invocation.selector);
IMP imp = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
//3. add the method to _target
class_addMethod([_target class], selector, imp, types);
}
//4. fire the method
[invocation setSelector:selector];
[invocation setTarget:_target];
[invocation invoke];
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *result = [_posingClass instanceMethodSignatureForSelector:aSelector];
return result;
}
@end
#pragma mark - NSObject (EMKPosingProxy)
@implementation NSObject (EMKPosingProxy)
-(id)EMK_posingProxy:(Class)class {
EMKPosingProxy *proxy = [EMKPosingProxy posingProxyWithTarget:self poseAs:class];
return proxy;
}
@end
#pragma mark - main
int main(int argc, const char * argv[])
{
@autoreleasepool {
NSString *urlText = @"http://example.com/this_should_be_lowercase_unless_a_rogue_class_is_playing_silly_buggers";
//create a prototype 'subclass'
NSURL *weirdUrl = [[NSURL EMK_subclassByReplacingInstanceMethod:@selector(lastPathComponent) withBlock:^id(id bself, ...) {
//We want to call the original implementaton but we can't use super.
//Instead we create a proxy which lets self pose as the class with the desired method implementation
NSString *properLastPathComponent = [[bself EMK_posingProxy:[NSURL self]] lastPathComponent];
return [properLastPathComponent uppercaseString];
}] URLWithString:urlText];
//This will be uppercase.
NSLog(@"%@", [weirdUrl lastPathComponent]);
//This is just to show that NSURL is unaffected.
NSURL *url = [NSURL URLWithString:urlText];
NSLog(@"%@", [url lastPathComponent]);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment