ios开发缓存策略,ios缓存机制

iOS NSCache 缓存策略

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缓存对象

iOS web缓存策略以及手动清除缓存

当我们使用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!

努力是为了当机会来临时不会错失机会。

共勉!

ios中关于方法缓存cache策略

文章部分出于自己的理解,有不对的地方,希望大家指正。

实例对象我们可以看作是一个指针,实例对象通过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个方法。

还有一点就是:当容器扩容之后,前面存储的方法也会随之清空。

参考链接:

iOS面试题:简单的描述一下 SDWebImage的缓存策略?

首先, SDWebImage 的图片缓存采用的是 Memory (内存) 和 Disk (硬盘) 双重 Cache 机制, SDImageCache 中有一个叫做 memCache 的属性,它是一个 NSCache 对象,用于实现我们对图片的 Memory Cache ,其实就是接受系统的内存警告通知,然后清除掉自身的图片缓存。 Disk Cache ,也就是文件缓存, SDWebImage 会将图片存放到 NSCachesDirectory 目录中,然后为每一个缓存文件生成一个 md5 文件名, 存放到文件中。 整体机制如下:

原文地址


本文名称:ios开发缓存策略,ios缓存机制
URL分享:http://ybzwz.com/article/dsggojp.html