從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 開啟除錯視窗:
讀取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