Created
August 5, 2012 10:19
-
-
Save BenedictC/3263712 to your computer and use it in GitHub Desktop.
Prototype inheritance and posing for Objective-C
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
// | |
// 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