Будущее издателя. Чтобы связать издателей в цепочку, используйте .flatMap
.
. Однако в вашем случае использования нет необходимости в цепочке фьючерсов, поскольку существует только одна асинхронная операция, а именно вызов requestAccess
. Если вы хотите инкапсулировать результат операции, которая может выдать ошибку, например ваш fetchContacts
, то, что вы хотите вернуть, - это не будущее, а результат.
Для иллюстрации я создам возможную Трубопровод, который делает то, что вы описываете. В ходе обсуждения я сначала покажу некоторый код, а затем обсудю этот код в указанном порядке.
Сначала я подготовлю несколько методов, которые мы можем вызывать по пути:
func checkAccess() -> Result<Bool, Error> {
Result<Bool, Error> {
let status = CNContactStore.authorizationStatus(for:.contacts)
switch status {
case .authorized: return true
case .notDetermined: return false
default:
enum NoPoint : Error { case userRefusedAuthorization }
throw NoPoint.userRefusedAuthorization
}
}
}
В checkAccess
мы смотрим, есть ли у нас авторизация. Есть только два случая, представляющих интерес; либо мы авторизованы, и в этом случае мы можем перейти к доступу к нашим контактам, либо мы не определены, и в этом случае мы можем запросить у пользователя авторизацию. Другие возможности не представляют интереса: мы знаем, что у нас нет разрешения и мы не можем его запросить. Поэтому я характеризую результат, как я уже говорил ранее, как результат:
.success(true)
означает, что у нас есть разрешение
.success(false)
означает у нас нет авторизации, но мы можем запросить ее
.failure
означает, что у нас нет авторизации, и нет никакого смысла в этом; Я делаю это пользовательскую ошибку, чтобы мы могли выбросить ее в наш конвейер и, таким образом, преждевременно завершить конвейер.
OK, перейдем к следующей функции.
func requestAccessFuture() -> Future<Bool, Error> {
Future<Bool, Error> { promise in
CNContactStore().requestAccess(for:.contacts) { ok, err in
if err != nil {
promise(.failure(err!))
} else {
promise(.success(ok)) // will be true
}
}
}
}
requestAccessFuture
воплощает единственную асинхронную операцию, а именно запрос доступа от пользователя. Так что я создаю будущее. Есть только две возможности: либо мы получим ошибку, либо мы получим Bool, равный true
. Нет обстоятельств, при которых мы не получаем никакой ошибки, кроме false
Bool. Поэтому я либо называю ошибку обещания ошибкой, либо я называю его успехом с Bool, который, как я случайно знаю, всегда будет true
.
func getMyEmailAddresses() -> Result<[CNLabeledValue<NSString>], Error> {
Result<[CNLabeledValue<NSString>], Error> {
let pred = CNContact.predicateForContacts(matchingName:"John Appleseed")
let jas = try CNContactStore().unifiedContacts(matching:pred, keysToFetch: [
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactGivenNameKey as CNKeyDescriptor,
CNContactEmailAddressesKey as CNKeyDescriptor
])
guard let ja = jas.first else {
enum NotFound : Error { case oops }
throw NotFound.oops
}
return ja.emailAddresses
}
}
getMyEmailAddresses
- это просто пример операции доступа к контакты. Такую операцию можно выполнить, поэтому я express еще раз в качестве результата.
Хорошо, теперь мы готовы построить конвейер! Здесь мы go.
self.checkAccess().publisher
Наш вызов checkAccess
дает Результат. Но у Результата есть издатель! Так что этот издатель - начало нашей цепочки. Если Результат не получил ошибку, этот издатель выдаст значение Bool. Если он действительно получил ошибку, издатель выдаст ее по конвейеру.
.flatMap { (gotAccess:Bool) -> AnyPublisher<Bool, Error> in
if gotAccess {
let just = Just(true).setFailureType(to:Error.self).eraseToAnyPublisher()
return just
} else {
let req = self.requestAccessFuture().eraseToAnyPublisher()
return req
}
}
Это единственный интересный шаг по конвейеру. Мы получаем Бул. Если это правда, у нас нет работы; но если оно ложно, нам нужно получить наше будущее и опубликовать его. То, как вы публикуете sh издателя, - .flatMap
; так что если gotAccess
ложно, мы получаем наше будущее и возвращаем его. Но что, если gotAccess
верно? Мы все еще должны вернуть издателя, и он должен быть того же типа, что и наше будущее. На самом деле он не должен быть Future, потому что мы можем стереть его в AnyPublisher. Но это должны быть те же типы , а именно Bool и Error.
Поэтому мы создаем Just и возвращаем его. В частности, мы возвращаем Just(true)
, чтобы указать, что мы авторизованы. Но мы должны перепрыгнуть через некоторые обручи, чтобы сопоставить тип ошибки с ошибкой, потому что тип ошибки Just - Никогда. Я делаю это, применяя setFailureType(to:)
.
Хорошо, все остальное легко.
.receive(on: DispatchQueue.global(qos: .userInitiated))
Мы прыгаем в фоновый поток, чтобы мы могли общаться с хранилищем контактов, не блокируя основной поток.
.compactMap { (auth:Bool) -> Result<[CNLabeledValue<NSString>], Error>? in
if auth {
return self.getMyEmailAddresses()
}
return nil
}
Если мы получим true
в этот момент, мы авторизованы, поэтому мы вызываем getMyEmailAddress
и возвращаем результат, который, как вы помните, является Результатом. Если мы получим false
, мы не хотим ничего делать; но нам не разрешено ничего возвращать из map
, поэтому вместо этого мы используем compactMap
, что позволяет нам возвращать nil
, что означает «ничего не делать». Поэтому, если мы получим ошибку вместо Bool, она будет просто передана по конвейеру без изменений.
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { completion in
if case let .failure(err) = completion {
print("error:", err)
}
}, receiveValue: { result in
if case let .success(emails) = result {
print("got emails:", emails)
}
})
Мы закончили, так что осталось только подготовиться к получению ошибки или электронных писем (обернутых в Результат), которые пришли по конвейеру. Я делаю это в качестве иллюстрации, просто возвращаясь к основному потоку и распечатывая то, что происходит у нас по конвейеру.