iOS利用NSURLCache給接口最方便最簡(jiǎn)單的做上緩存

在最近的版本迭代中,自己做了一些接口拉取數(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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容