iOS預加載Web頁面方案
iOS預加載Web頁面方案
可以預加載多個網址,然后在離線狀態去顯示那幾個網址,看看是不是都完全緩存下來了。
使用方法
在需要開啟預加載的地方創建
self.sCache = [STMURLCache create:^(STMURLCacheMk *mk) {
mk.whiteListsHost(whiteLists).whiteUserAgent(@"starming");
}];
這里是所有可設置項目,默認設置可以查看 model 的 get 方法
- (STMURLCacheMk *(^)(NSUInteger)) memoryCapacity; //內存容量
- (STMURLCacheMk *(^)(NSUInteger)) diskCapacity; //本地存儲容量
- (STMURLCacheMk *(^)(NSUInteger)) cacheTime; //緩存時間
- (STMURLCacheMk (^)(NSString )) subDirectory; //子目錄
- (STMURLCacheMk *(^)(BOOL)) isDownloadMode; //是否啟動下載模式
- (STMURLCacheMk (^)(NSArray )) whiteListsHost; //域名白名單
(STMURLCacheMk (^)(NSString )) whiteUserAgent; //WebView的user-agent白名單
(STMURLCacheMk (^)(NSString )) addHostWhiteList; //添加一個域名白名單
- (STMURLCacheMk (^)(NSString )) addRequestUrlWhiteList; //添加請求白名單
//NSURLProtocol相關設置
(STMURLCacheMk *(^)(BOOL)) isUsingURLProtocol; //是否使用NSURLProtocol,默認使用NSURLCache</code></pre>
也可以隨時更新這些設置項
[self.sCache update:^(STMURLCacheMk *mk) {
mk.isDownloadMode(YES);
}];
預加載名單可以按照整個 web 頁面請求進行預加載
[self.sCache preLoadByWebViewWithUrls:@[@"http://www.v2ex.com",@"http://www.github.com"];
如果需要按照單個資源列表進行預加載可以使用 preLoadByRequestWithUrls 這個方法。
白名單設置
對于只希望緩存特定域名或者地址的可以通過白名單進行設置,可以在創建時進行設置或者更新時設置。
NSString *whiteListStr = @"www.starming.com|www.github.com|www.v2ex.com|www.baidu.com";
NSMutableArray *whiteLists = [NSMutableArray arrayWithArray:[whiteListStr componentsSeparatedByString:@"|"]];
self.sCache = [STMURLCache create:^(STMURLCacheMk *mk) {
mk.whiteListsHost(whiteLists).whiteUserAgent(@"starming");
}];
這里的 whiteUserAgent 的設置會設置 webview 的 UserAgent,這樣能夠讓webview以外的網絡請求被過濾掉。
基本加載緩存實現原理
創建 STMURLCache 后設置 NSURLCache 的 URLCache ,在 cachedResponseForRequest 方法中獲取 NSURLRequest 判斷白名單,檢驗是否有與之對應的 Cache ,有就使用本地數據返回 NSCachedURLResponse ,沒有就通過網絡獲取數據數據緩存。 STMURLCache 對象釋放時將 NSURLCache 設置為不緩存,表示這次預加載完成不需要再緩存。當緩存空間超出設置大小會將其清空。
使用 NSURLProtocol 這種原理基本類似。
白名單實現原理
創建域名列表設置項 whiteListsHost 和 userAgent 設置項,在創建和更新時對其進行設置。在網絡請求開始通過設置項進行過濾。具體實現如下
//對于域名白名單的過濾
if (self.mk.cModel.whiteListsHost.count > 0) {
id isExist = [self.mk.cModel.whiteListsHost objectForKey:[self hostFromRequest:request]];
if (!isExist) {
return nil;
}
}
//User-Agent來過濾
if (self.mk.cModel.whiteUserAgent.length > 0) {
NSString *uAgent = [request.allHTTPHeaderFields objectForKey:@"User-Agent"];
if (uAgent) {
if (![uAgent hasSuffix:self.mk.cModel.whiteUserAgent]) {
return nil;
}
}
}
具體緩存實現
緩存的實現有兩種,一種是 NSURLCache 另一種是 NSURLProtocol , STMURLCache 同時支持了這兩種,通過 STMURLCacheModel 里的 isUsingURLProtocol 設置項來選擇使用哪個。
NSURLCache的實現
沒有緩存的 request 會對其進行請求將獲取數據按照hash地址存兩份于本地,一份是數據,一份記錄時間和類型,時間記錄可以用于判斷失效時間。對于判斷是否有緩存可以根據請求地址對應的文件進行判斷。具體實現如下:
- (NSCachedURLResponse *)localCacheResponeWithRequest:(NSURLRequest *)request {
__block NSCachedURLResponse *cachedResponse = nil;
NSString *filePath = [self filePathFromRequest:request isInfo:NO];
NSString *otherInfoPath = [self filePathFromRequest:request isInfo:YES];
NSDate *date = [NSDate date];
NSFileManager *fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:filePath]) {
//有緩存文件的情況
BOOL expire = false;
NSDictionary *otherInfo = [NSDictionary dictionaryWithContentsOfFile:otherInfoPath];
if (self.cacheTime > 0) {
NSInteger createTime = [[otherInfo objectForKey:@"time"] integerValue];
if (createTime + self.cacheTime < [date timeIntervalSince1970]) {
expire = true;
}
}
if (expire == false) {
//從緩存里讀取數據
NSData *data = [NSData dataWithContentsOfFile:filePath];
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:[otherInfo objectForKey:@"MIMEType"] expectedContentLength:data.length textEncodingName:[otherInfo objectForKey:@"textEncodingName"]];
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data];
return cachedResponse;
} else {
//cache失效了
[fm removeItemAtPath:filePath error:nil]; //清除緩存data
[fm removeItemAtPath:otherInfoPath error:nil]; //清除緩存其它信息
return nil;
}
} else {
//從網絡讀取
self.isSavedOnDisk = NO;
id isExist = [self.responseDic objectForKey:request.URL.absoluteString];
if (isExist == nil) {
[self.responseDic setValue:[NSNumber numberWithBool:TRUE] forKey:request.URL.absoluteString];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
cachedResponse = nil;
} else {
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"%f",[date timeIntervalSince1970]],@"time",response.MIMEType,@"MIMEType",response.textEncodingName,@"textEncodingName", nil];
BOOL resultO = [dic writeToFile:otherInfoPath atomically:YES];
BOOL result = [data writeToFile:filePath atomically:YES];
if (resultO == NO || result == NO) {
} else {
}
cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data];
}
}];
[task resume];
return cachedResponse;
}
return nil;
}
}
NSURLProtocol的實現
在設置配置項和更新配置項時需要創建一個 STMURLCacheModel 的單例來進行設置和更新配置項給 NSURLProtocol 的實現來使用。通過 isUsingURLProtocol 設置項區分, NSURLProtocol 是通過registerClass方式將protocol實現的進行注冊。
- (STMURLCache *)configWithMk {
self.mk.cModel.isSavedOnDisk = YES;
if (self.mk.cModel.isUsingURLProtocol) {
STMURLCacheModel *sModel = [STMURLCacheModel shareInstance];
sModel.cacheTime = self.mk.cModel.cacheTime;
sModel.diskCapacity = self.mk.cModel.diskCapacity;
sModel.diskPath = self.mk.cModel.diskPath;
sModel.cacheFolder = self.mk.cModel.cacheFolder;
sModel.subDirectory = self.mk.cModel.subDirectory;
sModel.whiteUserAgent = self.mk.cModel.whiteUserAgent;
sModel.whiteListsHost = self.mk.cModel.whiteListsHost;
[NSURLProtocol registerClass:[STMURLProtocol class]];
} else {
[NSURLCache setSharedURLCache:self];
}
return self;
}
關閉時兩者也是不同的,通過設置項進行區分
- (void)stop {
if (self.mk.cModel.isUsingURLProtocol) {
[NSURLProtocol unregisterClass:[STMURLProtocol class]];
} else {
NSURLCache *c = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:c];
}
[self.mk.cModel checkCapacity];
}
白名單處理還有讀取緩存和前者都類似,但是在緩存Data時 NSURLCached 的方案里是通過發起一次新的請求來獲取數據,而 NSURLProtocol 在 NSURLConnection 的 Delegate 里可以獲取到,少了一次網絡的請求,這里需要注意的是在 - (void) connection:(NSURLConnection )connection didReceiveData:(NSData )data 每次從這個回調里獲取的數據不是完整的,要在 - (void) connectionDidFinishLoading:(NSURLConnection *)connection 這個會調里將分段數據拼接成完整的數據保存下來。具體完整的代碼實現可以看 STMURLProtocol 里的代碼實現。
后記
通過 map 網絡請求可以緩存請求,也可以 mock 接口請求進行測試。
來自:http://www.cocoachina.com/ios/20161130/18230.html