From 404318dfeaf2af1d878321bfbab0fedaa7dd365e Mon Sep 17 00:00:00 2001 From: b123400 Date: Tue, 10 Sep 2013 02:56:10 +0800 Subject: [PATCH 1/7] Added support for function as a GAScriptObject Able to call GAScriptObject as a function --- Classes/GAScriptEngine.m | 2 +- Classes/GAScriptObject.h | 12 +++++++++++- Classes/GAScriptObject.m | 12 ++++++++++++ ga-js-runtime.js | 4 +++- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Classes/GAScriptEngine.m b/Classes/GAScriptEngine.m index 9a85831..c7d8a05 100644 --- a/Classes/GAScriptEngine.m +++ b/Classes/GAScriptEngine.m @@ -206,7 +206,7 @@ - (id)convertScriptResult:(NSString *)result result = [result substringFromIndex:2]; // Objects don't serialize to a string above. - if (jstype == 'o') + if (jstype == 'o' || jstype == 'f') { GAScriptObject* subObj = [[GAScriptObject alloc] initForReference:result withEngine:self]; return [subObj autorelease]; diff --git a/Classes/GAScriptObject.h b/Classes/GAScriptObject.h index 72cd18f..8505e3f 100755 --- a/Classes/GAScriptObject.h +++ b/Classes/GAScriptObject.h @@ -53,6 +53,16 @@ */ - (NSArray *)allKeys; +/** + * Call the object as function with no arguments on this object. + */ +-(id)callAsFunction; + +/** + * Call a function on this object, with arguments. + */ +-(id)callAsFunctionWithArguments:(NSArray*)arguments; + /** * Call a function with no arguments on this object. */ @@ -64,7 +74,7 @@ - (id)callFunction:(NSString *)functionName withObject:(id)argument; /** - * Call a function on this object, with a single argument. + * Call a function on this object, with arguments. */ - (id)callFunction:(NSString *)functionName withArguments:(NSArray *)arguments; diff --git a/Classes/GAScriptObject.m b/Classes/GAScriptObject.m index 15aa686..d6d9ace 100755 --- a/Classes/GAScriptObject.m +++ b/Classes/GAScriptObject.m @@ -132,6 +132,18 @@ - (NSArray *)allKeys return [m_engine evalWithFormat:@"GAJavaScript.propsOf(%@)", m_objReference]; } +-(id)callAsFunction +{ + return [m_engine evalWithFormat:@"GAJavaScript.callFunction(%@,window)", + m_objReference]; +} + +-(id)callAsFunctionWithArguments:(NSArray*)arguments +{ + return [m_engine evalWithFormat:@"GAJavaScript.callFunction(%@,window,[%@])", + m_objReference,[arguments stringForJavaScript]]; +} + - (id)callFunction:(NSString *)functionName { return [m_engine evalWithFormat:@"GAJavaScript.callFunction(%@.%@, %@)", diff --git a/ga-js-runtime.js b/ga-js-runtime.js index 023f73a..2f2e2f5 100644 --- a/ga-js-runtime.js +++ b/ga-js-runtime.js @@ -141,7 +141,9 @@ return { } else if (type === 'object') { return 'o:' + this.makeReference(value); - } + }else if (type === 'function') { + return 'f:' + this.makeReference(value); + } else if (type === 'null') { return 'x:'; } From 8b4b92cb77745730c7ec4edbdbf90cd9a03aed9e Mon Sep 17 00:00:00 2001 From: b123400 Date: Sat, 14 Sep 2013 17:08:22 +0800 Subject: [PATCH 2/7] Script object will not cause crashes when it is not released main thread. Change undefined-->nil to NSNull, so it can be put into array --- Classes/GAScriptEngine.m | 2 +- Classes/GAScriptObject.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/GAScriptEngine.m b/Classes/GAScriptEngine.m index c7d8a05..db8e759 100644 --- a/Classes/GAScriptEngine.m +++ b/Classes/GAScriptEngine.m @@ -234,7 +234,7 @@ - (id)convertScriptResult:(NSString *)result } else if (jstype == 'u') { - return nil; + return [NSNull null]; } else if (jstype == 'e') // JavaScript exception { diff --git a/Classes/GAScriptObject.m b/Classes/GAScriptObject.m index d6d9ace..6560e6d 100755 --- a/Classes/GAScriptObject.m +++ b/Classes/GAScriptObject.m @@ -71,7 +71,7 @@ - (id)initForReference:(NSString *)reference withEngine:(GAScriptEngine *)engine - (void)dealloc { - [self releaseReference]; + [self performSelectorOnMainThread:@selector(releaseReference) withObject:nil waitUntilDone:YES]; [m_objReference release]; [super dealloc]; From c8dd6bbeabbc97d1902d3670c54b38fe6bc77930 Mon Sep 17 00:00:00 2001 From: b123400 Date: Wed, 5 Feb 2014 17:20:03 +0800 Subject: [PATCH 3/7] stringForJavaScript already making an array, so dont need to wrap it. --- Classes/GAScriptObject.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/GAScriptObject.m b/Classes/GAScriptObject.m index 6560e6d..549ad03 100755 --- a/Classes/GAScriptObject.m +++ b/Classes/GAScriptObject.m @@ -140,7 +140,7 @@ -(id)callAsFunction -(id)callAsFunctionWithArguments:(NSArray*)arguments { - return [m_engine evalWithFormat:@"GAJavaScript.callFunction(%@,window,[%@])", + return [m_engine evalWithFormat:@"GAJavaScript.callFunction(%@,window,%@)", m_objReference,[arguments stringForJavaScript]]; } From 1798b04f22f1d61a75b9e8ac965641c1f3484c90 Mon Sep 17 00:00:00 2001 From: b123400 Date: Wed, 5 Feb 2014 17:30:06 +0800 Subject: [PATCH 4/7] Added callAsFunction to README as a solution to the no-return-value problem --- README.markdown | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.markdown b/README.markdown index 3c6b7f8..8b7391e 100644 --- a/README.markdown +++ b/README.markdown @@ -121,6 +121,16 @@ Like other UIWebView wrappers, GAJavaScript supports callbacks from JavaScript t Note that the call is asynchronous, due to the limitations of UIWebView. So there's no return value to performSelector(). However, you can make multiple invocations, and they will be executed in order. +A solution to the no-return-value problem is to use callback: +#### Objective-C + -(void)getValueWithCallback:(GAScriptObject*)callback{ + [callback callAsFunctionWithArguments:@[@"Hello"]]; + } +#### JavaScript + GAJavaScript.performSelector('getValueWithCallback:', function(value){ + // value == "Hello" + }); + And now with blocks: #### Objective-C From 606a4b8886e6bc11ad64e0e3458f6a44e47d4681 Mon Sep 17 00:00:00 2001 From: b123400 Date: Wed, 27 Aug 2014 00:06:26 +0900 Subject: [PATCH 5/7] Expose js runtime path and added custom injection This will allow application to manage when to inject the script. The current method cannot make sure js is injected before your code runs --- Classes/GAScriptEngine.h | 3 +++ Classes/GAScriptEngine.m | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Classes/GAScriptEngine.h b/Classes/GAScriptEngine.h index 85d7ce8..4b877a5 100644 --- a/Classes/GAScriptEngine.h +++ b/Classes/GAScriptEngine.h @@ -117,4 +117,7 @@ extern NSString* const GAJavaScriptErrorLine; */ - (id)evalWithFormat:(NSString *)script, ...; +- (NSString*)javascriptRunTimeFile; +- (NSString*)htmlWithEngineInjected:(NSString*)html; + @end diff --git a/Classes/GAScriptEngine.m b/Classes/GAScriptEngine.m index db8e759..b58d568 100644 --- a/Classes/GAScriptEngine.m +++ b/Classes/GAScriptEngine.m @@ -282,6 +282,21 @@ - (void)addBlockCallback:(GAScriptBlockObject *)blockObject #pragma mark Private +- (NSString*)javascriptRunTimeFile{ + return [[NSBundle mainBundle] pathForResource:@"ga-js-runtime" ofType:@"js"]; +} + +- (NSString*)htmlWithEngineInjected:(NSString*)html{ + if ([self scriptRuntimeIsLoaded]) // Don't re-evaluate the runtime javascript, because it will destroy all existing object references. + return html; + + NSString *runtimeFile = [self javascriptRunTimeFile]; + + NSString *injectedHtml = [html stringByReplacingOccurrencesOfString:@"" withString:[NSString stringWithFormat:@"",runtimeFile]]; + + return injectedHtml; +} + - (void)loadScriptRuntime { if ([self scriptRuntimeIsLoaded]) // Don't re-evaluate the runtime javascript, because it will destroy all existing object references. From 18fa6e287f19fe19f3df933444403e29d5a9faa9 Mon Sep 17 00:00:00 2001 From: b123400 Date: Tue, 9 Sep 2014 15:26:02 +0800 Subject: [PATCH 6/7] Set the delegate manually to avoid crash with BlocksKit --- Classes/GAScriptEngine.h | 2 +- Classes/GAScriptEngine.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Classes/GAScriptEngine.h b/Classes/GAScriptEngine.h index 4b877a5..29ec88d 100644 --- a/Classes/GAScriptEngine.h +++ b/Classes/GAScriptEngine.h @@ -72,7 +72,7 @@ extern NSString* const GAJavaScriptErrorLine; /** * The designated initializer. */ -- (id)initWithWebView:(UIWebView *)webView; +- (id)initWithWebView:(UIWebView *)webView delegate:(id)_delegate; /** * Initializer that creates a hidden UIWebView inside the provided view. Use this initializer diff --git a/Classes/GAScriptEngine.m b/Classes/GAScriptEngine.m index b58d568..c727d6d 100644 --- a/Classes/GAScriptEngine.m +++ b/Classes/GAScriptEngine.m @@ -73,12 +73,12 @@ + (GAScriptEngine *)scriptEngineForView:(UIWebView *)webView return (GAScriptEngine *)webView.delegate; } -- (id)initWithWebView:(UIWebView *)webView +- (id)initWithWebView:(UIWebView *)webView delegate:(id)_delegate { if ((self = [super init])) { m_webView = [webView retain]; - m_delegate = [webView delegate]; + m_delegate = _delegate; m_webView.delegate = self; m_receivers = [[NSMutableArray alloc] initWithCapacity:4]; From f2d4d9dc02005e529eb2a98035d22c8030af53ef Mon Sep 17 00:00:00 2001 From: b123400 Date: Wed, 9 Sep 2015 22:20:13 +0800 Subject: [PATCH 7/7] Handle the case where the engine (webview) dealloc before script objects --- Classes/GAScriptBlockObject.m | 2 +- Classes/GAScriptEngine.m | 22 +++++++++++++++++----- Classes/GAScriptObject.h | 4 +++- Classes/GAScriptObject.m | 4 ++++ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Classes/GAScriptBlockObject.m b/Classes/GAScriptBlockObject.m index ebe9ac8..ec6e8fa 100644 --- a/Classes/GAScriptBlockObject.m +++ b/Classes/GAScriptBlockObject.m @@ -44,7 +44,7 @@ - (id)initWithBlock:(GAScriptBlock)block if ((self = [super init])) { m_block = [block copy]; - m_blockId = [[NSString alloc] initWithFormat:@"block-%u", [self hash]]; + m_blockId = [[NSString alloc] initWithFormat:@"block-%lu", (unsigned long)[self hash]]; } return self; diff --git a/Classes/GAScriptEngine.m b/Classes/GAScriptEngine.m index c727d6d..02a26fa 100644 --- a/Classes/GAScriptEngine.m +++ b/Classes/GAScriptEngine.m @@ -41,6 +41,8 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE @interface GAScriptEngine () +@property (nonatomic, retain) NSMutableSet *jsObjects; + - (id)convertScriptResult:(NSString *)result; - (NSArray *)arrayFromJavaScript:(NSString *)result; @@ -81,7 +83,8 @@ - (id)initWithWebView:(UIWebView *)webView delegate:(id)_delegate m_delegate = _delegate; m_webView.delegate = self; - m_receivers = [[NSMutableArray alloc] initWithCapacity:4]; + m_receivers = [[NSMutableArray alloc] initWithCapacity:4]; + self.jsObjects = [NSMutableSet set]; } static dispatch_once_t onceToken; @@ -107,7 +110,7 @@ - (id)initWithSuperview:(UIView *)superview delegate:(id)dele [superview addSubview:webView]; [webView release]; - return [self initWithWebView:webView]; + return [self initWithWebView:webView delegate:delegate]; } - (void)dealloc @@ -119,7 +122,12 @@ - (void)dealloc [m_webView release]; [m_receivers release]; [m_blocks release]; - + + for (GAScriptObject *jsObject in self.jsObjects) { + [jsObject detachFromEngine]; + } + self.jsObjects = nil; + [super dealloc]; } @@ -128,6 +136,7 @@ - (GAScriptObject *)newScriptObject NSString* objRef = [m_webView stringByEvaluatingJavaScriptFromString:@"GAJavaScript.makeReference(new Object())"]; GAScriptObject* jsObject = [[GAScriptObject alloc] initForReference:objRef withEngine:self]; + [self.jsObjects addObject:jsObject]; return jsObject; } @@ -137,13 +146,15 @@ - (GAScriptObject *)newScriptObject:(NSString *)constructorName NSString* objRef = [m_webView stringByEvaluatingJavaScriptFromString:js]; GAScriptObject* jsObject = [[GAScriptObject alloc] initForReference:objRef withEngine:self]; - return jsObject; + [self.jsObjects addObject:jsObject]; + return jsObject; } - (GAScriptObject *)scriptObjectWithReference:(NSString *)reference { GAScriptObject* jsObject = [[GAScriptObject alloc] initForReference:reference withEngine:self]; - return [jsObject autorelease]; + [self.jsObjects addObject:jsObject]; + return [jsObject autorelease]; } - (id)evalWithFormat:(NSString *)format, ... @@ -209,6 +220,7 @@ - (id)convertScriptResult:(NSString *)result if (jstype == 'o' || jstype == 'f') { GAScriptObject* subObj = [[GAScriptObject alloc] initForReference:result withEngine:self]; + [self.jsObjects addObject:subObj]; return [subObj autorelease]; } else if (jstype == 'd') diff --git a/Classes/GAScriptObject.h b/Classes/GAScriptObject.h index 8505e3f..32a0274 100755 --- a/Classes/GAScriptObject.h +++ b/Classes/GAScriptObject.h @@ -47,7 +47,7 @@ * Designated initializer. */ - (id)initForReference:(NSString *)reference withEngine:(GAScriptEngine *)engine; - + /** * Array containing the names of all the JS properties. */ @@ -85,6 +85,8 @@ */ - (void)setFunctionForKey:(NSString *)key withBlock:(void(^)(NSArray* arguments))block; +- (void)detachFromEngine; + @end #pragma mark - diff --git a/Classes/GAScriptObject.m b/Classes/GAScriptObject.m index 549ad03..1a4a250 100755 --- a/Classes/GAScriptObject.m +++ b/Classes/GAScriptObject.m @@ -77,6 +77,10 @@ - (void)dealloc [super dealloc]; } +- (void)detachFromEngine { + m_engine = nil; +} + - (void)releaseReference { if ([m_objReference hasPrefix:@"GAJavaScript.ref["])