Создан ли надежный цикл ссылок в этом простом случае использования URLSession?

Я немного запутался в том, как создаются сильные ссылки и когда возникают циклы ссылок. Вот простой пример:

class Model {
    var foo: Data?
    
    func makeRequest(url: URL) {
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            
            // assume request completes successfully
            self.foo = data!
        }

        task.resume()
    }
}

class ViewController: UIViewController {
    var model = Model()
    let url = URL(string: "abc.com")! // assume URL is valid
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        model.makeRequest(url: url)
    }
}

Это мое понимание того, как ссылки работают в приведенном выше коде:

  1. Переменная model содержит сильную ссылку на экземпляр класса Model.
  2. URLSession содержит сильную ссылку на свою задачу данных, которая содержит сильную ссылку на закрытие.
  3. Замыкание экранирует функцию, и, поскольку ему необходимо обновить себя, оно содержит строгую ссылку на экземпляр модели.
  4. Однако экземпляр модели не содержит строгой ссылки на задачу данных, и поэтому цикл ссылок отсутствует.

Это верно? И если да, то я действительно не понимаю шаг 4. Почему экземпляр модели не содержит строгой ссылки на задачу данных, поскольку задача создается в функции класса Model?

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

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
76
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Здесь нет цикла (как вы заметили). Однако URLSession.shared, который никогда не исчезает, содержит ссылку на задачу, которая содержит ссылку на Model. Это означает, что Model не может быть освобожден до тех пор, пока задача не будет завершена. (Если бы у Model было свойство urlSession, то технически это был бы цикл, но на практике это ничего не изменило бы. «Петли» не волшебны. Если что-то, что живет вечно, содержит ссылку на что-то, оно сохранит этот объект живым. навсегда.)

Как правило, это хорошо. Задачи URLSession автоматически освобождают свой блок завершения, когда они завершаются, поэтому Model сохраняется только до тех пор, пока задача не будет выполнена. Пока Model не предполагает, что ViewController все еще существует (чего не должно быть), здесь нет ничего плохого в том, что касается ссылочных циклов.

Единственное, что плохо в этом коде, это то, что Model не удерживает задачу, поэтому может отменить ее или даже определить, что она выполняется (чтобы избежать параллельных дублирующих запросов). Это не является серьезной проблемой для простых приложений, но это полезно улучшить в более сложных приложениях.

Давайте рассмотрим ваши вопросы по одному:

Это мое понимание того, как ссылки работают в приведенном выше коде:

  1. Переменная model содержит сильную ссылку на экземпляр класса Model.

Правильно, контроллер представления сохраняет эту сильную ссылку до тех пор, пока сам контроллер представления не будет освобожден.

  1. URLSession имеет сильную ссылку на свою задачу данных, которая содержит сильную ссылку на закрытие.

URLSession поддерживает эту сильную ссылку до тех пор, пока запрос не завершится/не завершится ошибкой, после чего задача данных освобождается. Вам не нужно сохранять ссылку на задачу данных, так как URLSession автоматически зависает на ней на время запроса. При этом мы часто сохраняли собственную weak ссылку на него, которую мы использовали бы, если/когда мы могли бы захотеть отменить запрос. (См. ниже.)

  1. Замыкание экранирует функцию, и, поскольку ему необходимо обновить себя, оно содержит строгую ссылку на экземпляр модели.

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

Кроме того, мы обычно не будем этого делать. Часто мы использовали бы список захвата [weak self] в этом закрытии, чтобы он не сохранял эту сильную ссылку на self. Например, вы можете:

let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
    // see if `self` was released
    guard let self else { return }

    // see if request succeeded
    guard let data else {
        print(error ?? URLError(.badServerResponse))
        return
    }

    // if we got here, we have our `Data`; we always avoid forced unwrapping operators when dealing with data from a remote server
    self.foo = data
}
  1. Однако экземпляр Model не содержит строгой ссылки на задачу данных, и поэтому цикл ссылок отсутствует.

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


Еще лучше, если нам явно не нужно, чтобы задача продолжала работать, даже если Model по какой-то причине освобождается, мы бы canceltask, когда Model освобождается:

class Model {
    var foo: Data?
    private weak var task: URLSessionTask?

    deinit {
        task?.cancel()
    }
    
    func makeRequest(url: URL) {
        let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard let self else { return }
        
            guard let data else {
                print(error ?? URLError(.badServerResponse))
                return
            }
        
            self.foo = data
        }

        task.resume()
        self.task = task
    }
}

Обратите внимание, нам не нужно и не хочется сохранять сильную ссылку на URLSessionTask. (URLSession будет управлять жизненным циклом URLSessionTask.) Но мы сохраняем нашу собственную ссылку weak, которая будет автоматически установлена ​​на nil, когда URLSessionTask будет выполнено. Таким образом, если запрос еще не выполнен, когда Model освобождается, мы можем отменить запрос. Но если запрос уже выполнен, эта ссылка task будет автоматически установлена ​​​​на nil для нас, и в этом случае task?.cancel() станет «нет операции».

Излишне говорить, что ответ Роба Нэпьера правильный (+1) и должен быть принятым ответом. Я просто хотел добавить несколько уточнений…

Rob 13.04.2023 08:21

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