@escaping () -> Void работает не так, как ожидалось в Mojave

#swift #macos

#swift #macos

Вопрос:

Я разрабатываю приложение для macOS (Swift / Storyboard — RsyncOSX), и у меня возникла странная проблема в Mojave. Об этом сообщил пользователь, я разрабатываю Catalina (и Big Sur). Класс выполняет объект процесса и прослушивает два уведомления внутри func executeProcess . Есть два @escaping functions , которые выполняются каждый раз, когда появляются уведомления. Это отлично работает на Каталине и Биг-Суре, но не на Мохаве. В предыдущих версиях использовались протоколы и делегаты..

 class RsyncProcessCmdClosure: Delay {
    // Process termination and filehandler closures
    var processtermination: () -> Void
    var filehandler: () -> Void
    // Verify network connection
    ...
    // Observers
    weak var notifications_datahandle: NSObjectProtocol?
    weak var notifications_termination: NSObjectProtocol?
    // Arguments to command
    ...

    func executeProcess(outputprocess: OutputProcess?) {
        ...
        let outHandle = pipe.fileHandleForReading
        outHandle.waitForDataInBackgroundAndNotify()
        // Observator for reading data from pipe, observer is removed when Process terminates
        self.notifications_datahandle = NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: nil, queue: nil) { [weak self] _ in
            let data = outHandle.availableData
            if data.count > 0 {
                if let str = NSString(data: data, encoding: String.Encoding.utf8.rawValue) {
                    outputprocess?.addlinefromoutput(str: str as String)
                    // Send message about files
                    // ---> Execute closure
                    self?.filehandler()
                    if self?.termination ?? false {
                        self?.possibleerrorDelegate?.erroroutput()
                    }
                }
                outHandle.waitForDataInBackgroundAndNotify()
            }
        }
        // Observator Process termination, observer is removed when Process terminates
        self.notifications_termination = NotificationCenter.default.addObserver(forName: Process.didTerminateNotification, object: nil, queue: nil) { _ in
            self.delayWithSeconds(0.5) {
                self.termination = true
                // ---> Execute closure
                self.processtermination()
                // Must remove for deallocation
                ...
        }
    }
    ...

    init(arguments: [String]?,
         config: Configuration?,
         processtermination: @escaping () -> Void,
         filehandler: @escaping () -> Void)
    {
        self.arguments = arguments
        self.processtermination = processtermination
        self.filehandler = filehandler
        ...
    }

    deinit {
        self.monitor?.stopMonitoring()
        self.monitor = nil
    }
}
  

Комментарии:

1. «Это отлично работает на Каталине и Биг-Суре, но не на Мохаве» Что работает нормально, но не на Мохаве? Вы не можете просто бросить на нас целую кучу кода и сказать «сломано». Какая строка завершается ошибкой и откуда вы знаете?

2. Извините за слишком много кода… Я (на старом mac) переустановил Mojave 10.14.6 и Xcode 11.3.1, скомпилировал код, и объект процесса не реагирует на уведомления при использовании закрытий в Mojave. Точно такой же код, использующий делегаты, работает нормально в Mojave, но не с замыканиями..

3. Уменьшен код .. замыкания добавляются в класс методом init, и они вызываются, когда объекты процесса считывают два уведомления… Но не в Mojave…

4. Хорошо, я думаю , вы говорите: » addObserver(forName:object:queue:) вообще не работает в Mojave». Так ли это? Можете ли вы проверить эту гипотезу вне этого конкретного примера?

5. Подождите, я вижу слабость в вашем коде, см. Мой ответ.

Ответ №1:

Мне кажется, что ваш код никогда не должен работать. Для меня загадка, почему это происходит.

Вы говорите

 weak var notifications_datahandle: NSObjectProtocol?
weak var notifications_termination: NSObjectProtocol?
  

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

Возможно, что-то во внутреннем управлении объектами-наблюдателями уведомлений предотвращает это на Каталине и Биг-Суре, так что вам просто повезло. Но в основном я бы сказал, удалить weak . Вы уже используете weak self в замыканиях, поэтому вы нарушили цикл сохранения, и вам больше ничего не нужно делать.

РЕДАКТИРОВАТЬ Нет, это не так! Вы забыли использовать weak self во второй функции наблюдателя:

 self.notifications_termination = NotificationCenter.default.addObserver(forName: Process.didTerminateNotification, object: nil, queue: nil) { _ in
  

Вам нужно weak self там.


Обратите внимание, что вам необходимо отменить регистрацию объектов observer. Обычно это происходит в deinit . Вы вызовете

 NotificationCenter.default.removeObserver(notifications_datahandle)
NotificationCenter.default.removeObserver(notifications_termination)
  

Затем вы можете освободить объекты observer:

 notifications_datahandle = nil
notifications_termination = nil
  

Комментарии:

1. Это имеет смысл… Я удалю weak из наблюдателей и посмотрю, что произойдет..

2. Круто, спасибо. Конечно, если я совершенно не прав, я могу просто удалить этот ответ. 🙂

3. Ну, удаление weak из наблюдателей приводит к огромной утечке памяти.. Я все равно протестирую его снова на Mojave и посмотрю, что произойдет..

4. Хорошо, позвольте мне расширить свой ответ, чтобы объяснить правильный способ управления памятью наблюдателями уведомлений.

5. Итак, вы забыли использовать weak self во втором наблюдателе, это ваша утечка. Я тоже добавил это к своему ответу.