温生 發表於 2022-11-9 14:23:32

iOS NSCache和NSUrlCache缓存类实现示例详解

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>NSCache</li><ul class="second_class_ul"><li>缓存淘汰策略</li></ul><li>NSURLCache</li><ul class="second_class_ul"><li>iOS中定以的URLRequest缓存策略有以下几种:</li></ul><li>总结:</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>NSCache</h2>
<p>NSCache是Foundation框架提供的缓存类的实现,使用方式类似于可变字典,最重要的是它是线程安全的,而NSMutableDictionary不是线程安全的,在多线程环境下使用NSCache是更好的选择。 类的基本属性和方法:</p>
<div class="jb51code"><pre class="brush:cpp;">#import &lt;Foundation/NSObject.h&gt;
@class NSString;
@protocol NSCacheDelegate;
NS_ASSUME_NONNULL_BEGIN
API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0))
@interface NSCache &lt;KeyType, ObjectType&gt; : NSObject {
@private
    id _delegate;
    void *_private;
    void *_reserved;
}
@property (copy) NSString *name;
@property (nullable, assign) id&lt;NSCacheDelegate&gt; 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 &lt;NSObject&gt;
@optional
- (void)cache:(NSCache *)cache willEvictObject:(id)obj;
@end
NS_ASSUME_NONNULL_END
</pre></div>
<p class="maodian"></p><h3>缓存淘汰策略</h3>
<p>通过&nbsp;GNUstep&nbsp;提供的源码,我们得知其对于&nbsp;NSCache&nbsp;的处理是计算出一个平均访问次数,然后释放的是访问次数较少的对象,直到满足需要释放大小,也就是 LRU&nbsp;的机制。通过&nbsp;swift-corelibs-foundation&nbsp;源码我们得知,其首先存储链表结构中是按对象花费内存大小排序的,然后通过用户有无指定&nbsp;totalCostLimit&nbsp;大小限制来依次释放(先释放占用较小的对象),直到满足需要释放大小。然后再通过个数限制来释放,直到满足需要释放大小(依旧是先释放较小的对象)。</p>
<p>GNUstepFoundation 源码地址:github.com/gnustep/lib&hellip;</p>
<p>Swift Foundation 源码地址:github.com/apple/swift&hellip;</p>
<p>在内存不足时NSCache会自动释放存储的对象。</p>
<p>NSCache的键key不会被复制,所以key不需要实现NSCopying协议。</p>
<p>可以设置缓存的最大数量,当缓存数量满的时候,再添加将先删除先添加的对象再增加。</p>
<p><strong>唯一一个代理方法是一个对象将被删除时调用,调用方式有以下几种:</strong></p>
<ul><li>NSCache缓存对象自身被释放</li><li>手动调用removeObjectForKey:方法</li><li>手动调用removeAllObjects &nbsp; &nbsp;</li><li>缓存中对象的个数大于countLimit,或,缓存中对象的总cost值大于totalCostLimit &nbsp; &nbsp;</li><li>程序进入后台后</li><li>收到系统的内存警告</li></ul>
<p>基本用法:</p>
<div class="jb51code"><pre class="brush:cpp;">@interface NSCacheViewController ()&lt;NSCacheDelegate&gt;
@property(nonatomic,strong) NSCache *cache;
@end
@implementation NSCacheViewController
- (void)viewDidLoad {
    ;
    self.cache = [ init];
    //设置最大缓存数
    self.cache.countLimit = 5;
    //设置代理,实现代理方法,
    self.cache.delegate = self;
    self.cache.name = @"测试内存";
    for (int i=0; i&lt;10; i++) {
      NSString *str = ;
      ;
    }
    // Do any additional setup after loading the view.
}
//超出缓存部分会被释放, 收到内存警告时候系统也会释放一部分。
-(void)cache:(NSCache *)cache willEvictObject:(id)obj{
    NSLog(@"%@---%@",cache,obj);
}
@end
</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202211/20221109091919031.png" /></p>
<p><strong>实际应用 SDWebImage</strong></p>
<p>SDImageCacheConfig中可以配置是否使用内存做缓存,默认为YES</p>
<p>磁盘缓存的最大时长,默认为一周</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202211/20221109091919032.png" /></p>
<p>SDImage中内存缓存SDMemoryCache继承与NScache,缓存时候会在NSCache和SDMemoryCache的NSMapTable各存一份。读取时候也优先读取系统cache,如果不存在再读取SDMemoryCache,这样做的目标是防止一些系统缓存不可控因素。</p>
<div class="jb51code"><pre class="brush:cpp;">// `setObject:forKey:` just call this with 0 cost. Override this is enough
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
    ;
    if (!self.config.shouldUseWeakMemoryCache) {
      return;
    }
    if (key &amp;&amp; obj) {
      // Store weak cache
      SD_LOCK(_weakCacheLock);
      ;
      SD_UNLOCK(_weakCacheLock);
    }
}
</pre></div>
<p class="maodian"></p><h2>NSURLCache</h2>
<p>使用缓存的目的是为了使应用程序能更快速的响应用户输入,是程序高效的运行。有时候我们需要将远程web服务器获取的数据缓存起来,以空间换取时间,减少对同一个url多次请求,减轻服务器的压力,优化客户端网络,让用户体验更良好。</p>
<p>背景:NSURLCache : 在iOS5以前,apple不支持磁盘缓存,在iOS5的时候,允许磁盘缓存,(NSURLCache 是根据NSURLRequest 来实现的)只支持http,在iOS6以后,支持http和https。</p>
<p>缓存的实现说明:由于GET请求一般用来查询数据,POST请求一般是发大量数据给服务器处理(变动性比较大),因此一般只对GET请求进行缓存,而不对POST请求进行缓存。</p>
<p>缓存原理:一个NSURLRequest对应一个NSCachedURLResponse</p>
<p>Etag全称是Entity Tag,一般用于标识URL对象是否发生了改变。 使用Etag一般会出现如下的请求流程:</p>
<p>Etag有点类似于文件hash或者说是信息摘要。</p>
<p>当进行一次URL请求,服务端会返回&#39;Etag&#39;响应头,下次浏览器请求相同的URL时,浏览器会自动将它设置为请求头&#39;If-None-Match&#39;的值。服务器收到这个请求之后,就开始做信息校验工作将自己本次产生的Etag与请求传递过来的&#39;If-None-Match&#39;对比,如果相同,则返回HTTP状态码304,并且response数据体中没有数据。</p>
<p>第二次请求的时候从哪里获取到&#39;Etag&#39;的值并赋给请求头&#39;If-None-Match&#39;的?自然是浏览器的缓存中取出的。那么浏览器收到304状态码之后又干了什么?刚才说到response数据体中没有数据,但是浏览器仍需加载页面,它会从缓存中读取上次缓存的页面。</p>
<div class="jb51code"><pre class="brush:cpp;">//
//NSURLCacheViewController.m
//DemoTest2022
//
//Created by wy on 2022/10/19.
//
#import "NSURLCacheViewController.h"
@interface NSURLCacheViewController ()
//去服务器比对资源是否需要更新
@property (nonatomic, strong) NSString *lastModified;
@property (nonatomic, strong) NSString *etag;
@property(nonatomic,strong) UIImageView *imagev;
@end
@implementation NSURLCacheViewController
- (void)viewDidLoad {
    ;
    self.imagev = [ initWithFrame:CGRectMake(0, 100, 100, 100)];
    ;
    // Do any additional setup after loading the view.
}
-(void)requestTest{
    NSURL *url = ;
    NSMutableURLRequest *request = ;
    if (self.lastModified) {
      ;
    }
    if (self.etag) {
      ;
    }
    [[ dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                NSLog(@"error---%@",error);
            }else{
                NSData *tempData = data;
                NSString *responseStr = [ initWithData:tempData encoding:NSUTF8StringEncoding];
                self.lastModified = [(NSHTTPURLResponse *)responseallHeaderFields][@"Last-Modified"];
                self.etag =[(NSHTTPURLResponse *)response allHeaderFields][@"etag"];
                NSLog(@"response:%@", response);
                dispatch_sync(dispatch_get_main_queue(), ^{
                  self.imagev.image = ;
                });
            }
      }] resume] ;
}
- (void)touchesBegan:(NSSet&lt;UITouch *&gt; *)touches withEvent:(UIEvent *)event{
    ;
}
@end
</pre></div>
<p>这样每次请求都使用忽略缓存的策略,但是要附带着&quot;If-None-Match&quot;头,它的值是上次请求的响应头&quot;Etag&quot;的值,于是服务器会每次都实时检查文件的修改状态,得到一个准确的状态值,最后决定返回304还是200。若是200,iOS则直接使用新的response和新的数据;如果是304,则使用新的response和缓存中的data。</p>
<p>这样既能够获取到最新的数据有能够节约带宽。两全其美。</p>
<p class="maodian"></p><h3>iOS中定以的URLRequest缓存策略有以下几种:</h3>
<div class="jb51code"><pre class="brush:cpp;">typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
    NSURLRequestUseProtocolCachePolicy = 0,
    // 从不读取缓存,但请求后将response缓存起来
    NSURLRequestReloadIgnoringLocalCacheData = 1,
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,
    NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
    // 以下两种在取缓存时,可能取到的是过期数据
    NSURLRequestReturnCacheDataElseLoad = 2,// 缓存中没有才去发起请求加载,有就不进行网络请求了
    NSURLRequestReturnCacheDataDontLoad = 3,// 缓存中没有不加载,绝不发起网络请求,缓存中没有则返回错误
    NSURLRequestReloadRevalidatingCacheData = 5,//Unimplemented
};
</pre></div>
<p><strong>官方文档解释:</strong></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202211/20221109091919033.png" /></p>
<p>NSURLCache类通过将NSURLRequest对象映射到NSCachedURLResponse对象来实现URL加载请求响应的缓存。它提供了一个复合的内存和磁盘缓存,并允许您操作内存和磁盘部分的大小。您还可以控制缓存数据持久存储的路径。</p>
<p>在iOS中,当系统磁盘空间不足时,磁盘上的缓存可能会被清除,但只有在应用程序未运行时才会清除。</p>
<p>AFNetwork中用法:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202211/20221109091919034.png" /></p>
<p class="maodian"></p><h2>总结:</h2>
<p>NSCache 特点</p>
<ul><li>使用方便,类似字典</li><li>l线程安全</li><li>l内存不足时,NSCache会自动释放存储对象</li><li>NSCache的key不会被拷贝,不需要实现NSCopying协议</li><li>lNSDiscardableContent协议</li></ul>
<p>NSURLCache主要应用与网络请求数据缓存策略,优化网络请求性能优化。</p>
<p>以上就是iOS NSCache和NSUrlCache缓存类实现示例详解的详细内容,更多关于iOS NSCache NSUrlCache的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>iOS中的NSURLCache数据缓存类用法解析</li><li>iOS开发之1行代码实现缓存计算及清除缓存</li><li>iOS 清除xcode缓存和生成文件的方法</li><li>浅谈iOS UIWebView对H5的缓存功能</li><li>IOS中微信小程序播放缓存的音频文件的方法</li><li>iOS把图片缓存到本地的几种方法(总结)</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: iOS NSCache和NSUrlCache缓存类实现示例详解