NSCache的一点小小认识
在iOS开发的过程中,我们经常会遇到一个问题,那就是从网络下载的图片应该如何来存储,首先能够想到的可能就是使用字典把图片保存起来,那么下次再去请求的时候就可以直接使用而不需要下载了,但是使用字典未必是一个好的方案。其实NSCache类更好,因为它是Foundation框架专门为处理缓存而设计的。 NSCacheNSCache是一个类似于集合的容器,它也存储key-value对,这一点类...
在iOS开发的过程中,我们经常会遇到一个问题,那就是从网络下载的图片应该如何来存储,首先能够想到的可能就是使用字典把图片保存起来,那么下次再去请求的时候就可以直接使用而不需要下载了,但是使用字典未必是一个好的方案。其实NSCache类更好,因为它是Foundation框架专门为处理缓存而设计的。
NSCache
NSCache是一个类似于集合的容器,它也存储key-value对,这一点类似于NSDictionary类。我们通常需要缓存一些临时存储、短时间使用、但创建昂贵的对象,通过重用这些对象可以优化应用的性能,因为它们的值不需要重新计算。另外一方面,这些对象对于程序来说不是紧要的,可以在内存紧张时会被丢弃。如果对象被丢弃了,则下次使用时需要重新计算。
当一个key-value对在缓存中时,缓存维护它的一个强引用。存储在NSCache中的通用数据类型通常是实现了NSDiscardableContent协议的对象。在缓存中存储这类对象是有好处的,因为当不再需要它时,可以丢弃这些内容,以节省内存。默认情况下,缓存中的NSDiscardableContent对象在其内容被丢弃时,会被移除出缓存,尽管我们可以改变这种缓存策略。如果一个NSDiscardableContent被放进缓存,则在对象被移除时,缓存会调用discardContentIfPossible方法。
NSCache与可变集合有几点不同:
1:NSCache类结合了各种自动删除策略,以确保不会占用过多的系统内存。如果其它应用需要内存时,系统自动执行这些策略。当调用这些策略时,会从缓存中删除一些对象,以最大限度减少内存的占用。
2:NSCache是线程安全的,我们可以在不同的线程中添加、删除和查询缓存中的对象,而不需要锁定缓存区域。
3:不像NSMutableDictionary对象,一个缓存对象不会拷贝key对象。
这些特性对于NSCache类来说是必须的,因为在需要释放内存时,缓存必须异步地在幕后决定去自动修改自身。下面看一下头文件:
open class NSCache<KeyType, ObjectType> : NSObject where KeyType : AnyObject, ObjectType : AnyObject {
open var name: String
unowned(unsafe) open var delegate: NSCacheDelegate?
open func object(forKey key: KeyType) -> ObjectType?
open func setObject(_ obj: ObjectType, forKey key: KeyType) // 0 cost
open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int)
open func removeObject(forKey key: KeyType)
open func removeAllObjects()
open var totalCostLimit: Int // limits are imprecise/not strict
open var countLimit: Int // limits are imprecise/not strict
open var evictsObjectsWithDiscardedContent: Bool
}
public protocol NSCacheDelegate : NSObjectProtocol {
@available(watchOS 2.0, *)
optional public func cache(_ cache: NSCache<AnyObject, AnyObject>, willEvictObject obj: Any)
}
相关内容如下:
由上可知,NSCache类中东西并不多,NSCache提供了一组方法来存取key-value对,类似于NSMutableDictionary类。我们在存储对象时,可以为对象指定一个消耗值,即存储方法:setObject(obj: AnyObject, forKey key: AnyObject, cost g: Int)中的cost参数,
cost这个消耗值用于计算缓存中所有对象的一个消耗总和
1)当内存受限或者总消耗超过了限定的最大总消耗,则缓存能够开启一个丢弃过程以移除一些对象。不过,这个过程不能保证被丢弃对象的顺序。其结果是,如果我们试图操作这个消耗值来实现一些特殊的行为,则后果可能会损害我们的程序。
2)通常情况下,这个消耗值是对象的字节大小。如果这些信息不是现成的,则我们不应该去计算它,因为这样会使增加使用缓存的成本。如果我们没有可用的值传递,则直接传递0,或者是使用-setObject:forKey:方法,这个方法不需要传入一个消耗值。
countLimit:限定了缓存最多维护的对象的个数
默认值为0,表示不限制数量。但需要注意的是,这不是一个严格的限制。如果缓存的数量超过这个数量,缓存中的一个对象可能会被立即丢弃、或者稍后、也可能永远不会,具体依赖于缓存的实现细节。
totalCostLimit:限定缓存能维持的最大内存
默认值也是0,表示没有限制。当我们添加一个对象到缓存中时,我们可以为其指定一个消耗(cost),如对象的字节大小。如果添加这个对象到缓存导致缓存总的消耗超过totalCostLimit的值,则缓存会自动丢弃一些对象,直到总消耗低于totalCostLimit值。不过被丢弃的对象的顺序无法保证。需要注意的是totalCostLimit也不是一个严格限制,其去除策略是与countLimit一样的。相关例子可以看这里:Objective-C中的缓存
evictsObjectsWithDiscardedContent:该布尔值标识缓存是否自动舍弃那些内存已经被丢弃的对象,如果设置为YES,则在对象的内存被丢弃时舍弃对象。默认值为YES。
NSDiscardableContent协议
@protocol NSDiscardableContent
@required
- (BOOL)beginContentAccess;
- (void)endContentAccess;
- (void)discardContentIfPossible;
- (BOOL)isContentDiscarded;
@end
NSDiscardableContent是一个协议,实现这个协议的目的是为了让我们的对象在不被使用时,可以将其丢弃,以让程序占用更少的内存。
一个NSDiscardableContent对象的生命周期依赖于一个“counter”变量。一个NSDiscardableContent对象实际是一个可清理内存块,这个内存记录了对象当前是否被其它对象使用。如果这块内存正在被读取,或者仍然被需要,则它的counter变量是大于或等于1的;当它不再被使用时,就可以丢弃,此时counter变量将等于0。
当counter变量等于0时,如果当前时间点内存比较紧张的话,内存块就可能被丢弃。为了丢弃这些内容,可以调用对象的discardContentIfPossible方法,这样当counter变量等于0时将会释放相关的内存。而如果counter变量不为0,则该方法什么也不做。
默认情况下,NSDiscardableContent对象的counter变量初始值为1,以确保对象不会被内存管理系统立即释放。从这个点开始,我们就需要去跟踪counter变量的状态。为此。协议声明了两个方法:beginContentAccess和endContentAccess。其中调用beginContentAccess方法会增加对象的counter变量+1,这样就可以确保对象不会被丢弃。当不在需要对象的时候,通过调用endContentAccess方法使变量-1.通常是让对象的counter值变回为0,这样在对象的内容不再被需要时,就要以将其丢弃。
通常,使用NSCache会结合NSDiscardableContent协议,实现了这个协议的类需要在被引用之前,必须调用beginContentAccess来标记为可使用的,如果在使用之前没有调用beiginContentAccess,那么就会抛出异常。在使用结束之后,调用endContentAccess,来标记它为可以被释放的。如果实现了NSDiscardableContent协议的对象放入了NSCache中,那么,在清除它的时候,会调用discardContentIfPossible方法来判断引用状况,没有引用,则销毁。
NSPurgeableData
还有一个类是NSPurgeableData,和NSCache搭配起来使用,效果很好。NSPurgeableData是NSMutableData的子类并遵守了NSDiscardableContent协议。如果某个对象所占用的内存能够根据需要随时丢弃,那么就可以实现该协议所定义的接口,这就是说,当系统资源紧张时,可以把保存NSPurgeableData对象的那块内存释放掉。NSDiscardableContent中定义了isContentDiscarded可以用来查寻相关内存是否已经是否。
如果需要访问某个NSPurgeableData对象,可以调用其beginContentAccess方法,告诉它现在还不应该丢弃自己所占用的内存。用完之后,调用endContentAccess方法,告诉它在必要的时候丢弃自己所占用的内存,这些调用可以嵌套,所以说,™就像递增与递减引用计数所用的方法那样。只有对象的“引用计数”为0时才可以丢弃。
如果将NSPurgeableData对象加入NSCache,那么当该对象为系统所丢弃时,也会自动从缓存中移除。通过NSCache的evictsObjectsWithDiscardedContent属性,可以开启和关闭此功能。
我们也来做一个简单的图片缓存机制:
//创建应该缓存单例
extension NSCache {
class var sharedInstance : NSCache {
struct Static {
static let instance : NSCache = NSCache()
}
return Static.instance
}
}
class ImageLoader:NSObject{
class func downloadImageWithURL(urlString: String, completionHander: (image: UIImage?,url:String) -> Void) {
//异步获取图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
let cacheData = NSCache.sharedInstance.objectForKey(urlString) as? NSData
if let goodData = cacheData {
let imge = UIImage(data: goodData)
//返回主线程
dispatch_async(dispatch_get_main_queue(), {
completionHander(image: imge,url: urlString)
})
return
}
//如果没有图片则网络获取
let session = NSURLSession.sharedSession()
let url = NSURL(string: urlString)
let downloadTask = session.dataTaskWithURL(url!, completionHandler: { (data, res, error) in
guard error == nil && data != nil else {
completionHander(image: nil,url: urlString)
return
}
let image = UIImage(data: data!)
NSCache.sharedInstance.setObject(data!, forKey: urlString)
dispatch_async(dispatch_get_main_queue(), {
completionHander(image: image, url: urlString)
})
})
downloadTask.resume()
}
}
}
虽然NSCache用起来比较简单,但还需要注意几个地方:
1:不同的NSCache对象管理的缓存是不同的,不能只通过名称来区别。同一个名称对应的是不同的缓存。所以,如果你需要在多个地方管理同一个缓存对象,要么把对象传递过去使用,要么使用单例来解决这个问题。
2:系统对缓存的清理并不是必须的。如果你设定了缓存上限,那么超过上限时系统便会自动清理。如果没有设定上限,则系统只有在内存警告发生的时候才会去清理这些内存。
3:NSCache的缓存如果设定了 cost,清理缓存时并不保证移除顺序,也不会由于谁的 cost比较小就清除谁,所以如果你想使用cost,那就要注意好这些规则。
4:NSCache并不会复制对象,而只是对要缓存的对象做了强引用,所以你的缓存对象并不需要实现NSCopying协议,想想都比较开心。
5:NSCache只能缓存到内存中,如果下次启动,缓存对象被释放,这些缓存也会被释放。
构建缓存时选用NSCache而非NSDictionary中的几个要点:
1、实现缓存时应选用NSCache而非NSDictionary对象。因为NSCache可以提供优雅的自动删减功能,而且是“线程安全的”,NSDictionary并不具备此优势,要明白对于缓存来说,线程的安全是非常重要的。此外,它与字典不同,并不会拷贝键。
2、可以给NSCache对象设置上限,用以限制缓存中的对象总个数及“总成本”,而这些尺度则定义了缓存删减其中对象的时机,。但是绝对不要把这些尺度当成可靠的“硬限制”(hard limit),它们仅对NSCache起指导作用。
3、将NSPurgeableData与NSCache搭配使用,可以实现自动清除数据的功能,也就是说将NSPurgeableData对象所占用内存为系统所丢弃时,该对象自身也会从缓存中移除。
4、如果缓存使用得当,那么应用程序的响应速度就能提高。只有那种“重新计算起来很费事的”数据,才值得放入缓存,比如那些需要从网络获取或从磁盘读取的数据。
参考文章:
更多推荐
所有评论(0)