UIWebView與WKWebView,JavaScript的與OC交互,餅干管理看我就夠(轉載)

?iOS中的UIWebView與WKWebView,JavaScript的與OC交互,餅干管理看我就夠(上)

前言

iOS的開發中,用來顯示一個HTML頁,H5頁,經常會用的一個控件是的WebView。說到的WebView,你知道多少呢?是簡單的展示,還是要和OC交互實現比較復雜的功能呢?本文將為您介紹的iOS中的WebView中,并且由淺到深,一步步帶你了解并掌握的WebView的用法,JavaScript的與目標的交互,以及餅干的管理,JS的調試等。

文章因涉及到的內容較多,因此拆分成以下幾部分:

的iOS中的UIWebView與WKWebView,JavaScript的與OC交互,餅干管理看我就夠(上)

iOS中UIWebView與WKWebView,JavaScript與OC交互,Cookie管理看我就夠(中)(已發布??)

iOS中UIWebView與WKWebView,JavaScript與OC交互,Cookie管理看我就夠(下)(已發布??)

關于文中提到的一些內容,這里我準備了個Demo,有需要的小伙可以下載。

本文目錄

前言

UIWebView的

UIWebView的基本用法

一個UIWebView中的JavaScript與目標的交互

UIWebView OC調用JS

stringByEvaluatingJavaScriptFromString:

JavaScriptCore(iOS 7.0 +)

UIWebView JS調用OC

自定義網址方案(受保護網址)

JavaScriptCore(iOS 7.0 +)

UIWebView中的Cookie的管理

餅干簡介

曲奇管理

未完待續

UIWebView的

UIWebView的基本用法

首先要介紹的就是我們的老朋友UIWebView。相信對大多數小伙伴兒而言,UIWebView狀語從句:UILabel一樣,都是最早接觸的控件了,其實UIWebView用法英語諺語比較簡單(功能基本能滿足需求),簡單的創建,并且調用

- (void)loadRequest:(NSURLRequest*)request;- (void)loadHTMLString:(NSString*)string baseURL:(nullableNSURL*)baseURL;- (void)loadData:(NSData*)data MIMEType:(NSString*)MIMEType textEncodingName:(NSString*)textEncodingName baseURL:(NSURL*)baseURL;

這些方法,加載就可以了。

當然,如果需要監聽頁面加載的結果,或者需要判斷是否允許打開某個URL,需要那設置UIWebView的delegate,只代理需要遵循協議,并且在代理中實現下面的這些可選方法就可以:

__TVOS_PROHIBITED@protocolUIWebViewDelegate@optional- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType;- (void)webViewDidStartLoad:(UIWebView*)webView;- (void)webViewDidFinishLoad:(UIWebView*)webView;- (void)webView:(UIWebView*)webView didFailLoadWithError:(nullableNSError*)error;@end

一個UIWebView中的JavaScript與目標的交互

這里不詳細討論一些很好的第三方實現,比如WebViewJavascriptBridge單純的講講native端JS與OC的交互實現方式,讀完了下面的部分,相信你也會實現一個簡單的bridge了。

UIWebView OC調用JS

1. stringByEvaluatingJavaScriptFromString:

最常用的方法,很簡單,調用只要- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;就可以了,如:

self.navigationItem.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];

雖然比較方便,但是缺點也有:

該方法不能判斷調用了一個JS方法之后,是否發生了錯誤。當錯誤發生時,返回值為零,而當調用一個方法本身沒有返回值時,返回值也為零,所以無法判斷是否調用成功了。

返回值類型為nullable NSString *,就意味著當調用的JS方法有返回值時,都以字符串返回,不夠靈活。當返回值是一個JS的陣列時,還需要解析字符串,比較麻煩。

對于上述缺點,可以通過使用JavaScriptCore(iOS 7.0 +)來解決。

2. JavaScriptCore(iOS 7.0 +)

想必大家不會陌生吧,弄前些日子的沸沸揚揚的JSPatch被禁事件中,最核心的就是它了。因為JavaScriptCore的JS到OC的映射,可以替換各種JS方法成OC方法,其所以動態性(配合運行時的不安全性)也就成為了JSPatch被蘋果禁掉的最主要原因。講這里下UIWebView通過JavaScriptCore來實現OC-> JS。

其實WebKit的都有一個內嵌的JS環境,一般我們在頁面加載完成之后,獲取JS上下文,通過然后JSContext的evaluateScript:方法來獲取報道查看值。該因為得到方法的的英文一個JSValue對象,所以支持的JavaScript的數組,數字,字符串,對象等數據類型。

- (void)webViewDidFinishLoad:(UIWebView*)webView{//更新標題,這是上面的講過的方法//self.navigationItem.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];//獲取該UIWebView的javascript上下文JSContext *jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];//這也是一種獲取標題的方法。JSValue *value = [self.jsContext evaluateScript:@"document.title"];//更新標題self.navigationItem.title = value.toString;}

方法該解決了stringByEvaluatingJavaScriptFromString:報道查看值只是NSString的問題。

那么如果我執行了一個不存在的方法,比如

[self.jsContext evaluateScript:@"document.titlexxxx"];

那么必然會報錯,報錯了,可以通過@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);,設置該塊來獲取異常。

//在調用前,設置異常回調[self.jsContext setExceptionHandler:^(JSContext *context, JSValue *exception){NSLog(@"%@",exception);}];//執行方法JSValue *value = [self.jsContext evaluateScript:@"document.titlexxxx"];

該方法,很好也。解決的了stringByEvaluatingJavaScriptFromString:調用JS方法后,出現錯誤卻捕獲不到的缺點。

UIWebView JS調用OC

1.自定義URL方案(被攔截的URL)

比如darkangel://。方法是在html或者js中,點擊某個按鈕觸發事件時,跳轉到自定義URL Scheme構造的鏈接,而Objective-C中捕獲該鏈接,從中解析必要的參數,實現JS到OC的一次交互比如頁面中一個一個標簽,鏈接如下:

短信驗證登錄

而在目標C中,遵循只要了UIWebViewDelegate協議,那么每次打開一個鏈接之前,都會觸發方法

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType;

在該方法中,捕獲該鏈接,并且返回NO(阻止本次跳轉),從而執行對應的OC方法。

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType{//標準的URL包含scheme、host、port、path、query、fragment等NSURL*URL = request.URL;if([URL.scheme isEqualToString:@"darkangel"]) {if([URL.host isEqualToString:@"smsLogin"]) {NSLog(@"短信驗證碼登錄,參數為 %@", URL.query);returnNO;}}returnYES;}

當用戶點擊短信驗證登錄時,控制臺會輸出短信驗證碼登錄,參數為 username=12323123&code=892845。參數可以是一個json格式并且URLEncode過的字符串,這樣就可以實現復雜參數的傳遞(比如WebViewJavascriptBridge)。

優點:泛用性強,可以配合H5實現頁面動態化比如頁面中一個活動鏈接到活動詳情頁,當本地尚未開發完畢時,鏈接可以是一個H5鏈接,等到本地開發完畢時,可以通過該方法跳轉到本地頁面,實現頁面動態化。且該方案適用于安卓和iOS版,泛用性很強。

缺點:無法直接獲取本次交互的返回值,比較適合單向傳參,且不關心回調的情景,比如H5頁面跳轉到本地頁面等。

其實,WebViewJavascriptBridge使用的方案就是攔截URL,為了解決無法直接獲取返回值的缺點,它采用了將一個名為callback的function作為參數,通過一些封裝,傳遞到OC(js-> oc傳遞參數和callbackId),然后在OC端執行完畢,再通過block來回調回調(oc-?> js,傳遞返回值參數),實現異步獲取返回值,比如在js端調用

//JS調用OC的分享方法(當然需要OC提前注冊)share為方法名,shareData為參數,后面的為回調functionWebViewJavascriptBridge.callHandler('share', shareData,function(response){//OC端通過block回調分享成功或者失敗的結果alert(response);});

具體的可以看下它的源碼,還是很值得學習的。

2. JavaScriptCore(iOS 7.0 +)

除了攔截網址的方法,還可以利用上面提到的JavaScriptCore,它十分強大,強大在哪里呢?下面我們來一探究竟。

當然,還是需要在頁面加載完成時,先獲取JS上下文。獲取到之后,我們就可以進行強大的方法映射了。

比如JS中我定義了一個分享的方法

functionshare(title, imgUrl, link){//這里需要OC實現}

在OC中實現如下

- (void)webViewDidFinishLoad:(UIWebView*)webView{//將js的function映射到OC的方法[selfconvertJSFunctionsToOCMethods];}- (void)convertJSFunctionsToOCMethods{//獲取該UIWebview的javascript上下文//self持有jsContext//@property (nonatomic, strong) JSContext *jsContext;self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];//js調用oc//其中share就是js的方法名稱,賦給是一個block 里面是oc代碼//此方法最終將打印出所有接收到的參數,js參數是不固定的self.jsContext[@"share"] = ^() {NSArray*args = [JSContext currentArguments];//獲取到share里的所有參數//args中的元素是JSValue,需要轉成OC的對象NSMutableArray*messages = [NSMutableArrayarray];for(JSValue *objinargs) {[messages addObject:[obj toObject]];}NSLog(@"點擊分享js傳回的參數:\n%@", messages);};}

在HTML或者JS的某處,點擊一個標簽調用這個共享方法,并傳參,如

分享活動,領30元紅包

此時,如果用戶點擊了分享活動,領30元紅包這個標簽,那么在控制臺會打印出所有參數

上面的代碼實現了OC方法替換JS實現。它十分靈活,主要依賴這些API。

@interfaceJSContext(SubscriptSupport)/*!

@method

@abstract Get a particular property on the global object.

@result The JSValue for the global object's property.

*/- (JSValue *)objectForKeyedSubscript:(id)key;/*!

@method

@abstract Set a particular property on the global object.

*/- (void)setObject:(id)object forKeyedSubscript:(NSObject *)key;

self.jsContext[@"yourMethodName"] = your block;寫這樣不僅可以在有yourMethodName方法時替換該JS方法為OC實現,還會在該方法沒有時,添加方法。簡而言之,有則替換,無則添加

那如果我想寫一個有兩個參數,一個返回值的JS方法,OC應該怎么替換呢?

JS中

//該方法傳入兩個整數,求和,并返回結果functiontestAddMethod(a, b){//需要OC實現a+b,并返回returna + b;}//js調用console.log(testAddMethod(1,5));//output? 6

OC直接替換該方法

self.jsContext[@"testAddMethod"] = ^NSInteger(NSIntegera,NSIntegerb) {returna + b;};

那么當在JS調用

//js調用console.log(testAddMethod(1,5));//output? 6, 方法為 a + b

如果OC替換該方法為兩數相乘

self.jsContext[@"testAddMethod"] = ^NSInteger(NSIntegera,NSIntegerb) {returna * b;};

再次調用JS

console.log(testAddMethod(1,5));//output? 5,該方法變為了 a * b。

舉一反三,調用方法原實現,并且在原結果上乘以10。

//調用方法的本來實現,給原結果乘以10JSValue *value =self.jsContext[@"testAddMethod"];self.jsContext[@"testAddMethod"] = ^NSInteger(NSIntegera,NSIntegerb) {JSValue *resultValue = [value callWithArguments:[JSContext currentArguments]];returnresultValue.toInt32 *10;};

再次調用JS

console.log(testAddMethod(1,5));//output? 60,該方法變為了(a + b) * 10

上面的方法,都是同步函數,如果我想實現JS調用OC的方法,并且異步接收回調,那么該怎么做呢?比如H5中有一個分享按鈕,用戶點擊之后,調用本地分享(微信分享,微博分享等),在天然分享成功或者失敗時,回調H5頁面,告訴其分享結果,H5頁面刷新對應的UI,顯示分享成功或者失敗。

這個問題,需要對JS有一定了解。下面上的js代碼。

//聲明functionshare(shareData){vartitle = shareData.title;varimgUrl = shareData.imgUrl;varlink = shareData.link;varresult = shareData.result;//do something//這里模擬異步操作setTimeout(function(){//2s之后,回調true分享成功result(true);},2000);}//調用的時候需要這么寫share({title:"title",imgUrl:"http://img.dd.com/xxx.png",link: location.href,result:function(res){//函數作為參數console.log(res ?"success":"failure");}});

從封裝的角度上講,JS的share方法的參數的英文一個對象,該對象包含了幾個必要的字段,以及一個回調函數,這個回調函數有點像OC的block,調用者把一個function傳入一個function當作參數,在適當時候,內方法實現者調用該function,對實現調用者的異步回調那么如果此時OC來實現。share方法,該怎么做呢其實大概是這樣的?

//異步回調self.jsContext[@"share"] = ^(JSValue *shareData) {//首先這里要注意,回調的參數不能直接寫NSDictionary類型,為何呢?//仔細看,打印出的確實是一個NSDictionary,但是result字段對應的不是block而是一個NSDictionary? NSLog(@"%@", [shareData toObject]);//獲取shareData對象的result屬性,這個JSValue對應的其實是一個javascript的function。JSValue *resultFunction = [shareData valueForProperty:@"result"];//回調block,將js的function轉換為OC的blockvoid(^result)(BOOL) = ^(BOOLisSuccess) {[resultFunction callWithArguments:@[@(isSuccess)]];};//模擬異步回調dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"回調分享成功");result(YES);});};

其中一些坑,已經在代碼的注釋寫的比較清楚了,要這里注意JavaScript的function狀語從句:Objective-C的block的轉換。

從上面的一些探討和嘗試來看,證明足以JavaScriptCore的強大,這里不再展開,小伙伴們可以自行探索。

UIWebView中的Cookie的管理

餅干簡介

說到Cookie,或許有些小伙伴會比較陌生,有些小伙伴會比較熟悉。如果項目中,所有頁面都是純原生來實現的話,一般Cookie這個東西或許我們永遠也。不會接觸到。但是,這里還是要說一下Cookie,因為它真的很重要,由它產生的一些坑也很多。

Cookie在網絡利用的最多的地方,是用來記錄各種狀態。你比如在Safari中打開_百度,然后登陸自己的賬號,之后打開所有百度相關的頁面,都會是登陸狀態,而且當你關了電腦,下開機次再次打開Safari打開_百度,會發現還是登陸狀態,其實這個就利用了Cookie。Cookie中記錄了你百度賬號的一些信息,有效期等,也維持了跨域請求時登錄狀態的統計性。

看到可以Cookie的域各不相同,有效期也各不相同,一般.baidu.com這樣的域的Cookie就是為了跨域時,可以維持一些狀態。

那么在應用程序中,Cookie的最常用的就是維持登錄狀態了。一般本地端都有自己的一套完整登錄注冊邏輯,一般大部分頁面都是原生實現的。當然,也會有一些頁面是H5來實現的,雖然h5頁面在App中通過WebView加載或多或少都會有點性能問題,感覺不流暢或者體驗不好,但是它的靈活性是Native App無法比擬的。那么由此,便產生了一種需求,當本地端用戶是登錄狀態的,打開一個H5頁面,H5也要維持用戶的登錄狀態。

這個需求看似簡單,如何實現呢?一般的解決方案是本地保存登錄狀態的餅干,在打開H5頁面中,把餅干添加上,以此來維持登錄狀態。其實坑還是有很多的,比如用戶登錄或者退出了,H5頁面的登錄狀態也變了,需要刷新,什么時候刷新?WKWebView中Cookie丟失問題?簡單這里說下UIWebView的Cookie管理,后面的章節再介紹WKWebView。

曲奇管理

UIWebView的Cookie管理很簡單,一般不需要我們手動操作Cookie,所有因為Cookie都會被[NSHTTPCookieStorage sharedHTTPCookieStorage]這個單例管理,而且UIWebView會自動同步CookieStorage中的Cookie的,所以只要我們在本地端,正常登陸退出,H5在適當時候刷新,就可以正確的維持登錄狀態,不需要做多余的操作。

可能有一些情況下,我們需要在訪問某個鏈接時,一個添加固定Cookie用來做區分,就那么可以通過header來實現

NSMutableURLRequest*request = [NSMutableURLRequestrequestWithURL:[NSURLURLWithString:@"http://www.baidu.com"]];[request addValue:@"customCookieName=1314521;"forHTTPHeaderField:@"Set-Cookie"];[self.webView loadRequest:request];

也可以主動操作NSHTTPCookieStorage,添加一個自定義Cookie

NSHTTPCookie*cookie = [NSHTTPCookiecookieWithProperties:@{NSHTTPCookieName:@"customCookieName",NSHTTPCookieValue:@"1314521",NSHTTPCookieDomain:@".baidu.com",NSHTTPCookiePath:@"/"}];[[NSHTTPCookieStoragesharedHTTPCookieStorage] setCookie:cookie];//Cookie存在則覆蓋,不存在添加

還有一些常用的方法,如讀取所有Cookie

NSArray*cookies = [NSHTTPCookieStoragesharedHTTPCookieStorage].cookies;

Cookie轉換成HTTPHeaderFields,并到添加request的header中

//Cookies數組轉換為requestHeaderFieldsNSDictionary*requestHeaderFields = [NSHTTPCookierequestHeaderFieldsWithCookies:cookies];//設置請求頭request.allHTTPHeaderFields = requestHeaderFields;

來說本世紀的牛頓UIWebView的Cookie管理比較簡單,小伙伴們可以自己寫個演示測試一下,發揮你們的想象。

未完待續

關于UIWebView的介紹,使用以及UIWebView進行JS與OC的交互,Cookie的管理,就先簡單介紹到這里。如果有小伙伴對于WebViewJavascriptBridge比較感興趣,可以留言,根據留言我考慮一下寫一篇文章,分析它的詳細實現。

另外,將為后續介紹您WKWebView的用法英語諺語,一些OC與JS交互,餅干管理,在如何Safari中調試以及一些不為人知的坑等,敬請期待?

后續文章已發布:

iOS中UIWebView與WKWebView,JavaScript與OC交互,Cookie管理看我就夠(中)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容