在最近的版本迭代中,自己做了一些接口拉取數(shù)據(jù)方面的優(yōu)化,由于我們是以長(zhǎng)篇內(nèi)容為主體的app,對(duì)于長(zhǎng)篇富文本文章,客戶端這邊并沒(méi)有直接接入webview實(shí)現(xiàn),而是通過(guò)接口拉取并解析文章主體的html代碼,通過(guò)native的方法實(shí)現(xiàn)文章富文本,以后會(huì)寫(xiě)一些文章關(guān)于這方面的實(shí)現(xiàn)。由于文章發(fā)布完了之后,不會(huì)經(jīng)常的改動(dòng),那么客戶端要是每次去拉取文章的html代碼然后解析,會(huì)浪費(fèi)大量的網(wǎng)絡(luò)資源,所以在這個(gè)版本里,我跟后端配合用etag來(lái)實(shí)現(xiàn)文章接口的緩存,由于網(wǎng)上講這方面的文章不多,這篇文章就講講網(wǎng)絡(luò)接口緩存的實(shí)現(xiàn)方法
實(shí)現(xiàn)思路
首先講下實(shí)現(xiàn)的思路,很簡(jiǎn)單,接口在response的header里放入Etag字段,客戶端抓取到Etag的值并存取到本地,然后存取接口內(nèi)容到本地。之后客戶端在call這個(gè)接口的時(shí)候,在request的header里放入If-None-Match,對(duì)應(yīng)的值是之前存取的Etag的值,后端status code返回304告訴客戶端接口內(nèi)容沒(méi)有改變,客戶端使用該接口緩存在本地的內(nèi)容
Etag
Etag是什么?Etag能告知客戶端實(shí)體表示,它是一種可將資源以字符串形式做唯一性標(biāo)識(shí)的方式。服務(wù)器會(huì)為每份資源分配對(duì)應(yīng)的Etag值。另外,當(dāng)資源更新時(shí),Etag值也需要更新
NSURLCache
實(shí)現(xiàn)方法里,最煩的一件事就是怎么緩存接口內(nèi)容,一種方法是自己寫(xiě)一套代碼來(lái)實(shí)現(xiàn)緩存功能,SDWebImage就是這么實(shí)現(xiàn)緩存機(jī)制的,其實(shí)對(duì)于接口請(qǐng)求,iOS 系統(tǒng)已經(jīng)幫你做好了緩存,而且非常完善簡(jiǎn)單,這個(gè)方式叫NSURLCache。這種方式只需兩個(gè)步驟就能緩存網(wǎng)絡(luò)接口返回內(nèi)容:
- 第一步:客戶端設(shè)置緩存大小
AppDelegate.m
NSURLCache *urlCache = [[NSURLCache alloc] initWithMemoryCapacity:4*1024*1024 diskCapacity:100*1024*1024 diskPath:nil];
NSURLCache.sharedURLCache = urlCache;
- 第二步:客戶端發(fā)出
GET請(qǐng)求 - 第三步:Done,通過(guò)
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];可以獲取接口緩存
這兩個(gè)步驟之后,GET請(qǐng)求的內(nèi)容會(huì)被系統(tǒng)自動(dòng)緩存了,無(wú)需自己去實(shí)現(xiàn)內(nèi)容緩存,對(duì)于這種方法,Mattt大神說(shuō)過(guò)“無(wú)數(shù)開(kāi)發(fā)者嘗試自己做一個(gè)簡(jiǎn)陋而脆弱的系統(tǒng)來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)緩存的功能,殊不知 NSURLCache 只要兩行代碼就能搞定且好上 100 倍?!?/p>
Demo(NSURLSession)
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"url"] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30.f];
if (self.etag) {
[request setValue:self.etag forHTTPHeaderField:@"If-None-Match"];
}
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSHTTPURLResponse *urlResponse = (NSHTTPURLResponse *)response;
if (urlResponse.statusCode == 304) {
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (cachedResponse) {
data = cachedResponse.data;
}
} else {
if (urlResponse.allHeaderFields[@"Etag"]) {
NSString *etag = urlResponse.allHeaderFields[@"Etag"];
if (etag && etag.length > 0) {
[self saveEtag:etag];
}
}
}
}];
[task resume];
}
Demo(AFNetworking)
AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:baseURL] sessionConfiguration:config];
sessionManager.requestSerializer = [CustomJSONRequestSerializer serializer];
sessionManager.responseSerializer = [CustomJSONResponseSerializer serializer];
@interface CustomJSONRequestSerializer : AFJSONRequestSerializer
@end
@implementation CustomJSONRequestSerializer
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters error:(NSError *__autoreleasing _Nullable *)error
{
NSMutableURLRequest *request = [super requestWithMethod:method URLString:URLString parameters:parameters error:error];
// add request If-None-Match header
NSString *absoluteUrl = request.URL.absoluteString;
NSString *directory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"etags"];
NSString *fileName = [directory stringByAppendingPathComponent:[self cachedFileNameForKey:absoluteUrl]];
if ([[NSFileManager defaultManager] fileExistsAtPath:fileName]) {
NSString *etag = [NSKeyedUnarchiver unarchiveObjectWithFile:fileName];
if (etag && etag.length > 0) {
[request addValue:etag forHTTPHeaderField:@"If-None-Match"];
}
}
return request;
}
@end
@interface CustomJSONResponseSerializer : AFJSONResponseSerializer
@end
@implementation CustomJSONResponseSerializer
- (nullable id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing _Nullable *)error
{
NSUInteger responseStatusCode = 200;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
responseStatusCode = (NSUInteger)[(NSHTTPURLResponse *)response statusCode];
}
id responseObject;
if (responseStatusCode == 304) {
// fetch cached contents
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:task.currentRequest];
if (cachedResponse) {
responseObject = [super responseObjectForResponse:cachedResponse.response data:cachedResponse.data error:error];
} else {
// cached response was cleared, need to clear cached etag
NSString *directory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"etags"];
NSString *fileName = [self cachedFileNameForKey:response.URL.absoluteString];
NSString *cachedFileName = [directory stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] removeItemAtPath:cachedFileName error:nil];
}
return responseObject;
} else if (responseStatusCode >= 200 && responseStatusCode <= 299) {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.allHeaderFields[@"Etag"]) {
NSString *etag = httpResponse.allHeaderFields[@"Etag"];
if (etag && etag.length > 0) {
NSString *directory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"etags"];
if (![[NSFileManager defaultManager] fileExistsAtPath:directory]) {
[[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:NO attributes:nil error:nil];
}
NSString *fileName = [self cachedFileNameForKey:response.URL.absoluteString];
NSString *cachedFileName = [directory stringByAppendingPathComponent:fileName];
BOOL success = [NSKeyedArchiver archiveRootObject:etag toFile:cachedFileName];
}
}
}
responseObject = [super responseObjectForResponse:response data:data error:error];
return responseObject;
} else {
id responseObject = [super responseObjectForResponse:response data:data error:error];
if ([responseObject isKindOfClass:[NSDictionary class]]) {
*error = [NSError errorWithDomain:NSURLErrorDomain code:responseStatusCode userInfo:responseObject];
} else {
*error = [NSError errorWithDomain:NSURLErrorDomain code:responseStatusCode userInfo:nil];
}
return nil;
}
}
@end
轉(zhuǎn)載請(qǐng)注明出處,原文地址:http://kobedai.me/p9rsts-6o/