iOS - WKWebView Cookie

Garrett Gao發表於2019-03-12

從UIWebview換到WKWebView之後,會發現管理Cookie是很麻煩事,經常出現 App自定義Cookie的值丟失 或 更新不及時 的情況。蘋果iOS11之後也提供了WKWebView的Cookie API WKHTTPCookieStore,但是目前大多數App最低版本不可能設定最低版本到iOS11,所以我們只能想別的辦法。

什麼是Cookie?

Cookie是一種早期的客戶端儲存機制,起初是針對服務端指令碼設計使用的。以Cookie儲存的資料,不論服務端是否需要,每次HTTP請求都會把這些資料傳送到服務端。

Cookie是指Web瀏覽器儲存的少量資料,同時它是與具體的Web頁面或者站點相關。Cookie最早是設定為被服務端所用的,從最底層來看,作為http協議的一種擴充實現它。

Cookie資料會自動在Web瀏覽器和Web伺服器之間傳輸,因此服務端指令碼就可以讀寫儲存在客戶端的cookie值。

Cookie的屬性

除了 name 與 value,Cookie其他屬性:

domain:作用域名

舉例: 設定domain為 bj.jiehun.com.cn,則在app.jiehun.com.cn 等子域名不會生效; 如設定domain為 .jiehun.com.cn,則在jiehun.com.cn 的所有子域名生效。 注意:IP地址特殊,直接設定原地址即可。

path:作用路徑

舉例: 如在 www.jiehun.com.cn,設定 path=/m,則只在 www.jiehun.com.cn/m 或 /m子目錄下生效,如 www.jiehun.com.cn/app 就不會生效; 不設定預設為當前URL預設路徑;我們通常會設定 “/” 代表所有目錄。

expires:過期時間,格林威治時間 (GMT)字串格式,設定過期時間後將會儲存在一個檔案直到過期。

// 列印當前時間
var date = new Date();
date.setTime(date.getTime());
console.log(date.toGMTString());
// 列印結果為:Mon, 25 Feb 2019 15:35:03 GMT

// 設定cookie時間
document.cookie='name=value;expires=' + date.toGMTString();
複製程式碼

max-age:有效時長,單位秒。

// 設定 cookie 有效期 10秒
document.cookie='name=value;max-age=10;
複製程式碼

Cookie預設的有效期很短暫,只能維持在Web瀏覽器的回話期間,一旦使用者關閉瀏覽器,Cookie儲存的資料就全部丟失。 如果想要延長Cookie的有效期,可以通過設定max-age或expires屬性。一旦設定有效期,瀏覽器就會將Cookie資料儲存在一個檔案中,並且直到過了指定的有效期才會刪除該檔案。

secure: Bool值,是否為安全傳輸,如設定true,在Https或其他網路安全協議才會傳輸。

####演示 我們可以隨便找一個瀏覽器,如Safari,開啟後快捷鍵 command + alt + c 開啟除錯視窗:

image.png

讀取Cookie

document.cookie
複製程式碼

儲存Cookie

document.cookie='testName=testValue;path=/;max-age=60*60';
複製程式碼

因為Cookie的名和值中不允許包含分號、逗號和空格,因此,在儲存前可以採用全域性函式 encodeURIComponent()對值進行編碼。相應的,讀取cookie值的時候採用decodeURIComponent() 函式解碼。

刪除Cookie

相同的名字隨意設定一個值,並將max-age指定為0,相當於刪除Cookie。

document.cookie='testName=;domain=.jiehun.com.cn;path=/;max-age=0';
複製程式碼

Cookie的侷限性

個數限制300(現在已經可以超越),每個Cookie資料即名字和值的總量不能超過4KB。

WKWebView 管理Cookie

上面瞭解了Cookie的基本知識,我們如何應用到iOS開發中?這裡主要介紹WKWebview的使用方法。

因為JavaScript只能設定瀏覽器本地的cookie,往往客戶端第一次請求由開發者建立Request物件請求:

[webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"url"]]];

複製程式碼

所以第一次請求需要手動設定請求頭中的Cookie,可以繼承WKWebView重寫 loadRequest 方法,或者直接通過runtime Swizzling loadRequest 方法:

/**重寫 loadRequest 方法,在請求頭中新增自定義到cookie*/
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request{
    
    NSMutableURLRequest *newRequeset = [request mutableCopy];
    
    // 自定義cookie值
    NSDictionary *customCookieDic =@{
                                     @"testName1":@"value1",
                                     @"testName2":@"value2"
                                     };
    
    // 拼接cookie字元
    NSString *cookie = @"";
    for (NSString *key in customCookieDic.allKeys) {
        NSString *keyValue = [NSString stringWithFormat:@"%@=%@;",key,[customCookieDic objectForKey:key]];
        cookie = [cookie stringByAppendingString:keyValue];
    }
    
    // 設定到請求頭
    [newRequeset setValue:cookie forHTTPHeaderField:@"Cookie"];
    
    return [super loadRequest:newRequeset];
}
複製程式碼

此時請求發出,服務端可以收到我們請求頭中的cookie值,且請求頭的cookie會自動儲存到瀏覽器。

但我們發現請求頭中自動儲存到瀏覽器的cookie並不可靠,因為沒有設定域名和目錄的作用區,往往瀏覽器內二次跳轉就會丟失,我們如何來保證瀏覽器的Cookie永不丟失?

WKUserScript

Webkit支援通過 WKUserScript 向網頁中注入js指令碼:

// 設定程式碼塊
// @Source 指令碼程式碼
// @injectionTime 執行時機,網頁渲染前或渲染後
// @MainFrameOnly Bool值,YES只注入主幀,NO所有幀
WKUserScript *cookieInScript = [[WKUserScript alloc] initWithSource:@"js指令碼程式碼"
                                                              injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                                           forMainFrameOnly:NO];
// 插入指令碼
[webview.configuration.userContentController addUserScript:cookieInScript];
複製程式碼

所以,保證Cookie不丟失可以通過此方法注入設定cookie js程式碼,在頁面每次渲染都會設定,從而保證不會丟失。

每個cookie值可以設定為一個程式碼塊,為了表示每個程式碼塊的唯一,我們可以在注入指令碼的最後新增 註釋標示;其實就是一句註釋掉的程式碼,來確定cookie程式碼塊的唯一性,為以後刪除某個程式碼塊做鋪墊。 舉例設定某個cookie值的 js程式碼:

document.cookie ='cityId=10010;domain=.jiehun.com.cn;path=/';
// The cookie code  identified is cityId (程式碼塊標示)
複製程式碼

為了避免Cookie重複問題,可以直接把cookie設定在根域名。

刪除程式碼塊

每次呼叫 addUserScript方法插入指令碼塊,程式碼塊會儲存在WKUserContentController類的 userScripts陣列屬性中:

@interface WKUserContentController : NSObject <NSSecureCoding>

/*! @abstract The user scripts associated with this user content
 controller.
*/
@property (nonatomic, readonly, copy) NSArray<WKUserScript *> *userScripts;
複製程式碼

因為 userScripts 為只讀許可權,我們並不能修改,所以修改某個某一個cookie程式碼塊的時候,需要先全部清除,再把不需要刪除的程式碼塊重新新增進去,這裡就需要用到前面所說的程式碼塊註釋的唯一標示:

/**
 刪除某個程式碼片段
 @param tag 片段標示,//The cookie code  identified is cookieName
 */
- (void)deleteUserSciptWithTag:(NSString *)tag {
    
    if (tag) {
        WKUserContentController *userContentController = webView.configuration.userContentController;
        NSMutableArray<WKUserScript *> *array = [userContentController.userScripts mutableCopy];
        int i = 0;
        BOOL isHave = NO;
        for (WKUserScript* wkUScript in userContentController.userScripts) {
            // 通過註釋標示判斷程式碼塊是否為某個cookie
            if ([wkUScript.source containsString:tag]) {
                [array removeObjectAtIndex:i];
                isHave = YES;
                continue;
            }
            i ++;
        }
        
        if (isHave) {
            ///沒法修改陣列 只能移除全部 再將不需要刪除的cookie重新新增
            [userContentController removeAllUserScripts];
            for (WKUserScript* wkUScript in array) {
                [userContentController addUserScript:wkUScript];
            }
        }
    }
}
複製程式碼

通過這個Webkit可注入指令碼的邏輯,我們基本可以保證使用cookie在WKWebview準確性和不丟失。

下面也寫了一個Demo來演示如何通過一個代理方法來管理WKWebView所有Cookie: github地址:https://github.com/GaoGuohao/GGWkCookie

相關文章