NSCache的一点小小认识

6 篇文章 0 订阅
5 篇文章 0 订阅

在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、如果缓存使用得当,那么应用程序的响应速度就能提高。只有那种“重新计算起来很费事的”数据,才值得放入缓存,比如那些需要从网络获取或从磁盘读取的数据。

 

参考文章:

NSCache Class Reference

NSDiscardableContent

NSCache 源码分析  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: iOS内存管理版本记录如下: 1. iOS 2.0及更早版本:使用手动管理内存的方式。 2. iOS 3.0:引入了基于引用计数的自动内存管理,使用retain和release函数来增加或减少对象的引用计数。 3. iOS 5.0:引入了ARC(自动引用计数)机制,ARC会在编译时自动插入retain和release代码,减少手动管理内存的工作。 4. iOS 7.0:引入了内存诊断工具Memory Usage Report,可以监测App内存使用情况,帮助开发者优化内存管理。 5. iOS 8.0:引入了一些新的API,如NSCacheNSURLSession,使得内存管理更加方便和灵活。 6. iOS 11.0:引入了基于图片大小的UIImage渲染机制,减少了内存占用。 7. iOS 13.0:引入了叫做“Scene”的多任务环境,使得内存管理更加复杂,需要更加小心谨慎地处理内存问题。 总的来说,随着iOS版本的不断更新,内存管理的机制也在不断地完善和优化,使得iOS应用能够更加高效地使用内存,提高用户体验。 ### 回答2: iOS的内存管理是由操作系统自动管理的,在不同的版本中有所不同。 在iOS 5之前的版本中,内存管理主要依赖于手动管理引用计数(reference counting)来管理对象的生命周期。开发者需要手动调用retain和release方法来增加或减少对象的引用计数,以确保对象在不再需要时能够被正确释放。这种方式需要开发者非常谨慎地管理对象的引用,以避免内存泄漏或野指针等问题。 从iOS 5开始,iOS引入了自动引用计数(Automatic Reference Counting,ARC)的内存管理机制。ARC可以自动地插入retain、release和autorelease等方法的调用,使得开发者不再需要手动进行内存管理。开发者只需要关注对象的创建和使用,而不需要关心具体的内存管理细节。ARC减少了内存管理的工作量,提高了开发效率,并且减少了内存泄漏和野指针等问题的发生。不过,ARC并不是完全的自动化内存管理,开发者仍然需要遵循一些规则,比如避免循环引用等,以保证内存的正确释放。 随着iOS版本的不断更新,苹果不断改进和优化内存管理机制。每个新版本都带来了更好的性能和更高效的内存管理。开发者可以通过关注苹果的官方文档和开发者社区中的更新内容来了解每个版本中的具体变化和改进。 总结来说,iOS的内存管理从手动的引用计数到自动引用计数的演变,极大地简化了开发者的工作,并提高了应用的性能和稳定性。随着不断的改进和优化,iOS的内存管理会越来越高效和可靠。 ### 回答3: iOS内存管理版本记录是指苹果公司在不同版本的iOS操作系统中对于内存管理方面的改进和更新记录。随着iOS版本的不断迭代,苹果在内存管理方面进行了一系列的优化和改进,以提高系统的稳定性和性能。 首先,在早期的iOS版本中,苹果采用了手动内存管理的方式,即开发人员需要手动创建和释放内存,容易出现内存泄漏和内存溢出等问题。为了解决这些问题,苹果在iOS5版本中引入了自动引用计数(ARC)机制。ARC机制能够通过编译器自动生成内存管理代码,避免了手动管理内存带来的问题。 其次,iOS6版本引入了内存分页机制。这个机制能够将应用程序内存分成不同的页,将不常用的页置于闲置列表中,从而释放出更多的内存空间。这些闲置列表中的页能够在需要时快速恢复到内存中,减少了内存压力。 此外,iOS7版本中进一步提升了内存管理的能力。苹果在这个版本中引入了内存压缩技术,将内存中的数据进行压缩,从而提高了内存利用率。此外,iOS7还引入了资源清理功能,可以自动清理不再使用的资源,释放内存空间。 最后,在iOS13版本中,苹果进一步改进了内存管理策略。该版本中引入了后台内存优化功能,能够自动优化应用在后台运行时的内存占用,减少了后台应用对于系统内存的占用和影响。 综上所述,iOS内存管理版本记录反映了苹果在不同版本的iOS操作系统中对于内存管理方面的改进和优化。这些改进和优化使得iOS系统更加稳定和高效,并且提升了应用程序的性能和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值