前言
最近在看React Native源碼的時(shí)候狈蚤,發(fā)現(xiàn)內(nèi)部使用了一個(gè)JavaScriptCore框架绝葡,可以用來(lái)進(jìn)行OC和JS互相調(diào)用叫胖,借機(jī)寫(xiě)了一篇OC和JS互相調(diào)用招驴,為后續(xù)想了解React Native源碼的同學(xué)埋下伏筆篙程,當(dāng)然后期也會(huì)把自己所了解的React Native源碼實(shí)現(xiàn)跟大家分享。
如果喜歡我的文章别厘,可以關(guān)注我微博:袁崢Seemygo
一虱饿、JavaScriptCore常用的類(lèi)
JavaScriptCore作用:JavaScriptCore是蘋(píng)果原生API,用來(lái)JS和OC交互的丹允。
- JSContext: JS運(yùn)行環(huán)境郭厌,用它去執(zhí)行JS代碼袋倔,并且通過(guò)它去獲取JS里的數(shù)據(jù)
- JSValue: 用于接收J(rèn)S中獲取的數(shù)據(jù)類(lèi)型雕蔽,可以是任一對(duì)象,方法宾娜。
二批狐、OC調(diào)用JS
- 本質(zhì):JS代碼中已經(jīng)定義好變量和方法,通過(guò)OC去獲取前塔,并且調(diào)用
- 步驟:
- 1.創(chuàng)建JS運(yùn)行環(huán)境
- 2.執(zhí)行JS代碼
- 3.獲取JS數(shù)據(jù)(變量嚣艇,方法)
- 4.使用JS數(shù)據(jù),方法
2.1 獲取定義在JS中的變量
- 可以直接通過(guò)OC修改JS中變量的值
#pragma mark - 獲取JS中定義的變量
- (void)getJSVar
{
// JS代碼
NSString *jsCode = @"var arr = [1,2,3]";
// 創(chuàng)建JS運(yùn)行環(huán)境
JSContext *ctx = [[JSContext alloc] init];
// 執(zhí)行JS代碼
[ctx evaluateScript:jsCode];
// 因?yàn)樽兞恐苯佣x在JS中华弓,所以可以直接通過(guò)JSContext獲取食零,根據(jù)變量名稱(chēng)獲取,相當(dāng)于字典的Key
// 只有先執(zhí)行JS代碼寂屏,才能獲取變量
JSValue *jsArr = ctx[@"arr"];
jsArr[0] = @5;
// 打印結(jié)果:5,2,3
NSLog(@"%@",jsArr);
}
2.2 獲取定義在JS中的方法贰谣,并且調(diào)用
- 實(shí)現(xiàn)OC調(diào)用JS方法
#pragma mark - OC調(diào)用JS
// OC調(diào)用JS方法,并獲取返回結(jié)果
- (void)ocCallJSFunc
{
NSString *jsCode = @"function hello(say){"
" return say; "
"}";
// 創(chuàng)建JS運(yùn)行環(huán)境
JSContext *ctx = [[JSContext alloc] init];
// 因?yàn)榉椒ㄖ苯佣x在JS中迁霎,所以可以直接通過(guò)JSContext獲取吱抚,根據(jù)方法名稱(chēng)獲取,相當(dāng)于字典的Key
// 執(zhí)行JS代碼
[ctx evaluateScript:jsCode];
// 獲取JS方法考廉,只有先執(zhí)行JS代碼秘豹,才能獲取
JSValue *hello = ctx[@"hello"];
// OC調(diào)用JS方法,獲取方法返回值
JSValue *result = [hello callWithArguments:@[@"你好"]];
// 打印結(jié)果:你好
NSLog(@"%@",result);
}
三昌粤、JS調(diào)用OC中的block
- 本質(zhì):一開(kāi)始JS中并沒(méi)有OC的block既绕,所以沒(méi)法直接調(diào)用OC的block啄刹,需要把OC的block,在JS中生成方法凄贩,然后在通過(guò)JS調(diào)用鸵膏。
- 步驟:
- 1.創(chuàng)建JS運(yùn)行環(huán)境
- 2.在JS中生成對(duì)應(yīng)的OC代碼
- 3.使用JS調(diào)用,在JS環(huán)境中生成的block方法怎炊,就能調(diào)用到OC的block中.
3.1 JS調(diào)用OC中不帶參數(shù)的block
- 想通過(guò)JS調(diào)用OC中不帶參數(shù)的block
#pragma mark - JS調(diào)用OC中不帶參數(shù)的block
- (void)jsCallOCBlock1WithNoneArguments
{
// 創(chuàng)建JS運(yùn)行環(huán)境
JSContext *ctx = [[JSContext alloc] init];
// JS調(diào)用Block方式
// 由于JS本身沒(méi)有OC這個(gè)代碼谭企,需要給JS中賦值,就會(huì)自動(dòng)生成右邊的代碼.
// 相當(dāng)于在JS中定義一個(gè)叫eat的方法评肆,eat的實(shí)現(xiàn)就是block中的實(shí)現(xiàn)债查,只要調(diào)用eat,就會(huì)調(diào)用block
ctx[@"eat"] = ^(){
NSLog(@"吃東西");
};
// JS執(zhí)行代碼,就會(huì)直接調(diào)用到block中
NSString *jsCode = @"eat()";
[ctx evaluateScript:jsCode];
}
3.2 JS調(diào)用OC中帶參數(shù)的block
- 想通過(guò)JS調(diào)用OC中帶參數(shù)的block
- (void)jsCallOCBlockWithArguments
{
// 創(chuàng)建JS運(yùn)行環(huán)境
JSContext *ctx = [[JSContext alloc] init];
// 2.調(diào)用帶有參數(shù)的block
// 還是一樣的寫(xiě)法瓜挽,會(huì)在JS中生成eat方法盹廷,只不過(guò)通過(guò)[JSContext currentArguments]獲取JS執(zhí)行方法時(shí)的參數(shù)
ctx[@"eat"] = ^(){
// 獲取JS調(diào)用參數(shù)
NSArray *arguments = [JSContext currentArguments];
NSLog(@"吃%@",arguments[0]);
};
// JS執(zhí)行代碼,調(diào)用eat方法,并傳入?yún)?shù)面包
NSString *jsCode = @"eat('面包')";
[ctx evaluateScript:jsCode];
}
四久橙、JS調(diào)用OC中的類(lèi)
- 本質(zhì):一開(kāi)始JS中并沒(méi)有OC的類(lèi)俄占,需要先在JS中生成OC的類(lèi),然后在通過(guò)JS調(diào)用淆衷。
- 步驟
- 1.OC類(lèi)必須遵守JSExport協(xié)議缸榄,只要遵守JSExport協(xié)議,JS才會(huì)生成這個(gè)類(lèi)
- 2.但是還不夠祝拯,類(lèi)里面有屬性和方法甚带,也要在JS中生成
- 3.JSExport本身不自帶屬性和方法,需要自定義一個(gè)協(xié)議佳头,繼承JSExport鹰贵,在自己的協(xié)議中暴露需要在JS中用到的屬性和方法
- 4.這樣自己的類(lèi)只要繼承自己的協(xié)議就好,JS就會(huì)自動(dòng)生成類(lèi)康嘉,包括自己協(xié)議中聲明的屬性和方法
4.1 JS調(diào)用OC自定義類(lèi)
- 自定義協(xié)議(PersonJSExport)
@protocol PersonJSExport <JSExport>
@property (nonatomic, strong) NSString *name;
- (void)play;
// 調(diào)用多個(gè)參數(shù)的方法碉输,JS函數(shù)命名規(guī)則和OC還不一樣,很可能調(diào)用不到對(duì)應(yīng)的JS生成的函數(shù)亭珍,為了保證生成的JS函數(shù)和OC方法名一致敷钾,OC提供了一個(gè)宏JSExportAs,用來(lái)告訴JS應(yīng)該生成什么樣的函數(shù)對(duì)應(yīng)OC的方法块蚌,這樣就不會(huì)調(diào)錯(cuò)了闰非。
// PropertyName:JS函數(shù)生成的名字
// Selector:OC方法名
// JS就會(huì)自動(dòng)生成playGame這個(gè)方法
JSExportAs(playGame, - (void)playWithGame:(NSString *)game time:(NSString *)time);
@end
- 自定義類(lèi)(Person)
@interface Person : NSObject <PersonJSExport>
@property (nonatomic, strong) NSString *name;
- (void)playWithGame:(NSString *)game time:(NSString *)time;
@end
@implementation Person
- (void)play
{
NSLog(@"%@玩",_name);
}
- (void)playWithGame:(NSString *)game time:(NSString *)time
{
NSLog(@"%@在%@玩%@",_name,time,game);
}
@end
- 通過(guò)JS調(diào)用OC自定義類(lèi)
#pragma mark - JS調(diào)用OC自定義類(lèi)
- (void)jsCallOCCustomClass
{
// 創(chuàng)建Person對(duì)象
Person *p = [[Person alloc] init];
p.name = @"yz";
JSContext *ctx = [[JSContext alloc] init];
// 會(huì)在JS中生成Person對(duì)象,并且擁有所有值
// 前提:Person對(duì)象必須遵守JSExport協(xié)議峭范,
ctx[@"person"] = p;
// 執(zhí)行JS代碼
// 注意:這里的person一定要跟上面聲明的一樣财松,因?yàn)樯傻膶?duì)象是用person引用
// NSString *jsCode = @"person.play()";
NSString *jsCode = @"person.playGame('德州撲克','晚上')";
[ctx evaluateScript:jsCode];
}
4.1 JS調(diào)用OC系統(tǒng)類(lèi)
-
問(wèn)題:系統(tǒng)自帶的類(lèi),想要通過(guò)JS調(diào)用怎么辦,我們又沒(méi)辦法修改系統(tǒng)自帶類(lèi)的文件
- 和調(diào)用自定義類(lèi)一樣辆毡,也要弄個(gè)自定義協(xié)議繼承JSExport菜秦,描述需要暴露哪些屬性(想要把系統(tǒng)類(lèi)的哪些屬性暴露,就在自己的協(xié)議聲明)
- 通過(guò)runtime,給類(lèi)添加協(xié)議
自定義協(xié)議(UILabelJSExport)
@protocol UILabelJSExport <JSExport>
@property (nonatomic, strong) NSString *text;
@end
- JS調(diào)用OC系統(tǒng)類(lèi)
#pragma mark - JS調(diào)用OC系統(tǒng)類(lèi)
- (void)jsCallOCSystemClass
{
// 給系統(tǒng)類(lèi)添加協(xié)議
class_addProtocol([UILabel class], @protocol(UILabelJSExport));
// 創(chuàng)建UILabel
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
[self.view addSubview:label];
JSContext *ctx = [[JSContext alloc] init];
// 就會(huì)在JS中生成label對(duì)象舶掖,并且用laebl引用
ctx[@"label"] = label;
// 利用JS給label設(shè)置文本內(nèi)容
NSString *jsCode = @"label.text = 'Oh Year'";
[ctx evaluateScript:jsCode];
}