Быстрые замыкания, вызывающие сильное удержание цикла с самим собой - PullRequest
0 голосов
/ 09 мая 2018

Я просто хочу знать, правильно ли я понимаю это или нет. Таким образом, в соответствии с документами 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 Ответ

0 голосов
/ 09 мая 2018

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

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

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

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

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

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

Таким образом, если замыкание все еще активно, но объект, которому оно принадлежит, было освобождено, оператор охраны в начале обнаруживает, что 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 во время выполнения кода замыкания.)

...