Как сделать так, чтобы метод появлялся в «наборе перегрузки», только если другой метод может быть вызван? - PullRequest
0 голосов
/ 05 июля 2018

Рассмотрим расширение в NotificationCenter, которое делает особые вещи для объектов уведомлений, которые соответствуют определенным протоколам (да, они могут быть более распространенными, но это пример, чтобы помочь продемонстрировать мой настоящий вопрос).

extension NotificationCenter {

  func addObserver<Note: BasicNotification>(using block: @escaping (Note) -> ())
  -> NotificationToken {
    let observer = addObserver(forName: Note.notificationName, object: nil,
        queue: nil, using: { note in
      block(Note(notification: note))
    })
    return NotificationToken(observer: observer, center: self)
  }

  func addObserver<Note: CustomNotification>(using block: @escaping (Note) -> ())
  -> NotificationToken {
    let observer = addObserver(forName: Note.notificationName, object: nil,
        queue: nil, using: { note in
      block(note.object as! Note)
    })
    return NotificationToken(observer: observer, center: self)
  }

}

Теперь рассмотрим, когда мы хотим, чтобы это уведомление срабатывало только один раз, а затем отменило регистрацию ...

extension NotificationCenter {

  func observeOnce<Note: BasicNotification>(using block: @escaping (Note) -> ()) {
    var token: NotificationToken!
    token = addObserver(using: { (note: Note) in
      block(note)
      token.reset()
    })
  }

  func observeOnce<Note: CustomNotification>(using block: @escaping (Note) -> ()) {
    var token: NotificationToken!
    token = addObserver(using: { (note: Note) in
      block(note)
      token.reset()
    })
  }

}

Это точно такой же код. Что я действительно хочу, так это один observeOnce метод - я не хочу писать два из них.

Если я не использую условное соответствие ...

  func observeOnce<Note>(using block: @escaping (Note) -> ()) {
    var token: NotificationToken!
    token = addObserver(using: { (note: Note) in
      block(note)
      token.reset()
    })
  }

Я получаю ошибку Cannot invoke 'addObserver' with an argument list of type '(using: (Note) -> ())', которая имеет смысл.

Если я использую общий базовый протокол (который соответствует обоим) ...

  func observeOnce<Note: SomeNotification>(using block: @escaping (Note) -> ()) {
    var token: NotificationToken!
    token = addObserver(using: { (note: Note) in
      block(note)
      token.reset()
    })
  }

Я получаю точно такую ​​же ошибку, которая не имеет особого смысла - я бы ожидал, что вызов будет неоднозначным, а не существующим вообще.

Видя, что оператор & означает соответствие нескольким протоколам, я попытался использовать BasicNotification | CommonNotification в крайне маловероятном случае, когда это может иметь какое-то значение ... но, конечно, это не сработало.

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

В C ++ я бы сделал что-то вроде этого (не запускал его через компилятор - надеюсь, вы получите то, что я пытаюсь сделать) ...

template <typename T>
auto observeOnce(std::function<void (T)> block)
-> decltype(void(this->addObserver(std::move(block))))
{
  // do my stuff here
}

Приведенный выше код в основном означает, что функция observeOnce появляется только в наборе перегрузки, если можно вызвать addObserver(std::move(block)).

Итак, каков быстрый способ сделать то же самое?

1 Ответ

0 голосов
/ 11 июля 2018

Один из приемов, который вы можете использовать, - реорганизовать код. Вместо создания нескольких (общих) addObserver методов внутри NotificationCenter переместите их в типы уведомлений (базовое и настраиваемое уведомление) и формализуйте их с использованием протокола. Затем вы можете расширить этот протокол с помощью одной функции, чтобы добавить логику addOnce. Когда ваши базовые и пользовательские уведомления реализуют этот протокол, они автоматически наследуют эту новую функциональность addOnce без необходимости дублирования кода.

Refactoring scheme

Пример

Вот пример того, как реализовать эту идею:

Сначала создайте новый протокол ObservableNotification, который позволяет добавить наблюдателя block к NotificationCenter.

protocol ObservableNotification {
    static func addObserver(to center: NotificationCenter, using block: @escaping (Self)->Void) -> NotificationToken
}

Тогда пусть ваши протоколы уведомлений наследуются от этого ObservableNotification протокола

protocol NameableNotification {
    static var notificationName: NSNotification.Name {get}
}

protocol CustomNotification: NameableNotification, ObservableNotification {}
protocol BasicNotification: NameableNotification, ObservableNotification {
    init(notification: Notification)
}

И переместите ваши методы addObserver (из NotificationCenter) в соответствующие протоколы в качестве реализаций по умолчанию с использованием расширения протокола:

extension BasicNotification {
    static func addObserver(to center: NotificationCenter, using block: @escaping (Self)->Void) -> NotificationToken {
        let observer = center.addObserver(forName: Self.notificationName, object: nil, queue: nil) { note in
            block(Self(notification: note))
        }
        return NotificationToken(observer: observer, center: center)
    }
}

extension CustomNotification {
    static func addObserver(to center: NotificationCenter, using block: @escaping (Self)->Void) -> NotificationToken {
        let observer = center.addObserver(forName: Self.notificationName, object: nil, queue: nil) { note in
            block(note.object as! Self)
        }
        return NotificationToken(observer: observer, center: center)
    }
}

Таким образом, вы можете расширить протокол ObservableNotification с помощью реализации по умолчанию для метода observeOnce, и вы сможете вызывать его для каждого типа, который соответствует ObservableNotification (CustomNotification и BasicNotification в вашем дело). Вот так:

extension ObservableNotification {
    static func addOneTimeObserver(to center: NotificationCenter, using block: @escaping (Self)->Void) -> NotificationToken {
        var token: NotificationToken!
        token = addObserver(to: center) {
            block($0)
            token.reset()
        }
        return token
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...