#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
во втором наблюдателе, это ваша утечка. Я тоже добавил это к своему ответу.