iOS 使用Ajax實現與Javascript同步異步交互javascript
實現原理:java
1.Ajax能夠實現同步與異步請求 2.UIWebView能夠實現Ajax跨域請求 3.NSURLProtocol能夠攔截Ajax請求 4.NSURLProtocol能夠實現模擬響應結果
須要解決的問題:ajax
1.實現NSURLProtocol攔截Ajax請求 2.實現Ajax跨域,解決Ajax預檢請求問題 3.實現NSURLProtocol返回響應
對於上述問題,咱們定義本身的NSURLProtocol跨域
#import <Foundation/Foundation.h> @interface MyURLProtocol : NSURLProtocol @end
代碼實現app
咱們這裏指定schema爲 oschina://dom
對於其中可能遇到預檢請求問題,請參閱(Ajax跨域(CROS)請求中的Preflighted Requests)異步
@interface MyURLProtocol() @property(nomatic,strong) NSMutableDictionary * reponseHeader; @end @implementation MyURLProtocol //複寫canInitWithRequest,決定是否攔截請求 +(BOOL)canInitWithRequest:(NSURLRequest *)request{ //這裏實現對 oschina://syncHttpRequest和oschina://asyncHttpRequest攔截 if(request.URL.scheme!=nil && [[request.URL.scheme lowercaseString] isEqualToString:@"oschina"]) { if([request.URL.host isEqualToString:@"syncHttpRequest"] || [request.URL.host isEqualToString:@"asyncHttpRequest"]) { if(_reponseHeader==nil) { _reponseHeader = @{ @"Access-Control-Allow-Credentials":@"true", @"Access-Control-Allow-Origin":@"*", @"Access-Control-Expose-Headers":@"jsStr", @"Access-Control-Allow-Methods":@"GET,POST,PUT,OPTIONS,HEAD", @"Access-Control-Allow-Headers":@"Origin,jsStr,Content-Type,X-Request-Width", @"Access-Control-Max-Age":@"10", @"Cache-Control":@"no-cache,private", @"Pragma":@"no-cache,no-store", @"Expires":@"0", @"Connection":@"Close" }; } return YES; } } //若是不攔截,則返回NO return NO; } //複寫 canonicalRequestForRequest ,加工請求,這裏咱們能夠不加工,直接使用req + (NSURLRequest*) canonicalRequestForRequest:(NSURLRequest *)req { return req; } //複寫startLoading,並處理預檢請求 - (void) startLoading{ //處理跨域操做,若是是options操做。若是是跨域訪問會發送一個options請求,須要response一個權限纔會繼續走head請求 //此外,ajax發送的數據沒法被接收,須要一個自定義請求頭X-Javascript-Header,用來javascript->iOS傳遞數據 if ([self.request.HTTPMethod isEqualToString:@"OPTIONS"]) { NSDictionary * fields_resp = _reponseHeader; //響應ajax預檢請求 NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:[self.request URL] statusCode:200 HTTPVersion:@"1.1" headerFields:fields_resp]; [[self client] URLProtocol: self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [[self client] URLProtocol:self didLoadData:[NSData data]]; [[self client] URLProtocolDidFinishLoading:self]; }else{ //實現對ajax正式請求的解析與響應 [self doRequestToResponse]; } } -(void) doRequestToResponse { NSDictionary *dic = [self.request.allHTTPHeaderFields copy]; NSString *jsStr = dic[@"X-Javascript-Header"]; //獲取響應頭數據 NSString * userAgentInStorage = [[NSUserDefaults standardUserDefaults] stringForKey:@"UserAgent"]; NSString * userAgent = dic[@"User-Agent"]; //必要時保存user-Agent if([NSString isEmptyOrNil:userAgentInStorage] && ![NSString isEmptyOrNil:userAgent]) { [[NSUserDefaults standardUserDefaults] setObject:userAgent forKey:@"UserAgent"]; [[NSUserDefaults standardUserDefaults] synchronize]; } if([NSString isEmptyOrNil:jsStr]) { [self sendRequestErrorToClient]; return; } if([jsStr hasPrefix:@"@"]) { jsStr = [jsStr stringByReplacingOccurrencesOfString:@"@" withString:@""]; } NSData *data = [GTMBase64 decodeString:jsStr]; jsStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; // 轉換 jsStr = [jsStr stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]; jsStr = [jsStr stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"]; jsStr = [jsStr stringByReplacingOccurrencesOfString:@"\t" withString:@"\\t"]; jsStr = [jsStr stringByReplacingOccurrencesOfString:@"\0" withString:@"\\0"]; NSMutableDictionary *jsDic = [jsStr mutableObjectFromJSONString]; if(jsDic==nil) { NSString * tempJsStr = [jsStr stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]; jsDic = [tempJsStr mutableObjectFromJSONString]; } if(jsDic==nil) { [UMJS showToast:@"參數解析失敗!"]; return; } NSString *serviceName= jsDic[@"service"]; NSString *methodName = jsDic[@"method"]; id params = jsDic["params"]; [------------------處理響應的請結果------------------------] //1.開始處理,略 //發送相應數據到Ajax端,假定結果爲result NSString * response = [@{@"result":result,@"msg":@"Hello World",@"code":@1} JSONString]; [self sendResponseToClient:response]; [------------------處理響應的請結果------------------------] } -(void) sendResponseToClient:(NSString *) str { NSData *repData = [str dataUsingEncoding:NSUTF8StringEncoding]; NSMutableDictionary *respHeader = [NSMutableDictionary dictionaryWithDictionary:fields_resp]; respHeader[@"Content-Length"] = [NSString stringWithFormat:@"%ld",repData.length]; NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:[self.request URL] statusCode:200 HTTPVersion:@"1.1" headerFields:respHeader]; [[self client] URLProtocol: self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [[self client] URLProtocol:self didLoadData:repData]; [[self client] URLProtocolDidFinishLoading:self]; } //發送錯誤請求信息 -(void) sendRequestErrorToClient { NSData *data = [@"" dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary * fields_resp =_reponseHeader; NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:[self.request URL] statusCode:400 HTTPVersion:@"1.1" headerFields:fields_resp]; [[self client] URLProtocol: self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [[self client] URLProtocol:self didLoadData:data]; [[self client] URLProtocolDidFinishLoading:self]; } - (void) stopLoading{ // NSLog(@"stopLoading"); } //處理跳轉 (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response { if ([response isKindOfClass:[NSHTTPURLResponse class]]) { NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response; if ([HTTPResponse statusCode] == 301 || [HTTPResponse statusCode] == 302) { NSMutableURLRequest *mutableRequest = [request mutableCopy]; [mutableRequest setURL:[NSURL URLWithString:[[HTTPResponse allHeaderFields] objectForKey:@」Location」]]]; request = [mutableRequest copy]; [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response]; } } return request; }
自定義結束以後,咱們須要在AppDetegate或者UIViewController註冊一下才行,注意:屢次註冊也是能夠的,不會形成屢次攔截。async
[NSURLProtocol registerClass:[UyURLProtocol class]];
經過這種方式,咱們能夠實現iOS端數據處理,在Javascript端咱們須要實現2類調用,同步和異步this
//異步請求 function sendAsyncAjax(xJavascript, onload, onerror) { var xhr, results, url; url = 'oschina://asyncHttpRequest?rnd='+Math.random(); xhr = new XMLHttpRequest(); try{ xhr.open('POST', url, true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.setRequestHeader("Cache-Control", "no-cache,private"); xhr.setRequestHeader("Pragma", "no-cache,no-store"); xhr.setRequestHeader("User-Agent", navigator.userAgent); //經過X-Javascript-Header發送數據到iOS,注意,使用第三方Base64 encode xhr.setRequestHeader("X-Javascript-Header", Base64Util.encode(xJavascript)); xhr.onload = function (e) { if (this.status === 200) { results = JSON.parse(xhr.responseText); onload(results); }else{ onload({'e':e}); } }; xhr.onerror = function (e) { onerror({'e':e}); }; }catch(exception){ console.error(exception); }finally{ try{ xhr.send(null); }catch(exception2){} } } //同步請求 function sendSyncAjax(xJavascript) { var xhr, results, url; url = 'oschina://syncHttpRequest?rnd='+Math.random(); xhr = new XMLHttpRequest(); try{ xhr.open('POST', url, true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.setRequestHeader("Cache-Control", "no-cache,private"); xhr.setRequestHeader("Pragma", "no-cache,no-store"); xhr.setRequestHeader("User-Agent", navigator.userAgent); //經過X-Javascript-Header發送數據到iOS,注意,使用第三方Base64 encode xhr.setRequestHeader("X-Javascript-Header", Base64Util.encode(xJavascript)); }catch(exception){ console.error(exception); }finally{ try{ xhr.send(null); }catch(exception2){} } if (xhr.readyState == 4) { if (xhr.status == 200) { return xhr.execXhr.responseText; } else { return xhr.execXhr.responseText; } } return {}; }
而後咱們經過javascript調用url
var script = JSON.stringify({service:"NetworkUtil",method:"getNetworkInfo",params:{}}; sendAsyncAjax(script ,function(result){ }, function(error){ }); 或者 var script = JSON.stringify({service:"NetworkUtil",method:"getNetworkInfo",params:{}}; var result = sendSyncAjax(script);
通常來講NetworkUtil能夠調用的方法必須是類方法
@implementation NetworkUtil +(NSString * )getNetworkInfo { NSString * result = [略]; return result; } @end
咱們這裏實現了iOS平臺上同步異步的方法,Android平臺有系統提供的javascriptInterface接口,固然,咱們想要實現和本篇iOS相似的方式,咱們可使用ServerSocket方式來實現,具體過程可能使用到信號量的概念,這裏再也不贅述,又須要能夠留言。