Как я могу создать ссылочный цикл, используя dispatchQueues? - PullRequest
1 голос
/ 09 мая 2019

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

Но следующая установка не создает ссылочный цикл.

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution


class UsingQueue {
    var property : Int  = 5
    var queue : DispatchQueue? = DispatchQueue(label: "myQueue")

    func enqueue3() {
        print("enqueued")
        queue?.asyncAfter(deadline: .now() + 3) {
            print(self.property)
        }
    }

    deinit {
        print("UsingQueue deinited")
    }
}

var u : UsingQueue? = UsingQueue()
u?.enqueue3()
u = nil

Блок сохраняет self только в течение 3 секунд.Тогда выпускает это.Если я использую async вместо asyncAfter, то это почти сразу.

Из того, что я понимаю, настройка здесь такова:

self ---> queue
self <--- block

Очередь является просто оболочкой / оболочкой для блока.Вот почему, даже если я nil очередь, блок продолжит свое выполнение.Они независимы.

Так есть ли какая-либо установка, которая использует только очереди и создает опорные циклы?

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

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

Или вы хотите использовать [weak self] in, чтобы вы могли выйти досрочно, если ваш объект был освобожден.например, какой-то чисто пользовательский интерфейс, такой как остановка загрузочного счетчика, больше не нужен


FWIW Я понимаю, что если я использую затвор, то все иначе, например, если я делаю:

import PlaygroundSupport
import Foundation

PlaygroundPage.current.needsIndefiniteExecution
class UsingClosure {
    var property : Int  = 5

    var closure : (() -> Void)?

    func closing() {
        closure = {
            print(self.property)
        }
    }

    func execute() {
        closure!()
    }
    func release() {
        closure = nil
    }


    deinit {
        print("UsingClosure deinited")
    }
}


var cc : UsingClosure? = UsingClosure()
cc?.closing()
cc?.execute()
cc?.release() // Either this needs to be called or I need to use [weak self] for the closure otherwise there is a reference cycle
cc = nil

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

self ----> block
self <--- block

Следовательно, это ссылочный цикл, и он не освобождается, пока я не установлю блок на захват nil.

1 Ответ

2 голосов
/ 09 мая 2019

Вы говорите:

Исходя из того, что я понимаю, здесь есть установка:

self ---> queue
self <--- block

Очередь - это просто оболочка / оболочка для блока.Вот почему, даже если я nil очереди, блок продолжит свое выполнение.Они независимы.

Тот факт, что self имеет сильную ссылку на очередь, несущественен.Лучший способ думать об этом - это то, что сам GCD хранит ссылку на все очереди отправки, в которых есть что-либо в очереди.(Это аналог пользовательского экземпляра URLSession, который не будет освобожден до тех пор, пока не будут выполнены все задачи в этом сеансе.)

Таким образом, GCD сохраняет ссылку на очередь с отправленными задачами.Очередь сохраняет строгую ссылку на отправленные блоки / элементы.Блок в очереди сохраняет строгую ссылку на любые ссылочные типы, которые они захватывают.Когда отправленная задача завершается, она разрешает любые сильные ссылки на любые захваченные ссылочные типы и удаляется из очереди (если вы не сохраняете свою собственную ссылку на нее в другом месте.), Обычно таким образом разрешая любые циклы сильных ссылок.


Если не учитывать, что отсутствие [weak self] может привести к неприятностям, GCD по какой-то причине сохраняет ссылку на блок, например, на источники отправки.Классическим примером является повторяющийся таймер:

class Ticker {
    private var timer: DispatchSourceTimer?

    func startTicker() {    
        let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".ticker")
        timer = DispatchSource.makeTimerSource(queue: queue)
        timer!.schedule(deadline: .now(), repeating: 1)
        timer!.setEventHandler {                         // whoops; missing `[weak self]`
            self.tick()
        }
        timer!.resume()
    }

    func tick() { ... }
}

Даже если контроллер представления, в котором я запустил вышеупомянутый таймер, отключается, GCD продолжает активировать этот таймер, и Ticker не будет выпущен.Как показывает функция «Диаграмма отладочной памяти», блок, созданный в подпрограмме startTicker, постоянно сохраняет сильную ссылку на объект Ticker:

repeating timer memory graph

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

Другие сценарии включают отправку медленной (или неопределенной длины) задачигде вы хотите cancel это (например, в deinit):

class Calculator {
    private var item: DispatchWorkItem!

    deinit {
        item?.cancel()
        item = nil
    }

    func startCalculation() {
        let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".calcs")
        item = DispatchWorkItem {                         // whoops; missing `[weak self]`
            while true {
                if self.item?.isCancelled ?? true { break }
                self.calculateNextDataPoint()
            }
            self.item = nil
        }
        queue.async(execute: item)
    }

    func calculateNextDataPoint() {
        // some intense calculation here
    }
}

dispatch work item memory graph

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

  • Если мы просто собираемся обновить пользовательский интерфейс после выполнения задачи, нет необходимости держать контроллер представления и его представления в иерархии в ожидании некоторого обновления пользовательского интерфейса, если представлениеконтроллер был уволен.

  • Если нам нужно обновить хранилище данных после выполнения задачи, то мы определенно не хотим использовать [weak self], если мы хотим убедиться, что обновление произойдет.

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


Не имеет значения, но игровые площадки - ужасное место для проверки поведения памяти, потому что у них есть свои особенности.Гораздо лучше сделать это в реальном приложении.Кроме того, в реальном приложении у вас есть функция «Диаграмма отладочной памяти», где вы можете увидеть реальные сильные ссылки.См https://stackoverflow.com/a/30993476/1271826.

...