Objective C 中的实例缓存

发布于 2024-07-11 01:12:37 字数 1052 浏览 4 评论 0原文

我想缓存某个类的实例。 该类保留其所有实例的字典,当有人请求新实例时,该类会首先尝试满足缓存中的请求。 不过,内存管理存在一个小问题:字典缓存会保留插入的对象,因此它们永远不会被释放。 我确实希望它们被释放,因此我必须重载 release 方法,并且当保留计数降至 1 时,我可以从缓存中删除实例并让它被释放。

这是可行的,但我不喜欢乱搞 release 方法,并发现解决方案过于复杂。 我想我可以使用一些不保留它存储的对象的哈希类。 有这样的吗? 这个想法是,当某个实例的最后一个用户释放它时,该实例会自动从缓存中消失。

NSHashTable 似乎是我正在寻找的,但文档谈到“在垃圾收集环境中支持弱关系”。 没有垃圾回收也能工作吗?


澄清:除非有人真正需要它们,否则我无法将实例保留在内存中,这就是为什么我想在最后一个“真实”用户释放实例时从缓存中清除实例。


更好的解决方案: 这是在 iPhone 上,我想缓存一些纹理,另一方面我想在最后一个真正的持有者释放它们后立即将它们从内存中释放。 更简单的编码方法是通过另一个类(我们称之为 TextureManager)。 此类管理纹理实例并缓存它们,以便从缓存中提供对同名纹理的后续调用。 当最后一个用户释放纹理时,不需要立即清除缓存。 我们可以简单地将纹理缓存在内存中,当设备内存不足时,我们会收到内存不足警告并可以清除缓存。 这是一个更好的解决方案,因为缓存内容不会污染 Texture 类,我们不必搞乱 release 并且缓存命中的机会甚至更高。 TextureManager 可以抽象为 ResourceManager,这样它就可以缓存其他数据,而不仅仅是纹理。

I want to cache the instances of a certain class. The class keeps a dictionary of all its instances and when somebody requests a new instance, the class tries to satisfy the request from the cache first. There is a small problem with memory management though: The dictionary cache retains the inserted objects, so that they never get deallocated. I do want them to get deallocated, so that I had to overload the release method and when the retain count drops to one, I can remove the instance from cache and let it get deallocated.

This works, but I am not comfortable mucking around the release method and find the solution overly complicated. I thought I could use some hashing class that does not retain the objects it stores. Is there such? The idea is that when the last user of a certain instance releases it, the instance would automatically disappear from the cache.

NSHashTable seems to be what I am looking for, but the documentation talks about “supporting weak relationships in a garbage-collected environment.” Does it also work without garbage collection?


Clarification: I cannot afford to keep the instances in memory unless somebody really needs them, that is why I want to purge the instance from the cache when the last “real” user releases it.


Better solution: This was on the iPhone, I wanted to cache some textures and on the other hand I wanted to free them from memory as soon as the last real holder released them. The easier way to code this is through another class (let’s call it TextureManager). This class manages the texture instances and caches them, so that subsequent calls for texture with the same name are served from the cache. There is no need to purge the cache immediately as the last user releases the texture. We can simply keep the texture cached in memory and when the device gets short on memory, we receive the low memory warning and can purge the cache. This is a better solution, because the caching stuff does not pollute the Texture class, we do not have to mess with release and there is even a higher chance for cache hits. The TextureManager can be abstracted into a ResourceManager, so that it can cache other data, not only textures.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

旧人 2024-07-18 01:12:37

是的,您可以使用 NSHashTable 来构建本质上非保留的字典。 或者,您可以使用 NULL 调用 CFDictionaryCreate 来释放和保留回调。 然后,您可以通过免费桥接将结果简单地转换为 NSDictionary,并像普通 NSDictionary 一样使用它,除了不摆弄保留计数之外。

如果您这样做,字典将不会自动将引用归零,您需要确保在释放实例时将其删除。

Yes, you can use an NSHashTable to build what is essentially a non-retaining dictionary. Alternatively, you can call CFDictionaryCreate with NULL for release and retain callbacks. You can then simply typecast the result to a NSDictionary thanks to tollfree bridging, and use it just like a normal NSDictionary except for not fiddling with retain counts.

If you do this the dictionary will not automatically zero the reference, you will need to make sure to remove it when you dealloc an instance.

琴流音 2024-07-18 01:12:37

你想要的是一个归零弱引用(它不是“缓存管理算法的格雷尔”,它是一个众所周知的模式)。 问题在于,Objective C 仅在运行垃圾收集时才提供将弱引用清零的功能,而不是在手动内存管理程序中。 而且 iPhone 还不提供垃圾回收功能。

到目前为止所有的答案似乎都指向了一半的解决方案。

使用非保留引用是不够的,因为当引用的对象被释放时,您需要将其清零(或从字典中删除条目)。 然而,这必须在调用该对象的 -dealloc 方法之前完成,否则缓存的存在会让您面临对象复活的风险。 执行此操作的方法是在创建弱引用时动态子类该对象,并在动态创建的子类中重写 -release 以使用锁,并重写 -dealloc 将弱引用归零。

这通常是有效的,但对于免费桥接的 Core Foundation 对象来说却严重失败。 不幸的是,如果您需要将该技术扩展到免费桥接对象,则唯一的解决方案需要一些黑客攻击和未记录的内容(请参阅此处获取代码和说明),因此不适用于 iOS 或您想要销售的程序Mac 应用商店。

如果您需要在 Apple 商店上销售,因此必须避免未记录的内容,那么最好的替代方案是实现对保留缓存的锁定访问,然后在您想要释放内存时清除当前 -retainCount 值为 1 的引用。 只要对缓存的所有访问都是在持有锁的情况下完成的,如果您在持有锁时观察到计数为 1,您就知道如果您从缓存中删除对象(因此释放它),就没有人可以复活该对象)在放弃锁之前。

对于 iOS,您可以使用 UIApplicationDidReceiveMemoryWarningNotification 来触发清理。 在 Mac 上,您需要实现自己的逻辑:可能只是定期检查,甚至只是定期清理(两种解决方案也适用于 iOS)。

What you want is a zeroing weak reference (it's not a "Graal of cache managing algorithms", it's a well known pattern). The problem is that Objective C provides you with zeroing weak references only when running with garbage collection, not in manual memory managed programs. And the iPhone does not provide garbage collection (yet).

All the answers so far seem to point you to half-solutions.

Using a non-reataining reference is not sufficient because you will need to zero it out (or remove the entry from the dictionary) when the referenced object is deallocated. However this must be done BEFORE the -dealloc method of that object is called otherwise the very existence of the cache expose you to the risk that the object is resurrected. The way to do this is to dynamically subclass the object when you create the weak reference and, in the dynamically created subclass, override -release to use a lock and -dealloc to zero out the weak reference(s).

This works in general but it fails miserably for toll-free bridged Core Foundation objects. Unfortunately the only solution, if you need to to extend the technique to toll-free bridged objects, requires some hacking and undocumented stuff (see here for code and explanations) and is therefore not usable for iOS or programs that you want to sell on the Mac App Store.

If you need to sell on the Apple stores and must therefore avoid undocumented stuff, your best alternative is to implement locked access to a retaining cache and then scavenge it for references with a current -retainCount value of 1 when you want to release memory. As long as all accesses to the cache are done with the lock held, if you observe a count of 1 while holding the lock you know that there's no-one that can resurrect the object if you remove it from the cache (and therefore release it) before relinquishing the lock.

For iOS you can use UIApplicationDidReceiveMemoryWarningNotification to trigger the scavenging. On the mac you need to implement your own logic: maybe just a periodical check or even simply a periodical scavenging (both solutions would also work on iOS).

不醒的梦 2024-07-18 01:12:37

我刚刚通过使用 NSMutableDictionary 并注册 UIApplicationDidReceiveMemoryWarningNotification 来实现这种事情。 出现内存警告时,我从字典中删除了保留计数为 1 的所有内容...

I've just implemented this kind of thing by using an NSMutableDictionary and registering for UIApplicationDidReceiveMemoryWarningNotification. On a memory warning I remove anything from the dictionary with a retainCount of 1...

彩虹直至黑白 2024-07-18 01:12:37

使用 [NSValue valueWithNonretainedObject:] 将实例包装在 NSValue 中并将其放入字典中。 在实例dealloc方法中,从字典中删除相应的条目。 不要搞乱保留。

Use [NSValue valueWithNonretainedObject:] to wrap the instance in an NSValue and put that in the dictionary. In the instance dealloc method, remove the corresponding entry from the dictionary. No messing with retain.

够钟 2024-07-18 01:12:37

我的理解是,您想要实现缓存管理算法的 Graal:删除不再使用的项目。

您可能需要考虑其他标准,例如删除最近最少请求的项目。

My understanding is that you want to implement the Graal of cache managing algorithms: drop items that will no longer be used.

You may want to consider other criteria, such as dropping the least recently requested items.

故人爱我别走 2024-07-18 01:12:37

我认为解决这个问题的方法是在某处维护一个单独的计数或标志来指示缓存中的对象是否正在使用。 然后,您可以在处理完对象后检查这一点,或者每 n 秒运行一次检查以查看是否需要释放它。

我会避免任何涉及在从字典中删除对象之前释放对象的解决方案(使用 NSValue 的 valueWithNonretainedObject: 是实现此目的的另一种方法)。 从长远来看,这只会给你带来问题。

I think the way I would approach this is to maintain a separate count or a flag somewhere to indicate if the object in the cache is being used or not. You could then check this when you're done with an object, or just run a check every n seconds to see if it needs to be released or not.

I would avoid any solution involving releasing the object before removing it from the dictionary (using NSValue's valueWithNonretainedObject: would be another way to accomplish this). It would just cause you problems in the long run.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文