国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > iOS > 正文

ios下web嵌入開發學習總結 (一)

2019-11-06 10:04:49
字體:
來源:轉載
供稿:網友

iOS7以前我們對JS的操作只有webview里面一個函數 stringByEvaluatingjavaScriptFromString,JS對OC的回調都是基于URL的攔截進行的操作。大家用得比較多的是WebViewJavascriptBridge和EasyJSWebView這兩個開源庫,很多混合都采用的這種方式。

JavaScriptCore是webkit的一個重要組成部分,主要是對JS進行解析和提供執行環境。代碼是開源的,可以下下來看看(源碼)。iOS7后蘋果在iPhone平臺推出,極大的方便了我們對js的操作。我們可以脫離webview直接運行我們的js。

JavaScriptCore和我們相關的類不是很多,使用起來也非常簡單。

#import "JSContext.h"#import "JSValue.h"#import "JSManagedValue.h"#import "JSVirtualMachine.h"#import "JSExport.h"

JSContext

JS執行的環境,同時也通過JSVirtualMachine管理著所有對象的生命周期,每個JSValue都和JSContext相關聯并且強引用context。

JSValue

JS對象在JSVirtualMachine中的一個強引用,其實就是Hybird對象。我們對JS的操作都是通過它。并且每個JSValue都是強引用一個context。同時,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)

JSManagedValue

JS和OC對象的內存管理輔助對象。由于JS內存管理是垃圾回收,并且JS中的對象都是強引用,而OC是引用計數。如果雙方相互引用,勢必會造成循環引用,而導致內存泄露。我們可以用JSManagedValue保存JSValue來避免。

JSVirtualMachine

JS運行的虛擬機,有獨立的堆空間和垃圾回收機制。

JSExport

一個協議,如果JS對象想直接調用OC對象里面的方法和屬性,那么這個OC對象只要實現這個JSExport協議就可以了。

OC和JS之間的通信

兩者之間的通信還是很簡單的,直接看簡單代碼示例吧。

Objective-C -> JavaScript

self.context = [[JSContext alloc] init]; NSString *js = @"function add(a,b) {return a+b}"; [self.context evaluateScript:js]; JSValue *n = [self.context[@"add"] callWithArguments:@[@2, @3]]; NSLog(@"---%@", @([n toInt32]));//---5

步驟很簡單,創建一個JSContext對象,然后將JS代碼加載到context里面,最后取到這個函數對象,調用callWithArguments這個方法進行參數傳值。(JS里面函數也是對象)

JavaScript -> Objective-C

JS調用OC有兩個方法:block和JSExport PRotocol。

block(JS function):

self.context = [[JSContext alloc] init]; self.context[@"add"] = ^(NSInteger a, NSInteger b) { NSLog(@"---%@", @(a + b)); }; [self.context evaluateScript:@"add(2,3)"];

我們定義一個block,然后保存到context里面,其實就是轉換成了JS的function。然后我們直接執行這個function,調用的就是我們的block里面的內容了。

JSExport protocol:

//定義一個JSExport protocol@protocol JSExportTest <JSExport>- (NSInteger)add:(NSInteger)a b:(NSInteger)b;@property (nonatomic, assign) NSInteger sum;@end//建一個對象去實現這個協議:@interface jsprotocolObj : NSObject<JSExportTest>@end@implementation JSProtocolObj@synthesize sum = _sum;//實現協議方法- (NSInteger)add:(NSInteger)a b:(NSInteger)b{ return a+b;}//重寫setter方法方便打印信息,- (void)setSum:(NSInteger)sum{ NSLog(@"--%@", @(sum)); _sum = sum;}@end//在VC中進行測試@interface ViewController () <JSExportTest>@property (nonatomic, strong) JSProtocolObj *obj;@property (nonatomic, strong) JSContext *context;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; //創建context self.context = [[JSContext alloc] init]; //設置異常處理 self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) { [JSContext currentContext].exception = exception; NSLog(@"exception:%@",exception); }; //將obj添加到context中 self.context[@"OCObj"] = self.obj; //JS里面調用Obj方法,并將結果賦值給Obj的sum屬性 [self.context evaluateScript:@"OCObj.sum = OCObj.addB(2,3)"];}

demo很簡單,還是定義了一個兩個數相加的方法,還有一個保存結果的變量。在JS中進行調用這個對象的方法,并將結果賦值sum。唯一要注意的是OC的函數命名和JS函數命名規則問題。協議中定義的是add: b:,但是JS里面方法名字是addB(a,b)。可以通過JSExportAs這個宏轉換成JS的函數名字。

修改下代碼:

@protocol JSExportTest <JSExport>//用宏轉換下,將JS函數名字指定為add;JSExportAs(add, - (NSInteger)add:(NSInteger)a b:(NSInteger)b);@property (nonatomic, assign) NSInteger sum;@end//調用[self.context evaluateScript:@"OCObj.sum = OCObj.add(2,3)"];

我們可以定義自己的異常捕獲,可以把context,異常block改為自己的:

self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) { [JSContext currentContext].exception = exception; NSLog(@"exception:%@",exception); };

內存管理

現在來說說內存管理的注意點,OC使用的ARC,JS使用的是垃圾回收機制,并且所有的引用是都強引用,不過JS的循環引用,垃圾回收會幫它們打破。JavaScriptCore里面提供的API,正常情況下,OC和JS對象之間內存管理都無需我們去關心。不過還是有幾個注意點需要我們去留意下。

1、不要在block里面直接使用context,或者使用外部的JSValue對象。

//錯誤代碼:self.context[@"block"] = ^(){ JSValue *value = [JSValue valueWithObject:@"aaa" inContext:self.context];};

這個代碼,不用自己看了,編譯器都會提示你的。這個block里面使用self,很容易就看出來了。

//一個比較隱蔽的 JSValue *value = [JSValue valueWithObject:@"ssss" inContext:self.context]; self.context[@"log"] = ^(){ NSLog(@"%@",value); };

這個是block里面使用了外部的value,value對context和它管理的JS對象都是強引用。這個value被block所捕獲,這邊同樣也會內存泄露,context是銷毀不掉的。

//正確的做法,str對象是JS那邊傳遞過來。self.context[@"log"] = ^(NSString *str){ NSLog(@"%@",str); };

2、OC對象不要用屬性直接保存JSValue對象,因為這樣太容易循環引用了。

看個demo,把上面的示例改下:

//定義一個JSExport protocol@protocol JSExportTest <JSExport>//用來保存JS的對象@property (nonatomic, strong) JSvalue *jsValue;@end//建一個對象去實現這個協議:@interface JSProtocolObj : NSObject<JSExportTest>@end@implementation JSProtocolObj@synthesize jsValue = _jsValue;@end//在VC中進行測試@interface ViewController () <JSExportTest>@property (nonatomic, strong) JSProtocolObj *obj;@property (nonatomic, strong) JSContext *context;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; //創建context self.context = [[JSContext alloc] init]; //設置異常處理 self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) { [JSContext currentContext].exception = exception; NSLog(@"exception:%@",exception); }; //加載JS代碼到context中 [self.context evaluateScript: @"function callback (){}; function setObj(obj) { this.obj = obj; obj.jsValue=callback;}"]; //調用JS方法 [self.context[@"setObj"] callWithArguments:@[self.obj]]; }

上面的例子很簡單,調用JS方法,進行賦值,JS對象保留了傳進來的obj,最后,JS將自己的回調callback賦值給了obj,方便obj下次回調給JS;由于JS那邊保存了obj,而且obj這邊也保留了JS的回調。這樣就形成了循環引用。

怎么解決這個問題?我們只需要打破obj對JSValue對象的引用即可。當然,不是我們OC中的weak。而是之前說的內存管理輔助對象JSManagedValue

JSManagedValue 本身就是我們需要的弱引用。用官方的話來說叫garbage collection weak reference。但是它幫助我們持有JSValue對象必須同時滿足一下兩個條件(不翻譯了,翻譯了怪怪的!):

The JSManagedValue's JavaScript value is reachable from JavaScript

The owner of the managed reference is reachable in Objective-C. Manually adding or removing the managed reference in the JSVirtualMachine determines reachability.

意思很簡單,JSManagedValue 幫助我們保存JSValue,那里面保存的JS對象必須在JS中存在,同時 JSManagedValue 的owner在OC中也存在。我們可以通過它提供的兩個方法``` + (JSManagedValue)managedValueWithValue:(JSValue )value;

(JSManagedValue )managedValueWithValue:(JSValue )value andOwner:(id)owner創建JSManagedValue對象。通過JSVirtualMachine的方法- (void)addManagedReference:(id)object withOwner:(id)owner來建立這個弱引用關系。通過- (void)removeManagedReference:(id)object withOwner:(id)owner``` 來手動移除他們之間的聯系。

把剛剛的代碼改下:

//定義一個JSExport protocol@protocol JSExportTest <JSExport>//用來保存JS的對象@property (nonatomic, strong) JSValue *jsValue;@end//建一個對象去實現這個協議:@interface JSProtocolObj : NSObject<JSExportTest>//添加一個JSManagedValue用來保存JSValue@property (nonatomic, strong) JSManagedValue *managedValue;@end@implementation JSProtocolObj@synthesize jsValue = _jsValue;//重寫setter方法- (void)setJsValue:(JSValue *)jsValue{ _managedValue = [JSManagedValue managedValueWithValue:jsValue]; [[[JSContext currentContext] virtualMachine] addManagedReference:_managedValue withOwner:self];}@end//在VC中進行測試@interface ViewController () <JSExportTest>@property (nonatomic, strong) JSProtocolObj *obj;@property (nonatomic, strong) JSContext *context;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; //創建context self.context = [[JSContext alloc] init]; //設置異常處理 self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) { [JSContext currentContext].exception = exception; NSLog(@"exception:%@",exception); }; //加載JS代碼到context中 [self.context evaluateScript: @"function callback (){}; function setObj(obj) { this.obj = obj; obj.jsValue=callback; }"]; //調用JS方法 [self.context[@"setObj"] callWithArguments:@[self.obj]]; }

注:以上代碼只是為了突出用 JSManagedValue來保存 JSValue,所以重寫了 setter 方法。實際不會寫這么搓的姿勢。。。應該根據回調方法傳進來參數,進行保存 JSValue

3、不要在不同的 JSVirtualMachine 之間進行傳遞JS對象。

一個 JSVirtualMachine可以運行多個context,由于都是在同一個堆內存和同一個垃圾回收下,所以相互之間傳值是沒問題的。但是如果在不同的JSVirtualMachine傳值,垃圾回收就不知道他們之間的關系了,可能會引起異常。

線程

JavaScriptCore 線程是安全的,每個context運行的時候通過lock關聯的JSVirtualMachine。如果要進行并發操作,可以創建多個JSVirtualMachine實例進行操作。

與UIWebView的操作

通過上面的demo,應該差不多了解OC如何和JS進行通信。下面我們看看如何對 UIWebView 進行操作,我們不再通過URL攔截,我們直接取UIWebViewcontext,然后進行對JS操作。

UIWebView的finish的回調中進行獲取

- (void)webViewDidFinishLoad:(UIWebView *)webView{ self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];}

上面用了私有屬性,可能會被蘋果給拒了。這邊要注意的是每個頁面加載完都是一個新的context,但是都是同一個JSVirtualMachine。如果JS調用OC方法進行操作UI的時候,請注意線程是不是主線程。

參考:

http://blog.csdn.net/lizhongfu2013/article/details/9232129

https://developer.apple.com/videos/play/wwdc2013-615/


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 浪卡子县| 汉源县| 汕头市| 山丹县| 扎鲁特旗| 南雄市| 黎川县| 浦东新区| 阿图什市| 大洼县| 鄂伦春自治旗| 循化| 汝南县| 汕尾市| 南召县| 宁国市| 鲁甸县| 浠水县| 襄汾县| 安吉县| 德阳市| 乌鲁木齐县| 泾川县| 客服| 武山县| 耒阳市| 旬阳县| 堆龙德庆县| 清徐县| 财经| 扶风县| 莆田市| 伽师县| 永靖县| 靖江市| 保靖县| 和田县| 山西省| 卢龙县| 山西省| 独山县|