From 3d1e08a161c69878873055e33a43f019bc75b4d2 Mon Sep 17 00:00:00 2001 From: Zhenyu Liang Date: Mon, 27 Jul 2015 03:10:13 +0800 Subject: [PATCH] Rewrite XWVInvocation with Swift --- XWebView.xcodeproj/project.pbxproj | 22 +- XWebView/XWVBindingObject.swift | 34 +-- XWebView/XWVInvocation.h | 51 ----- XWebView/XWVInvocation.m | 318 -------------------------- XWebView/XWVInvocation.swift | 274 ++++++++++++++++++++++ XWebView/XWVLoader.swift | 2 +- XWebView/XWebView.h | 3 +- XWebViewTests/XWVInvocationTest.m | 84 ------- XWebViewTests/XWVInvocationTest.swift | 114 +++++++++ 9 files changed, 418 insertions(+), 484 deletions(-) delete mode 100644 XWebView/XWVInvocation.h delete mode 100644 XWebView/XWVInvocation.m create mode 100644 XWebView/XWVInvocation.swift delete mode 100644 XWebViewTests/XWVInvocationTest.m create mode 100644 XWebViewTests/XWVInvocationTest.swift diff --git a/XWebView.xcodeproj/project.pbxproj b/XWebView.xcodeproj/project.pbxproj index 5c2d558..3b5adb4 100644 --- a/XWebView.xcodeproj/project.pbxproj +++ b/XWebView.xcodeproj/project.pbxproj @@ -12,8 +12,6 @@ AB023EBE1A8C8BC700580A2A /* XWebView.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = EE62683519FA323900EFC3F8 /* XWebView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; AB2273E51AA6FDA700F9207A /* www in Resources */ = {isa = PBXBuildFile; fileRef = AB2273E41AA6FDA700F9207A /* www */; }; ABD3E8541A8CD08300F2BAB9 /* XWVInventoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABD3E8531A8CD08300F2BAB9 /* XWVInventoryTest.swift */; }; - ABD3E85E1A8CD49F00F2BAB9 /* XWVInvocationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ABD3E85D1A8CD49F00F2BAB9 /* XWVInvocationTest.m */; }; - ABD475E31A4129FC00F3BDEB /* XWVInvocation.h in Headers */ = {isa = PBXBuildFile; fileRef = EE62691D19FA52FC00EFC3F8 /* XWVInvocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; ABF68ECD1A6B45FC0058267B /* XWebView.h in Headers */ = {isa = PBXBuildFile; fileRef = EE62691C19FA52FC00EFC3F8 /* XWebView.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE0A1DD31A52775400C9E6D3 /* XWVChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0A1DD21A52775400C9E6D3 /* XWVChannel.swift */; }; EE131CA71B5F900400A9E790 /* XWVUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE131CA61B5F900400A9E790 /* XWVUserScript.swift */; }; @@ -26,7 +24,7 @@ EE3379391AE2E298009124A4 /* XWVTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3379381AE2E298009124A4 /* XWVTestCase.swift */; }; EE33793E1AE56875009124A4 /* XWVScriptObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE33793D1AE56875009124A4 /* XWVScriptObject.swift */; }; EE3379401AE57566009124A4 /* XWVScriptingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */; }; - EE62692419FA52FC00EFC3F8 /* XWVInvocation.m in Sources */ = {isa = PBXBuildFile; fileRef = EE62691E19FA52FC00EFC3F8 /* XWVInvocation.m */; }; + EE5BA7BD1B67DC940095AAE7 /* XWVInvocationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5BA7BC1B67DC940095AAE7 /* XWVInvocationTest.swift */; }; EE62692619FA52FC00EFC3F8 /* XWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE62692019FA52FC00EFC3F8 /* XWebView.swift */; }; EE71648F1A716C9F00078FF9 /* XWVHttpConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = EE71648B1A716C9F00078FF9 /* XWVHttpConnection.h */; }; EE7164901A716C9F00078FF9 /* XWVHttpConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = EE71648C1A716C9F00078FF9 /* XWVHttpConnection.m */; }; @@ -36,6 +34,7 @@ EE92C65F1B5ACF81000FE1DA /* XWVMetaObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */; }; EE92C6611B5AD7DB000FE1DA /* XWVMetaObjectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C6601B5AD7DB000FE1DA /* XWVMetaObjectTest.swift */; }; EE92C6621B5AD86E000FE1DA /* XWVMetaObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */; }; + EEDF30601B6555B900A21659 /* XWVInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDF305F1B6555B900A21659 /* XWVInvocation.swift */; }; EEE6F9A41AE02CF100A2EC89 /* XWVScripting.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A31AE02CF100A2EC89 /* XWVScripting.swift */; }; EEE6F9A61AE02E8600A2EC89 /* XWVObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A51AE02E8600A2EC89 /* XWVObject.swift */; }; EEE6F9A81AE02F5000A2EC89 /* XWVBindingObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE6F9A71AE02F5000A2EC89 /* XWVBindingObject.swift */; }; @@ -72,7 +71,6 @@ AB023EA41A8C506600580A2A /* XWebViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XWebViewTests.swift; sourceTree = ""; }; AB2273E41AA6FDA700F9207A /* www */ = {isa = PBXFileReference; lastKnownFileType = folder; path = www; sourceTree = ""; }; ABD3E8531A8CD08300F2BAB9 /* XWVInventoryTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVInventoryTest.swift; sourceTree = ""; }; - ABD3E85D1A8CD49F00F2BAB9 /* XWVInvocationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XWVInvocationTest.m; sourceTree = ""; }; EE0A1DD21A52775400C9E6D3 /* XWVChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVChannel.swift; path = XWebView/XWVChannel.swift; sourceTree = ""; }; EE131CA61B5F900400A9E790 /* XWVUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVUserScript.swift; path = XWebView/XWVUserScript.swift; sourceTree = ""; }; EE174E441A01FDDE00168D96 /* XWVInventory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVInventory.swift; path = XWebView/XWVInventory.swift; sourceTree = ""; }; @@ -84,11 +82,10 @@ EE3379381AE2E298009124A4 /* XWVTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVTestCase.swift; sourceTree = ""; }; EE33793D1AE56875009124A4 /* XWVScriptObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVScriptObject.swift; path = XWebView/XWVScriptObject.swift; sourceTree = ""; }; EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVScriptingTest.swift; sourceTree = ""; }; + EE5BA7BC1B67DC940095AAE7 /* XWVInvocationTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVInvocationTest.swift; sourceTree = ""; }; EE62683519FA323900EFC3F8 /* XWebView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = XWebView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE62691319FA52D100EFC3F8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = XWebView/Info.plist; sourceTree = ""; }; EE62691C19FA52FC00EFC3F8 /* XWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XWebView.h; path = XWebView/XWebView.h; sourceTree = ""; }; - EE62691D19FA52FC00EFC3F8 /* XWVInvocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XWVInvocation.h; path = XWebView/XWVInvocation.h; sourceTree = ""; }; - EE62691E19FA52FC00EFC3F8 /* XWVInvocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XWVInvocation.m; path = XWebView/XWVInvocation.m; sourceTree = ""; }; EE62692019FA52FC00EFC3F8 /* XWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWebView.swift; path = XWebView/XWebView.swift; sourceTree = ""; }; EE71648B1A716C9F00078FF9 /* XWVHttpConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XWVHttpConnection.h; path = XWebView/XWVHttpConnection.h; sourceTree = ""; }; EE71648C1A716C9F00078FF9 /* XWVHttpConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XWVHttpConnection.m; path = XWebView/XWVHttpConnection.m; sourceTree = ""; }; @@ -97,6 +94,7 @@ EE7886751A0D0CE30013A855 /* XWVLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVLoader.swift; path = XWebView/XWVLoader.swift; sourceTree = ""; }; EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVMetaObject.swift; path = XWebView/XWVMetaObject.swift; sourceTree = ""; }; EE92C6601B5AD7DB000FE1DA /* XWVMetaObjectTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XWVMetaObjectTest.swift; sourceTree = ""; }; + EEDF305F1B6555B900A21659 /* XWVInvocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVInvocation.swift; path = XWebView/XWVInvocation.swift; sourceTree = ""; }; EEE6F9A31AE02CF100A2EC89 /* XWVScripting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVScripting.swift; path = XWebView/XWVScripting.swift; sourceTree = ""; }; EEE6F9A51AE02E8600A2EC89 /* XWVObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVObject.swift; path = XWebView/XWVObject.swift; sourceTree = ""; }; EEE6F9A71AE02F5000A2EC89 /* XWVBindingObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XWVBindingObject.swift; path = XWebView/XWVBindingObject.swift; sourceTree = ""; }; @@ -127,17 +125,17 @@ isa = PBXGroup; children = ( AB2273E41AA6FDA700F9207A /* www */, - ABD3E85D1A8CD49F00F2BAB9 /* XWVInvocationTest.m */, EE3379381AE2E298009124A4 /* XWVTestCase.swift */, EE33793F1AE57566009124A4 /* XWVScriptingTest.swift */, EE2F487C1AE4B8F40088AF67 /* ObjectPlugin.swift */, + EE5BA7BC1B67DC940095AAE7 /* XWVInvocationTest.swift */, EE2F487E1AE4CD360088AF67 /* FunctionPlugin.swift */, EE2F48801AE4CE690088AF67 /* ConstructorPlugin.swift */, AB023EA41A8C506600580A2A /* XWebViewTests.swift */, ABD3E8531A8CD08300F2BAB9 /* XWVInventoryTest.swift */, EE30AAC31AD978DA006010A3 /* XWVLoaderTest.swift */, - AB023EA21A8C506600580A2A /* Supporting Files */, EE92C6601B5AD7DB000FE1DA /* XWVMetaObjectTest.swift */, + AB023EA21A8C506600580A2A /* Supporting Files */, ); path = XWebViewTests; sourceTree = ""; @@ -172,6 +170,7 @@ EE62683719FA323900EFC3F8 /* XWebView */ = { isa = PBXGroup; children = ( + EEDF305F1B6555B900A21659 /* XWVInvocation.swift */, EE131CA61B5F900400A9E790 /* XWVUserScript.swift */, EE92C65E1B5ACF81000FE1DA /* XWVMetaObject.swift */, EE71648B1A716C9F00078FF9 /* XWVHttpConnection.h */, @@ -184,8 +183,6 @@ EE174E441A01FDDE00168D96 /* XWVInventory.swift */, EE7886751A0D0CE30013A855 /* XWVLoader.swift */, EE174E771A0361CB00168D96 /* xwebview.js */, - EE62691D19FA52FC00EFC3F8 /* XWVInvocation.h */, - EE62691E19FA52FC00EFC3F8 /* XWVInvocation.m */, EEE6F9A31AE02CF100A2EC89 /* XWVScripting.swift */, EEE6F9A51AE02E8600A2EC89 /* XWVObject.swift */, EE33793D1AE56875009124A4 /* XWVScriptObject.swift */, @@ -211,7 +208,6 @@ buildActionMask = 2147483647; files = ( ABF68ECD1A6B45FC0058267B /* XWebView.h in Headers */, - ABD475E31A4129FC00F3BDEB /* XWVInvocation.h in Headers */, EE7164911A716C9F00078FF9 /* XWVHttpServer.h in Headers */, EE71648F1A716C9F00078FF9 /* XWVHttpConnection.h in Headers */, ); @@ -317,7 +313,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - ABD3E85E1A8CD49F00F2BAB9 /* XWVInvocationTest.m in Sources */, EEF27D551AE8F1F3004740CF /* XWVScripting.swift in Sources */, EE3379391AE2E298009124A4 /* XWVTestCase.swift in Sources */, ABD3E8541A8CD08300F2BAB9 /* XWVInventoryTest.swift in Sources */, @@ -327,6 +322,7 @@ EE92C6621B5AD86E000FE1DA /* XWVMetaObject.swift in Sources */, EE2F487F1AE4CD360088AF67 /* FunctionPlugin.swift in Sources */, EE92C6611B5AD7DB000FE1DA /* XWVMetaObjectTest.swift in Sources */, + EE5BA7BD1B67DC940095AAE7 /* XWVInvocationTest.swift in Sources */, EE2F487D1AE4B8F40088AF67 /* ObjectPlugin.swift in Sources */, EE2F48811AE4CE690088AF67 /* ConstructorPlugin.swift in Sources */, ); @@ -337,6 +333,7 @@ buildActionMask = 2147483647; files = ( EEE6F9A41AE02CF100A2EC89 /* XWVScripting.swift in Sources */, + EEDF30601B6555B900A21659 /* XWVInvocation.swift in Sources */, EE7886761A0D0CE30013A855 /* XWVLoader.swift in Sources */, EE174E451A01FDDE00168D96 /* XWVInventory.swift in Sources */, EEE6F9A81AE02F5000A2EC89 /* XWVBindingObject.swift in Sources */, @@ -344,7 +341,6 @@ EE7164921A716C9F00078FF9 /* XWVHttpServer.m in Sources */, EE92C65F1B5ACF81000FE1DA /* XWVMetaObject.swift in Sources */, EEE6F9A61AE02E8600A2EC89 /* XWVObject.swift in Sources */, - EE62692419FA52FC00EFC3F8 /* XWVInvocation.m in Sources */, EE33793E1AE56875009124A4 /* XWVScriptObject.swift in Sources */, EE0A1DD31A52775400C9E6D3 /* XWVChannel.swift in Sources */, EE62692619FA52FC00EFC3F8 /* XWebView.swift in Sources */, diff --git a/XWebView/XWVBindingObject.swift b/XWebView/XWVBindingObject.swift index aaca1d5..9e916a6 100644 --- a/XWebView/XWVBindingObject.swift +++ b/XWebView/XWVBindingObject.swift @@ -26,6 +26,9 @@ class XWVBindingObject : XWVScriptObject { self.object = object objc_setAssociatedObject(object, key, self, UInt(OBJC_ASSOCIATION_ASSIGN)) startKVO() + + // A trick for accessing static methods of the protocol + XWVScripting.self } init(namespace: String, channel: XWVChannel, arguments: [AnyObject]?) { @@ -39,6 +42,7 @@ class XWVBindingObject : XWVScriptObject { selector = sel if arity == Int32(args.count) - 1 || arity < 0 { promise = last(args) as? XWVScriptObject + args.removeLast() } default: break } @@ -47,7 +51,7 @@ class XWVBindingObject : XWVScriptObject { if selector == "initByScriptWithArguments:" { args = [args] } - object = XWVInvocation.constructOnThread(channel.thread, `class`: channel.typeInfo.plugin, initializer: selector, arguments: args) + object = XWVInvocation(target: channel.typeInfo.plugin.alloc()).call(selector, withObjects: args) objc_setAssociatedObject(object, key, self, UInt(OBJC_ASSOCIATION_ASSIGN)) startKVO() syncProperties() @@ -56,7 +60,7 @@ class XWVBindingObject : XWVScriptObject { private func syncProperties() { var script = "" for (name, member) in filter(channel.typeInfo, { $1.isProperty }) { - let val = XWVInvocation.callOnThread(channel.thread, target: object, selector: member.getter!, arguments: nil) + let val: AnyObject! = XWVInvocation(target: object).call(member.getter!, withObjects: nil) script += "\(namespace).$properties['\(name)'] = \(serialize(val));\n" } webView?.evaluateJavaScript(script, completionHandler: nil) @@ -64,7 +68,7 @@ class XWVBindingObject : XWVScriptObject { deinit { if (object as? XWVScripting)?.finalizeForScript != nil { - XWVInvocation.callOnThread(channel.thread, target: object, selector: Selector("finalizeForScript"), arguments: nil) + XWVInvocation(target: object)[Selector("finalizeForScript")]() } objc_setAssociatedObject(object, key, nil, UInt(OBJC_ASSOCIATION_ASSIGN)) stopKVO() @@ -79,10 +83,11 @@ class XWVBindingObject : XWVScriptObject { } if channel.queue != nil { dispatch_async(channel.queue) { - XWVInvocation.call(object, selector: selector, arguments: args) + XWVInvocation(target: object).call(selector, withObjects: args) } } else { - XWVInvocation.asyncCallOnThread(channel.thread, target: object, selector: selector, arguments: args) + // FIXME: Add NSThread support back while migrate to Swift 2.0 + XWVInvocation(target: object).call(selector, withObjects: args) } } } @@ -91,10 +96,11 @@ class XWVBindingObject : XWVScriptObject { let val: AnyObject = wrapScriptObject(value) if channel.queue != nil { dispatch_async(channel.queue) { - XWVInvocation.call(object, selector: setter, arguments: [val]) + XWVInvocation(target: object).call(setter, withObjects: [val]) } } else { - XWVInvocation.asyncCallOnThread(channel.thread, target: object, selector: setter, arguments: [val]) + // FIXME: Add NSThread support back while migrate to Swift 2.0 + XWVInvocation(target: self.object)[name] = val } } } @@ -102,29 +108,27 @@ class XWVBindingObject : XWVScriptObject { // override methods of XWVScriptObject override func callMethod(name: String, withArguments arguments: [AnyObject]?, resultHandler: ((AnyObject!) -> Void)?) { if let selector = channel.typeInfo[name]?.selector { - let result = XWVInvocation.call(object, selector: selector, arguments: arguments) - resultHandler?(result as? NSNumber ?? result.nonretainedObjectValue) + let result: AnyObject! = XWVInvocation(target: object).call(selector, withObjects: arguments) + resultHandler?(result) } else { super.callMethod(name, withArguments: arguments, resultHandler: resultHandler) } } override func callMethod(name: String, withArguments arguments: [AnyObject]?) -> AnyObject! { if let selector = channel.typeInfo[name]?.selector { - let result = XWVInvocation.call(object, selector: selector, arguments: arguments) - return result as? NSNumber ?? result.nonretainedObjectValue + return XWVInvocation(target: object).call(selector, withObjects: arguments) } return super.callMethod(name, withArguments: arguments) } override func value(forProperty name: String) -> AnyObject? { if let getter = channel.typeInfo[name]?.getter { - let result = XWVInvocation.call(object, selector: getter, arguments: nil) - return result as? NSNumber ?? result.nonretainedObjectValue + return XWVInvocation(target: object).call(getter, withObjects: nil) } return super.value(forProperty: name) } override func setValue(value: AnyObject?, forProperty name: String) { if let setter = channel.typeInfo[name]?.setter { - XWVInvocation.call(object, selector: setter, arguments: [value!]) + XWVInvocation(target: object)[name] = value } else { assert(channel.typeInfo[name] == nil, "Property '\(name)' is readonly") super.setValue(value, forProperty: name) @@ -136,7 +140,7 @@ class XWVBindingObject : XWVScriptObject { var prop = keyPath if channel.typeInfo[prop] == nil { if let scriptNameForKey = object.dynamicType.scriptNameForKey { - prop = scriptNameForKey((prop as NSString).UTF8String) ?? prop + prop = prop.withCString(scriptNameForKey) ?? prop } assert(channel.typeInfo[prop] != nil) } diff --git a/XWebView/XWVInvocation.h b/XWebView/XWVInvocation.h deleted file mode 100644 index 54459ee..0000000 --- a/XWebView/XWVInvocation.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - Copyright 2015 XWebView - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import - -@interface NSValue (XWVInvocation) - -@property (nonatomic, readonly) BOOL isNumber; -@property (nonatomic, readonly) BOOL isObject; -@property (nonatomic, readonly) BOOL isVoid; - -+ (NSValue *)valueWithInvocation:(NSInvocation *)invocation; - -@end - -@interface XWVInvocation : NSObject - -+ (id)construct:(Class)aClass initializer:(SEL)selector arguments:(NSArray *)args; -+ (id)constructOnThread:(NSThread *)thread class:(Class)aClass initializer:(SEL)selector arguments:(NSArray *)args; - -+ (NSValue *)call:(id)target selector:(SEL)selector arguments:(NSArray *)args; -+ (NSValue *)callOnThread:(NSThread *)thread target:(id)target selector:(SEL)selector arguments:(NSArray *)args; - -+ (void)asyncCall:(id)target selector:(SEL)selector arguments:(NSArray *)args; -+ (void)asyncCallOnThread:(NSThread *)thread target:(id)target selector:(SEL)selector arguments:(NSArray *)args; - -// Variadic methods - -+ (id)construct:(Class)aClass initializer:(SEL)selector, ...; -+ (id)constructOnThread:(NSThread *)thread class:(Class)aClass initializer:(SEL)selector, ...; - -+ (NSValue *)call:(id)target selector:(SEL)selector, ...; -+ (NSValue *)callOnThread:(NSThread *)thread target:(id)target selector:(SEL)selector, ...; - -+ (void)asyncCall:(id)target selector:(SEL)selector, ...; -+ (void)asyncCallOnThread:(NSThread *)thread target:(id)target selector:(SEL)selector, ...; - -@end diff --git a/XWebView/XWVInvocation.m b/XWebView/XWVInvocation.m deleted file mode 100644 index 6521c0f..0000000 --- a/XWebView/XWVInvocation.m +++ /dev/null @@ -1,318 +0,0 @@ -/* - Copyright 2015 XWebView - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import "objc/runtime.h" -#import "XWVInvocation.h" - -@interface NSNumber (XWVInvocation) -- (instancetype)initWithInvocation:(NSInvocation *)invocation; -- (void)getValue:(void *)buffer objCType:(const char *)type; -@end - -@interface NSInvocation (XWVInvocation) -+ (NSInvocation *)invocationWithTarget:(id)target selector:(SEL)selector arguments:(NSArray *)args; -+ (NSInvocation *)invocationWithTarget:(id)target selector:(SEL)selector valist:(va_list)valist; -- (void)setArguments:(NSArray *)args; -- (void)setVariableArguments:(va_list)valist; -@end - - -@implementation NSValue (XWVInvocation) - -+ (NSValue *)valueWithInvocation:(NSInvocation *)invocation { - if (!invocation) return nil; - - NSNumber *num = [[NSNumber alloc] initWithInvocation:invocation]; - if (num) return num; - - NSMethodSignature *sig = invocation.methodSignature; - const char *type = [sig methodReturnType]; - NSUInteger len = sig.methodReturnLength; - if (len > sizeof(uint64_t)) { - void *buffer = malloc(len); - NSValue *value = [NSValue valueWithBytes:buffer objCType:type]; - free(buffer); - return value; - } - - uint64_t value = 0; - if (len) - [invocation getReturnValue:&value]; - return [NSValue valueWithBytes:&value objCType:type]; -} - -- (BOOL)isNumber { - return [self isKindOfClass:NSNumber.class]; -} -- (BOOL)isObject { - return strcmp(self.objCType, @encode(id)) ? NO : YES; -} -- (BOOL)isVoid { - return strcmp(self.objCType, @encode(void)) ? NO : YES; -} - -@end - - -@implementation XWVInvocation - -+ (id)construct:(Class)aClass initializer:(SEL)selector arguments:(NSArray *)args { - return [XWVInvocation constructOnThread:nil class:aClass initializer:selector arguments:args]; -} -+ (id)constructOnThread:(NSThread *)thread class:(Class)aClass initializer:(SEL)selector arguments:(NSArray *)args { - id obj = [aClass alloc]; - NSInvocation* inv = [NSInvocation invocationWithTarget:obj selector:selector arguments:args]; - if (thread) - [inv performSelector:@selector(invokeWithTarget:) onThread:thread withObject:obj waitUntilDone:YES]; - else - [inv invoke]; - [inv getReturnValue:&obj]; - return obj; -} - -+ (NSValue *)call:(id)target selector:(SEL)selector arguments:(NSArray *)args { - return [XWVInvocation callOnThread:nil target:target selector:selector arguments:args]; -} -+ (NSValue *)callOnThread:(NSThread *)thread target:(id)target selector:(SEL)selector arguments:(NSArray *)args { - NSInvocation* inv = [NSInvocation invocationWithTarget:target selector:selector arguments:args]; - if (thread) - [inv performSelector:@selector(invokeWithTarget:) onThread:thread withObject:target waitUntilDone:YES]; - else - [inv invoke]; - return [NSValue valueWithInvocation:inv]; -} - -+ (void)asyncCall:(id)target selector:(SEL)selector arguments:(NSArray *)args { - return [XWVInvocation asyncCallOnThread:nil target:target selector:selector arguments:args]; -} -+ (void)asyncCallOnThread:(NSThread *)thread target:(id)target selector:(SEL)selector arguments:(NSArray *)args { - NSInvocation* inv = [NSInvocation invocationWithTarget:target selector:selector arguments:args]; - [inv retainArguments]; - [inv performSelector:@selector(invokeWithTarget:) onThread:(thread ?: NSThread.currentThread) withObject:target waitUntilDone:NO]; -} - -// Variadic methods - -+ (id)construct:(Class)aClass initializer:(SEL)selector, ... { - va_list ap; - va_start(ap, selector); - id obj = [aClass alloc]; - NSInvocation* inv = [NSInvocation invocationWithTarget:obj selector:selector valist:ap]; - va_end(ap); - - [inv invoke]; - [inv getReturnValue:&obj]; - return obj; -} -+ (id)constructOnThread:(NSThread *)thread class:(Class)aClass initializer:(SEL)selector, ... { - va_list ap; - va_start(ap, selector); - id obj = [aClass alloc]; - NSInvocation* inv = [NSInvocation invocationWithTarget:obj selector:selector valist:ap]; - va_end(ap); - - if (thread) - [inv performSelector:@selector(invokeWithTarget:) onThread:thread withObject:obj waitUntilDone:YES]; - else - [inv invoke]; - [inv getReturnValue:&obj]; - return obj; -} - -+ (NSValue *)call:(id)target selector:(SEL)selector, ... { - va_list ap; - va_start(ap, selector); - NSInvocation* inv = [NSInvocation invocationWithTarget:target selector:selector valist:ap]; - va_end(ap); - [inv invoke]; - return [NSValue valueWithInvocation:inv]; -} -+ (NSValue *)callOnThread:(NSThread *)thread target:(id)target selector:(SEL)selector, ... { - va_list ap; - va_start(ap, selector); - NSInvocation* inv = [NSInvocation invocationWithTarget:target selector:selector valist:ap]; - va_end(ap); - if (thread) - [inv performSelector:@selector(invokeWithTarget:) onThread:thread withObject:target waitUntilDone:YES]; - else - [inv invoke]; - return [NSValue valueWithInvocation:inv]; -} - -+ (void)asyncCall:(id)target selector:(SEL)selector, ... { - va_list ap; - va_start(ap, selector); - NSInvocation* inv = [NSInvocation invocationWithTarget:target selector:selector valist:ap]; - va_end(ap); - [inv retainArguments]; - [inv performSelector:@selector(invokeWithTarget:) onThread:NSThread.currentThread withObject:target waitUntilDone:NO]; -} -+ (void)asyncCallOnThread:(NSThread *)thread target:(id)target selector:(SEL)selector, ... { - va_list ap; - va_start(ap, selector); - NSInvocation* inv = [NSInvocation invocationWithTarget:target selector:selector valist:ap]; - va_end(ap); - [inv retainArguments]; - [inv performSelector:@selector(invokeWithTarget:) onThread:(thread ?: NSThread.currentThread) withObject:target waitUntilDone:NO]; -} - -@end - -////////////////////////////////////////////////////////////////////////// - -@implementation NSNumber (XWVInvocation) - -#define ISTYPE(t) (!strcmp(type, @encode(t))) -- (instancetype)initWithInvocation:(NSInvocation *)invocation { - NSMethodSignature *sig = invocation.methodSignature; - if (sig.methodReturnLength > sizeof(uint64_t) || !sig.methodReturnLength) - return nil; - - const char *type = [sig methodReturnType]; - uint64_t value = 0; - void *buffer = &value; - [invocation getReturnValue:buffer]; -#define NUMBER(type, suffix) return [self initWith##suffix: *(type *)buffer]; - if ISTYPE(BOOL) NUMBER(BOOL, Bool) - else if ISTYPE(char) NUMBER(char, Char) - else if ISTYPE(short) NUMBER(short, Short) - else if ISTYPE(int) NUMBER(int, Int) - else if ISTYPE(long) NUMBER(long, Long) - else if ISTYPE(long long) NUMBER(long long, LongLong) - else if ISTYPE(unsigned char) NUMBER(unsigned char, UnsignedChar) - else if ISTYPE(unsigned short) NUMBER(unsigned short, UnsignedShort) - else if ISTYPE(unsigned int) NUMBER(unsigned int, UnsignedInt) - else if ISTYPE(unsigned long) NUMBER(unsigned long, UnsignedLong) - else if ISTYPE(unsigned long long) NUMBER(unsigned long long, UnsignedLongLong) - else if ISTYPE(float) NUMBER(float, Float) - else if ISTYPE(double) NUMBER(double, Double) - else return nil; -#undef NUMBER -} - -- (void)getValue:(void *)buffer objCType:(const char *)type { -#define VALUE(type, prefix) *(type *)buffer = self.prefix##Value; - if ISTYPE(BOOL) VALUE(BOOL, bool) - else if ISTYPE(char) VALUE(char, char) - else if ISTYPE(short) VALUE(short, short) - else if ISTYPE(int) VALUE(int, int) - else if ISTYPE(long) VALUE(long, long) - else if ISTYPE(long long) VALUE(long long, longLong) - else if ISTYPE(unsigned char) VALUE(unsigned char, unsignedChar) - else if ISTYPE(unsigned short) VALUE(unsigned short, unsignedShort) - else if ISTYPE(unsigned int) VALUE(unsigned int, unsignedInt) - else if ISTYPE(unsigned long) VALUE(unsigned long, unsignedLong) - else if ISTYPE(unsigned long long) VALUE(unsigned long long, unsignedLongLong) - else if ISTYPE(float) VALUE(float, float) - else if ISTYPE(double) VALUE(double, double) - else [NSException raise:@"TypeError" format:@"'%s' is not a number type", type]; -#undef VALUE -} -#undef ISTYPE - -@end - - -@implementation NSInvocation (XWVInvocation) -+ (NSInvocation *)invocationWithTarget:(id)target selector:(SEL)selector arguments:(NSArray *)args { - Class class = object_getClass(target); - Method method = class_getInstanceMethod(class, selector); - NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:method_getTypeEncoding(method)]; - if (sig == nil) { - if (class_conformsToProtocol(class, @protocol(NSObject)) && [target isKindOfClass:NSObject.class]) - [target doesNotRecognizeSelector:selector]; - return nil; - } - if ((args ? args.count : 0) < sig.numberOfArguments - 2) - return nil; // Too few arguments - - NSInvocation* inv = [NSInvocation invocationWithMethodSignature:sig]; - inv.target = target; - inv.selector = selector; - if (args && args.count) - [inv setArguments:args]; - return inv; -} -+ (NSInvocation *)invocationWithTarget:(id)target selector:(SEL)selector valist:(va_list)valist { - NSMethodSignature *sig = [target methodSignatureForSelector:selector]; - if (sig == nil) { - [target doesNotRecognizeSelector:selector]; - return nil; - } - - NSInvocation* inv = [NSInvocation invocationWithMethodSignature:sig]; - inv.target = target; - inv.selector = selector; - [inv setVariableArguments:valist]; - return inv; -} - -- (void)setArguments:(NSArray *)args { - NSMethodSignature *sig = self.methodSignature; - NSUInteger cnt = MIN(sig.numberOfArguments - 2, args.count); - for(NSUInteger i = 0; i < cnt; ++i) { - const char *type = [sig getArgumentTypeAtIndex:i + 2]; - NSObject *val = [args objectAtIndex:i]; - void *buf = &val; - if (val == NSNull.null) { - // Convert NSNull to nil - val = nil; - } else if (strcmp(type, @encode(id))) { - if ([val isKindOfClass:NSNumber.class]) { - // Convert NSNumber to argument type - NSNumber* num = (NSNumber*)val; - unsigned long long data; - buf = &data; - [num getValue:buf objCType:type]; - } else if ([val isKindOfClass:NSValue.class]) { - // TODO: Convert NSValue to argument type - } - } - [self setArgument:buf atIndex:(i + 2)]; - } -} - -- (void)setVariableArguments:(va_list)valist { - NSMethodSignature *sig = self.methodSignature; - for (NSUInteger i = 2; i < sig.numberOfArguments; ++i) { - const char *type = [sig getArgumentTypeAtIndex: i]; - void *buf = NULL; - NSUInteger size; - NSGetSizeAndAlignment(type, NULL, &size); - if (!strcmp(type, @encode(float))) { - // The float value is promoted to double - float data = (float)va_arg(valist, double); - buf = &data; - } else if (size < sizeof(int)) { - // Types narrower than an int are promoted to int. - if (size == sizeof(short)) { - short data = (short)va_arg(valist, int); - buf = &data; - } else { - char data = (char)va_arg(valist, int); - buf = &data; - } - } else if (size <= sizeof(long long)) { - // Any type that size is less or equal than long long. - long long data = va_arg(valist, long long); - buf = &data; - } else { - NSAssert(false, @"structure type is unsupported."); - } - [self setArgument:buf atIndex: i]; - } -} -@end diff --git a/XWebView/XWVInvocation.swift b/XWebView/XWVInvocation.swift new file mode 100644 index 0000000..2fe5da2 --- /dev/null +++ b/XWebView/XWVInvocation.swift @@ -0,0 +1,274 @@ +/* + Copyright 2015 XWebView + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import Foundation +import ObjectiveC + +private let _NSInvocation: AnyClass = NSClassFromString("NSInvocation")! +private let _NSMethodSignature: AnyClass = NSClassFromString("NSMethodSignature")! + +public class XWVInvocation { + public final let target: AnyObject + + public init(target: AnyObject) { + self.target = target + } + + public func call(selector: Selector, withArguments arguments: [Any!]) -> Any! { + let method = class_getInstanceMethod(target.dynamicType, selector) + if method == nil { + // TODO: supports forwordingTargetForSelector: of NSObject? + (target as? NSObject)?.doesNotRecognizeSelector(selector) + // Not an NSObject, mimic the behavior of NSObject + let reason = "-[\(target.dynamicType) \(selector)]: unrecognized selector sent to instance \(unsafeAddressOf(target))" + withVaList([reason]) { NSLogv("%@", $0) } + NSException(name: NSInvalidArgumentException, reason: reason, userInfo: nil).raise() + } + + let sig = _NSMethodSignature.signatureWithObjCTypes(method_getTypeEncoding(method))! + let inv = _NSInvocation.invocationWithMethodSignature(sig) + + // Setup arguments + assert(arguments.count + 2 <= Int(sig.numberOfArguments), "Too many arguments for calling -[\(target.dynamicType) \(selector)]") + var args = [[Word]](count: arguments.count, repeatedValue: []) + for var i = 0; i < arguments.count; ++i { + let type = sig.getArgumentTypeAtIndex(i + 2) + let typeChar = Character(UnicodeScalar(UInt8(type[0]))) + + // Convert argument type to adapte requirement of method. + // Firstly, convert argument to appropriate object type. + var argument: Any! = self.dynamicType.convertToObjectFromAnyValue(arguments[i]) + assert(argument != nil || arguments[i] == nil, "Can't convert '\(arguments[i].dynamicType)' to object type") + if typeChar != "@", let obj: AnyObject = argument as? AnyObject { + // Convert back to scalar type as method requires. + argument = self.dynamicType.convertFromObject(obj, toObjCType: type) + } + + if typeChar == "f", let float = argument as? Float { + // Float type shouldn't be promoted to double if it is not variadic. + args[i] = [ Word(unsafeBitCast(float, UInt32.self)) ] + } else if let val = argument as? CVarArgType { + // Scalar(except float), pointer and Objective-C object types + args[i] = val.encode() + } else if let obj: AnyObject = argument as? AnyObject { + // Pure swift object type + args[i] = [ unsafeBitCast(unsafeAddressOf(obj), Word.self) ] + } else { + // Nil or unsupported type + assert(argument == nil, "Unsupported argument type '\(String(UTF8String: type))'") + var align: Int = 0 + NSGetSizeAndAlignment(sig.getArgumentTypeAtIndex(i), nil, &align) + args[i] = [Word](count: align / sizeof(Word.self), repeatedValue: 0) + } + args[i].withUnsafeBufferPointer() { + inv.setArgument(UnsafeMutablePointer($0.baseAddress), atIndex: i + 2) + } + } + + inv.selector = selector + inv.invokeWithTarget(target) + if sig.methodReturnLength == 0 { return Void() } + + // Fetch the return value + // TODO: Methods with 'ns_returns_retained' attribute cause leak of returned object. + let count = (sig.methodReturnLength + sizeof(Word.self) - 1) / sizeof(Word.self) + var words = [Word](count: count, repeatedValue: 0) + words.withUnsafeMutableBufferPointer() { + (inout buf: UnsafeMutableBufferPointer)->Void in + inv.getReturnValue(buf.baseAddress) + } + if sig.methodReturnLength <= sizeof(Word) { + // TODO: handle 64 bit type on 32-bit OS + return bitCastWord(words[0], toObjCType: sig.methodReturnType) + } + assertionFailure("Unsupported return type '\(String(UTF8String: sig.methodReturnType))'"); + return Void() + } + + public func call(selector: Selector, withArguments arguments: Any!...) -> Any! { + return call(selector, withArguments: arguments) + } + + // Helper for Objective-C, accept ObjC 'id' instead of Swift 'Any' type for in/out parameters . + @objc public func call(selector: Selector, withObjects objects: [AnyObject]?) -> AnyObject! { + let args: [Any!] = objects?.map() { $0 !== NSNull() ? ($0 as Any) : nil } ?? [] + let result = call(selector, withArguments: args) + return self.dynamicType.convertToObjectFromAnyValue(result) + } + + // Syntactic sugar for calling method + public subscript (selector: Selector) -> (Any!...)->Any! { + return { + (args: Any!...)->Any! in + self.call(selector, withArguments: args) + } + } +} + +extension XWVInvocation { + // Property accessor + public func getProperty(name: String) -> Any! { + let getter = getterOfName(name) + assert(getter != Selector(), "Property '\(name)' does not exist") + return getter != Selector() ? call(getter) : Void() + } + public func setValue(value: Any!, forProperty name: String) { + let setter = setterOfName(name) + assert(setter != Selector(), "Property '\(name)' " + + (getterOfName(name) == nil ? "does not exist" : "is readonly")) + assert(!(value is Void)) + if setter != Selector() { + call(setter, withArguments: value) + } + } + + // Syntactic sugar for accessing property + public subscript (name: String) -> Any! { + get { + return getProperty(name) + } + set { + setValue(newValue, forProperty: name) + } + } + + private func getterOfName(name: String) -> Selector { + var getter = Selector() + let property = class_getProperty(self.target.dynamicType, name) + if property != nil { + let attr = property_copyAttributeValue(property, "G") + getter = Selector(attr == nil ? name : String(UTF8String: attr)!) + free(attr) + } + return getter + } + private func setterOfName(name: String) -> Selector { + var setter = Selector() + let property = class_getProperty(self.target.dynamicType, name) + if property != nil { + var attr = property_copyAttributeValue(property, "R") + if attr == nil { + attr = property_copyAttributeValue(property, "S") + if attr == nil { + setter = Selector("set\(String(first(name)!).uppercaseString)\(dropFirst(name)):") + } else { + setter = Selector(String(UTF8String: attr)!) + } + } + free(attr) + } + return setter + } +} + +extension XWVInvocation { + // Type casting and conversion, reference: + // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html + + // Cast Word value to specified Objective-C type (not support 32-bit) + private func bitCastWord(word: Word, toObjCType type: UnsafePointer) -> Any? { + switch Character(UnicodeScalar(UInt8(type[0]))) { + case "c": return CChar(truncatingBitPattern: word) + case "i": return CInt(truncatingBitPattern: word) + case "s": return CShort(truncatingBitPattern: word) + case "l": return Int32(truncatingBitPattern: word) + case "q": return CLongLong(word) + case "C": return CUnsignedChar(truncatingBitPattern: word) + case "I": return CUnsignedInt(truncatingBitPattern: word) + case "S": return CUnsignedShort(truncatingBitPattern: word) + case "L": return UInt32(truncatingBitPattern: word) + case "Q": return unsafeBitCast(word, CUnsignedLongLong.self) + case "f": return unsafeBitCast(UInt32(truncatingBitPattern: word), CFloat.self) + case "d": return unsafeBitCast(word, CDouble.self) + case "B": return unsafeBitCast(UInt8(truncatingBitPattern: word), CBool.self) + case "v": return unsafeBitCast(word, Void.self) + case "*": return unsafeBitCast(word, UnsafePointer.self) + case "@": return word != 0 ? unsafeBitCast(word, AnyObject.self) : nil + case "#": return unsafeBitCast(word, AnyClass.self) + case ":": return unsafeBitCast(word, Selector.self) + case "^", "?": return unsafeBitCast(word, COpaquePointer.self) + default: assertionFailure("Unknown Objective-C type encoding '\(String(UTF8String: type))'") + } + return Void() + } + + // Convert AnyObject to appropriate Objective-C type + private class func convertFromObject(object: AnyObject, toObjCType type: UnsafePointer) -> Any! { + let num = object as? NSNumber + switch Character(UnicodeScalar(UInt8(type[0]))) { + case "c": return num?.charValue + case "i": return num?.intValue + case "s": return num?.shortValue + case "l": return num?.intValue + case "q": return num?.longLongValue + case "C": return num?.unsignedCharValue + case "I": return num?.unsignedIntValue + case "S": return num?.unsignedShortValue + case "L": return num?.unsignedIntValue + case "Q": return num?.unsignedLongLongValue + case "f": return num?.floatValue + case "d": return num?.doubleValue + case "B": return num?.boolValue + case "v": return Void() + case "*": return (object as? String)?.nulTerminatedUTF8.withUnsafeBufferPointer({ COpaquePointer($0.baseAddress) }) + case ":": return object is String ? Selector(object as! String) : Selector() + case "@": return object + case "#": return object + case "^", "?": return (object as? NSValue)?.pointerValue() + default: assertionFailure("Unknown Objective-C type encoding '\(String(UTF8String: type))'") + } + return nil + } + + // Convert Any value to appropriate Objective-C object + public class func convertToObjectFromAnyValue(value: Any!) -> AnyObject! { + if value == nil || value is AnyObject { + // Some scalar types (Int, UInt, Bool, Float and Double) can be converted automatically by runtime. + return value as? AnyObject + } + + if let i8 = value as? Int8 { return NSNumber(char: i8) } else + if let i16 = value as? Int16 { return NSNumber(short: i16) } else + if let i32 = value as? Int32 { return NSNumber(int: i32) } else + if let i64 = value as? Int64 { return NSNumber(longLong: i64) } else + if let u8 = value as? UInt8 { return NSNumber(unsignedChar: u8) } else + if let u16 = value as? UInt16 { return NSNumber(unsignedShort: u16) } else + if let u32 = value as? UInt32 { return NSNumber(unsignedInt: u32) } else + if let u64 = value as? UInt64 { return NSNumber(unsignedLongLong: u64) } else + if let us = value as? UnicodeScalar { return NSNumber(unsignedInt: us.value) } else + if let sel = value as? Selector { return sel.description } else + if let ptr = value as? COpaquePointer { return NSValue(pointer: UnsafePointer(ptr)) } + //assertionFailure("Can't convert '\(value.dynamicType)' to AnyObject") + return nil + } +} + +// Additional Swift types which can be represented in C type. +extension Bool: CVarArgType { + public func encode() -> [Word] { + return [ Word(self) ] + } +} +extension UnicodeScalar: CVarArgType { + public func encode() -> [Word] { + return [ Word(self.value) ] + } +} +extension Selector: CVarArgType { + public func encode() -> [Word] { + return [ unsafeBitCast(self, Word.self) ] + } +} diff --git a/XWebView/XWVLoader.swift b/XWebView/XWVLoader.swift index 786640b..13156da 100644 --- a/XWebView/XWVLoader.swift +++ b/XWebView/XWVLoader.swift @@ -28,7 +28,7 @@ public class XWVLoader: NSObject, XWVScripting { if let plugin: AnyClass = _inventory.plugin(forNamespace: namespace), let channel = scriptObject?.channel { let initializer = Selector(argument == nil ? "init" : "initWitArgument:") let args: [AnyObject]? = argument == nil ? nil : [argument!] - let object = XWVInvocation.constructOnThread(channel.thread, `class`: plugin, initializer: initializer, arguments: args) as! NSObject! + let object: AnyObject! = XWVInvocation(target: plugin.alloc()).call(initializer, withObjects: args) if object != nil, let obj = channel.webView?.loadPlugin(object, namespace: namespace) { promiseObject.callMethod("resolve", withArguments: [obj], resultHandler: nil) return diff --git a/XWebView/XWebView.h b/XWebView/XWebView.h index 88b2b76..6c00c72 100644 --- a/XWebView/XWebView.h +++ b/XWebView/XWebView.h @@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ +*/ #import @@ -25,4 +25,3 @@ FOUNDATION_EXPORT const unsigned char XWebViewVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import #import "XWVHttpServer.h" -#import "XWVInvocation.h" diff --git a/XWebViewTests/XWVInvocationTest.m b/XWebViewTests/XWVInvocationTest.m deleted file mode 100644 index faa063f..0000000 --- a/XWebViewTests/XWVInvocationTest.m +++ /dev/null @@ -1,84 +0,0 @@ -/* - Copyright 2015 XWebView - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#import -#import "XWVInvocation.h" - -@interface ClassForInvocationTest : NSObject -@property(nonatomic, copy) NSString* name; - -- (id)initWithName:(NSString*)name; -- (void)asyncMethod:(XCTestExpectation*)expectation; - -@end - -@implementation ClassForInvocationTest - -- (id)initWithName:(NSString *)name { - if (self = [super init]) { - _name = name; - } - return self; -} - -- (NSString*)getName { - return _name; -} - -- (void)asyncMethod:(XCTestExpectation*)expectation { - [expectation fulfill]; -} - -@end - -@interface XWVInvocationTest : XCTestCase -@property(nonatomic, strong) ClassForInvocationTest* demo; - -@end - -@implementation XWVInvocationTest - -- (void)setUp { - [super setUp]; - self.demo = [[ClassForInvocationTest alloc] initWithName:@"TestObject"]; -} - -- (void)tearDown { - [super tearDown]; - self.demo = nil; -} - -- (void)testConstruct { - XCTAssertNotNil([XWVInvocation construct:ClassForInvocationTest.class initializer:NSSelectorFromString(@"initWithName:") arguments:@[@"AnotherTestObject"]]); -} - -- (void)testCall { - NSValue* value = [XWVInvocation call:self.demo selector:NSSelectorFromString(@"getName") arguments:nil]; - NSString* name = [NSString stringWithFormat:@"%@", [value pointerValue]]; - XCTAssertEqualObjects(@"TestObject", name); -} - -- (void)testAsyncCall { - XCTestExpectation *expectation = [self expectationWithDescription:@"AsyncCall"]; - [XWVInvocation asyncCall:self.demo selector:NSSelectorFromString(@"asyncMethod:"), expectation]; - [self waitForExpectationsWithTimeout:0.1 handler:^(NSError* error) { - if (error) { - XCTAssert(NO, @"testAsyncCall failed"); - } - }]; -} - -@end diff --git a/XWebViewTests/XWVInvocationTest.swift b/XWebViewTests/XWVInvocationTest.swift new file mode 100644 index 0000000..9a2a2d0 --- /dev/null +++ b/XWebViewTests/XWVInvocationTest.swift @@ -0,0 +1,114 @@ +/* + Copyright 2015 XWebView + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import XCTest +import XWebView + +class InvocationTarget: NSObject { + @objc class ObjectForLeakTest { + let expectation: XCTestExpectation + init(expectation: XCTestExpectation) { + self.expectation = expectation + } + deinit { + expectation.fulfill() + } + } + + var integer: Int = 123 + + func dummy() {} + func echo(bool b: Bool) -> Bool { return b } + func echo(int i: Int) -> Int { return i } + func echo(int8 i8: Int8) -> Int8 { return i8 } + func echo(int16 i16: Int16) -> Int16 { return i16 } + func echo(int32 i32: Int32) -> Int32 { return i32 } + func echo(int64 i64: Int64) -> Int64 { return i64 } + func echo(uint u: UInt) -> UInt { return u } + func echo(uint8 u8: UInt8) -> UInt8 { return u8 } + func echo(uint16 u16: UInt16) -> UInt16 { return u16 } + func echo(uint32 u32: UInt32) -> UInt32 { return u32 } + func echo(uint64 u64: UInt64) -> UInt64 { return u64 } + func echo(float f: Float) -> Float { return f } + func echo(double d: Double) -> Double { return d } + func echo(unicode u: UnicodeScalar) -> UnicodeScalar { return u } + func echo(string s: String) -> String { return s } + func echo(selector s: Selector) -> Selector { return s } + func echo(`class` c: AnyClass) -> AnyClass { return c } + + func add(a: Int, _ b: Int) -> Int { return a + b } + func concat(a: String, _ b: String) -> String { return a + b } + func convert(num: NSNumber) -> Int { return num.integerValue } + + func leak(expectation: XCTestExpectation) -> AnyObject { + return ObjectForLeakTest(expectation: expectation) + } +} + +class InvocationTests : XCTestCase { + var target: InvocationTarget! + var inv: XWVInvocation! + override func setUp() { + target = InvocationTarget() + inv = XWVInvocation(target: target) + } + override func tearDown() { + target = nil + inv = nil + } + + func testMethods() { + XCTAssertTrue(inv[Selector("dummy")]() is Void) + XCTAssertTrue(inv[Selector("echoWithBool:")](Bool(true)) as? Bool == true) + XCTAssertTrue(inv[Selector("echoWithInt:")](Int(-11)) as? Int64 == -11) + XCTAssertTrue(inv[Selector("echoWithInt8:")](Int8(-22)) as? Int8 == -22) + XCTAssertTrue(inv[Selector("echoWithInt16:")](Int16(-33)) as? Int16 == -33) + XCTAssertTrue(inv[Selector("echoWithInt32:")](Int32(-44)) as? Int32 == -44) + XCTAssertTrue(inv[Selector("echoWithInt64:")](Int64(-55)) as? Int64 == -55) + XCTAssertTrue(inv[Selector("echoWithUint:")](UInt(11)) as? UInt64 == 11) + XCTAssertTrue(inv[Selector("echoWithUint8:")](UInt8(22)) as? UInt8 == 22) + XCTAssertTrue(inv[Selector("echoWithUint16:")](UInt16(33)) as? UInt16 == 33) + XCTAssertTrue(inv[Selector("echoWithUint32:")](UInt32(44)) as? UInt32 == 44) + XCTAssertTrue(inv[Selector("echoWithUint64:")](UInt64(55)) as? UInt64 == 55) + XCTAssertTrue(inv[Selector("echoWithFloat:")](Float(12.34)) as? Float == 12.34) + XCTAssertTrue(inv[Selector("echoWithDouble:")](Double(-56.78)) as? Double == -56.78) + XCTAssertTrue(inv[Selector("echoWithUnicode:")](UnicodeScalar(78)) as? Int32 == 78) + XCTAssertTrue(inv[Selector("echoWithString:")]("abc") as? String == "abc") + let selector = Selector("echoWithSelector:") + XCTAssertTrue(inv[selector](selector) as? Selector == selector) + let cls = self.dynamicType + XCTAssertTrue(inv[Selector("echoWithClass:")](cls) as? AnyClass === cls) + + XCTAssertTrue(inv[Selector("convert:")](UInt8(12)) as? Int64 == 12) + XCTAssertTrue(inv[Selector("add::")](2, 3) as? Int64 == 5) + XCTAssertTrue(inv[Selector("concat::")]("ab", "cd") as? String == "abcd") + } + + func testProperty() { + XCTAssertTrue(inv["integer"] as? Int64 == 123) + inv["integer"] = 321 + XCTAssertTrue(inv["integer"] as? Int64 == 321) + } + + func testLeak() { + autoreleasepool { + let expectation = expectationWithDescription("leak") + let obj = inv.call(Selector("leak:"), withArguments: expectation) as! InvocationTarget.ObjectForLeakTest + XCTAssertEqual(expectation, obj.expectation) + } + waitForExpectationsWithTimeout(2, handler: nil) + } +}