認識JavaScriptCore.framework
githubDemo:https://github.com/wangjinshan/JSCDemo
項目演示
正文
JavaScriptCore.framework 是蘋果在ios7之后新增的框架,是對 UIWebView的一次封裝,方便開發(fā)者使用,使用JavaScriptCore.framework可以輕松實現(xiàn) ios與js的交互
JavaScriptCore的組成
JavaScriptCore中主要的類
詳細介紹
1, JSContext --- 在OC中創(chuàng)建JavaScript運行的上下文環(huán)境
// 創(chuàng)建JSContext對象,獲得JavaScript運行的上下文環(huán)
- (instancetype)init;
// 在特定的對象空間上創(chuàng)建JSContext對象,獲得JavaScript運行的上下文環(huán)境
- (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;
// 運行一段js代碼,輸出結(jié)果為JSValue類型
- (JSValue *)evaluateScript:(NSString *)script;
// iOS 8.0以后可以調(diào)用此方法
- (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)sourceURL NS_AVAILABLE(10_10, 8_0);
// 獲取當前正在運行的JavaScript上下文環(huán)境
+ (JSContext *)currentContext;
// 返回結(jié)果當前執(zhí)行的js函數(shù) function () { [native code] } 亏吝,iOS 8.0以后可以調(diào)用此方法
+ (JSValue *)currentCallee NS_AVAILABLE(10_10, 8_0);
// 返回結(jié)果當前方法的調(diào)用者[object Window]
+ (JSValue *)currentThis;
// 返回結(jié)果為當前被調(diào)用方法的參數(shù)
+ (NSArray *)currentArguments;
// js的全局變量 [object Window]
@property (readonly, strong) JSValue *globalObject;
// 返回js 調(diào)用時候的異常信息
@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);
// 異常捕獲中錯誤值處理
@property (strong) JSValue *exception;
// 上下文的名字
@property (copy) NSString *name NS_AVAILABLE(10_10, 8_0);
JSValue --- JavaScript中的變量和方法敞咧,可以轉(zhuǎn)成OC數(shù)據(jù)類型,每個JSValue都和JSContext相關(guān)聯(lián)并且強引用context
OC 和 js 數(shù)據(jù)對照表
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) |
// 在context創(chuàng)建BOOL的JS變量
+ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context;
// 修改JS對象的屬性的值
- (void)setValue:(id)value forProperty:(NSString *)property;
// 調(diào)用者JSValue對象為JS中的方法名稱,arguments為參數(shù),調(diào)用JS中Window直接調(diào)用的方法
- (JSValue *)callWithArguments:(NSArray *)arguments;
// 調(diào)用者JSValue對象為JS中的全局對象名稱鲁僚,method為全局對象的方法名稱旭斥,arguments為參數(shù)
- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;
// JS中的結(jié)構(gòu)體類型轉(zhuǎn)換為OC
+ (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context;
// 將JS變量轉(zhuǎn)換成OC中的BOOL類型/提供了其他方法的轉(zhuǎn)換
- (BOOL)toBool;
// JS中是否有這個對象
// @property (readonly) BOOL isUndefined;
// 比較兩個JS對象是否相等
- (BOOL)isEqualToObject:(id)value;
3, JSExport --- JS調(diào)用OC中的方法和屬性寫在繼承自JSExport的協(xié)議當中容达,OC對象實現(xiàn)自定義的協(xié)議
官方給的例子
//@textblock
@protocol MyClassJavaScriptMethods <JSExport>
- (void)foo;
@end
// 方法實現(xiàn)
@interface MyClass : NSObject <MyClassJavaScriptMethods>
- (void)foo;
- (void)bar;
@end
//@/textblock
4, JSManagedValue --- JS和OC對象的內(nèi)存管理輔助對象,主要用來保存JSValue對象,解決OC對象中存儲js的值,導(dǎo)致的循環(huán)引用問題
// 初始化
- (instancetype)initWithValue:(JSValue *)value;
+ (JSManagedValue *)managedValueWithValue:(JSValue *)value;
+ (JSManagedValue *)managedValueWithValue:(JSValue *)value andOwner:(id)owner NS_AVAILABLE(10_10, 8_0);
JSManagedValue本身只弱引用js值垂券,需要調(diào)用JSVirtualMachine的addManagedReference:withOwner:把它添加到JSVirtualMachine中董饰,這樣如果JavaScript能夠找到該JSValue的Objective-C owner,該JSValue的引用就不會被釋放圆米。
5, JSVirtualMachine --- JS運行的虛擬機卒暂,有獨立的堆空間和垃圾回收機制,運行在不同虛擬機環(huán)境的JSContext可以通過此類通信娄帖。
// 初始化
- (instancetype)init;
// 添加
- (void)addManagedReference:(id)object withOwner:(id)owner;
// 移除
- (void)removeManagedReference:(id)object withOwner:(id)owner;
到此 JavaScriptCore.framework 能夠使用的基本api已經(jīng)介紹完畢
下面我們以簡單集成SMSDK為 實際例子進行講解
首先創(chuàng)建必要的三個文件 SMSDK.html/ SMSDK.cc SMSDK.js 在viewDidLoad中創(chuàng)建webview進行加載SDMSDK.html
iOS調(diào)用 js
js中方法的實現(xiàn)
在 SMSDK.js中 創(chuàng)建 ios需要的初始化對象 并保留一個 對象接口 方便在 html中 調(diào)用這個方法
SMSDK.js
function SMSDK()
{
// 不能添加 alert() 等外部的方法
// alert(--qq--);
var name = "金山";
this.initSDK = function (hello)
{
var initData ={};
var appkey =
{
"appkey":"f3fc6baa9ac4"
}
var appSecrect=
{
"appSecrect":"7f3dedcb36d92deebcb373af921d635a"
}
initData["appkey"] = appkey;
initData["appSecrect"] = appSecrect;
return initData;
};
// 必須使用this 關(guān)鍵字
}
var $smsdk = new SMSDK();
在 SMSDK.html中進行引用并 創(chuàng)建js 方法
<script>
function initSDK(hello)
{
return $smsdk.initSDK(hello);
}
</script>
這里的 initSDK(); 方法就是留給ios 調(diào)用的方法
我們同樣選擇在網(wǎng)頁加載完成的方法中進行方法調(diào)用
-(void) webViewDidFinishLoad:(UIWebView *)webView
{
[self initSMSDK];
}
實現(xiàn) 方法
-(void) initSMSDK
{
// 創(chuàng)建上下文
// 1.這種方式需要傳入一個JSVirtualMachine對象也祠,如果傳nil,會導(dǎo)致應(yīng)用崩潰的近速。
JSVirtualMachine *JSVM = [[JSVirtualMachine alloc] init];
JSContext *jscontext = [[JSContext alloc] initWithVirtualMachine:JSVM];
2.這種方式诈嘿,內(nèi)部會自動創(chuàng)建一個JSVirtualMachine對象,可以通過JSCtx.virtualMachine
// 看其是否創(chuàng)建了一個JSVirtualMachine對象削葱。
JSContext *jscontext = [[JSContext alloc] init];
/**********以上的方法經(jīng)過測試都不好使*************/
正確的姿勢
1, 創(chuàng)建上下文,意思就是讓oc和js 同處于一個環(huán)境,方便進行方法調(diào)用
JSContext *jscontext = [self.mywebview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
2 參數(shù)接受 注意一定輸傳遞方法的名字,不要加()// 和webview的調(diào)用方法的區(qū)別
JSValue *jsvalue = jscontext[@"initSDK"]; // 返回的是方法的名字和內(nèi)容
3 調(diào)用函數(shù)/傳遞參數(shù)
JSValue *initData = [jsvalue callWithArguments:@[@"從oc中傳遞第一個參數(shù)進去"]];
接收到的字典對象進行解析,并調(diào)用SMSDK的初始化方法
NSDictionary *dic = [initData toDictionary];
NSString *appkey = dic[@"appkey"][@"appkey"];
NSString *appSecrect = dic[@"appSecrect"][@"appSecrect"];
[SMSSDK registerApp:appkey withSecret:appSecrect];
}
我們通過打印上面的 appkey 等參數(shù)就知道 實現(xiàn)了ios調(diào)用 js的方法
js調(diào)用 ios
可以通過兩種方式在JavaScript中調(diào)用Objective-C:
Blocks: 對應(yīng)JS函數(shù)
JSExport協(xié)議: 對應(yīng)JS對象
我們先實現(xiàn) block的方法
Block的方法
首先在html中 寫一個button的點擊方法
<button id="getCode" onclick="getCode($smsdk.getCode())">獲取手機號</button>
getCode()方法就是將來 ios 中需要注冊的方法體, 里面是對js返回參數(shù)方法的調(diào)用 相當于 getCode('返回給js的參數(shù),在參數(shù)列表中調(diào)用就可以');
過程和上面一樣
// 1,獲取上下文 // 下文weakSelf.jsContext 中已經(jīng)在屬性中保存jscontext
JSContext *_jsContext = [self.mywebview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 2 異常捕獲機制
_jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
context.exception = exception;
//別忘了給exception賦值奖亚,否則JSContext的異常信息為空
NSLog(@"---錯誤數(shù)據(jù)的處理----%@",exception);
};
__weak typeof (self) weakSelf = self; // 防止循環(huán)引用就加上 __weak
_jsContext[@"getCode"] = ^ (){
NSArray *arr =[JSContext currentArguments]; // 獲取當前的上下文,然后加載 js返回的參數(shù)列表
for (id objc in arr) {
weakSelf.phoneNumber = objc;
[SMSSDK getVerificationCodeByMethod:SMSGetCodeMethodSMS phoneNumber:objc zone:@"86" customIdentifier:nil result:^(NSError *error) {
JSContext *jscontext = weakSelf.jsContext;
if (!error)
{
// ios回調(diào) js的代碼 并傳遞參數(shù)給 js
JSValue *jsvalue = jscontext[@"getCodeCallBack"]; // 注入方法
[jsvalue callWithArguments:@[@"獲取短信驗證碼成功"]]; // 調(diào)用方法
}
else
{
JSValue *jsvalue = jscontext[@"getCodeCallBack"]; // 注入方法
[jsvalue callWithArguments:@[@"獲取驗證碼失敗"]];
}
}];
}
};
通過上面的方法我們知道, block 方法 ios 給 js傳遞的是方法 ,如果傳遞 對象則需要用到 協(xié)議的方法
首先我們來看一個最簡單的例子 :
在 js 上寫一個 點擊事件,然后調(diào)用 ios 的方法
在 js中
在 SMDK.html中添加
<div>
<input type= "button" width="50%" height="5%" id = "Button" value = "需要測試" onclick = "objc.takePicture()"></button>
</div>
說明: 這里的 objc 對象就是將來我們需要在 ios中注冊的對象, takePicture() ,就是在協(xié)議中的方法
在viewController.m中,定義協(xié)議
/**
* 實現(xiàn)js代理,js調(diào)用ios的入口就在這里
*/
@protocol JSDelegate <JSExport>
- (void)getImage:(id)parameter;// 這個方法就是window.document.iosDelegate.getImage(JSON.stringify(parameter)); 中的 getImage()方法
@end
簽訂協(xié)議: 注意這里的協(xié)議不需要實現(xiàn),在外部也不需要設(shè)置代理,直接實現(xiàn)就可以
@interface ViewController ()<UIWebViewDelegate,JSDelegate,UINavigationControllerDelegate,UIImagePickerControllerDelegate,JSOCViewControllerExport>
在網(wǎng)頁加載完成的代理中實現(xiàn), 上下文
self.jsContext = [self.myWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 測試處理
self.jsContext[@"objc"] = self;//掛上代理 iosDelegate是window.document.iosDelegate.getImage(JSON.stringify(parameter)); 中的 iosDelegate
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception){
context.exception = exception;
NSLog(@"js方法寫錯了 錯誤的信息都會在此處輸出:%@",exception);
};
最后一步就是實現(xiàn) 在 js中聲明的協(xié)議方法
// 協(xié)議實現(xiàn)
- (void)getImage:(id)parameter
{
NSArray *arr =[JSContext currentArguments];
for (id objc in arr)
{
NSLog(@"=-----%@",[objc toDictionary]);
}
[self beginOpenPhoto]; // 相機的處理,
}
實現(xiàn)相機的方法,很簡單, 里面的數(shù)據(jù)傳遞和上文中說到的一個樣子,這里將不再贅述.
到此, 利用協(xié)議實現(xiàn) js 調(diào)用 ios的方法基本完成
關(guān)于內(nèi)存泄漏,循環(huán)引用的問題 注意不要在 block中直接 引用外部 強引用的對象就可以
__weak typeof (self) weakSelf = self; // 防止循環(huán)引用就加上 __weak
_jsContext[@"getCode"] = ^ (id oc){
NSArray *arr =[JSContext currentArguments]; // 獲取當前的上下文
for (id objc in arr) {
weakSelf.phoneNumber = objc;
這個 weakSelf 的處理就ok
githubDemo:https://github.com/wangjinshan/JSCDemo