奇怪的NSCache驱逐行为

时间:2011-04-04 17:00:39

标签: objective-c cocoa caching memory

我一直在使用NSCache来存储因性能原因而应该缓存的项目,因为重新创建它们相当昂贵。理解NSCache行为让我有些头疼。

我按如下方式初始化我的NSCache:

   _cellCache = [[NSCache alloc] init];
   [_cellCache setDelegate:self];
   [_cellCache setEvictsObjectsWithDiscardedContent:NO];   // never evict cells

缓存中保存的对象实现NSDiscardableContent协议。现在我的问题是NSCache类在几个实例中似乎没有正常工作。

1)首先是较小的问题。 NSCache的setCountLimit:声明:

  

将计数限制设置为小于或等于0的数字将不会影响缓存的最大大小。

有人能说清楚这意味着什么吗?即,NSCache是​​最大的,只有在其他地方需要内存时才会发出 discardContentIfPossible 消息?或者NSCache是​​最小的,它会立即发出 discardContentIfPossible 消息?

前者更有意义,但测试似乎表明后者正在发生的事情。如果我在缓存对象中记录对 discardContentIfPossible 方法的调用,我发现它几乎立即被调用 - 在缓存中只添加了24个项目后(每个项目少于0.5) MB)。

好。所以我尝试设置一个大的计数限制 - 比我需要的更多 - 通过添加以下行:

   [_cellCache setCountLimit:10000000];

然后几乎不会立即发送 discardContentIfPossible 消息。我必须将更多内容加载到缓存中并在这些消息开始发生之前使用它一段时间,这更有意义。

那么这里的预期行为是什么?

2)更大的问题。文档说明:

  

默认情况下,如果丢弃其内容,则会自动从缓存中删除缓存中的NSDiscardableContent对象,但可以更改此自动删除策略。如果将NSDiscardableContent对象放入缓存中,则缓存在删除时会调用 discardContentIfPossible

所以我将驱逐政策设置为NO(如上所述),因此对象永远不会被驱逐。相反,当调用 discardContentIfPossible 时,我会根据特殊条件清除并释放缓存对象的内部数据。也就是说,我可能决定在某些情况下不会实际清除和丢弃数据(例如,如果该项目最近已被使用过)。在这种情况下,我只是从 discardContentIfPossible 方法返回而没有丢弃任何东西。这个想法是,最近没有使用的其他一些对象会在某个时刻获得消息,而且它可以丢弃它的内容。

现在,有趣的是,所有这些似乎都适用于一段时间的大量使用。加载大量内容,放置和访问缓存中的对象等。经过一段时间,当我尝试访问NSCache中的任意对象时,它实际上并不存在!不知何故,它似​​乎已被删除 - 即使我专门将驱逐政策设置为NO。

好。因此,实现委托方法 cache:willEvictObject:表明它永远不会被调用。这意味着该对象实际上并没有被逐出。但是它从NSCache中神秘地消失了,因为它在将来的查找中找不到 - 或者它与之关联的键不再映射到原始对象。但是怎么会发生这种情况呢?

顺便说一下,我与我的值对象(CALayer)关联的关键对象是NSURL容器/包装器,因为我不能直接使用NSURL,因为NSCache不复制密钥 - 只保留它们,并且以后用于查找的NSURL密钥可能不是最初用于加载具有该对象的缓存的完全原始对象(仅相同的URL字符串)。使用NSURL包装器/容器,我可以确保它是用于将原始值对象添加到NSCache的确切原始密钥对象。

任何人都可以对此有所了解吗?

1 个答案:

答案 0 :(得分:13)

你的评论“从不驱逐细胞”不是-setEvictsObjectsWithDiscardedContent:所说的。它说它不会因为它们的内容被丢弃而驱逐细胞。这与说永远不会驱逐细胞不一样。您仍然可以超过最大尺寸或数量,但仍然可以逐出。可丢弃内容的优点是您可以放弃某些内容而无需从缓存中删除自己。然后,您将根据需要重建丢弃的内容,但可能不必重建整个对象。在某些情况下,这可能是一场胜利。

这就把我们带到了你的第一个问题。是的,NSCache在达到最大尺寸时开始逐出。这就是它被称为“最大尺寸”的原因。您没有指出这是Mac还是iPhone,但在这两种情况下,您都不希望缓存增长,直到内存耗尽。这在许多方面都很糟糕。在Mac上,你将在内存耗尽之前开始长时间交换。在iOS中,您不希望开始向每个其他进程发送内存警告,因为一个进程对其缓存感到疯狂。在任何一种情况下,太大的缓存都会提供较差的性能。所以放入NSCache的对象应该总是在任何时候被驱逐。