├── .gitignore ├── MABlockForwarding.h ├── MABlockForwarding.m └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | *.dSYM 3 | -------------------------------------------------------------------------------- /MABlockForwarding.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | 5 | typedef void (^BlockInterposer)(NSInvocation *inv, void (^call)(void)); 6 | 7 | id MAForwardingBlock(BlockInterposer interposer, id block); 8 | 9 | id MAMemoize(id block); 10 | -------------------------------------------------------------------------------- /MABlockForwarding.m: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | #import "MABlockForwarding.h" 4 | 5 | struct BlockDescriptor 6 | { 7 | unsigned long reserved; 8 | unsigned long size; 9 | void *rest[1]; 10 | }; 11 | 12 | struct Block 13 | { 14 | void *isa; 15 | int flags; 16 | int reserved; 17 | void *invoke; 18 | struct BlockDescriptor *descriptor; 19 | }; 20 | 21 | enum { 22 | BLOCK_HAS_COPY_DISPOSE = (1 << 25), 23 | BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code 24 | BLOCK_IS_GLOBAL = (1 << 28), 25 | BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE 26 | BLOCK_HAS_SIGNATURE = (1 << 30), 27 | }; 28 | 29 | static void *BlockImpl(id block) 30 | { 31 | return ((struct Block *)block)->invoke; 32 | } 33 | 34 | static const char *BlockSig(id blockObj) 35 | { 36 | struct Block *block = (void *)blockObj; 37 | struct BlockDescriptor *descriptor = block->descriptor; 38 | 39 | assert(block->flags & BLOCK_HAS_SIGNATURE); 40 | 41 | int index = 0; 42 | if(block->flags & BLOCK_HAS_COPY_DISPOSE) 43 | index += 2; 44 | 45 | return descriptor->rest[index]; 46 | } 47 | 48 | @interface NSInvocation (PrivateHack) 49 | - (void)invokeUsingIMP: (IMP)imp; 50 | @end 51 | 52 | @interface MAFakeBlock : NSObject 53 | { 54 | int _flags; 55 | int _reserved; 56 | IMP _invoke; 57 | struct BlockDescriptor *_descriptor; 58 | 59 | id _forwardingBlock; 60 | BlockInterposer _interposer; 61 | } 62 | 63 | - (id)initWithBlock: (id)block interposer: (BlockInterposer)interposer; 64 | 65 | @end 66 | 67 | @implementation MAFakeBlock 68 | 69 | - (id)initWithBlock: (id)block interposer: (BlockInterposer)interposer 70 | { 71 | if((self = [super init])) 72 | { 73 | _forwardingBlock = [block copy]; 74 | _interposer = [interposer copy]; 75 | 76 | // NB: The bottom 16 bits represent the block's retain count 77 | _flags = ((struct Block *) block)->flags & ~0xFFFF; 78 | 79 | _descriptor = malloc(sizeof(struct BlockDescriptor)); 80 | _descriptor->size = class_getInstanceSize([self class]); 81 | 82 | int index = 0; 83 | if (_flags & BLOCK_HAS_COPY_DISPOSE) 84 | index += 2; 85 | 86 | _descriptor->rest[index] = (void *) BlockSig(block); 87 | 88 | if (_flags & BLOCK_HAS_STRET) 89 | _invoke = (IMP) _objc_msgForward_stret; 90 | else 91 | _invoke = _objc_msgForward; 92 | } 93 | return self; 94 | } 95 | 96 | - (void)dealloc 97 | { 98 | [_forwardingBlock release]; 99 | [_interposer release]; 100 | free(_descriptor); 101 | 102 | [super dealloc]; 103 | } 104 | 105 | - (NSMethodSignature *)methodSignatureForSelector: (SEL)sel 106 | { 107 | const char *types = BlockSig(_forwardingBlock); 108 | NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes: types]; 109 | while([sig numberOfArguments] < 2) 110 | { 111 | types = [[NSString stringWithFormat: @"%s%s", types, @encode(void *)] UTF8String]; 112 | sig = [NSMethodSignature signatureWithObjCTypes: types]; 113 | } 114 | return sig; 115 | } 116 | 117 | - (void)forwardInvocation: (NSInvocation *)inv 118 | { 119 | [inv setTarget: _forwardingBlock]; 120 | _interposer(inv, ^{ 121 | [inv invokeUsingIMP: BlockImpl(_forwardingBlock)]; 122 | }); 123 | } 124 | 125 | - (id)copyWithZone: (NSZone *)zone 126 | { 127 | return [self retain]; 128 | } 129 | 130 | @end 131 | 132 | id MAForwardingBlock(BlockInterposer interposer, id block) 133 | { 134 | return [[[MAFakeBlock alloc] initWithBlock: block interposer: interposer] autorelease]; 135 | } 136 | 137 | id MAMemoize(id block) { 138 | NSMutableDictionary *memory = [NSMutableDictionary dictionary]; 139 | 140 | return MAForwardingBlock(^(NSInvocation *inv, void (^call)(void)) { 141 | NSMethodSignature *sig = [inv methodSignature]; 142 | NSMutableArray *args = [NSMutableArray array]; 143 | 144 | for(unsigned i = 1; i < [sig numberOfArguments]; i++) 145 | { 146 | id arg = nil; 147 | const char *type = [sig getArgumentTypeAtIndex: i]; 148 | if(type[0] == @encode(id)[0]) 149 | { 150 | [inv getArgument: &arg atIndex: i]; 151 | if([arg conformsToProtocol: @protocol(NSCopying)]) 152 | arg = [[arg copy] autorelease]; 153 | } 154 | else if(type[0] == @encode(char *)[0]) 155 | { 156 | char *str; 157 | [inv getArgument: &str atIndex: i]; 158 | arg = [NSData dataWithBytes: str length: strlen(str)]; 159 | } 160 | else 161 | { 162 | NSUInteger size; 163 | NSGetSizeAndAlignment(type, &size, NULL); 164 | arg = [NSMutableData dataWithLength: size]; 165 | [inv getArgument: [arg mutableBytes] atIndex: i]; 166 | } 167 | 168 | if(!arg) 169 | arg = [NSNull null]; 170 | 171 | [args addObject: arg]; 172 | } 173 | 174 | const char *type = [sig methodReturnType]; 175 | BOOL isObj = type[0] == @encode(id)[0]; 176 | BOOL isCStr = type[0] == @encode(char *)[0]; 177 | 178 | id result; 179 | @synchronized(memory) 180 | { 181 | result = [[[memory objectForKey: args] retain] autorelease]; 182 | } 183 | 184 | if(!result) 185 | { 186 | call(); 187 | if(isObj) 188 | { 189 | [inv getReturnValue: &result]; 190 | } 191 | else if(isCStr) 192 | { 193 | char *str; 194 | [inv getReturnValue: &str]; 195 | result = str ? [NSData dataWithBytes: str length: strlen(str) + 1] : NULL; 196 | } 197 | else 198 | { 199 | NSUInteger size; 200 | NSGetSizeAndAlignment(type, &size, NULL); 201 | result = [NSMutableData dataWithLength: size]; 202 | [inv getReturnValue: [result mutableBytes]]; 203 | } 204 | 205 | if(!result) 206 | result = [NSNull null]; 207 | 208 | @synchronized(memory) 209 | { 210 | [memory setObject: result forKey: args]; 211 | } 212 | } 213 | else 214 | { 215 | if(result == [NSNull null]) 216 | result = nil; 217 | 218 | if(isObj) 219 | { 220 | [inv setReturnValue: &result]; 221 | } 222 | else if(isCStr) 223 | { 224 | const char *str = [result bytes]; 225 | [inv setReturnValue: &str]; 226 | } 227 | else 228 | { 229 | [inv setReturnValue: [result mutableBytes]]; 230 | } 231 | } 232 | }, block); 233 | } 234 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | // clang -framework Foundation -W -Wall -Wno-unused-parameter -g main.m MABlockForwarding.m 2 | 3 | #import 4 | 5 | #import "MABlockForwarding.h" 6 | 7 | 8 | int main(int argc, char **argv) 9 | { 10 | [NSAutoreleasePool new]; 11 | 12 | int (^intblock)(int) = MAMemoize(^(int x) { 13 | NSLog(@"called with %d", x); 14 | return x + 1; 15 | }); 16 | 17 | intblock = [[intblock copy] autorelease]; 18 | 19 | NSLog(@"%d", intblock(1)); 20 | NSLog(@"%d", intblock(2)); 21 | NSLog(@"%d", intblock(2)); 22 | NSLog(@"%d", intblock(1)); 23 | 24 | id (^objblock)(NSString *, NSString *) = MAMemoize(^(NSString *a, NSString *b) { 25 | NSLog(@"called with %@ %@", a, b); 26 | return [a stringByAppendingString: b]; 27 | }); 28 | 29 | NSLog(@"%@", objblock(@"hello", @"world")); 30 | NSLog(@"%@", objblock(@"hi", @"bob")); 31 | NSLog(@"%@", objblock(@"hi", @"bob")); 32 | NSLog(@"%@", objblock(@"hello", @"world")); 33 | 34 | char *(^cstrblock)(char *, char *) = MAMemoize(^(char *a, char *b) { 35 | NSLog(@"called with %s %s", a, b); 36 | return [[NSString stringWithFormat: @"%s%s", a, b] UTF8String]; 37 | }); 38 | 39 | NSLog(@"%s", cstrblock("hello", "world")); 40 | NSLog(@"%s", cstrblock("hi", "bob")); 41 | NSLog(@"%s", cstrblock("hi", "bob")); 42 | NSLog(@"%s", cstrblock("hello", "world")); 43 | 44 | __block uint64_t (^fib)(int) = MAMemoize(^uint64_t (int n) { 45 | if(n <= 1) 46 | return 1; 47 | else 48 | return fib(n - 1) + fib(n - 2); 49 | }); 50 | 51 | for(int i = 0; i < 60; i++) 52 | NSLog(@"%llu", (unsigned long long)fib(i)); 53 | } 54 | --------------------------------------------------------------------------------