NSCache源码阅读与分析

2021-12-07  本文已影响0人  不吃香菜11

NSCache

NSCache一个可变集合,用于临时存储在资源不足时可能被收回的临时键值对。 NSCache的特点:

基于GNUstep源码探索NSCache

GNUstep下载地址github.com/gnustep

打开源码,在headers/Foundation下找到NSCache.h文件

@interface GS_GENERIC_CLASS(NSCache, KeyT, ValT) : NSObject
{
#if GS_EXPOSE(NSCache)
  @private
  NSUInteger _costLimit;//缓存的最大开销,所有对象的内存加起来
  NSUInteger _totalCost;// 当前储存的对象的总开销
  NSUInteger _countLimit;// 缓存对象的最大数量
  /** The delegate object, notified when objects are about to be evicted. */
  id _delegate;
  BOOL _evictsObjectsWithDiscardedContent;//表示是否应该回收废弃内容的标志,默认YES
  NSString *_name;//缓存的名字
  NSMapTable *_objects;//对象的名字与对象的映射, 跟字典很相似,下文会分析
  /** LRU ordering of all potentially-evictable objects in this cache. */
  GS_GENERIC_CLASS(NSMutableArray, ValT) *_accesses;//缓存中可回收对象的LRU算法排序,将按此排序来释放对象
  int64_t _totalAccesses;// 当前对象的总访问次数
  - (NSUInteger) countLimit;
  - (void) setCountLimit: (NSUInteger)lim;
  - (NSUInteger) totalCostLimit;
  - (void) setTotalCostLimit: (NSUInteger)lim;

外界能给开发者使用的只有_countLimit,totalCostLimit和_evictsObjectsWithDiscardedConten

NSMapTable解析

/** Return a map table initialised using the specified options for
 * keys and values.
 */
+ (instancetype) mapTableWithKeyOptions: (NSPointerFunctionsOptions)keyOptions
                           valueOptions: (NSPointerFunctionsOptions)valueOptions;
/** Initialiser using option bitmasks to describe the keys and values.
 */
- (instancetype) initWithKeyOptions: (NSPointerFunctionsOptions)keyOptions
                       valueOptions: (NSPointerFunctionsOptions)valueOptions
                           capacity: (NSUInteger)initialCapacity;

/** Initialiser using full pointer function information to describe
 * the keys and values.
 */
- (instancetype) initWithKeyPointerFunctions: (NSPointerFunctions*)keyFunctions
                       valuePointerFunctions: (NSPointerFunctions*)valueFunctions
                                    capacity: (NSUInteger)initialCapacity;
enum {
  NSPointerFunctionsStrongMemory = (0<<0),//常用 强引用存储对象
  NSPointerFunctionsZeroingWeakMemory = (1<<0),
  NSPointerFunctionsOpaqueMemory = (2<<0),
  NSPointerFunctionsMallocMemory = (3<<0),
  NSPointerFunctionsMachVirtualMemory = (4<<0),
  NSPointerFunctionsWeakMemory = (5<<0),//常用 弱引用存储对象
  NSPointerFunctionsObjectPersonality = (0<<8),
  NSPointerFunctionsOpaquePersonality = (1<<8),
  NSPointerFunctionsObjectPointerPersonality = (2<<8),
  NSPointerFunctionsCStringPersonality = (3<<8),
  NSPointerFunctionsStructPersonality = (4<<8),
  NSPointerFunctionsIntegerPersonality = (5<<8),
  NSPointerFunctionsCopyIn = (1<<16)//常用 copy存储对象
}; 
typedef NSUInteger NSPointerFunctionsOptions;

NSDcitionary或者NSMutableDictionary中对于key和value的内存管理是,对key进行copy,对value进行强引用,只有满足NSCopying协议的对象才能成为key值。

NSMaptable可以通过弱引用来持有keys和values,所以当key或者value被deallocated的时候,所存储的实体也会被移除

然后来到NSCache.m

@interface _GSCachedObject : NSObject
{
  @public
  id object;                // 对象
  NSString *key;        // 键值
  int accessCount;  // 当前对象的访问次数  LRU算法排序的计数
  NSUInteger cost;  // 开销
  BOOL isEvictable; // 是否应该被释放
}
@end

然后找到NSCache到初始化方法

- (id) init
{
  if (nil == (self = [super init]))
    {
      return nil;
    }
  ASSIGN(_objects,[NSMapTable strongToStrongObjectsMapTable]);
  _accesses = [NSMutableArray new];//可变数组存储LRU算法排序的计数
  return self;
}
+ (id) strongToStrongObjectsMapTable
{
  return [self mapTableWithKeyOptions: NSPointerFunctionsObjectPersonality
                         valueOptions: NSPointerFunctionsObjectPersonality];
}
  /** Use the -hash and -isEqual: methods for storing objects, and the
   * -description method to describe them. */
  NSPointerFunctionsObjectPersonality = (0<<8),

缓存方法解析

直接来到NSCache.m,看看设置缓存对象的方法

- (void) setObject: (id)obj forKey: (id)key cost: (NSUInteger)num
{
  _GSCachedObject *oldObject = [_objects objectForKey: key];//上文中有GSCachedObject的定义
  _GSCachedObject *newObject;

  if (nil != oldObject)//判断之前是否储存了相同key值的对象,有则移除旧对象
    {
      [self removeObjectForKey: oldObject->key];
    }
  [self _evictObjectsToMakeSpaceForObjectWithCost: num];//NSCache的自动回收内存算法
  newObject = [_GSCachedObject new];
  newObject->object = RETAIN(obj); //强引用对象
  newObject->key = RETAIN(key);      //强引用key
  newObject->cost = num;
  if ([obj conformsToProtocol: @protocol(NSDiscardableContent)])
    {//判断是否实现了NSDiscardableContent协议,实现了就把它加到lru排序的这个可变数组_accesses里
      newObject->isEvictable = YES;
      [_accesses addObject: newObject];
    }
  [_objects setObject: newObject forKey: key];
  RELEASE(newObject);
  _totalCost += num;//更新总开销
}
- (void)_evictObjectsToMakeSpaceForObjectWithCost: (NSUInteger)cost
{
  NSUInteger spaceNeeded = 0; //需要清除的空间
  NSUInteger count = [_objects count];

  if (_costLimit > 0 && _totalCost + cost > _costLimit)
    {
      spaceNeeded = _totalCost + cost - _costLimit;//计算需要清除的空间
    }

  // 需要清除空间才需要执行以下代码
  if (count > 0 && (spaceNeeded > 0 || count >= _countLimit))
    {
      NSMutableArray *evictedKeys = nil;
    // _totalAccesses / (double)count 总访问次数/缓存对象个数 = 平均访问次数
      NSUInteger averageAccesses = ((_totalAccesses / (double)count) * 0.2) + 1;//计算平均访问次数 关键公式
      NSEnumerator *e = [_accesses objectEnumerator];//获取LRU算法排序后的对象数组
      _GSCachedObject *obj;

      if (_evictsObjectsWithDiscardedContent)//NSCache是否自动回收废弃内容
  {
        evictedKeys = [[NSMutableArray alloc] init];//自动回收则创建evictedKeys,用来存放可回收对象
    }
    
      while (nil != (obj = [e nextObject]))//按LRU排序后的对象数组遍历对象
    {
      // 如果对象的访问次数小于平均访问次数,
      if (obj->accessCount < averageAccesses && obj->isEvictable)
        {
          [obj->object discardContentIfPossible];//标识这个对象是可销毁的,如果计数变量为0时将会释放这个对象
          if ([obj->object isContentDiscarded])
        {
          NSUInteger cost = obj->cost;
          obj->cost = 0;
          obj->isEvictable = NO;
          if (_evictsObjectsWithDiscardedContent)//查看对象是否可回收 可回收则加入可回收数组
            {
              [evictedKeys addObject: obj->key];
            }
          _totalCost -= cost;//当前总开销 减去 已经释放的开销
          if (cost > spaceNeeded)// 释放开销大于需要清除空间时则退出循环
            {
              break;
            }
          spaceNeeded -= cost; //需要清除空间 减去 已经释放的开销
        }
        }
    }
      if (_evictsObjectsWithDiscardedContent)
    {//如果NSCache自动回收废弃内容,则将需自动回收数组的对象都回收
      NSString *key;
      e = [evictedKeys objectEnumerator];
      while (nil != (key = [e nextObject]))
        {
          [self removeObjectForKey: key];//回收对象以后,将对应Key移除
        }
    }
    [evictedKeys release];
    }
}

从以上源码分析,我们可以知道NSCache的自动回收机制的实现是依赖于lru算法的,每当NSCache超出限额需要释放空间时,系统就会按照LRU算法排序后的缓存对象数组进行遍历,通过公式算出平均访问次数,只要缓存对象的访问次数小于平均访问次数就将该缓存对象放进可回收数组中,当释放开销大于需要清除空间时则退出遍历循环,释放可回收对象。

LRU 最少最近使用算法 ,缓存替换时总是替换最少最近使用的对象,保留最近使用的对象。

参考资料:NSCache OC及Swift底层源码详解 - 掘金 (juejin.cn)

上一篇下一篇

猜你喜欢

热点阅读