參與工做時間比較長了,隨着Web前端行業的發展(你們都懂得..),客戶端與Web端的交互也愈來愈頻繁。其實本人不太喜歡依賴第三方,那種看不到摸不着的東西用起來總感受不是很安心,同時也是爲了保證雙方都可以高效完成交互的途中不出現一些意料不到的異常,對此,研究了一下JavaScriptCore
這個庫仍是頗有必要的,並分別結合UIWebView
以及WKWebView
作了一下交互總結。javascript
寫的比較多,若是是第一次接觸這個庫,建議仍是看一看;若是時間比較緊,想直接知道結果的,送你一個捷徑😀傳送門,有幫助能夠Star一下,十分感謝前端
<input/>
輸入一個字符串,經過點擊按鈕設置成導航標題"以將<#字符串#>"設置成導航Title
,並在網頁最底下的label顯示出來。分別使用UIWebView
以及WKWebView
實現效果以下:
java
類庫裏面有12個類(還有兩個是負責導入相關類的頭文件以及一個關於WebKit的宏定義);在基本的交互過程當中,其實最常使用的有三個:JSContext、JSValue、JSExport
ios
簡單的理解爲執行JavaScript的一個環境,就好像咱們在繪製View時候須要獲取的CGContext
同樣,JS的執行須要在此環境之下。git
能夠理解成 一種供iOS數據結構與JS數據結構相互轉換的包裝,也能夠當作一種橋接關係,咱們執行JS獲取的結果就是經過JSValue對象進行包裝傳給客戶端進行處理的,類型轉換官方文檔描述以下:github
Objective-C type | JavaScript type
--------------------+---------------------
nil | undefined
NSNull | null
NSString | string
NSNumber | number, boolean
NSDictionary | Object object
NSArray | Array object
NSDate | Date object
NSBlock (1) | Function object (1)
id (2) | Wrapper object (2)
Class (3) | Constructor object (3)複製代碼
JavaScriptType
返回的JSValue
數據可經過JSValue.toXXX()
轉成客戶端相應的數據結構;反之,客戶端對象也能夠經過JSValue()
的構造方法將相應的數據結構封裝成JSValue。web
這是一個協議,官方文檔沒有暴露出任何的open協議方法,能夠理解爲一個空協議。bash
一般用法是自定義一個CustomExport : JSExport
,裏面將JS能夠調用的屬性或者方法進行暴露,JS就能夠直接使用暴露的屬性與方法了。微信
ObjC方法定義樣式是很是特殊的,但官方文檔給出了轉換後JS調用的樣式:數據結構
//Objective-C
- (void)doFoo:(id)foo withBar:(id)bar;
//JS
doFooWithBar(foo,bar)複製代碼
但這樣會有一個缺點,萬一,方法有不少個參,拼接起來的JS方法名簡直就是日了X;不過這點Apple已經幫咱們想到了,使用JSExportAs
宏,能夠將方法名簡化,就像Swift
中的typealias
以及ObjC
中的typedef
。
//這樣在JS中直接調用doFoo(foo,bar)便可
JSExportAs(doFoo,
- (void)doFoo:(id)foo withBar:(id)bar
);複製代碼
以上三個文件就算理解完了,下面來一段小應用😀。
let context = JSContext()
//方法函數定義採用的是ES6語法,由於最近正在學習RN,習慣這麼寫了呢😀
let _ = context?.evaluateScript("var textnumber = 1")
let _ = context?.evaluateScript("var names = ['Yue','Xiao','Wen']")
let _ = context?.evaluateScript("var triple = (value) => value + 3")
let returnValue = context?.evaluateScript("triple(3)") //由於有返回值,須要接收一下
//打印結果:returnValue = Optional(6)
print("__testValueInContext --- returnValue = \(returnValue?.toNumber())")複製代碼
//經過變量名字獲取對象
let names = context?.objectForKeyedSubscript("names")
//經過定義順序的下標獲取對象,就是取['Yue','Xiao','Wen']的第0個元素
let firstName = names?.objectAtIndexedSubscript(0) //Yue
//打印結果:names = Optional([Yue, Xiao, Wen]) firstName = Optional(Yue)
print("__testValueInContext --- names = \(names?.toArray())\nfirstName = \(firstName)")
/// 得到context建立的函數變量
let function = context?.objectForKeyedSubscript("triple") //運行 let result = function?.call(withArguments: [3]) //打印結果:context-function's result = Optional(6) print("__testValueInContext --- context-function's result = \(result?.toNumber())")複製代碼
/// 捕獲JS運行錯誤
context?.exceptionHandler = {(context,exception) in
print("__testValueInContext --- JS error = \(exception)\n")//打印錯誤
}
/** 執行一個錯誤的js,由於沒有函數Triple(上面的方法名第一字母是小寫的),會調用上面的exceptionHandler 打印結果: JS error = Optional(ReferenceError: Can't find variable: Triple) */
let _ = context?.evaluateScript("Triple(3)")複製代碼
仔細看看JSValue
的類型轉換,就能夠知道,JS中方法就是客戶端中的閉包,不過這裏樓主採用了Swift和ObjC混編模式,至於緣由下面會說一下:
//得到處理完畢的數據
let result = RITLJSCoreObject.textJavaScriptUseiOS(inObjC: "Hello")
//結果 I am Objc, result = Optional("Hello I am append String")
print("I am Objc, result = \(result?.toString())\n")複製代碼
實現方法:
+(JSValue *)textJavaScriptUseiOSInObjC:(NSString *)value
{
JSContext * context = [JSContext new];
//設置block
context[@"stringHandler"] = ^(NSString * oldValue){
NSMutableString * valueHandler = [[NSMutableString alloc]initWithString:oldValue];
[valueHandler appendString:@" I am append String"];
return valueHandler;
};
NSString * js = [NSString stringWithFormat:@"stringHandler('%@')",value];
//注入
return [context evaluateScript:js];
}複製代碼
Swift
版本以下,功能實如今本人看來應該是同樣的,但在進行注入的時候出現了問題,致使執行方法出現了undefined
。
多是`Swift`的一個bug,也多是我使用不當
若是是我使用錯了,還請知道緣由的小夥伴私信一下,十分感謝。複製代碼
let context = JSContext()
//初始化一個閉包
let stringHandler : (String) -> String = { (value) in
var value = value
value.append(" I am appending word with closure!")
return value
}
//封裝成JSValue
let handerValue = JSValue(object: stringHandler, in: context)
//問題語句$$$$,我懷疑是注入失敗..見鬼了
context?.setObject(handerValue, forKeyedSubscript: "stringHandler" as NSString)
let result = context?.evaluateScript("stringHandler('Hello')")
// 結果:I am Swift ,result = Optional("undefined") - - 很無解有沒有!!!!
print("I am Swift ,result = \(result?.toString())\n")複製代碼
終於能夠運用上面的一些方法來實現功能啦。
// 默認爲WKWebView
var ritl_tyle = "WKWebView";
// 肯定是webView仍是WKWebView
function sureType(value){
ritl_tyle = value;
};
// 按鈕點擊
function buttonDidTap (){
var inputValue = $('#input').val()
if (ritl_tyle == "UIWebView"){//若是是UIWebView
RITLExportObject.say(inputValue)//經過注入的對象進行通知客戶端
}
else if (ritl_tyle == "WKWebView"){//若是是WKWebView
alert("WKWebView");
window.webkit.messageHandlers.ChangedMessage.postMessage(inputValue);
}
};
function iosTellSomething(value){
//document.getElementById("label").value = "收到啦";//設置給label
$('#label').text(value);
}複製代碼
定義一個自定義的協議RITLJSExport
,這裏仍然採用混編模式,由於我仍是Swfit
注入失敗了...
@protocol RITLJSExport <NSObject,JSExport>
// 相似typedef 將saySomething定義爲say,便於JS調用
JSExportAs(say,
- (void)saySomething:(NSString *)thing
);
@end
@interface RITLExportObject : NSObject
/// 進行的回調
@property (nonatomic, copy) void(^dosomething)(NSString *);
/// 將本身註冊到JSContext
- (void)registerSelfToContext:(JSContext *)context;
@end
@interface RITLExportObject (RITLJSExport)<RITLJSExport>
@end複製代碼
在UIWebViewDelegate
中的webViewDidFinishLoad()
方法中對JSContext
進行截取,並執行操做:
// MARK: UIWebView-Delegate 系列
extension RITLJSWebViewController : UIWebViewDelegate {
func webViewDidFinishLoad(_ webView: UIWebView) {
//得到JSContent對象
guard let context : JSContext = webView.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as! JSContext? else {
return
}
//告訴web,這裏是UIWebView
webView.stringByEvaluatingJavaScript(from: "sureType('UIWebView')")
/* 使用的ObjC的Export對象 */
let exportObject = RITLExportObject()
exportObject.dosomething = { [weak self](value) in
guard let value = value else { return }
self?.navigationItem.title = value //設置導航欄
//執行js告知,修改導航欄完畢
webView.stringByEvaluatingJavaScript(from: "iosTellSomething('已將\(value)設置成導航Title')")//迴應
}
//進行注入
exportObject.registerSelf(to: context)
}
}
複製代碼
首先有一點,WKWebView
是獲取不到JSContext
的,那咋辦?不要緊,WKWebView
提供給了咱們很是便利的交互,不詳細說了,以前寫的一篇博文已經介紹了,有興趣能夠看看iOS開發-------基於WKWebView的原生與JavaScript數據交互
添加JavaScript
交互
// 使用WkWebView
lazy var wkWebView : WKWebView = {
let webView: WKWebView = WKWebView(frame: self.view.bounds)
webView.navigationDelegate = self
webView.uiDelegate = self
webView.configuration.userContentController.add(RITLSciptMessageHandler(self), name: "ChangedMessage")// 添加處理
return webView
}()複製代碼
在WKNavigationDelegate中告知web當前使用webView的類型:
// 是爲了使用JS確認一下類型,實際開發不須要在這個代理下進行以下操做
extension RITLJSWebViewController : WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!){
//確認類型
webView.evaluateJavaScript("sureType('WKWebView')", completionHandler: nil)
}
}複製代碼
履行WKScriptMessageHandler
協議,完成交互操做便可
// MARK: WKWebView-Delegate 系列
extension RITLJSWebViewController : WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
{
//若是body體是約定好的字符串,而且經過標誌ChangedMessage傳遞而且存在body體
guard message.body is String,message.name == "ChangedMessage",let body:String = message.body as? String else { return }
navigationItem.title = body//設置導航
//執行通知HTML
wkWebView.evaluateJavaScript("iosTellSomething('已將\(body)設置成導航Title')") { (_, error) in
print("error = \(error?.localizedDescription)")
}
}
}複製代碼
最後記得移除哦
deinit {
print("\(type(of: self)) deinit")
if ritl_useWkWebView {
wkWebView.configuration.userContentController.removeAllUserScripts()
}
}複製代碼
這樣子,基於JavaScriptCore的UIWebView以及WKWebView交互就算圓滿完成啦,歡迎前去Start
此文章來源於第三方轉載!!
微信號13142121176