Skip to content

Commit 5f31326

Browse files
Support NSInvocation for function arguments. Fixed date conversion.
1 parent 8cc9a37 commit 5f31326

11 files changed

Lines changed: 150 additions & 58 deletions

Classes/GAScriptEngine.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
UIWebView* m_webView;
4141
id<UIWebViewDelegate> m_delegate;
4242

43-
NSMutableArray* m_receivers;
43+
NSMutableArray* m_receivers;
44+
NSMutableDictionary* m_invocations;
4445
}
4546

4647
/**
@@ -74,4 +75,9 @@
7475
*/
7576
- (id)callFunction:(NSString *)functionName;
7677

78+
/*
79+
* Call a function on this object, with a single argument.
80+
*/
81+
- (id)callFunction:(NSString *)functionName withObject:(id)argument;
82+
7783
@end

Classes/GAScriptEngine.m

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,21 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
2828

2929
#import "GAScriptEngine.h"
3030
#import "GAScriptObject.h"
31+
#import "NSObject+GAJavaScript.h"
3132

3233
@interface GAScriptEngine ()
3334

34-
/*
35+
/**
3536
* Loads the GAJavaScript runtime into this webview. This method should be called in the
3637
* UIWebViewDelegate webViewDidFinishLoad: method.
3738
*/
3839
- (void)loadScriptRuntime;
3940

41+
/**
42+
* Saves (retains) the given argument if it's needed for an asynchronous callback.
43+
*/
44+
- (void)retainCallArgumentIfNecessary:(id)argument;
45+
4046
- (void)makeLotsaCalls;
4147

4248
- (void)callReceiversForSelector:(SEL)theSelector withArguments:(NSArray *)arguments;
@@ -58,6 +64,8 @@ - (id)initWithWebView:(UIWebView *)webView
5864
m_webView.delegate = self;
5965

6066
m_receivers = [[NSMutableArray alloc] initWithCapacity:4];
67+
68+
m_invocations = [[NSMutableDictionary alloc] initWithCapacity:4];
6169
}
6270

6371
return self;
@@ -67,6 +75,7 @@ - (void)dealloc
6775
{
6876
[m_webView release];
6977
[m_receivers release];
78+
[m_invocations release];
7079

7180
[super dealloc];
7281
}
@@ -99,6 +108,13 @@ - (id)callFunction:(NSString *)functionName
99108
return [[self scriptObjectWithReference:@"window"] callFunction:functionName];
100109
}
101110

111+
- (id)callFunction:(NSString *)functionName withObject:(id)argument
112+
{
113+
return [[self scriptObjectWithReference:@"window"] callFunction:functionName withObject:argument];
114+
}
115+
116+
#pragma mark Private
117+
102118
- (void)loadScriptRuntime
103119
{
104120
NSString* scriptFile = [[NSBundle mainBundle] pathForResource:@"ga-js-runtime" ofType:@"js"];
@@ -107,6 +123,12 @@ - (void)loadScriptRuntime
107123
[m_webView stringByEvaluatingJavaScriptFromString:scriptData];
108124
}
109125

126+
- (void)retainCallArgumentIfNecessary:(id)argument
127+
{
128+
if ([argument isKindOfClass:[NSInvocation class]])
129+
[m_invocations setObject:argument forKey:[NSNumber numberWithUnsignedInt:[argument hash]]];
130+
}
131+
110132
- (void)makeLotsaCalls
111133
{
112134
id calls = [self scriptObjectWithReference:@"GAJavaScript.calls"];
@@ -119,10 +141,22 @@ - (void)makeLotsaCalls
119141
id call = [calls objectAtIndex:i];
120142

121143
NSString* selName = [call valueForKey:@"sel"];
144+
NSNumber* invName = [call valueForKey:@"inv"];
122145
NSArray* arguments = [call valueForKey:@"args"];
123-
SEL theSelector = NSSelectorFromString(selName);
124-
125-
[self callReceiversForSelector:theSelector withArguments:arguments];
146+
147+
if (selName != nil)
148+
{
149+
SEL theSelector = NSSelectorFromString(selName);
150+
[self callReceiversForSelector:theSelector withArguments:arguments];
151+
}
152+
else if (invName != nil)
153+
{
154+
NSInvocation* invocation = [m_invocations objectForKey:invName];
155+
[invocation setArgumentsFromJavaScript:arguments];
156+
[invocation invoke];
157+
158+
[m_invocations removeObjectForKey:invName];
159+
}
126160
}
127161
}
128162

@@ -139,12 +173,9 @@ - (void)callReceiversForSelector:(SEL)theSelector withArguments:(NSArray *)argum
139173

140174
NSMethodSignature* methodSig = [receiver methodSignatureForSelector:theSelector];
141175
NSInvocation* inv = [NSInvocation invocationWithMethodSignature:methodSig];
142-
NSInteger argIndex = 2;
143-
144-
for (id arg in arguments)
145-
[inv setArgument:&arg atIndex:argIndex++];
146176

147177
[inv setSelector:theSelector];
178+
[inv setArgumentsFromJavaScript:arguments];
148179
[inv invokeWithTarget:receiver];
149180

150181
// Ignore return values...

Classes/GAScriptObject.m

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
2727
*/
2828

2929
#import "GAScriptObject.h"
30+
#import "GAScriptMethodSignatures.h"
3031
#import "NSObject+GAJavaScript.h"
3132

3233
typedef struct /* GAScriptObjectEnumState */
@@ -164,7 +165,10 @@ - (id)callFunction:(NSString *)functionName withObject:(id)argument
164165
{
165166
NSString* js = [NSString stringWithFormat:@"GAJavaScript.callFunction(%@.%@, %@, [%@])",
166167
m_objReference, functionName, m_objReference, [argument stringForJavaScript]];
167-
NSString* result = [m_webView stringByEvaluatingJavaScriptFromString:js];
168+
NSString* result = [m_webView stringByEvaluatingJavaScriptFromString:js];
169+
170+
id scriptEngine = m_webView.delegate;
171+
[scriptEngine retainCallArgumentIfNecessary:argument];
168172

169173
return [self convertScriptResult:result reference:m_objReference];
170174
}
@@ -246,8 +250,8 @@ - (id)convertScriptResult:(NSString *)result reference:(NSString *)reference
246250
}
247251
else if (jstype == 'd')
248252
{
249-
NSNumber* timeVal = [kNumFormatter numberFromString:result];
250-
return [NSDate dateWithTimeIntervalSince1970:[timeVal doubleValue]];
253+
NSNumber* millisecsSince1970 = [kNumFormatter numberFromString:result];
254+
return [NSDate dateWithTimeIntervalSince1970:[millisecsSince1970 doubleValue] / 1000];
251255
}
252256
else if (jstype == 'n')
253257
{
@@ -356,7 +360,7 @@ - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
356360

357361
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
358362
{
359-
return [NSObject findInAllClassesMethodSignatureForSelector:aSelector];
363+
return [GAScriptMethodSignatures findMethodSignatureForSelector:aSelector];
360364
}
361365

362366
- (void)forwardInvocation:(NSInvocation *)anInvocation
@@ -402,8 +406,8 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation
402406

403407
//TODO: Handle non-Object types.
404408
//
405-
if (retVal != nil && [methodSig methodReturnLength] > 0)
406-
[anInvocation setReturnValue:retVal];
409+
if ([methodSig methodReturnLength] > 0)
410+
[anInvocation setReturnValue:&retVal];
407411
}
408412

409413
@end

Classes/NSObject+GAJavaScript.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,6 @@
3333
*/
3434
@interface NSObject (GAJavaScript)
3535

36-
/**
37-
* Used by the script object to find a method signature for the given selector.
38-
*/
39-
+ (NSMethodSignature *)findInAllClassesMethodSignatureForSelector:(SEL)aSelector;
40-
4136
/*
4237
* The default implementation for all objects is to return [self description] quoted for JS.
4338
*/
@@ -115,6 +110,19 @@
115110

116111
#pragma mark -
117112

113+
/*
114+
* Returns a callback closure for this invocation.
115+
*/
116+
@interface NSInvocation (GAJavaScript)
117+
118+
- (NSString *)stringForJavaScript;
119+
120+
- (void)setArgumentsFromJavaScript:(NSArray *)arguments;
121+
122+
@end
123+
124+
#pragma mark -
125+
118126
/*
119127
* Returns NO so that errors can be checked via an "if" statement.
120128
*/

Classes/NSObject+GAJavaScript.m

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,40 +29,8 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
2929
#import "NSObject+GAJavaScript.h"
3030
#import <objc/runtime.h>
3131

32-
static int s_numClasses = 0;
33-
static Class* s_classList = NULL;
34-
3532
@implementation NSObject (GAJavaScript)
3633

37-
+ (NSMethodSignature *)findInAllClassesMethodSignatureForSelector:(SEL)aSelector
38-
{
39-
if (s_classList == NULL)
40-
{
41-
int numClasses = objc_getClassList(NULL, 0);
42-
43-
if (numClasses > 0 )
44-
{
45-
s_classList = malloc(sizeof(Class) * numClasses);
46-
s_numClasses = objc_getClassList(s_classList, numClasses);
47-
}
48-
}
49-
50-
// If performance becomes a concern, we can build a cache of these mappings.
51-
//
52-
for (int i = 0; i < s_numClasses; ++i)
53-
{
54-
Class aClass = s_classList[i];
55-
56-
if (aClass == [NSProxy class]) // Throws an exception
57-
continue;
58-
59-
if ([aClass instancesRespondToSelector:aSelector])
60-
return [aClass instanceMethodSignatureForSelector:aSelector];
61-
}
62-
63-
return nil;
64-
}
65-
6634
- (NSString *)stringForJavaScript
6735
{
6836
unsigned int numProps = 0;
@@ -179,7 +147,7 @@ @implementation NSDate (GAJavaScript)
179147

180148
- (NSString *)stringForJavaScript
181149
{
182-
return [NSString stringWithFormat:@"new Date(%.0f)", [self timeIntervalSince1970]];
150+
return [NSString stringWithFormat:@"new Date(%.0f)", [self timeIntervalSince1970] * 1000];
183151
}
184152

185153
@end
@@ -236,6 +204,29 @@ - (NSString *)stringForJavaScript
236204

237205
#pragma mark -
238206

207+
@implementation NSInvocation (GAJavaScript)
208+
209+
- (NSString *)stringForJavaScript
210+
{
211+
return [NSString stringWithFormat:@"function () { GAJavaScript.invocation(%u, arguments); }", [self hash]];
212+
}
213+
214+
- (void)setArgumentsFromJavaScript:(NSArray *)arguments
215+
{
216+
NSInteger argIndex = 2;
217+
218+
for (id arg in arguments)
219+
{
220+
// TODO: Need to get the address right based on types...
221+
//
222+
[self setArgument:&arg atIndex:argIndex++];
223+
}
224+
}
225+
226+
@end
227+
228+
#pragma mark -
229+
239230
@implementation NSError (GAJavaScript)
240231

241232
- (BOOL)isJavaScriptTrue

GAJavaScript.xcodeproj/project.pbxproj

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
493386AD12B582A300FBE23C /* NSObject+GAJavaScript.m in Sources */ = {isa = PBXBuildFile; fileRef = 493386AB12B582A300FBE23C /* NSObject+GAJavaScript.m */; };
2121
4933871412B58C9E00FBE23C /* TWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4933870F12B58C5700FBE23C /* TWebView.m */; };
2222
4983385F13782DFC0006B26A /* ApplicationDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4983385E13782DFC0006B26A /* ApplicationDelegate.m */; };
23+
49839832138C7CFA003A23D6 /* TScriptObjectBlocks.m in Sources */ = {isa = PBXBuildFile; fileRef = 49839831138C7CFA003A23D6 /* TScriptObjectBlocks.m */; };
24+
498A2C5013928F6600B95CC9 /* GAScriptMethodSignatures.h in Headers */ = {isa = PBXBuildFile; fileRef = 498A2C4E13928F6600B95CC9 /* GAScriptMethodSignatures.h */; };
25+
498A2C5113928F6600B95CC9 /* GAScriptMethodSignatures.m in Sources */ = {isa = PBXBuildFile; fileRef = 498A2C4F13928F6600B95CC9 /* GAScriptMethodSignatures.m */; };
2326
49F1DBDE12A3F64A004C8736 /* GAScriptObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 49F1DBDA12A3F64A004C8736 /* GAScriptObject.h */; };
2427
49F1DBDF12A3F64A004C8736 /* GAScriptObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 49F1DBDB12A3F64A004C8736 /* GAScriptObject.m */; };
2528
49F1DBE512A3F67A004C8736 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49F1DBE412A3F67A004C8736 /* UIKit.framework */; };
@@ -59,6 +62,10 @@
5962
4933870F12B58C5700FBE23C /* TWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TWebView.m; path = Tests/TWebView.m; sourceTree = "<group>"; };
6063
4983385D13782DFC0006B26A /* ApplicationDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ApplicationDelegate.h; path = Tests/ApplicationDelegate.h; sourceTree = "<group>"; };
6164
4983385E13782DFC0006B26A /* ApplicationDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ApplicationDelegate.m; path = Tests/ApplicationDelegate.m; sourceTree = "<group>"; };
65+
49839830138C7CFA003A23D6 /* TScriptObjectBlocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TScriptObjectBlocks.h; path = Tests/TScriptObjectBlocks.h; sourceTree = "<group>"; };
66+
49839831138C7CFA003A23D6 /* TScriptObjectBlocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TScriptObjectBlocks.m; path = Tests/TScriptObjectBlocks.m; sourceTree = "<group>"; };
67+
498A2C4E13928F6600B95CC9 /* GAScriptMethodSignatures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GAScriptMethodSignatures.h; path = Classes/GAScriptMethodSignatures.h; sourceTree = "<group>"; };
68+
498A2C4F13928F6600B95CC9 /* GAScriptMethodSignatures.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GAScriptMethodSignatures.m; path = Classes/GAScriptMethodSignatures.m; sourceTree = "<group>"; };
6269
49F1DBDA12A3F64A004C8736 /* GAScriptObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GAScriptObject.h; path = Classes/GAScriptObject.h; sourceTree = "<group>"; };
6370
49F1DBDB12A3F64A004C8736 /* GAScriptObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GAScriptObject.m; path = Classes/GAScriptObject.m; sourceTree = "<group>"; };
6471
49F1DBE412A3F67A004C8736 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
@@ -132,6 +139,8 @@
132139
2D8500AD1381647F00758EA2 /* GAScriptEngine.m */,
133140
49F1DBDA12A3F64A004C8736 /* GAScriptObject.h */,
134141
49F1DBDB12A3F64A004C8736 /* GAScriptObject.m */,
142+
498A2C4E13928F6600B95CC9 /* GAScriptMethodSignatures.h */,
143+
498A2C4F13928F6600B95CC9 /* GAScriptMethodSignatures.m */,
135144
493386AA12B582A300FBE23C /* NSObject+GAJavaScript.h */,
136145
493386AB12B582A300FBE23C /* NSObject+GAJavaScript.m */,
137146
49F1DC8D12A3F7E2004C8736 /* UIWebView+GAJavaScript.h */,
@@ -155,10 +164,12 @@
155164
children = (
156165
4983385D13782DFC0006B26A /* ApplicationDelegate.h */,
157166
4983385E13782DFC0006B26A /* ApplicationDelegate.m */,
158-
4933848412B4373600FBE23C /* TScriptObject.h */,
159-
4933848512B4373600FBE23C /* TScriptObject.m */,
160167
2D5994851382B0F0006B728D /* TScriptEngine.h */,
161168
2D5994861382B0F0006B728D /* TScriptEngine.m */,
169+
4933848412B4373600FBE23C /* TScriptObject.h */,
170+
4933848512B4373600FBE23C /* TScriptObject.m */,
171+
49839830138C7CFA003A23D6 /* TScriptObjectBlocks.h */,
172+
49839831138C7CFA003A23D6 /* TScriptObjectBlocks.m */,
162173
4933870E12B58C5700FBE23C /* TWebView.h */,
163174
4933870F12B58C5700FBE23C /* TWebView.m */,
164175
493383A012B4338200FBE23C /* GAJavaScriptTests-Info.plist */,
@@ -179,6 +190,7 @@
179190
49F1DC8F12A3F7E2004C8736 /* UIWebView+GAJavaScript.h in Headers */,
180191
493386AC12B582A300FBE23C /* NSObject+GAJavaScript.h in Headers */,
181192
2D8500AE1381647F00758EA2 /* GAScriptEngine.h in Headers */,
193+
498A2C5013928F6600B95CC9 /* GAScriptMethodSignatures.h in Headers */,
182194
);
183195
runOnlyForDeploymentPostprocessing = 0;
184196
};
@@ -268,6 +280,7 @@
268280
4933871412B58C9E00FBE23C /* TWebView.m in Sources */,
269281
4983385F13782DFC0006B26A /* ApplicationDelegate.m in Sources */,
270282
2D5994871382B0F0006B728D /* TScriptEngine.m in Sources */,
283+
49839832138C7CFA003A23D6 /* TScriptObjectBlocks.m in Sources */,
271284
);
272285
runOnlyForDeploymentPostprocessing = 0;
273286
};
@@ -279,6 +292,7 @@
279292
49F1DC9012A3F7E2004C8736 /* UIWebView+GAJavaScript.m in Sources */,
280293
493386AD12B582A300FBE23C /* NSObject+GAJavaScript.m in Sources */,
281294
2D8500AF1381647F00758EA2 /* GAScriptEngine.m in Sources */,
295+
498A2C5113928F6600B95CC9 /* GAScriptMethodSignatures.m in Sources */,
282296
);
283297
runOnlyForDeploymentPostprocessing = 0;
284298
};

Tests/TScriptEngine.m

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,19 @@ - (void)testMultipleCallbacks
7171
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:1.0];
7272
}
7373

74+
- (void)testCallbackAsArgument
75+
{
76+
[self prepare:@selector(invocationCallback:andString:andDate:)];
77+
78+
NSMethodSignature* sig = [self methodSignatureForSelector:@selector(invocationCallback:andString:andDate:)];
79+
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:sig];
80+
[invocation setSelector:@selector(invocationCallback:andString:andDate:)];
81+
[invocation setTarget:self];
82+
[_engine callFunction:@"testCallbackAsArgument" withObject:invocation];
83+
84+
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:1.0];
85+
}
86+
7487
- (void)callbackNoArgs
7588
{
7689
// NSLog(@"Callback() from JavaScript");
@@ -88,4 +101,11 @@ - (void)callbackOneArg:(NSString *)theArgument
88101
[self notify:status];
89102
}
90103

104+
- (void)invocationCallback:(NSString *)arg1 andString:(NSString *)arg2 andDate:(NSDate *)arg3
105+
{
106+
// NSLog(@"Callback from Invocation %@ %@ %@", arg1, arg2, arg3);
107+
108+
[self notify:kGHUnitWaitStatusSuccess];
109+
}
110+
91111
@end

0 commit comments

Comments
 (0)