Быстрая утечка неизвестного, когда «принадлежит» представляемому представлению - PullRequest
1 голос
/ 20 марта 2019

Я испытываю утечку с неизвестным «я» в условиях, когда, насколько мне известно, утечки не должно быть. Позвольте мне показать пример, он немного надуман, так что потерпите меня, я попытался привести простейший случай, который мог.

Предположим, у меня есть простой контроллер представления, который выполняет замыкание для 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 Ответ

0 голосов
/ 20 марта 2019

Согласно моему текущему пониманию, 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. 

Итак, вы должны справиться здесь. И, таким образом, проблема «утечки» была решена.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...