iOS 使用Ajax实现与Javascript同步异步交互
实现原理:
1.Ajax可以实现同步与异步请求
2.UIWebView可以实现Ajax跨域请求
3.NSURLProtocol可以拦截Ajax请求
4.NSURLProtocol可以实现模拟响应结果
需要解决的问题:
1.实现NSURLProtocol拦截Ajax请求
2.实现Ajax跨域,解决Ajax预检请求问题
3.实现NSURLProtocol返回响应
对于上述问题,我们定义自己的NSURLProtocol
#import <Foundation/Foundation.h>
@interface MyURLProtocol : NSURLProtocol
@end
代码实现
我们这里指定schema为 oschina://
对于其中可能遇到预检请求问题,请参阅(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注册一下才行,注意:多次注册也是可以的,不会造成多次拦截。
[NSURLProtocol registerClass:[UyURLProtocol class]];
通过这种方式,我们可以实现iOS端数据处理,在Javascript端我们需要实现2类调用,同步和异步
//异步请求
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调用
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方式来实现,具体过程可能使用到信号量的概念,这里不再赘述,又需要可以留言。