Created
September 21, 2010 14:17
-
-
Save JensAyton/589740 to your computer and use it in GitHub Desktop.
Hack to enable the use of block ivars as IBAction targets. Stuffs a bunch of nastiness in NSObject, but transparenty works for classes implemeting actions: create a property of type BlockAction, assign a block to it, and use the property name (with a trai
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
/* | |
Hack to enable the use of block ivars as IBAction targets. Stuffs a bunch | |
of nastiness in NSObject, but transparenty works for classes implemeting | |
actions: create a property of type BlockAction, assign a block to it, | |
and use the property name (with a trailing colon) as the target selector. | |
Did I mention it's a hack? There are two major problems: | |
* Type safety. It assumes any block property will have the correct | |
signature. Can be partially worked around when compiling with Clang, | |
which embeds @encode() strings into blocks, but this is undocumented. | |
Also, @encode() strings are unreliable, but they're sufficient to | |
recognise valid actions. If you want to try this, see | |
http://github.com/mikeash/MABlockClosure | |
* It introduces slow code into some core runtime methods. This could be | |
alleviated with a cache. | |
Why? Because of this: | |
http://stackoverflow.com/questions/3738611/stored-block-closure-as-ibaction/ | |
*/ | |
#import <objc/runtime.h> | |
typedef void(^BlockAction)(id sender); | |
@interface BlockActionTestAppDelegate : NSObject <NSApplicationDelegate> | |
{ | |
NSWindow *window; | |
BlockAction testAction; | |
} | |
@property (assign) IBOutlet NSWindow *window; | |
@property (copy) BlockAction testAction; | |
@end | |
@interface BlockActionTestAppDelegate (BlockActions) | |
// Note that this isn't implemented anywhere. | |
- (IBAction) testAction:(id)sender; | |
@end | |
@interface NSObject (BlockActions) | |
+ (void) ja_blockActionsInit; | |
- (BOOL) ja_blockActionsRespondsToSelector:(SEL)selector; | |
@end | |
@implementation BlockActionTestAppDelegate | |
@synthesize window, testAction; | |
+ (void) initialize | |
{ | |
[NSObject ja_blockActionsInit]; | |
} | |
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification | |
{ | |
self.testAction = ^(id sender){ NSLog(@"Test action with sender: %@", sender); }; | |
} | |
@end | |
@implementation NSObject (BlockActions) | |
static BOOL inited; | |
- (SEL) ja_blockActionsPropertySelectorForActionSelector:(SEL)selector | |
{ | |
// If I was considering using this in real code, which I'm not, I'd use a per-class cache. | |
assert(inited); | |
const char *name = sel_getName(selector); | |
size_t index = 0; | |
// Ensure that it's a unary method, i.e. there is exactly one colon at the end. | |
for (;;) | |
{ | |
char curr = name[index++]; | |
if (curr == ':') | |
{ | |
if (name[index] == '\0') break; | |
else return NULL; | |
} | |
else if (curr == '\0') return NULL; | |
} | |
// Create corresponding selector without trailing colon. | |
char propName[index]; | |
memcpy(propName, selector, index - 1); | |
propName[index - 1] = '\0'; | |
SEL propSel = sel_getUid(propName); | |
if ([self ja_blockActionsRespondsToSelector:propSel]) | |
{ | |
/* Check if it's a property of block type. | |
Does not check type signature of the block. This is possible, but | |
undocumented, when using Clang. For details, see | |
http://github.com/mikeash/MABlockClosure | |
*/ | |
objc_property_t propInfo = class_getProperty([self class], propName); | |
if (propInfo != NULL) | |
{ | |
NSString *propAttrStr = [NSString stringWithUTF8String:property_getAttributes(propInfo)]; | |
if (propAttrStr.length > 0) | |
{ | |
NSArray *propAttrs = [propAttrStr componentsSeparatedByString:@","]; | |
for (NSString *attr in propAttrs) | |
{ | |
if ([attr isEqualToString:@"T@?"]) | |
{ | |
return propSel; | |
} | |
} | |
} | |
} | |
} | |
return NULL; | |
} | |
- (void) ja_blockActionsForwardInvocation:(NSInvocation *)invocation | |
{ | |
SEL propSel = [self ja_blockActionsPropertySelectorForActionSelector:invocation.selector]; | |
if (propSel != NULL) | |
{ | |
BlockAction action = [self performSelector:propSel]; | |
id sender; | |
[invocation getArgument:&sender atIndex:2]; | |
action(sender); | |
} | |
else | |
{ | |
[self ja_blockActionsForwardInvocation:invocation]; | |
} | |
} | |
- (BOOL) ja_blockActionsRespondsToSelector:(SEL)selector | |
{ | |
BOOL exists = [self ja_blockActionsRespondsToSelector:selector]; | |
if (!exists) | |
{ | |
exists = [self ja_blockActionsPropertySelectorForActionSelector:selector] != NULL; | |
} | |
return exists; | |
} | |
- (IBAction) ja_blockActionsActionSignature:(id)sender | |
{ | |
} | |
- (NSMethodSignature *) ja_blockActionsMethodSignatureForSelector:(SEL)selector | |
{ | |
NSMethodSignature *result = [self ja_blockActionsMethodSignatureForSelector:selector]; | |
if (result == nil && [self ja_blockActionsPropertySelectorForActionSelector:selector] != NULL) | |
{ | |
result = [self ja_blockActionsMethodSignatureForSelector:@selector(ja_blockActionsActionSignature:)]; | |
} | |
return result; | |
} | |
// Lifted straight from http://www.cocoadev.com/index.pl?MethodSwizzling | |
static void Swizzle(Class c, SEL orig, SEL new) | |
{ | |
Method origMethod = class_getInstanceMethod(c, orig); | |
Method newMethod = class_getInstanceMethod(c, new); | |
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) | |
{ | |
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); | |
} | |
else | |
{ | |
method_exchangeImplementations(origMethod, newMethod); | |
} | |
} | |
+ (void) ja_blockActionsInit | |
{ | |
if (!inited) | |
{ | |
inited = YES; | |
Class nsobject = [NSObject class]; | |
Swizzle(nsobject, @selector(forwardInvocation:), @selector(ja_blockActionsForwardInvocation:)); | |
Swizzle(nsobject, @selector(respondsToSelector:), @selector(ja_blockActionsRespondsToSelector:)); | |
Swizzle(nsobject, @selector(methodSignatureForSelector:), @selector(ja_blockActionsMethodSignatureForSelector:)); | |
} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I’d completely forgotten about
+resolveInstanceMethod:
, that is indeed a better approach.