我有自定义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)
行,因此内存使用量会跳跃。看看:
我做错了什么?我正在使用Xcode 9,iOS 11.2模拟器。
答案 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
中的UILabel
,image
中的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所说的内容(我自己的重点):
执行任何清理必要以准备视图以供再次使用。
只做真正需要做的事情,而不是更多事情。
在过去的一年里,我实现了许多cellForItemAt
和tableView
数据源,但我真的需要使用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)
UITableViewCell对象的内容视图是单元格显示的内容的默认超级视图。如果您想通过简单地添加其他视图来自定义单元格,则应将它们添加到内容视图中,以便在单元格进入和退出编辑模式时将它们正确定位。