UICollectionView 中的高内存使用 [重复]
Posted
技术标签:
【中文标题】UICollectionView 中的高内存使用 [重复]【英文标题】:High memory usage in UICollectionView [duplicate] 【发布时间】:2016-03-22 22:13:46 【问题描述】:我目前的任务是一个 ios 键盘扩展,它提供所有 iOS 支持的表情符号(是的,我知道 iOS 有一个内置的表情符号键盘,但目标是在键盘扩展中包含一个)。
对于这个 Emoji 布局,它基本上应该是一个滚动视图,其中所有表情符号按网格顺序排列,我决定使用 UICollectionView,因为它只创建有限数量的单元格并重复使用它们。 (有相当多的表情符号,超过 1'000 个。)这些单元格只包含一个 UILabel,它将表情符号作为其文本保存,并带有一个 GestureRecognizer 以插入点击的 Emoji。
但是,当我滚动浏览列表时,我可以看到内存使用量从大约 16-18MB 增加到超过 33MB。虽然这不会在我的 iPhone 5s 上触发内存警告,但它也可能在其他设备上触发,因为应用扩展只占用了非常少量的资源。
编辑:有时我会收到内存警告,主要是在切换回“正常”键盘布局时。大多数情况下,切换回来时内存使用量会降至 20MB 以下,但并非总是如此。
如何减少此 Emoji 布局使用的内存量?
class EmojiView: UICollectionViewCell
//...
override init(frame: CGRect)
super.init(frame: frame)
self.userInteractionEnabled = true
let l = UILabel(frame: self.contentView.frame)
l.textAlignment = .Center
self.contentView.addSubview(l)
let tapper = UITapGestureRecognizer(target: self, action: "tap:")
self.addGestureRecognizer(tapper)
override func prepareForReuse()
super.prepareForReuse()
//We know that there only is one subview of type UILabel
(self.contentView.subviews[0] as! UILabel).text = nil
//...
class EmojiViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout
//...
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
//The reuse id "emojiCell" is registered in the view's init.
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("emojiCell", forIndexPath: indexPath)
//Get recently used emojis
if indexPath.section == 0
(cell.contentView.subviews[0] as! UILabel).text = recent.keys[recent.startIndex.advancedBy(indexPath.item)]
//Get emoji from full, hardcoded list
else if indexPath.section == 1
(cell.contentView.subviews[0] as! UILabel).text = emojiList[indexPath.item]
return cell
//Two sections: recently used and complete list
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int
return 2
let emojiList: [String] = [
"\u1F600",
"\u1F601",
"\u1F602",
//...
// I can't loop over a range, there are
// unused values and gaps in between.
]
如果您需要更多代码和/或信息,请告诉我。
编辑:我的猜测是 iOS 将渲染的表情符号保留在内存中的某个位置,尽管在重用之前将文本设置为 nil
。但我可能完全错了......
编辑:按照 JasonNam 的建议,我使用 Xcode 的 Leaks 工具运行键盘。在那里我注意到了两件事:
VM: CoreAnimation
在滚动时会达到大约 6-7MB,但我想这在滚动浏览集合视图时可能是正常的。
Malloc 16.00KB
,从以千字节为单位的值开始,在滚动整个列表时会达到 17MB,因此分配了很多内存,但实际上我看不到其他任何东西使用它。
但没有报告泄漏。
EDIT2:我刚刚检查了CFGetRetainCount
(在使用 ARC 时仍然有效),一旦设置了 prepareForReuse
中的 nil 值,String 对象就没有任何引用了。 p>
我正在使用 iOS 9.2 的 iPhone 5s 上进行测试,但问题也出现在使用 iPhone 6s Plus 的模拟器中。
EDIT3:有人遇到了完全相同的问题here,但由于标题奇怪,我到现在都没有找到。似乎唯一的解决方案是将 UIImageViews 与列表中的 UIImages 一起使用,因为 UICollectionView 中的 UIImages 在单元重用时会正确释放。
【问题讨论】:
您是否尝试过使用 Instruments 进行检查?您可以确定记忆的去向。 @JasonNam 请看我的编辑。 好吧实际上一千个 UILabel 可以容纳一些内存。您是否尝试将单元格的数量减少到 100 个?对内存使用有影响吗? 没有数以千计的 UILabel,就是这样:UICollectionViews(如果按上述方式实现)仅初始化将同时出现的子视图,然后通过更改内容重用它们(在这种情况下UILabel.text)。初始化的 UILabel 的实际数量是 56(调试输出)。并且内存使用量与滚动成正比。 啊哈,好吧,我只是提醒了重用单元格。好的,让我们看看 【参考方案1】:我认为您不使用情节提要来设计集合视图。我四处搜索,发现您需要在填充集合视图单元格之前使用标识符注册类。尝试在 viewDidLoad 上调用以下方法。
collectionView.registerClass(UICollectionViewCell.self , forCellWithReuseIdentifier: "emojiCell")
【讨论】:
我已经在init
中这样做了。但是,我正在注册我的 UICollectionViewCell 子类,其他任何东西都没有多大意义。这应该如何减少内存使用?单元重用本身没有问题。【参考方案2】:
这很有趣,在我的测试项目中,我注释掉了 EmojiView 中的 prepareForReuse 部分,内存使用变得稳定,项目从 19MB 开始并且从未超过 21MB,(self.contentView.subviews[0] 为! UILabel).text = nil 导致我的测试出现问题。
【讨论】:
有趣的是,将其注释掉并不会改变我键盘中的任何内容。您在哪个设备上进行测试? 而数据源包含多少个元素? 我刚刚创建了一个包含 1000 个字符串的数组,我是在模拟器中完成的,你能给我你的表情符号字符串列表吗?因为在我的测试中,我只是重复了相同的字符串 1000 次。 这里是:dl.dropboxusercontent.com/u/326576/emojis.swift(尚不包含多字符表情符号) 从 28.7MB 开始,到 57.7MB,然后回到 49.1MB,唯一的区别是 emoji 字符串列表,我相信这是使用的 emoji 字体造成的。【参考方案3】:由于您有内存问题,您应该尝试延迟加载标签。
// Define an emojiLabel property in EmojiView.h
var emojiLabel: UILabel!
// Lazy load your views in your EmojiView.m
lazy var emojiLabel: UILabel =
var tempLabel: UIImageView = UILabel(frame: self.contentView.frame)
tempLabel.textAlignment = .Center
tempLabel.userInteractionEnabled = true
contentView.addSubview(tempLabel)
return tempLabel;
()
override func prepareForReuse()
super.prepareForReuse()
emojiLabel.removeFromSuperview()
emojiLabel = nil
//...
class EmojiViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout
//...
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
//The reuse id "emojiCell" is registered in the view's init.
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("emojiCell", forIndexPath: indexPath) as! EmojiView
//Get recently used emojis
if indexPath.section == 0
cell.emojiLabel.text = recent.keys[recent.startIndex.advancedBy(indexPath.item)]
//Get emoji from full, hardcoded list
else if indexPath.section == 1
cell.emojiLabel.text = emojiList[indexPath.item]
return cell
这样您就可以确定滚动时标签会被释放。
现在我有一个问题。为什么要在 EmojiViews 中添加手势识别器? UICollectionView 已经通过它的 didSelectItemAtIndexPath: 委托实现了这个功能。为每个加载的单元格分配额外的手势识别器非常繁重。
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
let cell : UICollectionViewCell = collectionView.cellForItemAtIndexPath(indexPath) as! EmojiView
// Do stuff here
总而言之,我建议在 EmojiViews.m 中删除整个 init 函数,对标签使用延迟加载,对选择事件使用 didSelectItemAtIndexPath: 委托。
注意:我不习惯 swift 所以我的代码可能包含一些错误。
【讨论】:
问题不在于标签本身,而在于 UIKit 的字体字形缓存(参见我的最新编辑)。集合视图已经使用单元重用,所以这不是问题。但是,我明白您对手势识别器的看法,但这并不是什么大问题,因为只有 56 个单元格同时存在。你仍然是对的。以上是关于UICollectionView 中的高内存使用 [重复]的主要内容,如果未能解决你的问题,请参考以下文章
处理由于 Firebase 查询中的高元素计数而导致的内存使用情况
处理由于 Firebase 查询中的高元素计数而导致的内存使用情况