Быстрая утечка неизвестного, когда "принадлежит" представляемому представлению
Я испытываю утечку с неизвестным "я" в условиях, когда, насколько мне известно, утечки не должно быть. Позвольте мне показать пример, он немного надуманный, так что терпите меня, я попытался привести простейшее из возможных случаев.
Предположим, у меня есть простой контроллер представления, который выполняет замыкание для viewDidLoad:
class ViewController2: UIViewController {
var onDidLoad: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
onDidLoad?()
}
}
и класс ViewHandler, который владеет экземпляром этого контроллера представления и внедряет вызов функции notify в свое закрытие, используя неизвестную ссылку:
class ViewHandler {
private let viewController2 = ViewController2()
func getViewController() -> ViewController2 {
viewController2.onDidLoad = { [unowned self] in
self.notify()
}
return viewController2
}
func notify() {
print("My viewcontroller has loaded its view!")
}
}
Затем, когда его контроллер представления представлен другим контроллером представления, ViewHandler протекает при обнулении:
class ViewController: UIViewController {
private var viewHandler: ViewHandler?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewHandler = ViewHandler()
self.present(viewHandler!.getViewController(), animated: true, completion: nil)
viewHandler = nil // ViewHandler is leaking here.
}
}
Я знаю, что пример может показаться немного надуманным, но, насколько я знаю, утечки не должно быть. Позвольте мне попытаться сломать это:
Перед представлением ViewHandler.ViewController2 владение должно выглядеть следующим образом:
ViewController -> ViewHandler -> ViewController2 -|
^ |
|_ _ _ _ unowned _ _ _ _ _ |
После представления ViewHandler.ViewController2 владение должно выглядеть следующим образом:
_______________________________
| v
ViewController -> ViewHandler -> ViewController2 -|
^ |
|_ _ _ _ unowned _ _ _ _ _ |
После удаления ViewHandler владение должно выглядеть так:
_______________________________
| v
ViewController ViewHandler -> ViewController2 -|
^ |
|_ _ _ _ unowned _ _ _ _ _ |
Ничто не владеет ViewHandler, и его следует выпустить. Однако это не так, и ViewHandler протекает.
Если я изменю ссылку в списке захвата замыкания, введенного в onDidLoad, на слабый, утечки не будет, и ViewHandler будет выпущен, как и ожидалось:
func getViewController() -> ViewController2 {
viewController2.onDidLoad = { [weak self] in
self?.notify()
}
return viewController2
}
Кроме того, что-то, что я не могу объяснить, если я оставлю ссылку как неизвестную и сделаю ViewHandler наследуемой от NSObject, ViewHandler будет выпущен, как ожидается, и утечки не будет:
class ViewHandler: NSObject {
private let viewController2 = ViewController2()
func getViewController() -> ViewController2 {
viewController2.onDidLoad = { [unowned self] in
self.notify()
}
return viewController2
}
....
}
Кто-нибудь, кто может объяснить, что происходит?
1 ответ
Согласно моему нынешнему пониманию, NSObject, который соответствует NSObjectProtocol. Этот тип объектов был соединен с Objective-C, который имеет развитое управление памятью. И когда вы используете class
Большинство из нас все еще используют этот класс. Это не должно повредить, если вы создаете класс из NSObject.
Управление swift class
кажется, работает с небольшим экспериментальным стилем, так как люди предпочитают использовать structure
когда возможно. Так что не странно, что некоторые виды поведения неожиданны.
Поэтому, когда вы выбираете swift class
, вы должны больше думать в соответствии с этим опытом. Но хорошая сторона в том, что они могут принести некоторые новые и стабильные функции, которые отличаются от классического NSObject.
Чтобы сделать это просто, просто удалите vc2 как частную переменную.
class ViewHandler {
func getViewController() -> ViewController2 {
let viewController2 = ViewController2()
viewController2.onDidLoad = { [unowned self] in
self.notify()
}
return viewController2
}
func notify() {
print("My viewcontroller has loaded its view!")
}
}
В этом случае утечка все еще существует. С этим трудно судить unowned
, Собственно, собственность viewHandler перешла на vc2.
Когда vc2 выпущен, утечки также ушли. Это какая-то временная утечка.
var firstTime: Bool = true
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if firstTime{
viewHandler = ViewHandler()
let vc = viewHandler!.getViewController()
self.present(vc, animated: true, completion: nil)
viewHandler = nil // ViewHandler is leaking here.
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
vc.dismiss(animated: true, completion: nil)
// leaking is over.
}
}
firstTime.toggle()
}
Даже конкретная собственность принадлежит vc.onDidLoad
, Если
viewHandler = nil // ViewHandler is leaking here.
или
vc.onDidLoad?() // error "ViewHandler has been dealloc!!"
или же
vc.onDidLoad = nil. // There is no error here.
Таким образом, вы должны справиться здесь. И, таким образом, проблема "утечки" была решена.