UICollectionView

时间:2015-12-16 16:55:52

标签: ios iphone swift memory-management uicollectionview

我目前的任务是iOS键盘扩展程序,其中包括所有iOS支持的表情符号(是的,我知道iOS有一个内置表情符号键盘,但目标是在键盘扩展程序中包含一个)。

对于这个基本上应该是滚动视图的表情符号布局,其中所有表情符号都按网格顺序排列,我决定使用UICollectionView,因为它只创建有限数量的单元格并重用它们。 (有很多表情符号,超过1� 000。)这些单元格只包含一个UILabel,它将表情符号作为文本,并使用GestureRecognizer插入抽头表情符号。

然而,当我滚动列表时,我可以看到内存使用量在16-18MB到33MB左右爆炸。虽然这并没有在我的iPhone 5s上触发内存警告,但它可能在其他设备上也是如此,因为应用程序扩展仅专用于非常少量的资源。

编辑:有时候我会收到内存警告,主要是在切换回“正常”状态时。键盘布局。大多数情况下,切换回来时内存使用量会降至20MB以下,但并非总是如此。

如何减少此表情符号布局使用的内存量?

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] = [
    "\u{1F600}",
    "\u{1F601}",
    "\u{1F602}",
    //...
    // 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对象没有任何引用集。

我在使用iOS 9.2的iPhone 5s上进行测试,但问题也出现在使用iPhone 6s Plus的模拟器中。

EDIT3 :有人遇到完全相同的问题here,但由于名称很奇怪,到目前为止我还没有找到它。似乎唯一的解决方案是在列表中使用UIImageViews和UIImages,因为UICollectionView中的UIImages在单元重用上正确发布。

3 个答案:

答案 0 :(得分:6)

非常有趣,在我的测试项目中,我在EmojiView中注释了prepareForReuse部分,内存使用情况变得稳定,项目开始时为19MB,从未超过21MB,(self.contentView.subviews [0] as !UILabel)。text = nil导致我的测试中的问题。

答案 1 :(得分:2)

我认为您不使用故事板来设计集合视图。我搜索了一下,发现在填充集合视图单元格之前需要注册带有标识符的类。尝试在viewDidLoad或其他东西上调用以下方法。

collectionView.registerClass(UICollectionViewCell.self , forCellWithReuseIdentifier: "emojiCell")

答案 2 :(得分:1)

由于您有内存问题,因此应尝试延迟加载标签。

// 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:delegate实现了此功能。为每个加载的单元格分配额外的gestureRecognizer非常重。

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){

    let cell : UICollectionViewCell = collectionView.cellForItemAtIndexPath(indexPath) as! EmojiView
    // Do stuff here
}

总而言之,我建议在EmojiViews.m中删除整个init函数,对标签使用延迟加载,并为选择事件使用didSelectItemAtIndexPath:delegate。

注意:我不习惯快速,所以我的代码可能包含一些错误。