項目中涉及OC與網頁的交互,查找資料時看到了JavaScriptCore.framework,就對照文章ios7 JavaScriptCore.framework+自己的理解整理了一下,通過注釋進行相關的解釋。
參考:
示例代碼:( YYJavaScriopCoreDemo)
實際使用:iOS+JavaScriptCore.framework的基本使用(二)
使用的時候引用 #import <JavaScriptCore/JavaScriptCore.h>
-
JS與OC變量之間的轉換
/**
* 1. JSContext:為javaScript提供運行環境
* 2. JSContext通過-evaluateScript:方法執行JavaScript代碼,
并且代碼中的方法、變量等信息都會被存儲在JSContext實例中以便在需要的時候使用
* 3. JSValue: OC對象與JS對象之間的轉換橋梁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) * */ /** JS與OC變量之間的轉換 */ - (void)jsValue2OCValue{ // 1. 創建javaScript的運行環境 JSContext *jsC = [[JSContext alloc] init]; // 2. 執行js代碼: - (JSValue *)evaluateScript:(NSString *)script NSString *jsCode = @"var jsv1 = 123; var jsv2 = 'jsString' "; [jsC evaluateScript:jsCode]; // 3. 通過JSValue獲取js中的變量jsv1 JSValue *jsv1 = jsC[@"jsv1"]; // 4. 將JSValue轉換為OC變量 int ocv1 = [jsv1 toInt32]; NSLog(@"%d",ocv1); // 5. 獲取變量jsv2并轉換 JSValue *jsv2 = jsC[@"jsv2"]; NSString *ocv2 = [jsv2 toString]; NSLog(@"%@",ocv2); }
-
JSValue的使用
/** * JSValue的使用 * JSValue通過下標可以獲取對象及對象的屬性值,也可以通過下標直接取值和賦值 */ - (void)jsValueProperty{ // 1. 創建js運行環境并執行js代碼 JSContext *jsC = [[JSContext alloc] init]; NSString *jsCode = @"var jsvArr = [123,'jsString'] "; [jsC evaluateScript:jsCode]; // 2. 以下標的方式,通過變量名從JSContext中獲取jsvArr JSValue *jsvArr = jsC[@"jsvArr"]; NSLog(@"------Before set-----\n"); NSLog(@"%@",jsvArr); // 3. 直接通過下標賦值(js無下標越位,自動延展數組大小) jsvArr[3] = @(YES); NSLog(@"------After set-----\n"); NSLog(@"%@",jsvArr); // 4. 獲取數組的屬性值:length NSLog(@"------Property-----\n"); NSLog(@"Array length = %@,%@",jsvArr[@"length"],[jsvArr[@"length"] class]); }
-
JS調用OC方法
/** JS調用OC方法
* 1. 方式:在JSContext中傳入OC的Block當做JS的function
* 2. 獲取JS參數列表:JSContext的方法,+(JSContext *)currentArguments
* 3. 獲取調用該方法的對象:JSContext的方法,+ (JSValue )currentThis)
/
/ JS調用OC方法 */
- (void)js_call_oc_block{// 1. 創建js運行環境 JSContext *jsC = [[JSContext alloc] init]; // 2. 定義block方法供js使用 jsC[@"log"] = ^(){ NSLog(@"-------Begin Log-------"); // 獲取調用該方法時的參數 NSArray *args = [JSContext currentArguments]; for (JSValue *jsVal in args) { id dict = [jsVal toObject]; NSLog(@"%@", dict); } // 獲取調用該方法的對象 JSValue *this = [JSContext currentThis]; NSLog(@"this: %@",this); NSLog(@"-------End Log-------"); }; // 3. js直接調用oc傳入的block NSString *jsCode = @"log('logVal1', [7, 21], { hello:'world', js:100 });"; [jsC evaluateScript:jsCode]; // 4. js直接使用oc傳入的變量 jsC[@"newJSValue1"] = @(456); [jsC evaluateScript:@"log(newJSValue1)"]; }
-
OC調用JS方法
/* * OC調用JS方法: * 1. JS的Func不能轉化成OC的Block:JavaScript方法的參數不固定,Block的參數個數和類型已經返回類型都是固定的 * 2. 方式1: JSValue的方法-(JSValue *)callWithArguments:(NSArray *)arguments可以運行JS的func * 3. 方式2: JSValue的方法- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments直接簡單地調用對象上的方法。 * 4. 對于方式2,如果是全局函數,用JSContext的globalObject對象調用;如果是某JavaScript對象上的方法,就應該用相應的JSValue對象調用。 */ /** OC調用JS方法 */ - (void)oc_call_js_func{ // 1. 創建js運行環境并執行js代碼 JSContext *jsC = [[JSContext alloc] init]; NSString *jsCode = @"function add(a, b) { return a + b; }"; [jsC evaluateScript:jsCode]; // 2. 獲取方法 JSValue *add = jsC[@"add"]; NSLog(@"Func: %@", add); // 3. 方式1調用js的Func JSValue *sum1 = [add callWithArguments:@[@(7), @(21)]]; NSLog(@"Sum1: %d",[sum1 toInt32]); // 4. 方式2調用js的Func:全局函數 JSValue *sum2 = [[jsC globalObject] invokeMethod:@"add" withArguments:@[@(1), @(2)]]; NSLog(@"Sum2: %d",[sum2 toInt32]); }
-
異常處理
/*
* 異常處理
* OC異常會在運行時被Xcode捕獲,而在JSContext中執行的JavaScript異常只會被JSContext捕獲并存儲在exception屬性上,不會向外拋出。
* 如果需要監測JSContext的異常,最合理的方式是給JSContext對象設置exceptionHandler
*/- (void)jsExceptionHandler{ JSContext *context = [[JSContext alloc] init]; context.exceptionHandler = ^(JSContext *con, JSValue *exception) { NSLog(@"%@", exception); //別忘了給exception賦值,否則JSContext的異常信息為空 con.exception = exception; }; [context evaluateScript:@"arrar[0] = 21"]; }
-
Block使用注意
/* * 在Block內都不要直接使用其外部定義的JSContext對象或者JSValue,否則會造成循環引用使得內存無法被正確釋放。 * 應該將其當做參數傳入到Block中,或者通過JSContext的類方法+ (JSContext *)currentContext獲得。 * 原因: 1. JSContext會強引用Block 2. Block引用外部對象時會做強引用 3. 每個JSValue都會強引用一個JSContext 4. block引用外部JScontext(箭頭都代表強引用): JSContext --> Block --> JSContext 循環引用 5. block引用外部JSValue: JSValue --> JSContext --> Block --> JSValue 循環引用 */
-
JSExport的使用
// 自定義JSExport協議
@protocol OCPersonExport <JSExport>
@property (nonatomic, strong) NSDictionary *extMes;
- (NSString *)fullName;
- (void)doSometing:(id)something withSomeone:(id)someone;// 自定義js調用時的方法名:宏JSExportAs(functionName, ocFunction) JSExportAs(setInfo, - (void)setFirstName:(NSString *)fn lastName:(NSString *)ln desc:(NSDictionary *)desc); @end // 遵守協議的類 @interface OCPerson : NSObject<OCPersonExport> @property (nonatomic, copy) NSString *firstName; @property (nonatomic, copy) NSString *lastName; @end @implementation OCPerson @synthesize extMes; - (NSString *)fullName { return [NSString stringWithFormat:@"%@·%@", self.firstName, self.lastName]; } - (void)doSometing:(id)something withSomeone:(OCPerson *)someone{ NSLog(@"%@ %@ %@",self.fullName,something,someone.fullName); } - (void)setFirstName:(NSString *)fn lastName:(NSString *)ln desc:(NSDictionary *)desc{ self.firstName = fn; self.lastName = ln; self.extMes = desc; } @end
.
/*
* 1. 所有JSExport協議(或子協議)中定義的方法和屬性,都可以在JSContext中被使用
* 2. 遵守了JSExport協議(或子協議)的類,額外定義的方法/屬性,JSContext訪問不到
* 3. JSExport可以正確反映屬性聲明,例如readonly的屬性,在JavaScript中也就只能讀取值而不能賦值。
* 4. 對于多參數的方法,為能被js使用,JavaScriptCore會對其進行轉換。
* 4.1 轉換方式:將OC方法冒號后的字母大寫,并移除冒號, 參數移到后面。
* 4.2 例如:方法- (void)doSometing:(id)something withSomeone:(id)someone;
* 在JavaScript調用就是:doSometingWithSomeone(something, someone);
* 5. 自定義js調用時的方法名:宏JSExportAs(functionName, ocFunction),在js中只要調用方法名functionName,就可以訪問oc對象的ocFunction方法
*/
/** JSExport的使用 */
- (void)jsExportTest{
// 1.初始化
JSContext *jsC = [[JSContext alloc] init];
jsC[@"log"] = ^(){
NSArray *args = [JSContext currentArguments];
for (JSValue *jsVal in args) {
NSLog(@"%@", jsVal);
}
};
jsC.exceptionHandler = ^(JSContext *con, JSValue *exception) {
NSLog(@"%@", exception);
con.exception = exception;
};
// 2.創建oc類,并傳入js運行環境中
OCPerson *person = [[OCPerson alloc] init];
person.firstName = @"O";
person.lastName = @"C";
person.extMes = @{@"desc":@"這是OC類1",@"value":@(123)};
jsC[@"person"] = person;
// 3.js使用oc對象的屬性和方法
// 3.1 調用OCPersonExport中的方法 -(NSString *)fullName;
[jsC evaluateScript:@"log('-----fullName-----',person.fullName());"];
// 3.2 未獲取到,OCPersonExport沒有定義該屬性/方法
[jsC evaluateScript:@"log('-----firstName-----',person.firstName);"];
// 3.3 未獲取到,OCPersonExport沒有定義該屬性/方法
[jsC evaluateScript:@"log('-----lastName-----',person.lastName);"];
// 3.4 獲取OCPersonExport的屬性值extMes;
[jsC evaluateScript:@"log('-----extMes-----','desc:', person.extMes.desc, 'value:', person.extMes.value);"];
// 3.5 修改OCPersonExport的屬性值extMes;
[jsC evaluateScript:@"person.extMes = {desc:'被js修改后的OC類1'}"];
// 3.6 獲取屬性extMes
[jsC evaluateScript:@"log('-----extMes-----','desc:', person.extMes.desc, 'value:', person.extMes.value);"];
// 4. oc對象里面的值也確實被修改了
NSLog(@"-----AFTER-----");
NSLog(@"\n%@", person.extMes);
// 5. 多參數方法調用
OCPerson *person2 = [[OCPerson alloc] init];
person2.firstName = @"J";
person2.lastName = @"S";
jsC[@"person2"] = person2;
NSLog(@"---JS調用OC多參數方法---");
[jsC evaluateScript:@"person.doSometingWithSomeone('talk to', person2);"]; // 獲取屬性
// 6. 自定義js方法名
// JSExportAs(setInfo, - (void)setFirstName:(NSString *)fn lastName:(NSString *)ln desc:(NSDictionary *)desc);
// js調用setInfo(),相當于oc調用方法 -setFirstName:lastName:desc:
NSLog(@"---定義js方法名---");
[jsC evaluateScript:@"person2.setInfo('JS', 'New', {desc: ' call custom functionName'});"];
[jsC evaluateScript:@"var str = person2.fullName() + person2.extMes.desc; log(str);"];
}
-
JSExport的使用--為已定義的類擴展協議--class_addProtocol
//定義JSExport子協議 @protocol JSUITextFieldExport <JSExport> @property(nonatomic,copy) NSString *text; @end /** * 為已定義的類擴展協議--class_addProtocol * 1. 自定義的OC類,可以繼承自定義的JSExport的協議實現與JavaScript的交互 * 2. 對于已經定義好的系統類或者外部類,預先不會定義協議提供與JavaScript的交互,OC可以在運行時實時對類拓展協議。 */ - (void)extendJSExportProtocal{ // 1. 初始化 JSContext *jsC = [[JSContext alloc] init]; jsC[@"log"] = ^(){ NSArray *args = [JSContext currentArguments]; for (JSValue *jsVal in args) { NSLog(@"%@", jsVal); } }; jsC.exceptionHandler = ^(JSContext *con, JSValue *exception) { NSLog(@"%@", exception); con.exception = exception; }; // 2. 運行時擴展協議 class_addProtocol([UITextField class], @protocol(JSUITextFieldExport)); UITextField *textField = [[UITextField alloc]init]; textField.text = @"0"; jsC[@"textField"] = textField; // 3. 獲取擴展協議的屬性,并賦值 NSLog(@"--為已定義的類擴展協議--"); NSString *script = @"var num = parseInt(textField.text, 10);"http:// js獲取屬性值 "textField.text = 10;" // js賦值 "var string = 'oldText:'+num+' newText:'+textField.text;" "log(string)"; [jsC evaluateScript:script]; }
-
內存管理
/** 內存處理
* 1. Objective-C是基于引用計數MRC/自動引用計數ARC, javaScript是垃圾回收機制GC, JavaScript對對象的引用為強引用* 2. js引用oc變量異常情況:在一個方法中創建了一個臨時的OC對象,將其加入到JSContext后被js使用,js會對該變量進行強引用不會釋放回收(不會增加變量的引用計數器的值),但是OC上的對象可能在方法調用結束后,引用計數變0而被回收內存,此后JavaScript層面容易造成錯誤訪問。 * 3. oc引用js變量異常情況:如果用JSContext創建了對象或者數組,返回JSValue到Objective-C,即使把JSValue變量retain下,但可能因為JavaScript中因為變量沒有了引用而被釋放內存,那么對應的JSValue也沒有用了。 * 4. JSVirtualMachine: JSVirtualMachine就是一個用于保存弱引用對象的數組,加入該數組的弱引用對象因為會被該數組 retain,保證了使用時不會被釋放,當數組里的對象不再需要時,就從數組中移除,沒有了引用的對象就會被系統釋放。 * 5. JSManagedValue:將 JSValue 轉為 JSManagedValue 類型后,可以添加到 JSVirtualMachine 對象中,這樣能夠保證你在使用過程中 JSValue 對象不會被釋放掉,當你不再需要該 JSValue 對象后,從 JSVirtualMachine 中移除該 JSManagedValue 對象,JSValue 對象就會被釋放并置空。) */ - (void)memoryTest{ // 1. 初始化 JSContext *jsC = [[JSContext alloc] init]; JSValue *value = [[JSValue alloc]init]; JSManagedValue *managedV = [JSManagedValue managedValueWithValue:value]; // 2. 需要的時候加入virtualMachine中 [jsC.virtualMachine addManagedReference:managedV withOwner:self]; // 3. 不需要的時候移除 [jsC.virtualMachine removeManagedReference:managedV withOwner:self]; }