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

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

Обратите внимание, как я ставлю [weak self] перед оператором in. Это позволяет замыканию знать, что оно содержит только слабую ссылку на себя, а не сильную ссылку. Предполагается, что IM использует «слабый», когда «я» может пережить закрытие, или «непринадлежащий», когда закрытие и «я» живут в течение одной и той же продолжительности.

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

Причина, по которой я спрашиваю об этом, заключается в том, что я использую обратные вызовы для некоторых моих кнопок в своих представлениях. И эти обратные вызовы вызывают self, но self в этом сценарии — это контроллер представления, который отвечает на обратный вызов, а не само представление. Вот где я сомневаюсь в себе, потому что из того предложения, которое я выделил, я не думаю, что мне нужно ставить [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)
     }
 }

person Esko918    schedule 09.05.2018    source источник


Ответы (1)


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

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

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

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

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

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

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

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

Представьте себе это:

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 во время выполнения кода закрытия.)

person Duncan C    schedule 09.05.2018
comment
Честно говоря, чувак, я помню тебя по iPhoneDEvSdk.com, и ты был просто божественен, когда дело доходило до информации об iOS. Я даже не прочитал все это, чтобы знать, что ваш ответ правильный LOL. Спасибо, что разъяснили это для меня, я сейчас прочитаю остальное. - person Esko918; 10.05.2018
comment
Мне нужно 1 перейти к каждому закрытию, где я имею в виду использование обратного вызова для нажатия кнопки, и добавить [weak self]. Затем мне также нужно установить сильную переменную self, если self может освободиться до запуска закрытия. Мой единственный другой вопрос: когда нам не нужно настраивать список захвата при запуске функции из себя? Скажем, например, в HTTP-вызове. Поскольку замыкание принадлежит классу, вызывающему HTTP-метод, а затем этот метод существует только для области действия той функции, которая его вызывает. Мне не нужно было бы использовать слабое я, верно? - person Esko918; 11.05.2018