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 中的高内存使用 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

使用纹理图集时 SpriteKit 中的高内存使用率

处理由于 Firebase 查询中的高元素计数而导致的内存使用情况

处理由于 Firebase 查询中的高元素计数而导致的内存使用情况

在 UICollectionView 中加载用户相册时内存增长失控

GitLab CE 的高内存使用优化

各种变量在内存中的高地址低地址