iOS开发之OC与JS交互

2019-04-08  本文已影响0人  喝酸奶舔下盖

一、交互方式

iOS开发中与前端的交互:

1.拦截url(适用UIWebView和WKWebView)
2. JavaScriptCore(适用于UIWebView,iOS7+)
3.WKScriptMessageHandler(适用于WKWebView,iOS8+)
4. WebViewJavascriptBridge(三方框架,适用于UIWebView和WKWebView)

1.拦截url

(1)web调用原生:

a.和前端小伙伴协定好协议,如sjaction://authorization表示淘宝授权,sjaction://location表示获取定位。
b.实现UIWebView代理的shouldStartLoadWithRequest:navigationType:方法,在方法中对url进行拦截,如果是步骤a中定义好的协议则执行对应原生代码,返回false,否则返回true继续加载原url。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
    if ([request.URL.absoluteString hasPrefix:@"sjaction://authorization"]) {
        //启动相机
       return NO;
    }
    return YES;
}

前端代码:

<a href="sjaction://authorization">淘宝授权</a>

(2)原生调用js

若(1)中打开淘宝授权之后,需要把授权结果返回给web页,直接调用UIWebView的stringByEvaluatingJavaScriptFromString:方法,或者WKWebView的 evaluateJavaScript:completionHandler:方法。

[self.webView stringByEvaluatingJavaScriptFromString:@"authorization Result('success')"];

2. JavaScriptCore

拦截ulr方法中web调用原生只适合简单的调用,如果要传递参数,虽然也可以拼接在url上(如sjaction://authorization?xxx=aaa),但是需要我们自行对字符串进行分割解析,并且特殊字符(汉字...)需要编码。在iOS7系统提供了JavaScriptCore,可以更科学的实现js与原生的交互。

(1)js调用原生

a、新建类继承自NSObject(如DAKAWebViewJS)
b、定义宏JSExportAs
c、.h文件中声明一个代理并遵循JSExport,代理内的方法和js定义的方法名一致
d、.m文件中实现b代理中对应的方法,可以在方法内处理事件或通知代理

#define JSExportAs(PropertyName, Selector) \
@optional Selector __JS_EXPORT_AS__##PropertyName:(id)argument; @required Selector
#import "DakaBaseWebViewController.h"
#import <JavaScriptCore/JavaScriptCore.h>

NS_ASSUME_NONNULL_BEGIN

@protocol DAKAWebViewJSProtocol<JSExport>

JSExportAs(openShoppingCartDetail, - (void)openShoppingCartDetail:(NSString *)productId);


@end

@interface DAKAWebViewJS : NSObject<DAKAWebViewJSProtocol>

@property (weak, nonatomic) DakaBaseWebViewController *dakaWebVC;

- (void)openShoppingCartDetail:(NSString *)productId;

@end


@interface ShoppingCartListViewController : DakaBaseWebViewController

@end
@interface ShoppingCartListViewController ()<UIWebViewDelegate, DAKAWebViewJSProtocol>

@property (strong, nonatomic)DAKAWebViewJS *webViewJS;
@end

@implementation DAKAWebViewJS

- (void)openShoppingCartDetail:(id)productId {
//js需要调用oc的方法实现
}
@end

@implementation ShoppingCartListViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

}
@end

e、在UIWebView加载完成的代理中把DAKAWebViewJS实例对象类注入到JS中,在js中调用方法就会调用到原生DAKAWebViewJS实例对象中对应的方法了。

-(void)webViewDidFinishLoad:(UIWebView *)webView {
    JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    context[@"iOSNative"] = self.webViewJS;
}

(2)原生调用js

//alertTest为前端函数
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSString *textJS = @"alertTest('js中alert弹出的message')";
[context evaluateScript:textJS];

3. WKScriptMessageHandler

a.初始化WKWebView时,调用addScriptMessageHandler:name:方法,name为js中的方法名,如:authorization

- (void)customInitWKWebView{
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    configuration.userContentController = [[WKUserContentController alloc] init];
    [configuration.userContentController addScriptMessageHandler:self name:@"authorization"];

    WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
    webView.UIDelegate = self;
}

前端:

window.webkit.messageHandlers.authorization.postMessage() 

b.实现WKScriptMessageHandler代理方法,当js调用authorization方法时,会回调此代理方法:

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    if ([message.name isEqualToString:@"authorization"]) {
        //调用原生授权
     }
}

4.WebViewJavascriptBridge

一个三方框架,详细见官方文档:GitHub地址

二、NSURLProtocol拦截http请求(可以对前端数据进行处理)

NSURLProtocol 是苹果为我们提供的 URL Loading System 的一部分,这是一张从官方文档贴过来的图片:


NSURLProtocol.png

  官方文档对 NSURLProtocol 的描述是这样的:

An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports.

在每一个 HTTP 请求开始时,URL 加载系统创建一个合适的 NSURLProtocol 对象处理对应的 URL 请求,而我们需要做的就是写一个继承自 NSURLProtocol 的类,并通过 - registerClass: 方法注册我们的协议类,然后 URL 加载系统就会在请求发出时使用我们创建的协议对象对该请求进行处理。

这样,我们需要解决的核心问题就变成了如何使用 NSURLProtocol 来处理所有的网络请求,这里使用苹果官方文档中的 CustomHTTPProtocol 进行介绍。

在工程中创建继承NSURLProtocol的自定义DakaURLProtocol
.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

static NSString * const URLProtocolHandledKey =   @"URLProtocolHandledKey";

@interface DakaURLProtocol : NSURLProtocol<NSURLConnectionDelegate>

@property (nonatomic, strong) NSURLConnection *connection;

@end

NS_ASSUME_NONNULL_END

.m

#import "DakaURLProtocol.h"
#import "ShoppingCartManager.h"

@interface DakaURLProtocol()

@property (strong, nonatomic) NSMutableString *shoppingCartData;

@end

@implementation DakaURLProtocol

//This method determines whether this protocol can handle the given request.
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    //只处理http和https请求
    NSString *scheme = [[request URL] scheme];
    
    if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
          [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame)) {
        //看看是否已经处理过了,防止无限循环
        if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
            return NO;
        }
        // 可以做一些网络请求的需要的事情(如:带cookie等等)
        return YES;
    }
    return NO;
}
    
//This method returns a canonical version of the given request.
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}
    
//Compares two requests for equivalence with regard to caching.
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
    return [super requestIsCacheEquivalent:a toRequest:b];
}
    
//Starts protocol-specific loading of a request.
- (void)startLoading {
    if ([self.shoppingCartData length] == 0) {
        self.shoppingCartData = [NSMutableString string];
    }
    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
    //打标签,防止无限循环
    [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
    self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
    
}
    
//tops protocol-specific loading of a request.
- (void)stopLoading {
    [self.connection cancel];
}

#pragma mark - NSURLConnectionDelegate
/// 网络请求返回数据
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    NSHTTPURLResponse *httpresponse = (NSHTTPURLResponse *)response;
    if([httpresponse respondsToSelector:@selector(allHeaderFields)]){
        NSDictionary *di  = [httpresponse allHeaderFields];
        NSArray *keys = [di allKeys];
        for(int i=0; i<di.count;i++){
            NSString *key = [keys objectAtIndex:i];
            NSString *value=[di objectForKey:key];
            if([key rangeOfString:@"Set-Cookie"].location != NSNotFound)
            {
                NSLog(@"response_header_value -- %@",value);
                // 获取Session
            }
        }
    }
    
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}

- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.client URLProtocol:self didLoadData:data];
    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    // NSUTF8StringEncoding 中文编码
    if (str) {
        if ([connection.currentRequest.URL.path isEqualToString:@"/h5/mtop.trade.querybag/5.0"]) {
            self.shoppingCartData = [NSMutableString stringWithString:[self.shoppingCartData stringByAppendingString:str]];
        }
    }
}

- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
    [self.client URLProtocolDidFinishLoading:self];
    if (self.shoppingCartData && [self.shoppingCartData length] > 0) {
        self.shoppingCartData = [NSMutableString stringWithString:[self.shoppingCartData stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
        [self.shoppingCartData deleteCharactersInRange:NSMakeRange(0, [@"mtopjsonp1(" length])];
        [self.shoppingCartData deleteCharactersInRange:NSMakeRange([self.shoppingCartData length] - 1, 1)];
        
        NSData *jsonData = [[NSString stringWithString:self.shoppingCartData] dataUsingEncoding:NSUTF8StringEncoding];
        NSError *err;
        NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
                                                            options:NSJSONReadingMutableContainers
                                                              error:&err];
        if(err) {
            NSLog(@"json解析失败:%@",err);
        } else {
            [ShoppingCartManager shared].shoppingDic = dic[@"data"][@"data"];
            
        }
    }
    
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [self.client URLProtocol:self didFailWithError:error];
}

@end

需要注意的是 NSURLProtocol 只能拦截 UIURLConnection、NSURLSession 和 UIWebView 中的请求,对于 WKWebView 中发出的网络请求也无能为力,如果真的要拦截来自 WKWebView 中的请求,还是需要实现 WKWebView对应的 WKNavigationDelegate,并在代理方法中获取请求。 无论是 NSURLProtocol、NSURLConnection 还是 NSURLSession 都会走底层的 socket,但是 WKWebView 可能由于基于 WebKit,并不会执行 socket 相关的函数对 HTTP 请求进行处理。

具体使用

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [NSURLProtocol registerClass:[DakaURLProtocol class]];
}
- (void)dealloc {
    [NSURLProtocol unregisterClass:[DakaURLProtocol class]];
}

三、OC中实现前端页面的修改

使用jQuery获取html相应标签,插入、删除指定标签...

//获取class为itemv2的div标签(返回一个数组,第一个即为页面中最前面的一个)
var x = document.querySelectorAll("div.itemv2");

form.se-form顶部插入一张图片

var aaa = document.querySelectorAll("form.se-form");
var bigImg = document.createElement("img")
bigImg.style="width:100%; height:200px";
bigImg.src="https://cdn1.showjoy.com/images/4f/4f65c3ad01894f3b837267efe6b7720c.jpg";
aaa[0].before(bigImg)

删除class为navi-bar的标签

var dakaNaviBar = document.getElementsByClassName("navi-bar")
if(dakaNaviBar.length > 0) {
    dakaNaviBar[0].parentElement.removeChild(dakaNaviBar[0]);
}

具体实现

将上面对应的js代码转成NSString,在UIWebview的webViewDidFinishLoad代理方法中实现

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    NSString *jsStr1 = [NSString stringWithFormat:@"var dakaNaviBar = document.getElementsByClassName(\"navi-bar\");"
                            "if(dakaNaviBar.length > 0) {"
                                "dakaNaviBar[0].parentElement.removeChild(dakaNaviBar[0]);"
                            "}"
    [webView stringByEvaluatingJavaScriptFromString:jsStr1];
}
上一篇下一篇

猜你喜欢

热点阅读