iOS 好文iOS MarkUIWebView和JS交互

iOS WKWebView 与 UIWebView Cookie

2017-03-19  本文已影响3549人  沙琪玛dd

WKWebView概述

首先简单熟悉一下WKWebView的属性方法

// webview 配置
@property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration;

//配置初始化方法
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];

//WKPreferences偏好设置
config.preferences = [[WKPreferences alloc] init];
// 默认为0
config.preferences.minimumFontSize = 10;
// 默认认为YES
config.preferences.javaScriptEnabled = YES;
// 在iOS上默认为NO,表示不能自动通过窗口打开
config.preferences.javaScriptCanOpenWindowsAutomatically = NO;

// 导航代理 
@property (nullable, nonatomic, weak) id <WKNavigationDelegate>navigationDelegate;

// 用户交互代理
@property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;
// 与UIWebView一样的加载请求API
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;

// 直接加载HTML
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
 
// 直接加载data
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString*)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL NS_AVAILABLE(10_11, 9_0);

// 停止加载数据
- (void)stopLoading;

// 执行JS代码
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;

// 只读属性,所有添加的WKUserScript都在这里可以获取到
@property (nonatomic, readonly, copy) NSArray<WKUserScript *> *userScripts;
 
// 注入JS
- (void)addUserScript:(WKUserScript *)userScript;
 
// 移除所有注入的JS
- (void)removeAllUserScripts;
 
// 添加scriptMessageHandler到所有的frames中,则都可以通过
// window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
// 发送消息
// JS要调用原生的方法的方式
- (void)addScriptMessageHandler:(id<WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
 
// 根据name移除所注入的scriptMessageHandler
- (void)removeScriptMessageHandlerForName:(NSString *)name;

在WKUserContentController中,所有使用到WKUserScript。WKUserContentController是用于与JS交互的类,而所注入的JS是WKUserScript对象。它的所有属性和方法如下:

// JS源代码
@property (nonatomic, readonly, copy) NSString *source;
 
// JS注入时间
@property (nonatomic, readonly) WKUserScriptInjectionTime injectionTime;
 
// 只读属性,表示JS是否应该注入到所有的frames中还是只有main frame.
@property (nonatomic, readonly, getter=isForMainFrameOnly) BOOLforMainFrameOnly;
 
// 初始化方法,用于创建WKUserScript对象
// source:JS源代码
// injectionTime:JS注入的时间
// forMainFrameOnly:是否只注入main frame
- (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;
 
@protocol WKNavigationDelegate <NSObject>
 
@optional
 
// 决定导航的动作,通常用于处理跨域的链接能否导航。WebKit对跨域进行了安全检查限制,不允许跨域,因此我们要对不能跨域的链接
// 单独处理。但是,对于Safari是允许跨域的,不用这么处理。
// 这个是决定是否Request
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
 
// 决定是否接收响应
// 这个是决定是否接收response
// 要获取response,通过WKNavigationResponse对象获取
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
 
// 当main frame的导航开始请求时,会调用此方法
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
 
// 当main frame接收到服务重定向时,会回调此方法
- (void)webView:(WKWebView *)webViewdidReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation*)navigation;
 
// 当main frame开始加载数据失败时,会回调
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
 
// 当main frame的web内容开始到达时,会回调
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;
 
// 当main frame导航完成时,会回调
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
 
// 当main frame最后下载数据失败时,会回调
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
 
// 这与用于授权验证的API,与AFN、UIWebView的授权验证API是一样的
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler;
 
// 当web content处理完成时,会回调
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);
 
@end

typedef NS_ENUM(NSInteger, WKNavigationActionPolicy) {
    WKNavigationActionPolicyCancel,
    WKNavigationActionPolicyAllow,
} NS_ENUM_AVAILABLE(10_10, 8_0);

WKWebView 和 UIWebView 的 Cookie 同步问题

WKWebView 的 Cookie 机制:
//defaultDataStore 是默认选择的存储容器
+ (WKWebsiteDataStore *)defaultDataStore;

//nonPersistentDataStore会禁止任何数据写入文件系统,可用于无痕浏览
+ (WKWebsiteDataStore *)nonPersistentDataStore;

//可以查看到容器中存储的网站数据的所有种类
+ (NSSet<NSString *> *)allWebsiteDataTypes;

//以下分别是获取容器中的数据记录和删除数据记录的方法

- (void)fetchDataRecordsOfTypes:(NSSet<NSString *> *)dataTypes completionHandler:(void (^)(NSArray<WKWebsiteDataRecord *> *))completionHandler;

- (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes forDataRecords:(NSArray<WKWebsiteDataRecord *> *)dataRecords completionHandler:(void (^)(void))completionHandler;

- (void)removeDataOfTypes:(NSSet<NSString *> *)websiteDataTypes modifiedSince:(NSDate *)date completionHandler:(void (^)(void))completionHandler;

WKWebsiteDataStore 中的Web数据是以 WKWebsiteDataRecord 类的形式保存的,我们可以通过 fetchDataRecordsOfTypes 方法获取到 datastore 中的 record 数据。但是当我们打开 WKWebsiteDataRecord 类的头文件的时候就会发现:

@interface WKWebsiteDataRecord : NSObject

/*! @abstract The display name for the data record. This is usually the domain name. */
@property (nonatomic, readonly, copy) NSString *displayName;

/*! @abstract The various types of website data that exist for this data record. */
@property (nonatomic, readonly, copy) NSSet<NSString *> *dataTypes;

@end
  WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore];
    [dataStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] completionHandler:^(NSArray<WKWebsiteDataRecord *> * _Nonnull records) {
        for (WKWebsiteDataRecord *record  in records)
        {
            NSLog(@"++++++++++++++++%@",[record description]);
        }
    }];
UIWebView 的 Cookie 机制

UIWebView 在浏览网页后会将网页中的 cookie 自动存入 NSHTTPCookieStorage 标准容器中。在后续访问中会将 cookie 自动带到 request 请求当中。比如,NSHTTPCookieStorage 中存储了一个Cookie,name=a;value=b;domain=y.qq.com;expires=Sat,02 May 2017 23:20:25 GMT; 则通 过 UIWebView 发起请求 http://y.qq.com,则请求头会自动带上cookie,而通过 WKWebView 发起请求,请求头不会自动带上该cookie。

初步解决方案

其实主要要做的只有两步,1、获取Cookie,2、注入Cookie

1、获取Cookie
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
    NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
    //读取wkwebview中的cookie 方法1
    for (NSHTTPCookie *cookie in cookies) {
//        [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
        NSLog(@"wkwebview中的cookie:%@", cookie);
 
    }
    //读取wkwebview中的cookie 方法2 读取Set-Cookie字段
    NSString *cookieString = [[response allHeaderFields] valueForKey:@"Set-Cookie"];
    NSLog(@"wkwebview中的cookie:%@", cookieString);
 
    //看看存入到了NSHTTPCookieStorage了没有
    NSHTTPCookieStorage *cookieJar2 = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *cookie in cookieJar2.cookies) {
        NSLog(@"NSHTTPCookieStorage中的cookie%@", cookie);
    }
    decisionHandler(WKNavigationResponsePolicyAllow);
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
{
    
    [webView evaluateJavaScript:[NSString stringWithFormat:@"document.cookie"] completionHandler:^(id _Nullable response, NSError * _Nullable error) {
        if (response != 0) {
            NSLog(@"\n\n\n\n\n\n document.cookie%@,%@",response,error);
        }
    }];
}
2、注入Cookie
//取出 storage 中的cookie并将其拼接成正确的形式
NSArray<NSHTTPCookie *> *tmp = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];    
    NSMutableString *jscode_Cookie = [@"" mutableCopy];
    [tmp enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"%@   =  %@", obj.name, obj.value);
        [jscode_Cookie appendString:[NSString stringWithFormat:@"document.cookie =  '%@=%@';", obj.name, obj.value]];
    }];

WKUserContentController* userContentController = WKUserContentController.new;
    WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: @"" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    
    [userContentController addUserScript:cookieScript];
    WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
    webViewConfig.userContentController = userContentController;
WKWebView * webView = [[WKWebView alloc] initWithFrame:CGRectMake(/*set your values*/) configuration:webViewConfig];
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    [webView evaluateJavaScript:@"document.cookie ='TeskCookieKey1=TeskCookieValue1';" completionHandler:^(id result, NSError *error) {
        //...
    }];
}
NSURL *url = request.URL;
NSMutableString *cookies = [NSMutableString string];
NSMutableURLRequest *requestObj = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
    
NSArray *tmp = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
NSDictionary *dicCookies = [NSHTTPCookie requestHeaderFieldsWithCookies:tmp];
NSString *cookie = [self readCurrentCookie];
[requestObj setValue:cookie forHTTPHeaderField:@"Cookie"];
[_webView loadRequest:requestObj];


-(NSString *)readCurrentCookie{
    NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
    NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
    NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *cookie in [cookieJar cookies]) {
        [cookieDic setObject:cookie.value forKey:cookie.name];
    }
    
    // cookie重复,先放到字典进行去重,再进行拼接
    for (NSString *key in cookieDic) {
          NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [cookieDic valueForKey:key]];
            [cookieValue appendString:appendString];
    }
    return cookieValue;
}
(1)JS注入的Cookie,比如PHP代码在Cookie容器中取是取不到的,直接js document.cookie能读取到

NSMutableURLRequest 注入的PHP等动态语言直接能从$_COOKIE对象中获取到

(2)app退出后 cookie 丢失的问题
//修改保存 cookie 到storage的方法

- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(nonnull WKNavigationResponse *)navigationResponse decisionHandler:(nonnull void (^)(WKNavigationResponsePolicy))decisionHandler{
    NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
    NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
    
    NSString *cookieString = [[response allHeaderFields] valueForKey:@"Set-Cookie"];
    
    for (NSHTTPCookie *cookie in cookies) {
        [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
    }
    

    NSMutableArray *cookieArray = [[NSMutableArray alloc] init];
    for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
        [cookieArray addObject:cookie.name];
        NSMutableDictionary *cookieProperties = [NSMutableDictionary dictionary];
        [cookieProperties setObject:cookie.name forKey:NSHTTPCookieName];
        [cookieProperties setObject:cookie.value forKey:NSHTTPCookieValue];
        [cookieProperties setObject:cookie.domain forKey:NSHTTPCookieDomain];
        [cookieProperties setObject:cookie.path forKey:NSHTTPCookiePath];
        [cookieProperties setObject:[NSNumber numberWithInt:cookie.version] forKey:NSHTTPCookieVersion];
        
        [cookieProperties setObject:[[NSDate date] dateByAddingTimeInterval:2629743] forKey:NSHTTPCookieExpires];
        
        [[NSUserDefaults standardUserDefaults] setValue:cookieProperties forKey:cookie.name];
        [[NSUserDefaults standardUserDefaults] synchronize];
        
    }
    
    [[NSUserDefaults standardUserDefaults] setValue:cookieArray forKey:@"cookieArray"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    
       
    //清除WKWebView的Cookie,之后删除
    WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore];
    [dataStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
                     completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records) {
                         for (WKWebsiteDataRecord *record  in records)
                         {
                             //                             if ( [record.displayName containsString:@"baidu"]) //取消备注,可以针对某域名清除,否则是全清
                             //                             {
                             [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes
                                                                       forDataRecords:@[record]
                                                                    completionHandler:^{
                                                                        NSLog(@"Cookies for %@ deleted successfully",record.displayName);
                                                                    }];
                             //                             }
                         }  
                     }];
    
    decisionHandler(WKNavigationResponsePolicyAllow);
}


//修改从 storage 中读取 cookie 的方法
-(NSString *)readCurrentCookie{
    
    NSMutableArray* cookieDictionary = [[NSUserDefaults standardUserDefaults] valueForKey:@"cookieArray"];
    NSLog(@"cookie dictionary found is %@",cookieDictionary);
    
    for (int i=0; i < cookieDictionary.count; i++)
    {
        NSLog(@"cookie found is %@",[cookieDictionary objectAtIndex:i]);
        NSMutableDictionary* cookieDictionary1 = [[NSUserDefaults standardUserDefaults] valueForKey:[cookieDictionary objectAtIndex:i]];
        NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieDictionary1];
        [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
    }

    NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
    NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
    NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *cookie in [cookieJar cookies]) {
        [cookieDic setObject:cookie.value forKey:cookie.name];
    }
    
    // cookie重复,先放到字典进行去重,再进行拼接
    for (NSString *key in cookieDic) {
//        if ([key isEqualToString:@"nweb_qa"] || [key isEqualToString:@"z_c0"] || [key isEqualToString:@"_xsrf"]) {
            NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [cookieDic valueForKey:key]];
            [cookieValue appendString:appendString];
//        }
    }
    return cookieValue;
}

结语

上一篇下一篇

猜你喜欢

热点阅读