1.概念:NSCache缓存策略中主要使用的是_GSCachedObject类,下图是_GSCachedObject的源码,定义中重点的分别是缓存的访问次数,缓存当前消耗的大小,是否能够被清除的标记
创新互联公司从2013年创立,是专业互联网技术服务公司,拥有项目网站设计、成都网站设计网站策划,项目实施与项目整合能力。我们以让每一个梦想脱颖而出为使命,1280元泰安做网站,已为上家服务,为泰安各地企业和个人服务,联系电话:18980820575
2.核心缓存策略源码
- (void)_evictObjectsToMakeSpaceForObjectWithCost: (NSUInteger)cost
{
/** 计算需要清除的空间 */
NSUInteger spaceNeeded = 0;
/** 缓存数量 */
NSUInteger count = [_objects count];
if (_costLimit 0 _totalCost + cost _costLimit)
{
/** spaceNeeded:计算需要清除的空间 = _totalCost:总的消耗内存大小+已有的 -_costLimit:限制消耗大小 */
spaceNeeded = _totalCost + cost - _costLimit;
}
// Only evict if we need the space.
if (count 0 (spaceNeeded 0 || count = _countLimit))
{
NSMutableArray *evictedKeys = nil;
// Round up slightly.
/**
averageAccesses:平均访问次数
_totalAccesses:所有访问次数的总和
count:内存中的缓存对象
几乎淘汰一半的缓存,所以乘0.2
(_totalAccesses / (double)count) * 0.2)可能为0,所以+1
*/
NSUInteger averageAccesses = ((_totalAccesses / (double)count) * 0.2) + 1;
NSEnumerator *e = [_accesses objectEnumerator];
_GSCachedObject *obj;
if (_evictsObjectsWithDiscardedContent)
{
evictedKeys = [[NSMutableArray alloc] init];
}
while (nil != (obj = [e nextObject]))
{
// Don't evict frequently accessed objects.
/** 当前的访问次数 是否小于平均的访问次数 且 当前对象可移除 */
if (obj-accessCount averageAccesses obj-isEvictable)
{
/** 发送通知,释放内存 */
[obj-object discardContentIfPossible];
if ([obj-object isContentDiscarded])
{
NSUInteger cost = obj-cost;
// Evicted objects have no cost.
obj-cost = 0;
// Don't try evicting this again in future; it's gone already.
obj-isEvictable = NO;
// Remove this object as well as its contents if required
if (_evictsObjectsWithDiscardedContent)
{
[evictedKeys addObject: obj-key];
}
_totalCost -= cost;
// If we've freed enough space, give up
/** 消耗的 需要清除的空间 */
if (cost spaceNeeded)
{
break;
}
spaceNeeded -= cost;
}
}
}
// Evict all of the objects whose content we have discarded if required
if (_evictsObjectsWithDiscardedContent)
{
NSString *key;
e = [evictedKeys objectEnumerator];
while (nil != (key = [e nextObject]))
{
[self removeObjectForKey: key];
}
}
[evictedKeys release];
}
}
2.淘汰策略:在while循环中通过比对cost spaceNeeded来进行缓存对象obj的移除
open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int) {
let g = max(g, 0)
let keyRef = NSCacheKey(key)
/** 加锁 */
_lock.lock()
let costDiff: Int
/** 查找缓存列表中是否有key对应的缓存对象,有的话替换 */
/**
_entries:
key:NSCacheKey
value:NSCacheEntry
*/
if let entry = _entries[keyRef] {
costDiff = g - entry.cost
entry.cost = g
entry.value = obj
if costDiff != 0 {
/** 移除旧的缓存 */
remove(entry)
/** 添加新的缓存 */
insert(entry)
}
} else {
let entry = NSCacheEntry(key: key, value: obj, cost: g)
_entries[keyRef] = entry
insert(entry)
costDiff = g
}
_totalCost += costDiff
/** totalCostLimit:所有消耗大小限制 ,_totalCost:当前消耗,*/
var purgeAmount = (totalCostLimit 0) ? (_totalCost - totalCostLimit) : 0
while purgeAmount 0 {
/** head:链表头结点 */
if let entry = _head {
/** 回调函数 */
delegate?.cache(unsafeDowncast(self, to:NSCacheAnyObject, AnyObject.self), willEvictObject: entry.value)
_totalCost -= entry.cost
purgeAmount -= entry.cost
/** 移除缓存对象 */
remove(entry) // _head will be changed to next entry in remove(_:)
/** 表中entry对应的key也置为nil */
_entries[NSCacheKey(entry.key)] = nil
} else {
break
}
}
/** countLimit:缓存数量限制,_entries.count:当前缓存对象的数量 */
var purgeCount = (countLimit 0) ? (_entries.count - countLimit) : 0
while purgeCount 0 {
/** head:链表头结点 */
if let entry = _head {
/** 回调函数 */
delegate?.cache(unsafeDowncast(self, to:NSCacheAnyObject, AnyObject.self), willEvictObject: entry.value)
_totalCost -= entry.cost
/** 每移除一次,缓存数量 - 1 */
purgeCount -= 1
/** 移除缓存对象 */
remove(entry) // _head will be changed to next entry in remove(_:)
/** 表中entry对应的key也置为nil */
_entries[NSCacheKey(entry.key)] = nil
} else {
break
}
}
/** 解锁 */
_lock.unlock()
}
通过insert函数可以看出,通过cost排序,在外部会优先删除占用内存小的缓存对象
swift缓存策略:
1.通过totalCostLimit所有的消耗大小限制和当前总消耗大小做比对,大于零进行while循环移除entry缓存对象
2.通过countLimit缓存数量限制和当前缓存对象的数量大小做差值,大于零进行while循环移除entry缓存对象
当我们使用webview加载html资源时,本质上就是向服务器索取资源的http请求过程,如果我们不注意资源的缓存策略的话,就可能会造成这样那样的问题,比如:实时性要求较高的功能却老是走缓存不更新,有些基本不会变动的页面却又每次都重新去服务器拉请求。
iOS自带的缓存策略,提供了一个内存和磁盘混合的缓存,一共有7种缓存策略,使用较多的是其中的四种( 下方编号1,2,5,6 )
上面介绍了iOS自带的缓存控制 NSURLRequestCachePolicy ,也说到当 NSURLRequestCachePolicy 设为默认的 NSURLRequestUseProtocolCachePolicy 时,主要是根据http的缓存策略来决定是否使用缓存。
那么就简单的介绍一下,http的缓存控制和缓存校验。
在http中,控制缓存开关的字段有两个,Pragma和Cache-Control
Pragma有两个字段no-cache和expires,当pragma为no-cache时表示禁用缓存,expires的值是一个GMT时间,表示该缓存的有效时间。但是已经被逐步抛弃了,有些网站为了向下兼容还保留了这两个字段。
Cache-Control除了在响应中使用,在请求中也可以使用。
在请求中使用,Cache-Control可选的值有:
在响应中使用,Cache-Control可选的值有:
在缓存中,我们需要一个机制来验证缓存是否有效。比如服务器的资源更新了,客户端需要及时刷新缓存;又或者客户端的资源过了有效期,但服务器上的资源还是旧的,此时不需要重新发送。缓存校验就是用来解决这些问题的,在http1.1中,主要关注下 Last-Modified 和 etag 这两个字段。
服务端在返回资源时,会将该资源的最后更改时间通过 Last-Modified 字段返回给客户端。客户端下次请求时通过 If-Modified-Since 或者 If-UnModified-Since 带上 Last-Modified ,服务端检查该时间是否与服务器的最后修改时间一致:如果一致,则返回304状态码,不反悔资源;如果不一致,则返回200和修改后的资源,并带上新的时间。
单纯的以修改时间来判断还是有缺陷,比如文件的最后修改时间变了,但内容没变。对于这样的情况,我们可以使用etag来处理。
etag的方式是这样:服务器通过某个算法对资源进行计算,取得一串值(类似于文件的md5值),之后将该值通过etag返回给客户端,客户端下次请求时通过If-None-Match或If-Match带上该值,服务器对该值进行对比校验:如果一致则不要返回资源。
当我们的webview缓存到一定的峰值的时候,需要手动的清除一下wenview的缓存,方法如下:
找出web缓存的路径,清空该路径
webKit除了清除缓存的API
觉得有用,请帮忙点亮红心
Better Late Than Never!
努力是为了当机会来临时不会错失机会。
共勉!
文章部分出于自己的理解,有不对的地方,希望大家指正。
实例对象我们可以看作是一个指针,实例对象通过isa指针指向类对象,类对象通过isa指针指向元类对象,类对象和元类对象本身其实都是objc_class结构体,里面存放着我们需要的方法列表等。
那么当我们方法调用的时候,方法是如何缓存的呢?
当我们方法调用的时候,首先通过isa指针找到类对象,然后在类对象的方法列表里面查找相对应的方法,找到以后会加入到我们方法缓存里面,等下次我们再调用的时候,首先会先从缓存里面去查找相关方法,更加的效率。
那如果我们调用的是父类里面的方法呢,其实父类对象里面的方法是不可以缓存到子类方法缓存列表里面的,所以,当我们调用的是父类对象里面的方法的时候,其方法不会在子类对象方法缓存列表里面进行缓存。
元类对象的相关缓存同类对象。
至于缓存扩容和具体的缓存策略:
在arm64结构,也就是真机环境下,刚开始初始化的缓存方法的容器的长度2,当容器的长度小于8时,是满容量了才扩容。当容器的长度大于8时,是7/8扩容。也就是说当容器的长度为8时,容器可以存储8个方法。当容器的长度为16时,当第15个方法需要存储进来的时候,容器就要扩容了。
在x86_64架构下,刚开始初始化的容器的长度为4,是3/4扩容。这里的3/4扩容指的是:如果容器的长度为4,当第3个数据需要存储的时候,就要扩容了。如果容器的长度为8,当第6个数据需要存储的时候,就要扩容了。也就是说容器只能存储容器长度的3/4减1个方法。
还有一点就是:当容器扩容之后,前面存储的方法也会随之清空。
参考链接:
首先, SDWebImage 的图片缓存采用的是 Memory (内存) 和 Disk (硬盘) 双重 Cache 机制, SDImageCache 中有一个叫做 memCache 的属性,它是一个 NSCache 对象,用于实现我们对图片的 Memory Cache ,其实就是接受系统的内存警告通知,然后清除掉自身的图片缓存。 Disk Cache ,也就是文件缓存, SDWebImage 会将图片存放到 NSCachesDirectory 目录中,然后为每一个缓存文件生成一个 md5 文件名, 存放到文件中。 整体机制如下:
原文地址