I have a ViewController (called MainViewController
) which is backed by a ViewModel (called MainViewModel
).
The ViewModel has a variable that defines which child ViewController MainViewController
should present as its child.
My problem is that, when a child is removed in favor of another child, it never gets deinit
'ed.
Here's the code:
MainViewController:
class MainViewController: UIViewController {
var viewModel: MainViewModel!
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.viewController
.subscribe(onNext: { [weak self] vc in
self?.newVc(vc)
})
.disposed(by: disposeBag)
}
static func instantiate(viewModel: MainViewModel) -> MainViewController {
let vc = MainViewController()
vc.viewModel = viewModel
return vc
}
private func newVc(_ vc: UIViewController) {
remove(childViewController: children.first)
addFullScreen(childViewController: vc)
}
}
MainViewModel:
class MainViewModel {
lazy var viewController: Observable<UIViewController> = {
return Observable.just(ColorViewController(.green))
.delay(RxTimeInterval.seconds(3), scheduler: MainScheduler.instance)
.startWith(ColorViewController(.yellow))
}()
}
You see in MainViewModel
s viewController
variable, that it first emits a yellow ColorViewController, and 3 seconds later a green one. ColorViewController
is a basic subclass of UIViewController, with a colored view, and a the deinit
-method overwritten. This methods isn't called, when the yellow ColorViewController is removed..
Who holds the reference to that yellow ColorViewController, and how to fix it?
Bonus code:
extension UIViewController {
public func addFullScreen(childViewController child: UIViewController) {
guard child.parent == nil else { return }
addChild(child)
view.addSubview(child.view)
child.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: child.view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: child.view.trailingAnchor),
view.topAnchor.constraint(equalTo: child.view.topAnchor),
view.bottomAnchor.constraint(equalTo: child.view.bottomAnchor)
])
child.didMove(toParent: self)
}
public func remove(childViewController child: UIViewController?) {
guard let child = child else { return }
guard child.parent != nil else { return }
child.willMove(toParent: nil)
child.view.removeFromSuperview()
child.removeFromParent()
}
}
UPDATE:
So I changed the viewController
-variable to this:
lazy var viewController: Observable<UIViewController> = {
return Observable<Int>.interval(RxTimeInterval.seconds(3), scheduler: MainScheduler.instance)
.scan(0, accumulator: { (prev, next) -> Int in return prev + 1 })
.map { index -> UIViewController in
let modul = index % 3
print("Index: \(index): \(modul)")
switch modul {
case 0: return ColorViewController(.yellow, tag: "Yellow")
case 1: return ColorViewController(.blue, tag: "Blue")
case 2: return ColorViewController(.green, tag: "Green")
default: return ColorViewController(.red, tag: "Red")
}
}.startWith(ColorViewController(.cyan, tag: "Initial 1"),
ColorViewController(.purple, tag: "Initial 2"))
.take(10)
}()
Now I see that all the ColorViewController
's generated in the .map
are deinitialized as expected. But the two passed into .startWith
, are never deinitialized, not even after .take(10)
causes the Observable to complete. Does that make sense to anyone?
Open up the visual memory debugger to find out who is holding on to the view controller that you want to have released. Here is an article about it: https://useyourloaf.com/blog/xcode-visual-memory-debugger/
And a video from Apple: https://developer.apple.com/videos/play/wwdc2018/416
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.