Быстрые замыкания, вызывающие сильное удержание цикла с самим собой

Я просто хочу знать, правильно ли я понимаю это или нет. Таким образом, в соответствии с документами Apple, когда вы создаете замыкание как свойство экземпляра класса, и это замыкание ссылается на себя (класс, создавший свойство замыкания), это приведет к сильному циклу сохранения, в конечном счете, класс и замыкание не будут освобождены, Так что в терминах непрофессионалов это означает, что если у меня есть класс, у которого есть свойство, и это свойство является замыканием, и как только я назначу функциональность этого замыкания внутри класса, который объявляет свойство замыкания, которое вызовет сильный цикл сохранения. Вот быстрый пример того, что я имею в виду

class SomeViewController{
  let myClosure:()->Void

  public func someFunction(){
    ....bunch of code
    myClosure = {
       self.dismiss(blahBlahBlah)
    }
  }
}

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

class SomeViewController{
  let myClosure:()->Void

  public func someFunction(){
    ....bunch of code
    myClosure = { [weak self] in
       self?.dismiss(blahBlahBlah)
    }
  }
}

Обратите внимание, как я поставил [слабое я] перед оператором in. Это позволяет закрытию знать только слабую ссылку на себя, а не сильную ссылку. IM должен использовать слабое, когда "я" может жить вне замыкания, или "неизвестное", когда замыкание и "я" живут одинаково.

Я получил эту информацию отсюда Автоматический подсчет ссылок и в разделе "Сильные циклы ссылок для замыканий" этой ссылки, вот это предложение: "Сильный цикл ссылок также может произойти, если вы назначаете замыкание свойству экземпляра класса и телу это закрытие захватывает случай: " Я почти на 90% уверен, что правильно понимаю, но есть только 10% сомнений. Так я правильно понял?

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

class SomeViewController {
    let someSubview:UIView

    override viewDidLoad() {
       //Some Subview has a button and in that view I just have some action that gets fired off calling the callback here in the view controller I don't need to use the [weak self] in this scenario because closure property is not in this class correct?
       someSubview.someButtonsCallback = {
       ....run code then 
       self?.dismiss(blahBlahBlah)
     }
 }

1 ответ

Решение

Да, это все еще может вызвать цикл сохранения.

Самым простым циклом сохранения являются 2 объекта, каждый из которых имеет сильные ссылки друг на друга, но возможны также 3-сторонние и более крупные циклы сохранения.

В вашем случае у вас есть контроллер представления, вид которого содержит кнопку (сильная ссылка). Кнопка имеет сильную ссылку на замыкание. Закрытие строго ссылается на контроллер представления, используя self. Таким образом, вид владеет кнопкой. Кнопка владеет закрытием. Закрытие владеет контроллером представления. Если вы отклоните контроллер представления (скажем, это был модальный режим), то он ДОЛЖЕН быть освобожден. Однако, поскольку у вас есть этот трехсторонний цикл сохранения, он не будет освобожден. Вы должны добавить метод deinit в свой контроллер представления с оператором print и попробовать его.

Решение состоит в том, чтобы добавить список захвата ([weak self] немного) так же, как вы сделали в своем первом примере.

Обратите внимание, что распространенным шаблоном является добавление списка захвата, а затем сопоставление слабой переменной с сильной переменной внутри замыкания:

let myClosure = { [weak self] in 
  guard let strongSelf = self else { return }
  //...
  strongSelf.doSomething()
}

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

В некоторых случаях также возможно, что объект в списке захвата (self в этих примерах) может быть освобожден в середине выполняемого замыкания, что может вызвать непредсказуемое поведение. (Gory details: это только в том случае, когда замыкание запускается в другом потоке, отличном от владельца объекта в списке захвата, но обработчики завершения довольно часто запускаются в фоновом потоке, поэтому это происходит)

Вообразите это:

let myClosure = { [weak self] in 

  self?.step1() //1

  //time-consuming code

  self?.property = newValue //2

  //more time-consuming code

  self?.doSomething() //3

  //even more time-consuming code

  self?.doSomethingElse() //4
}

С помощью приведенного выше кода, если замыкание выполняется в фоновом потоке, возможно, что self все еще будет действительным на шаге 1, но к моменту выполнения шага 2 self будет освобождено. То же самое относится к шагам 3 и 4. Добавляя guard strongSelf = self else { return } в начале замыкания вы тестируете на входе замыкания, чтобы убедиться, что self по-прежнему действует, и, если это так, вы заставляете замыкание создавать надежную ссылку, которая живет только столько времени, сколько замыкание выполняет, и это предотвращает self от освобождения во время выполнения кода закрытия.)

Другие вопросы по тегам