一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情。
- 本文主要介绍一下在
tableview中cell复用问题,以及解决办法。
1. 现象
我们在日常开发经常会遇到关于cell复用的一些问题
这里我们简单模拟一个场景,我们的cell上面的点击事件绑定了button,我们点击的时候响应其对应的按钮的事件,这里我们就是打印当前的行数,可以发现我们的点击的时候由于复用导致我们按钮点击事件绑定多次,当我们点击屏幕外的按钮时候多个
响应事件。
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! TableViewCell
cell.button.rx.tap
.subscribe(onNext: {
print("点击了:",indexPath.row,indexPath.section)
}).disposed(by: disposeBag)
这里我们分析下:由于cell的复用,之前的cell已经绑定了tap事件,后面从复用池取出的时候还是会绑定点击事件,而
没有解除之前的关系。按照我们之前的思路,我们的按钮点击事件应该是跟随我们的cell,那么是否可以用disposeBag来管理,我们此时的disposeBag是当前控制器的而不是cell的。
第二次点击,可以发现并没有解决。其实也是很好理解我们的cell是没有销毁的,自然不会调用cell的dispose。
2. 解决方式
我们可以手动的解除比如我们手动的在每次绑定事件前,解除关系。
确实解决了,但是我们发现它对于没有复用的情况,所有情况都是采用主动销毁。事实上我们希望可以在复用的时候解除关系,主动改变垃圾袋。
override func prepareForReuse() {
super.prepareForReuse()
disposeBag = DisposeBag()
}
在我们cell复用的时候使用,这样也是符合我们的逻辑
3. 自定义
我们之前关于内存的使用的情况中,我们创建的序列跟随页面销毁而销毁
我们垃圾袋不是持有者控制器控制的时候会造成内存泄露,即使我们的页面销毁了。
我们使用rx.deallocating来跟随页面销毁而调用dispose
那么对于我们的cell是否有类似的方法,我们想要当我们复用的时候进行结束已持有的关系。
并没有,我们使用销毁可以看到里面有关于deallocating的拓展
获取正在调用该方法的对象此时是dealloc,我们的DeallocatingProxy类是相当于一个中介者,会把原有序列转换该序列,销毁的时候发送complete事件。
也就是通过中间体对我们
系统的方法进行监听,生成一个messageSent的序列,当发改变的时候的时候转换一个中间的序列。
我们自定义实现
extension Reactive where Base: UITableViewCell {
public var prepareForReuse: RxSwift.Observable<Void> {
var prepareForReuseKey: Int8 = 0
if let prepareForReuseOB = objc_getAssociatedObject(base, &prepareForReuseKey) as? Observable<Void> {
return prepareForReuseOB
}
// sentMessage 响应方法执行之前
let prepareForReuseOB = Observable.of(
sentMessage(#selector(Base.prepareForReuse)).map { _ in }
, deallocated)
.merge()
objc_setAssociatedObject(base, &prepareForReuseKey, prepareForReuseOB, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
return prepareForReuseOB
}
public var reuseBag: DisposeBag {
MainScheduler.ensureExecutingOnScheduler()
var prepareForReuseBag: Int8 = 0
if let bag = objc_getAssociatedObject(base, &prepareForReuseBag) as? DisposeBag {
return bag
}
let bag = DisposeBag()
objc_setAssociatedObject(base, &prepareForReuseBag, bag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
_ = sentMessage(#selector(Base.prepareForReuse))
.subscribe(onNext: { [weak base] _ in
let newBag = DisposeBag()
guard let base = base else {return}
objc_setAssociatedObject(base, &prepareForReuseBag, newBag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
})
return bag
}
}
这里,我们通过监听复用的方法,通过map转换rx Swift的deallocated的方法,从而达到deallocating的效果。
使用,达到我们想要的效果。
4. 总结
我们通过对cell复用的探究,了解了对于事件订阅的生命周期的管理。仿照系统的拓展我们可以实现我们自己的方法,对一些没有拓展的系统方法我们自己进行拓展。