iOS NSCache和NSUrlCache緩存類實(shí)現(xiàn)示例詳解
NSCache
NSCache是Foundation框架提供的緩存類的實(shí)現(xiàn),使用方式類似于可變字典,最重要的是它是線程安全的,而NSMutableDictionary不是線程安全的,在多線程環(huán)境下使用NSCache是更好的選擇。 類的基本屬性和方法:
#import <Foundation/NSObject.h>
@class NSString;
@protocol NSCacheDelegate;
NS_ASSUME_NONNULL_BEGIN
API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0))
@interface NSCache <KeyType, ObjectType> : NSObject {
@private
id _delegate;
void *_private[5];
void *_reserved;
}
@property (copy) NSString *name;
@property (nullable, assign) id<NSCacheDelegate> delegate;
- (nullable ObjectType)objectForKey:(KeyType)key;
- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
- (void)removeObjectForKey:(KeyType)key;
- (void)removeAllObjects;
@property NSUInteger totalCostLimit; // limits are imprecise/not strict
@property NSUInteger countLimit; // limits are imprecise/not strict
@property BOOL evictsObjectsWithDiscardedContent;
@end
@protocol NSCacheDelegate <NSObject>
@optional
- (void)cache:(NSCache *)cache willEvictObject:(id)obj;
@end
NS_ASSUME_NONNULL_END
緩存淘汰策略
通過(guò) GNUstep 提供的源碼,我們得知其對(duì)于 NSCache 的處理是計(jì)算出一個(gè)平均訪問(wèn)次數(shù),然后釋放的是訪問(wèn)次數(shù)較少的對(duì)象,直到滿足需要釋放大小,也就是 LRU 的機(jī)制。通過(guò) swift-corelibs-foundation 源碼我們得知,其首先存儲(chǔ)鏈表結(jié)構(gòu)中是按對(duì)象花費(fèi)內(nèi)存大小排序的,然后通過(guò)用戶有無(wú)指定 totalCostLimit 大小限制來(lái)依次釋放(先釋放占用較小的對(duì)象),直到滿足需要釋放大小。然后再通過(guò)個(gè)數(shù)限制來(lái)釋放,直到滿足需要釋放大?。ㄒ琅f是先釋放較小的對(duì)象)。
GNUstepFoundation 源碼地址:github.com/gnustep/lib…
Swift Foundation 源碼地址:github.com/apple/swift…
在內(nèi)存不足時(shí)NSCache會(huì)自動(dòng)釋放存儲(chǔ)的對(duì)象。
NSCache的鍵key不會(huì)被復(fù)制,所以key不需要實(shí)現(xiàn)NSCopying協(xié)議。
可以設(shè)置緩存的最大數(shù)量,當(dāng)緩存數(shù)量滿的時(shí)候,再添加將先刪除先添加的對(duì)象再增加。
唯一一個(gè)代理方法是一個(gè)對(duì)象將被刪除時(shí)調(diào)用,調(diào)用方式有以下幾種:
- NSCache緩存對(duì)象自身被釋放
- 手動(dòng)調(diào)用removeObjectForKey:方法
- 手動(dòng)調(diào)用removeAllObjects
- 緩存中對(duì)象的個(gè)數(shù)大于countLimit,或,緩存中對(duì)象的總cost值大于totalCostLimit
- 程序進(jìn)入后臺(tái)后
- 收到系統(tǒng)的內(nèi)存警告
基本用法:
@interface NSCacheViewController ()<NSCacheDelegate>
@property(nonatomic,strong) NSCache *cache;
@end
@implementation NSCacheViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.cache = [[NSCache alloc] init];
//設(shè)置最大緩存數(shù)
self.cache.countLimit = 5;
//設(shè)置代理,實(shí)現(xiàn)代理方法,
self.cache.delegate = self;
self.cache.name = @"測(cè)試內(nèi)存";
for (int i=0; i<10; i++) {
NSString *str = [NSString stringWithFormat:@"%d",i];
[self.cache setObject:str forKey:str];
}
// Do any additional setup after loading the view.
}
//超出緩存部分會(huì)被釋放, 收到內(nèi)存警告時(shí)候系統(tǒng)也會(huì)釋放一部分。
-(void)cache:(NSCache *)cache willEvictObject:(id)obj{
NSLog(@"%@---%@",cache,obj);
}
@end

實(shí)際應(yīng)用 SDWebImage
SDImageCacheConfig中可以配置是否使用內(nèi)存做緩存,默認(rèn)為YES
磁盤(pán)緩存的最大時(shí)長(zhǎng),默認(rèn)為一周

SDImage中內(nèi)存緩存SDMemoryCache繼承與NScache,緩存時(shí)候會(huì)在NSCache和SDMemoryCache的NSMapTable各存一份。讀取時(shí)候也優(yōu)先讀取系統(tǒng)cache,如果不存在再讀取SDMemoryCache,這樣做的目標(biāo)是防止一些系統(tǒng)緩存不可控因素。
// `setObject:forKey:` just call this with 0 cost. Override this is enough
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
[super setObject:obj forKey:key cost:g];
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
if (key && obj) {
// Store weak cache
SD_LOCK(_weakCacheLock);
[self.weakCache setObject:obj forKey:key];
SD_UNLOCK(_weakCacheLock);
}
}
NSURLCache
使用緩存的目的是為了使應(yīng)用程序能更快速的響應(yīng)用戶輸入,是程序高效的運(yùn)行。有時(shí)候我們需要將遠(yuǎn)程web服務(wù)器獲取的數(shù)據(jù)緩存起來(lái),以空間換取時(shí)間,減少對(duì)同一個(gè)url多次請(qǐng)求,減輕服務(wù)器的壓力,優(yōu)化客戶端網(wǎng)絡(luò),讓用戶體驗(yàn)更良好。
背景:NSURLCache : 在iOS5以前,apple不支持磁盤(pán)緩存,在iOS5的時(shí)候,允許磁盤(pán)緩存,(NSURLCache 是根據(jù)NSURLRequest 來(lái)實(shí)現(xiàn)的)只支持http,在iOS6以后,支持http和https。
緩存的實(shí)現(xiàn)說(shuō)明:由于GET請(qǐng)求一般用來(lái)查詢數(shù)據(jù),POST請(qǐng)求一般是發(fā)大量數(shù)據(jù)給服務(wù)器處理(變動(dòng)性比較大),因此一般只對(duì)GET請(qǐng)求進(jìn)行緩存,而不對(duì)POST請(qǐng)求進(jìn)行緩存。
緩存原理:一個(gè)NSURLRequest對(duì)應(yīng)一個(gè)NSCachedURLResponse
Etag全稱是Entity Tag,一般用于標(biāo)識(shí)URL對(duì)象是否發(fā)生了改變。 使用Etag一般會(huì)出現(xiàn)如下的請(qǐng)求流程:
Etag有點(diǎn)類似于文件hash或者說(shuō)是信息摘要。
當(dāng)進(jìn)行一次URL請(qǐng)求,服務(wù)端會(huì)返回'Etag'響應(yīng)頭,下次瀏覽器請(qǐng)求相同的URL時(shí),瀏覽器會(huì)自動(dòng)將它設(shè)置為請(qǐng)求頭'If-None-Match'的值。服務(wù)器收到這個(gè)請(qǐng)求之后,就開(kāi)始做信息校驗(yàn)工作將自己本次產(chǎn)生的Etag與請(qǐng)求傳遞過(guò)來(lái)的'If-None-Match'對(duì)比,如果相同,則返回HTTP狀態(tài)碼304,并且response數(shù)據(jù)體中沒(méi)有數(shù)據(jù)。
第二次請(qǐng)求的時(shí)候從哪里獲取到'Etag'的值并賦給請(qǐng)求頭'If-None-Match'的?自然是瀏覽器的緩存中取出的。那么瀏覽器收到304狀態(tài)碼之后又干了什么?剛才說(shuō)到response數(shù)據(jù)體中沒(méi)有數(shù)據(jù),但是瀏覽器仍需加載頁(yè)面,它會(huì)從緩存中讀取上次緩存的頁(yè)面。
//
// NSURLCacheViewController.m
// DemoTest2022
//
// Created by wy on 2022/10/19.
//
#import "NSURLCacheViewController.h"
@interface NSURLCacheViewController ()
//去服務(wù)器比對(duì)資源是否需要更新
@property (nonatomic, strong) NSString *lastModified;
@property (nonatomic, strong) NSString *etag;
@property(nonatomic,strong) UIImageView *imagev;
@end
@implementation NSURLCacheViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.imagev = [[UIImageView alloc] initWithFrame:CGRectMake(0, 100, 100, 100)];
[self.view addSubview:self.imagev];
// Do any additional setup after loading the view.
}
-(void)requestTest{
NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/50x50.png"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:(NSURLRequestReloadIgnoringCacheData) timeoutInterval:15] ;
if (self.lastModified) {
[request setValue:self.lastModified forHTTPHeaderField:@"If-Modified-Since"];
}
if (self.etag) {
[request setValue:self.etag forHTTPHeaderField:@"If-None-Match"];
}
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"error---%@",error);
}else{
NSData *tempData = data;
NSString *responseStr = [[NSString alloc] initWithData:tempData encoding:NSUTF8StringEncoding];
self.lastModified = [(NSHTTPURLResponse *)response allHeaderFields][@"Last-Modified"];
self.etag =[(NSHTTPURLResponse *)response allHeaderFields][@"etag"];
NSLog(@"response:%@", response);
dispatch_sync(dispatch_get_main_queue(), ^{
self.imagev.image = [UIImage imageWithData:tempData];
});
}
}] resume] ;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self requestTest];
}
@end
這樣每次請(qǐng)求都使用忽略緩存的策略,但是要附帶著"If-None-Match"頭,它的值是上次請(qǐng)求的響應(yīng)頭"Etag"的值,于是服務(wù)器會(huì)每次都實(shí)時(shí)檢查文件的修改狀態(tài),得到一個(gè)準(zhǔn)確的狀態(tài)值,最后決定返回304還是200。若是200,iOS則直接使用新的response和新的數(shù)據(jù);如果是304,則使用新的response和緩存中的data。
這樣既能夠獲取到最新的數(shù)據(jù)有能夠節(jié)約帶寬。兩全其美。
iOS中定以的URLRequest緩存策略有以下幾種:
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
NSURLRequestUseProtocolCachePolicy = 0,
// 從不讀取緩存,但請(qǐng)求后將response緩存起來(lái)
NSURLRequestReloadIgnoringLocalCacheData = 1,
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
// 以下兩種在取緩存時(shí),可能取到的是過(guò)期數(shù)據(jù)
NSURLRequestReturnCacheDataElseLoad = 2,// 緩存中沒(méi)有才去發(fā)起請(qǐng)求加載,有就不進(jìn)行網(wǎng)絡(luò)請(qǐng)求了
NSURLRequestReturnCacheDataDontLoad = 3,// 緩存中沒(méi)有不加載,絕不發(fā)起網(wǎng)絡(luò)請(qǐng)求,緩存中沒(méi)有則返回錯(cuò)誤
NSURLRequestReloadRevalidatingCacheData = 5,//Unimplemented
};
官方文檔解釋:

NSURLCache類通過(guò)將NSURLRequest對(duì)象映射到NSCachedURLResponse對(duì)象來(lái)實(shí)現(xiàn)URL加載請(qǐng)求響應(yīng)的緩存。它提供了一個(gè)復(fù)合的內(nèi)存和磁盤(pán)緩存,并允許您操作內(nèi)存和磁盤(pán)部分的大小。您還可以控制緩存數(shù)據(jù)持久存儲(chǔ)的路徑。
在iOS中,當(dāng)系統(tǒng)磁盤(pán)空間不足時(shí),磁盤(pán)上的緩存可能會(huì)被清除,但只有在應(yīng)用程序未運(yùn)行時(shí)才會(huì)清除。
AFNetwork中用法:

總結(jié):
NSCache 特點(diǎn)
- 使用方便,類似字典
- l線程安全
- l內(nèi)存不足時(shí),NSCache會(huì)自動(dòng)釋放存儲(chǔ)對(duì)象
- NSCache的key不會(huì)被拷貝,不需要實(shí)現(xiàn)NSCopying協(xié)議
- lNSDiscardableContent協(xié)議
NSURLCache主要應(yīng)用與網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)緩存策略,優(yōu)化網(wǎng)絡(luò)請(qǐng)求性能優(yōu)化。
以上就是iOS NSCache和NSUrlCache緩存類實(shí)現(xiàn)示例詳解的詳細(xì)內(nèi)容,更多關(guān)于iOS NSCache NSUrlCache的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS應(yīng)用內(nèi)實(shí)現(xiàn)跳轉(zhuǎn)到手機(jī)淘寶天貓的方法
這篇文章主要給大家介紹了關(guān)于iOS應(yīng)用內(nèi)如何實(shí)現(xiàn)跳轉(zhuǎn)到手機(jī)淘寶天貓的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12
IOS 中UIKit-UIPageControl利用delegate定位圓點(diǎn)位置
這篇文章主要介紹了IOS 中UIKit-UIPageControl利用delegate定位圓點(diǎn)位置 的相關(guān)資料,需要的朋友可以參考下2017-04-04
iOS如何獲取設(shè)備型號(hào)的最新方法總結(jié)
在開(kāi)發(fā)中,我們經(jīng)常需要獲取設(shè)備的型號(hào)以進(jìn)行數(shù)據(jù)統(tǒng)計(jì)或者做不同的適配。這篇文章主要給大家介紹了關(guān)于iOS如何獲取設(shè)備型號(hào)的最新方法,需要的朋友可以參考下2018-11-11
iOS UIBezierPath實(shí)現(xiàn)餅狀圖
這篇文章主要為大家詳細(xì)介紹了iOS UIBezierPath實(shí)現(xiàn)餅狀圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03
iOS中使用Fastlane實(shí)現(xiàn)自動(dòng)化打包和發(fā)布
Fastlane是一套使用Ruby寫(xiě)的自動(dòng)化工具集,用于iOS和Android的自動(dòng)化打包、發(fā)布等工作,可以節(jié)省大量的時(shí)間。下面給大家介紹ios fastlane 自動(dòng)化打包和發(fā)布的安裝方法,需要的朋友參考下吧2017-05-05
iOS開(kāi)發(fā)仿電商類APP首頁(yè)實(shí)例
本篇文章主要介紹了iOS開(kāi)發(fā)仿電商類APP首頁(yè)實(shí)例,主要是利用ui布局,具有一定的參考價(jià)值,有需要的可以了解一下。2016-11-11

