iOS NSCache内存优化
来源:互联网 发布:hadoop ssh端口配置 编辑:程序博客网 时间:2024/06/06 02:21
做Java语言开发的都知道LruCache类, iOS与其对应的就是NSCache, 它俩功能类似但实现原理不同。 在Xcode API Reference里有NSCache类的详细介绍, 简要介绍一下:
1、NSCache是苹果提供的缓存类,主流的三方图片框架都用它,例如OC版的SDWebImage和Swift版的Kingfisher。
2、NSCache在系统内存低时,会自动释放一些对象(有点像Android的弱引用);但实际编码过程中,推荐在低内存的警告函数里主动释放NSCache对象。
3、NSCache自动释放内存的算法是不确定的, 有时是按照LRU释放,有时随机释放;算是个坑,要重点关注!!!
4、NSCache的API是线程安全的, 不用担心多线程并发问题;
5、NSCache是Key-Value数据结构,其中key是强引用,不实现NSCoping协议。
NSCache类重要属性:
1、countLimit: 就是缓存对象的总数,默认值是0,即不限制;
2、totalCostLimit: 缓存对象的总大小,超出上限时会先释放足够的内存空间出来再执行插入操作;
3、delegate协议属性:缓存的value对象可以实现该NSDiscardableContent
协议,当NSCache要释放对象时会执行回调函数,在回调函数里释放value对象申请的其它资源。
场景: Android ListView的item有图片,如果不做处理,滑动列表时内存会一直增长(大多数app都用了图片库,但忽视了其内存回收的原理。简要说就是注册ListView的recycleListener或者重载控件的onDetach函数,控件释放图片的引用)。
那么iOS会不会出现类似的问题呢? 写个tableview,10000条记录,每条记录都是1个图片和1行文字, 快速滑动列表并观察内存变化, 发现内存并没增长!!!
这里要注意获取UIImage对象时的区别:
1、使用named方式iOS会将图片缓存到内存, 以文件名为关键字在内存缓存中查,查不到时才从资源里读文件;优点是频繁访问时速度快,缺点是吃内存;
2、使用contensOfFile方式就是每次都从资源里读文件,不做内存缓存;
//let image = UIImage(named: "image1") //iOS会将图片缓存到内存,下次先通过名字从内存查
let path = Bundle.main.path(forResource: "image1", ofType: "png" )
let image = UIImage(contentsOfFile: path!) //不缓存,每次都读文件和解码
因为是要验证是否内存增长,所以在demo中使用contentsOfFile方式。
demo中扩展了UIImage类并实现NSDiscardableContent协议, 查询滑动列表时的日志发现。 NSCache在释放value对象时,会执行value的discardContentIfPossible, 从名字就可以看出该回调函数的作用是释放value对象申请的资源。
cache release: <_TtCC12NSCacheTable14ViewController8ImageExt: 0x7c8391f0>, {560, 398}discardContentIfPossible: 1cache release: <_TtCC12NSCacheTable14ViewController8ImageExt: 0x7c8de1c0>, {560, 398}discardContentIfPossible: 1cache release: <_TtCC12NSCacheTable14ViewController8ImageExt: 0x7c734ba0>, {560, 398}discardContentIfPossible: 1
测试代码:
class ViewController: UIViewController, UITableViewDataSource, NSCacheDelegate { var tablewView: UITableView? let CELL_IDENTIFIER = "cell_image" var cache: NSCache<NSString, NSObject>? //有子对象需要释放资源时可以实现NSDiscardableContent接口 class ImageExt: UIImage, NSDiscardableContent { var count = 1 var isReleased = false //主动调用,做逻辑判断。 NSCache不会调用 public func beginContentAccess() -> Bool { print("beginContentAccess: \(count)") count += 1 return true } //主动调用,做逻辑判断。 NSCache不会调用 public func endContentAccess() { print("endContentAccess: \(count)") count -= 1 } //NSCache类会回调执行该方法 public func discardContentIfPossible() { print("discardContentIfPossible: \(count)") if count == 0 && !isReleased { //.....执行释放流程 isReleased = true } else { isReleased = false } } //主动调用,做逻辑判断。 NSCache不会调用 public func isContentDiscarded() -> Bool { print("isContentDiscarded: \(count)") return isReleased } } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. tablewView = UITableView(frame: self.view.frame) tablewView?.backgroundColor = UIColor.white tablewView?.dataSource = self tablewView?.register(ImageTableViewCell.self, forCellReuseIdentifier: CELL_IDENTIFIER) tablewView?.estimatedRowHeight = 150 tablewView?.rowHeight = UITableViewAutomaticDimension tablewView?.separatorColor = UIColor.gray tablewView?.separatorStyle = .singleLine tablewView?.tableFooterView = UIView() //解决空白横线问题 self.view.addSubview(tablewView!) cache = NSCache<NSString, NSObject>() cache?.countLimit = 10 //最多10个 cache?.totalCostLimit = 50 * 1024 * 1024 //最多缓存50M字节 cache?.delegate = self } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. cache?.removeAllObjects() //释放内存 } public func cache(_ cache: NSCache<AnyObject, AnyObject>, willEvictObject obj: Any) { print("cache release: \(obj)") } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 100000 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCell(withIdentifier: CELL_IDENTIFIER, for: indexPath) as? ImageTableViewCell if (cell == nil) { cell = ImageTableViewCell(style: .default, reuseIdentifier: CELL_IDENTIFIER) } //说明:iOS跟Android的listview机制不同,iOS可以自动释放无效item的内存!!!!!! //let image = UIImage(named: "image1") //iOS会将图片缓存到内存,下次先通过名字从内存查 let path = Bundle.main.path(forResource: "image1", ofType: "png" ) let image = UIImage(contentsOfFile: path!) //不缓存,每次都读文件和解码 let imageExt = ImageExt(contentsOfFile: path!) cache?.setObject(imageExt!, forKey: "\(indexPath.row)" as NSString, cost: 1 * 1024 * 1024) //模拟1M字节 //cache?.setObject(image!, forKey: "\(indexPath.row)" as NSString, cost: 1 * 1024 * 1024) //模拟5M字节 //cell?.icon?.image = cache?.object(forKey: "\(indexPath.row)" as NSString) as! UIImage cell?.icon?.image = image return cell! } func numberOfSections(in tableView: UITableView) -> Int { return 1 }}
class ImageTableViewCell: UITableViewCell { var icon: UIImageView? var name: UILabel? override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) initViews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } /** * 初始化控件 * 注意: snapkit的offset函数在top/left使用正数、right/bottom使用负数! */ func initViews() { icon = UIImageView() icon?.frame = CGRect(x: 0, y: 0, width: 50, height: 50) //let image = UIImage(named: "image_1") //缓存起来,下次读取先从内存查 //let path = Bundle.main.path(forResource: "image1", ofType: "png") //let image = UIImage(contentsOfFile: path!) //不缓存,每次都重新加载 //icon?.image = image self.contentView.addSubview(icon!) icon?.snp.makeConstraints{ make in make.width.height.equalTo(50) make.top.equalTo(self.contentView).offset(10) make.left.equalTo(self.contentView.snp.left).offset(5) make.centerY.equalTo(self.contentView) make.bottom.equalTo(self.contentView).offset(-10) //这是是负数-10 } name = UILabel() name?.font = UIFont.systemFont(ofSize: 16) name?.textColor = .black name?.text = "测试死啦地方;少量的看风景;的说法快速的" name?.adjustsFontSizeToFitWidth = true self.contentView.addSubview(name!) name?.snp.makeConstraints{ make in make.centerY.equalTo(self.contentView) make.left.equalTo(icon!.snp.right).offset(10) make.right.equalTo(self.contentView).offset(-10) //负数! } } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state }}
- iOS NSCache内存优化
- IOS之NSCache解析
- iOS NSCache使用
- iOS之NSCache介绍
- iOS 【Mutithreading-NSCache】
- iOS NSCache 用法
- ios-NSCache简单介绍
- iOS开发之缓存框架、内存缓存、磁盘缓存、NSCache、TMMemoryCache、PINMemoryCache、YYMemoryCache、TMDiskCache、PINDiskCache
- 【iOS】利用NSCache提升效率
- iOS---NSCache的简单使用
- NSCache在IOS中的使用
- IOS学习 NSCache 缓存类
- ios之NSCache的使用
- NSCache
- NSCache
- NSCache
- NSCache
- NSCache
- SPI标准
- js中一系列的兼容---惰性载入优化
- poj1125 Stockbroker Grapevine 最短路 Floyd 思考
- 微服务:配置中心
- nodejs23
- iOS NSCache内存优化
- sliksvn下载与安装
- Coursera机器学习 Week8 笔记
- centos搭建Hadoop2.4伪分布式
- [Python][小知识] Python字符串前 加 u、r、b 的含义
- Jin Ge Jin Qu hao (01背包学习 紫薯)
- JDBI官网翻译版
- artTemplate模板引擎使用
- 在 C++ 中子类继承和调用父类的构造函数方法