使用UILabel

时间:2018-01-18 21:24:40

标签: ios swift memory-leaks uicollectionview uicollectionviewcell

我有自定义UICollectionViewCell。向左和向右滚动时(水平UICollectionView),每次内存使用量增加0.2MB。

我相信我正在单元格对象中正确实现prepareForReuse();在其中我删除了单元格的所有子视图。

对于我的集合视图单元格中的didSet对象,我在单元格中调用setupViews()。我添加了一个带有约束的UIImageView并将其添加为子视图。这很好。

但是,当我使用UILabel()时,就会发生内存泄漏。当我查看乐器时,我可以看到:每次在两个单元格之间滚动时, VM:UILabel(CALayer)会重复创建! UIImageView

不会发生这种情况

以防它是相关的,这是我的单元格中的prepareForReuse方法:

override func prepareForReuse() {
    super.prepareForReuse()

    self.moreButtonDelegate = nil

    for subview in subviews {
        subview.removeConstraints(subview.constraints)
        subview.removeFromSuperview()
    }

    self.removeFromSuperview() // BURN EVERYTHING
}

这是我的代码:

private func setupViews() -> Void {
    let imageView = myImageView // A lazy class property returns this

    innerView.addSubview(imageView) // innerView is just another UIView within this cell

    // Now I add constraints for imageView
}

因此,如上所述,没有内存泄漏。看起来ARC正确清理所有内容,因为即使使用图像,内存使用量也不会呈指数级增长。

但是,当我在imageView ...

下面添加时
let address = UILabel()
address.translatesAutoresizingMaskIntoConstraints = false
address.text = "TEST"
address.font = UIFont.systemFont(ofSize: 22)
address.adjustsFontSizeToFitWidth = true

// Then I add constraints

我在单元格之间的每个滚动上出现一个新的VM: UILabel (CALayer)行,因此内存使用量会跳跃。看看:

ui label memory leak

我做错了什么?我正在使用Xcode 9,iOS 11.2模拟器。

1 个答案:

答案 0 :(得分:2)

我不确定这会解决您的特定问题,但我相信您误解了prepareForReuse,我认为这很可能会导致您的代码出现问题。我们来看看你的实现:

override func prepareForReuse() {
    super.prepareForReuse()

    self.moreButtonDelegate = nil

    for subview in subviews {
        subview.removeConstraints(subview.constraints)
        subview.removeFromSuperview()
    }

    self.removeFromSuperview() // BURN EVERYTHING
}

我相信你正在看prepareForReuse完全错误。重用的主要目的是减少由创建单元视图(对象实例化,创建视图层次结构,布局等)引起的开销。你不想烧掉一切!相反,您希望尽可能多地保留contentView。理想情况下,您只会更改视图的内容(例如:text中的UILabelimage中的UIImageView等等,或者某些属性({{1}等等。)。

您可以使用backgroundColor取消一些您开始呈现单元格的重量级操作,但是当单元格从视图中删除时可能没有结束,并且应该在其他地方重用。例如,当您从Web下载内容时,用户可能会快速滚动,并且在下载和显示Web图像之前,单元格会离开屏幕。现在,如果单元格被重用,则很可能会显示旧的下载图像 - 因此在prepareForReuse中您可以取消此操作。

结论 - 我相信你的情况prepareForReuse中没有任何操作真的有帮助 - 反之亦然,因为集合视图必须再次从头开始重新创建单元格的整个UI(这意味着对象实例化的所有开销等)。我的第一个建议是放弃整个prepareForReuse实施。

其次,放弃prepareForReuse实施后,重构单元格,使其只创建一次UI,理想情况下在prepareForReuse

initializer

然后,在class UI: UITableViewCell { override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupViews() } } 中配置其内容,即为标签设置文本,为图像视图设置图像等。

最后,请记住documentation所说的内容(我自己的重点):

  

执行任何清理必要以准备视图以供再次使用。

只做真正需要做的事情,而不是更多事情。

在过去的一年里,我实现了许多cellForItemAttableView数据源,但我真的需要使用collectionView两次(对于上面提到的图像下载示例)。< / p>

编辑

我的意思的例子:

prepareForReuse
class CustomTableViewController: UITableViewController {

    var models: [Model] = [Model(name: "Milan")]

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(CustomCell.self, forCellReuseIdentifier: "customCell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return models.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! CustomCell
        // you just want to set the contents, not to recreate the UI components
        cell.configure(for: models[indexPath.row])
        return cell
    }
}

此外,始终使用单元格struct Model { var name: String = "" } class CustomCell: UITableViewCell { // create it once private let nameLabel = UILabel() override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) // setup view once setupViews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupViews() { // add it to view self.contentView.addSubview(nameLabel) // setup configuration nameLabel.textColor = UIColor.red // lay it out nameLabel.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ nameLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 8), nameLabel.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8), nameLabel.leftAnchor.constraint(equalTo: self.contentView.leftAnchor, constant: 8), nameLabel.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -8), ]) } // this is what you call in cellForRowAt func configure(for model: Model) { nameLabel.text = model.name // someImageView.image = model.image // etc. } override func prepareForReuse() { super.prepareForReuse() // if it is super important, reset the content, cancel operations, etc., but there is no reason to recreate the UI // so e.g. this might be ok (although in this case completely unnecessary): nameLabel.text = nil // but you definitely don't want to do this (that's done once at the cell initialization): // nameLabel = UILabel() // setupViews() } } ,而不是直接使用单元格。请注意,我使用了这个:

contentView

而不是:

self.contentView.addSubview(nameLabel)

Official docs

  

UITableViewCell对象的内容视图是单元格显示的内容的默认超级视图。如果您想通过简单地添加其他视图来自定义单元格,则应将它们添加到内容视图中,以便在单元格进入和退出编辑模式时将它们正确定位。