forked from mikeash/MAFuture
-
Notifications
You must be signed in to change notification settings - Fork 0
/
MACompoundFuture.m
187 lines (152 loc) · 6.23 KB
/
MACompoundFuture.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "MAFuture.h"
#import "MAFutureInternal.h"
#import "MAMethodSignatureCache.h"
#define ENABLE_LOGGING 0
#if ENABLE_LOGGING
#define LOG(...) NSLog(__VA_ARGS__)
#else
#define LOG(...)
#endif
@interface _MACompoundFuture : _MALazyBlockFuture
{
}
@end
@implementation _MACompoundFuture
- (BOOL)_canFutureSelector: (SEL)sel
{
NSMethodSignature *sig = [[MAMethodSignatureCache sharedCache] cachedMethodSignatureForSelector: sel];
if(!sig) return NO;
else if([sig methodReturnType][0] != @encode(id)[0]) return NO;
// it exists, returns an object, but does it return any non-objects by reference?
unsigned num = [sig numberOfArguments];
for(unsigned i = 2; i < num; i++)
{
const char *type = [sig getArgumentTypeAtIndex: i];
// if it's a pointer to a non-object, bail out
if(type[0] == '^' && type[1] != '@')
return NO;
}
// we survived this far, all is well
return YES;
}
- (id)forwardingTargetForSelector: (SEL)sel
{
LOG(@"forwardingTargetForSelector: %p %@", self, NSStringFromSelector(sel));
id value = [self futureValue];
if(value)
return value;
else if([self _canFutureSelector: sel])
return nil;
else
return [self resolveFuture];
}
- (NSMethodSignature *)methodSignatureForSelector: (SEL)sel
{
LOG(@"methodSignatureForSelector: %p %@", self, NSStringFromSelector(sel));
NSMethodSignature *sig = [[self futureValue] methodSignatureForSelector: sel];
if(!sig)
sig = [[MAMethodSignatureCache sharedCache] cachedMethodSignatureForSelector: sel];
if(!sig)
sig = [[self resolveFuture] methodSignatureForSelector: sel];
return sig;
}
- (void)forwardInvocation: (NSInvocation *)invocation
{
LOG(@"forwardInvocation: %p %@", self, NSStringFromSelector([invocation selector]));
[_lock lock];
id value = _value;
BOOL resolved = _resolved;
[_lock unlock];
if(resolved)
{
LOG(@"forwardInvocation: %p forwarding to %p", invocation, value);
[invocation invokeWithTarget: value];
}
else
{
// look for return-by-reference objects
_MALazyBlockFuture *invocationFuture = nil;
NSMutableArray *parameterDatas = nil;
NSMethodSignature *sig = [invocation methodSignature];
unsigned num = [sig numberOfArguments];
for(unsigned i = 2; i < num; i++)
{
const char *type = [sig getArgumentTypeAtIndex: i];
if(type[0] == '^' && type[1] == '@')
{
// get the existing pointer-to-object
id *parameterValue;
[invocation getArgument: ¶meterValue atIndex: i];
// if it's NULL, then we don't need to do anything
if(parameterValue)
{
LOG(@"forwardInvocation: %p found return-by-reference object at argument index %u", self, i);
// allocate space to receive the final computed value
NSMutableData *newParameterSpace = [NSMutableData dataWithLength: sizeof(id)];
id *newParameterValue = [newParameterSpace mutableBytes];
// set the parameter to point to the new space
[invocation setArgument: &newParameterValue atIndex: i];
// create a future to refer to the invocation, so that it
// only gets invoked once no matter how many
// compound futures reference it
if(!invocationFuture)
{
parameterDatas = [NSMutableArray array];
invocationFuture = [[_MALazyBlockFuture alloc] initWithBlock: ^{
[invocation invokeWithTarget: [self resolveFuture]];
// keep all parameter datas alive until the invocation is resolved
// by capturing the variable
[parameterDatas self];
return (id)nil;
}];
[invocationFuture autorelease];
}
[parameterDatas addObject: newParameterSpace];
// create the compound future that we'll "return" in this argument
_MACompoundFuture *parameterFuture = [[_MACompoundFuture alloc] initWithBlock: ^{
[invocationFuture resolveFuture];
// capture the NSMutableData to ensure that it stays live
// interior pointer problem
[newParameterSpace self];
return *newParameterValue;
}];
// and now "return" it
*parameterValue = parameterFuture;
// memory management
[parameterFuture autorelease];
}
}
}
[invocation retainArguments];
_MACompoundFuture *returnFuture = [[_MACompoundFuture alloc] initWithBlock:^{
id value = nil;
if(invocationFuture)
[invocationFuture resolveFuture];
else
[invocation invokeWithTarget: [self resolveFuture]];
[invocation getReturnValue: &value];
return value;
}];
LOG(@"forwardInvocation: %p creating new compound future %p", invocation, returnFuture);
[invocation setReturnValue: &returnFuture];
[returnFuture release];
}
}
@end
#undef MACompoundBackgroundFuture
id MACompoundBackgroundFuture(id (^block)(void))
{
id blockFuture = MABackgroundFuture(block);
_MACompoundFuture *compoundFuture = [[_MACompoundFuture alloc] initWithBlock: ^{
return [blockFuture resolveFuture];
}];
return [compoundFuture autorelease];
}
#undef MACompoundLazyFuture
id MACompoundLazyFuture(id (^block)(void))
{
_MACompoundFuture *compoundFuture = [[_MACompoundFuture alloc] initWithBlock: block];
return [compoundFuture autorelease];
}