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方式来实现,具体过程可能使用到信号量的概念,这里再也不赘述,又须要能够留言。