怎样完成 javascript “同步”挪用 app 代码

缘起

在 App 夹杂开辟中,app 层向 js 层供应接口有两种体式格局,一种是同步接口,一种是异步接口(不清楚什么是同步的请看这里的议论)。为了保证 web 流通,大部分时刻,我们应当运用异步接口,然则某些情况下,我们能够更需要同步接口。同步接口的优点在于,起首 js 能够经由过程返回值获得实行效果;其次,在夹杂式开辟中,app 层导出的某些 api 根据语义就应当是同步的,否则会很新鲜——一个能够在 for 轮回中运用的,实行非常快的接口,比方读写某个设置项,设想成异步会很新鲜。

那末怎样向 js 层导出同步接口呢?

题目的中心

我们晓得,在 Android 框架中,经由过程 WebView.addJavascriptInterface() 这个函数,能够将 java 接口导出到 js 层,而且如许导出的接口是同步接口。然则在 iOS 的 Cocoa 框架中,想导出同步接口却不轻易,究其原因,是由于 UIWebView 和 WKWebView 没有 addJavascriptInterface 如许的功用。同时,Android 这个功用爆出过安全漏洞,那末,我们有没有别的体式格局完成同步挪用呢?我们以 iOS UIWebView 为例供应一种完成,WKWebView 和 Android 也能够参考。

为了找到题目的症结,我们看一下 iOS 中完成 js 挪用 app 的通行要领:

起首,自定义 UIWebViewDelegate,在函数 shouldStartLoadWithRequest:navigationType: 中阻拦要求。

- (BOOL) webView:(UIWebView* _Nonnull)webView
    shouldStartLoadWithRequest:(NSURLRequest* _Nonnull)request
                navigationType:(UIWebViewNavigationType)navigationType {
    if ([request.HTTPMethod compare:@"GET" options:NSCaseInsensitiveSearch] != NSOrderedSame) {
        // 不处置惩罚非 get 要求
        return YES;
    }
     
    NSURL* url = request.URL;
 
    if ([url.scheme isEqualToString:@'YourCustomProtocol']) {
        return [self onMyRequest:request];
    }
 
    return YES;
}

这类做法实质上就是将函数挪用敕令转化为 url,经由过程要求的体式格局关照 app 层,个中 onMyRequest: 是自定义的 request 相应函数。为了发送要求,js 层要竖立一个隐蔽的 iframe 元素,每次发送要求时修正 iframe 元素的 src 属性,app 即可阻拦到相应要求。

/**
 * js 向 native 通报音讯
 * @method js_sendMessageToNativeAsync
 * @memberof JSToNativeIOSPolyfill
 * @public
 * @param str {String} 音讯字符串,由 HybridMessage 转换而来
 */
JSToNativeIOSPolyfill.prototype.js_sendMessageToNativeAsync = function (str) {
    if (!this.ifr_) {
        this._prepareIfr();
    }
 
    this.ifr_.src = 'YourCustomProtocol://__message_send__?msg=' + encodeURIComponent(str); 
}

当 app 实行完 js 挪用的功用,实行效果没法直接返回,为了返回效果,广泛采用回调函数体式格局——js 层纪录一个 callback,app 经由过程 UIWebView 的 stringByEvaluatingJavaScriptFromString 函数挪用这个 callback(相似 jsonp 的机制)。

注重,如许封装的接口,自然是异步接口。由于 js_sendMessageToNativeAsync 这个函数会马上返回,不会比及实行效果发回来。

所以,我们要想方法把 js 代码“壅塞”住。

同步 XMLHttpRequest

请回想一下,js 中是用什么要领能把 UI 线程代码“壅塞”住,同时又不跑满 CPU?

var async = false;
var url = 'http://baidu.com';
var method = 'GET';
var req = new XMLHttpRequest();

req.open(method, url, async);
req.send(null);

“同步”ajax(实在没这个词,ajax 内在异步的意义)能够!在 baidu 的相应没返回之前,这段代码会一向壅塞。一般来说同步要求是不允许运用的,有致使 UI 卡顿的风险。然则在这里由于我们并不会真的去远端要求内容,所以无妨一用。

至此完成体式格局已比较清楚了,梳理一下思绪:

  • 运用同步 XMLHttpRequest 合营特别组织的 URL 关照 app层。

  • app 层阻拦要求实行功用,将效果作为 Response 返回。

  • XMLHttpRequest.send() 返回,经由过程 status 和 responseText 获得效果。

要求阻拦

那末,怎样阻拦要求呢?人人晓得,UIWebViewDelegate 是不会阻拦 XMLHttpRequest 要求的,然则 iOS 最少给了我们两个位置阻拦这类要求——NSURLCache 和 NSURLProtocol。

一、NSURLCache

NSURLCache 是 iOS 中用来完成自定义缓存的类,当你创建了自定义的 NSURLCache 子类对象,并将其设置为全局缓存管理器,一切的要求都邑先到这里搜检有没有缓存(假如你没禁掉缓存的话)。我们能够借助这个性子阻拦到接口挪用要求,实行并返回数据。

- (NSCachedURLResponse*) cachedResponseForRequest:(NSURLRequest *)request {
    if ([request.HTTPMethod compare:@"GET" options:NSCaseInsensitiveSearch] != NSOrderedSame) {
        // 只对 get 要求做自定义处置惩罚
        return [super cachedResponseForRequest:request];
    }
 
    NSURL* url = request.URL;
    NSString* path = url.path;
    NSString* query = url.query;
 
    if (path == nil || query == nil) {
        return [super cachedResponseForRequest:request];
    }
     
    LOGF(@"url = %@, path = %@, query = %@", url, path, query);
 
    if ([path isEqualToString:@"__env_get__"]) {
        // 读环境变量
        return [self getEnvValueByURL:url]; //*
    } else if ([path isEqualToString:@"__env_set__"]) {
        // 写环境变量
        return [self setEnvValueByURL:url];
    }
 
    return [super cachedResponseForRequest:request];
}

注重解释有 * 号的一行,等于实行 app 接口,返回效果。这里的效果是一个 NSCachedResponse 对象,就不赘述了。

二、NSURLProtocol

NSURLProtocol 是 Cocoa 中处置惩罚自定义 scheme 的类。这个类的运用更庞杂一些,但它比拟 NSURLCache 的优点是,能够运用自定义协定 scheme,防备 URL 和实在 URL 殽杂,而且自定义 scheme 在异步接口机制中也有运用,当你的 app 中同时存在两种机制时,能够运用 scheme 使得代码更清楚。

+ (BOOL) canInitWithRequest:(NSURLRequest* _Nonnull)request {
    //只处置惩罚特定 scheme
    NSString* scheme = [[request URL] scheme];
    if ([scheme compare:@"YourCustomProtocol"] == NSOrderedSame) {
        return YES;
    }
 
    return NO;
}
 
+ (NSURLRequest* _Nonnull) canonicalRequestForRequest:(NSURLRequest* _Nonnull)request {
    return request;
}
 
- (BirdyURLProtocol* _Nonnull) initWithRequest:(NSURLRequest* _Nonnull)request
                                cachedResponse:(NSCachedURLResponse* _Nullable)cachedResponse
                                        client:(id<NSURLProtocolClient> _Nullable)client {
    self = [super initWithRequest:request cachedResponse:cachedResponse client:client];
    return self;
}
 
- (void) startLoading {
    NSURLRequest* connectionRequest = [self.request copy];
    NSCachedURLResponse* cachedResponse = [[YourURLCache sharedURLCache] cachedResponseForRequest:connectionRequest];
     
    if (cachedResponse != nil) {
        NSURLResponse* response = cachedResponse.response;
        NSData* data = cachedResponse.data;
 
        [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
        [[self client] URLProtocol:self didLoadData:data];
        [[self client] URLProtocolDidFinishLoading:self];
    } else {
        NSError* error = [NSError errorWithDomain:@"Bad Hybrid Request"
                                             code:400
                                         userInfo:nil];
 
        [[self client] URLProtocol:self didFailWithError:error];
    }
}

注重,以上代码我借用了前面 YourURLCache 的完成,现实这是没必要的。只是为了轻易演示。

总结

以上就是完成 javascript “同步”挪用 app 代码的要领,个中心就是运用同步 XMLHttpRequest 壅塞代码,以及 app 层阻拦要求。事实上,这个要领和操作体系以及开辟框架无关,在 Android 体系中,也能够完成如许的机制。

    原文作者:Y3G
    原文地址: https://segmentfault.com/a/1190000005742513
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞