Избегать слабости для простых операций? - PullRequest
3 голосов
/ 17 февраля 2020

Для коротких операций допустимо ли избегать [weak self]? Например, URLSession сохранит закрытие из dataTask(with:completion:):

final class ViewController: UIViewController {
  let label = UILabel()

  override func viewDidLoad() {
    super.viewDidLoad()
    URLSession.shared.dataTask(with: url) { data, response, error in
      guard let data = data else { return }
      let decodedString = String(bytes: data, encoding: .utf8)

      DispatchQueue.main.async {
        self.label.text = decodedString
      }
    }.resume()
  }
}

В этом случае закрытие сильно захватывает self, что означает, что даже если это ViewController удерживается в памяти закрытие. URLSession будет удерживать закрытие до тех пор, пока задача данных не будет завершена, что означает, что жизненный цикл ViewController потенциально может быть продлен до завершения dataTask.

В этой ситуации следует использовать списки захвата для избежать этого поведения? Верны ли мои рассуждения о том, что здесь нет эталонного цикла?

Ответы [ 5 ]

3 голосов
/ 17 февраля 2020

Если вы беспокоитесь о ссылочных циклах, вы обычно не получаете их при использовании URL-запросов. Дело в том, что запрос URL заканчивается рано или поздно (через несколько минут), и ваш контроллер освобождается. Референтный цикл является только временным и не вызовет утечку памяти.

Вопрос в том, хотите ли вы сохранить контроллер в памяти, даже если пользователь уже закрыл контроллер, и он никогда не будет показано снова. Это, вероятно, не вызовет никаких проблем, но все же расточительно. Вы удерживаете память, которая вам не нужна, и которую нельзя использовать повторно.

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

По моему мнению, вам не следует особо волноваться о ссылочных циклах и больше думать о владении. Сильная ссылка означает, что что-то принадлежит. В запросе нет причин «владеть» контроллером. Все наоборот - контроллер владеет запросом и управляет им. Если нет собственности, я бы использовал weak только для ясности.

3 голосов
/ 17 февраля 2020

жизненный цикл ViewController потенциально может быть продлен до тех пор, пока dataTask не завершит

Так что вопрос в том, будет ли это согласованным. Это может быть даже хорошая вещь. Если это так, тогда все в порядке, и нет необходимости в weak self, так как нет цикла сохранения, поскольку сеанс URL является общим.

Но когда сеанс url является свойством экземпляра и имеет реальный делегат, все намного сложнее, и вы действительно можете получить цикл сохранения, потому что сеанс сохраняет свой делегат, который может сохранять сеанс.

2 голосов
/ 18 февраля 2020

Вы определенно должны использовать [weak self] здесь, не из-за какого-либо риска сильного ссылочного цикла, а просто потому, что это закрытие существует исключительно для обновления метки. Нет смысла в написании кода, который намеренно сохраняет контроллер представления и его представления в памяти, поэтому вы можете обновить метку в представлении, которое, возможно, было отклонено и больше не видно.

Ключевое слово weak существует не только для того, чтобы избежать циклов сильных ссылок, но и для точного представления принадлежности объекта и управления продолжительностью жизни объекта. Вы не должны искажать граф владения объектами только ради сохранения нескольких нажатий клавиш, связанных с [weak self] списком захвата.

2 голосов
/ 17 февраля 2020
1001 * Является ли мое рассуждение верно, что нет ссылки цикла здесь?

Там нет ссылки цикла здесь. ViewController не сохраняет обработчик завершения dataTask. Вы можете думать об этом как о iOS, сохраняющем строгую ссылку как на контроллер представления, так и на обработчик завершения, а обработчик завершения также сохраняет строгую ссылку на контроллер представления. Не существует строгой ссылки от контроллера представления обратно на обработчик завершения или на какую-либо цепочку объектов со ссылкой на обработчик завершения, поэтому вы свободны от циклов. Ищите этот же шаблон в UIView.animate, где вы снова отправляете замыкания в iOS вместо локального их хранения.

Для кратковременных операций приемлемо ли избегать [weak self]?

Продолжительность работы не имеет значения. Два соответствующих вопроса:

  1. Существует ли цикл ссылок?
  2. Будет ли цикл ссылок нарушен?

Возьмите этот пример:

class BadVC: UIViewController {
    private lazy var cycleMaker: () -> Void = { print(self) }

    override func loadView() {
        view = UIView()
        cycleMaker()
    }
}

BadVC здесь удается создать ссылочный цикл, который никогда не будет прерван, как только он загрузит свое представление. Тот факт, что cycleMaker() будет выполняться в наносекундах, не спасает нас от утечки памяти.


Прагматически, существует третий вопрос:

Позволяет ли этот код избежать постоянных эталонных циклов таким образом, чтобы его было трудно понять, легко сломать или ненадежно, так что эталонные циклы могут появиться в будущем из-за неправильного использования или модификации?

Вы можете разбить эталонные циклы вручную. Например:

class StillBadVC: UIViewController {
    private lazy var cycleMaker: () -> Void = { print(self) }

    override func loadView() {
        view = UIView()
        cycleMaker()
    }

    func breakCycle() {
        cycleMaker = { }
    }
}

Здесь мы находимся в опасности, потому что StillBadVC имеет сильную ссылку на cycleMaker, а cycleMaker фиксирует сильную ссылку на StillBadVC. Цикл будет прерван до тех пор, пока кто-нибудь не забудет вызвать breakCycle(), после чего контроллер представления удалит свою сильную ссылку на cycleMaker, что позволит cycleMaker освободить место. Однако цикл будет не прерываться, если кто-то забудет позвонить breakCycle(). Вызов метода с именем breakCycle() обычно не является частью контракта на использование контроллера представления, поэтому мы ожидаем, что StillBadVC приведет к утечкам памяти на практике.

0 голосов
/ 17 февраля 2020

Я думаю, что вы уже получили свой ответ здесь. Это не эталонный цикл.

Но для построения системного подхода c мой совет здесь еще один более простой. Забудьте об утечках и прочем.

Подумайте о владении и управлении потоком и затем управлении памятью

  • Владение: Нужно ли этому объекту владеть этим другим объектом? Должен ли объект иметь свой делегат? Должно ли подпредставление иметь свое происхождение? Является ли этот запрос владельцем этого viewController?
  • Flow Control: Как скоро я хочу отменить выделение этого объекта? Сразу или после удаления вида с экрана?
  • Управление памятью: Является ли это сильным эталонным циклом?

Этот процесс мышления не только помогает вам Отличить guish настоящая утечка памяти от чего-то, что не является утечкой. Это также помогает вам лучше проектировать и читать ваш код, а не просто рабски сбрасывать [weak self] повсюду.

...