View
86
Download
1
Category
Preview:
Citation preview
Promises
Поговорим о промисах
• Какие проблемы стояли перед создателями?
• Что это и зачем они нужны?
• Создадим собственные промисы
• Используем их в демо-проекте
• Рассмотрим их преимущества и недостатки
Кто придумал промисы?
Карл Хьюитт
Барбара Лисков
Дэниэл Фридман
Проблемы
• Уменьшение задержки при вычислениях на удаленных машинах
• Необходимость максимально использовать возможности систем с множеством процессоров
Идеи
• Распараллелить вычисления
• Переиспользовать получающийся результат
АProcess 1: B+
АProcess 2: C+
Process 3:
Future A
Future A
Future B
Future C
Process Queue Result
Future A
Result A
Result A
Futures (H. Baker, C. Hewitt)
Fork 1:
Fork 2:
Queue:
Promises (B. Liskov, L. Shira)
Record Grade
Record Grade
Record Grade
Promise Student A
Promise Student B
Promise Student C
PrintStudent A
Print Student B
PrintStudent C
DataBase:
Стандартная модель
Background:
Main:
Promise
Future Show Picture
Download Picture
Picture
Как это могло бы выглядеть в коде?
let downloadPromise = Promise<UIImage>() let downloadFuture = downloadPromise.future
downloadFuture .then { image in self.imageView.image = image } .error { err in …
}
downloadPromise.resolve(image)
Разные реализации в разных языках
• С++: promise + future
• Scala: promise + future
• Javascript: только promise
• Swift: нет в стандартной библиотекев PromiseKit - только Promise
Для нашей реализации
Promise == FutureБудем использовать единую сущность
Основные Use-Cases
Последовательныеоперации:
Параллельные операции:
Record Promise
Bank Backend
Upload Promise Show alert
Local Backend
CombinedPromise
Show user profile
Последовательные операции
func record() -> Promise<RecordSequence> func upload(sequence: RecordSequence) -> Promise<Void>
record.then(upload).then { // code showing alert }
… а как бы это выглядело с callback’ами
record { recording in upload(recording) { result, error in
if let result = result { // code showing alert } // error handling }
}
Параллельные операцииfunc bankUserInfo() -> Promise<BankUserInfoConfiguration>
func localBackendUserInfo() -> Promise<LocalBackendUserInfoConfiguration>
combine(localBackendUserInfo, bankUserInfo) .then { local, bank in
… show(userInfoConfiguration) }
Создадим собственные промисы!
Promise
Спецификация
Операции выполняются сразу же после создания
Можно записать результат один раз
Ошибка прерывает выполнение всей
Выполняется на произвольной очереди
Метод combine и другиеМетод then - цепочка промисов
В любой момент можно получить доступ к результату
Под капотом
var result: Result<T>?
var handlers: [PromiseHandler<T>] = []
let queue = DispatchQueue(label: "promise.queue", attributes: .concurrent)
Конструктор
let promise = Promise<Int> { resolve, reject in resolve(someValue)
}
Closure, которые дергают внутренние методы Promise
Реализация конструктора
init(closure: PromiseClosure<T>) { closure(resolve, receivedError) }
func resolve(_ parameter: T) { guard result == nil else { return }
result = .resolved(parameter) handlers.forEach { $0(result!) } }
Выполняется сразу после создания
Метод then
downloadPromise.then { object in return something(object)
}
нужно продумать несколько вариаций:
например, then принимает другой promise в качестве параметра
Реализация метода thenfunc then(onQueue q: DispatchQueue, closure: (T) throws -> U) -> Promise<U> {
}
return Promise<U> { resolve, reject in
}
self.addHandler { result in q.async { switch result { case .resolved(let parameter): resolve(try closure(parameter)) case .rejected(let error): reject(error) } } }
Аналогичная реализация метода error
Делегаты - отдельная история
Сложно обернуть в callback
Как нам преобразовать их в Promise?
Ответ: передаем значение в Promise извне
Делегатыtypealias PromiseTuple<T>
(promise: Promise<T>, resolve: (T) -> (), reject : (Error) -> ())
self.delegatePromise = Promise.promiseTuple()
func delegateMethod() { delegatePromise.resolve(response) }
Метод combine
combine([promise1, promise2]).then { results in … print(results) }
воспользуемся DispatchGroup
Метод combine
let resultGroup = DispatchGroup() var objects: [T] = [] let promiseTuple = Promise<[T]>.promiseTuple()
resultGroup.enter() promise.then { value in resultGroup.leave() objects.append(value)
... } .error { err in promiseTuple.reject(err) }
func combine<T>(q: DispatchQueue, promises: [Promise<T>]) -> Promise<[T]>
Для каждого promise
Combine (завершение)
resultGroup.notify(queue: q) { promiseTuple.resolve(objects) }
return promiseTuple.promise
Метод combine возвращает promise сразу же, до завершения вычислений
Иные методы
• when: аналогично combine
• after: DispatchQueue.asyncAfter
• отмена promises == вызов ошибки
Применим знания на практике!
Use case No. 1
Изменение положения девайса
Изменение бита
Use case No. 2
Изменение позиции секвенсора
Изменение UI Проигрывание звука
Use case No.3
Кнопка сохранить
Проигрываем 2 квадрата
Загружаем на сервер
Показываем алерт
Use case No.3
recordingPromise = sampler.recordBars() .then(parseService.saveSequences) .then { self.view.animateRecord(false) self.view.showAlert(message:…) }
.error { … }
ViewModel
Use case No.4
Кнопка загрузить
Загружаем случайный бит
Передаем его в семплер
Показываем алерт
Use case No.4
downloadingPromise = parseService.loadRandomSequences() .then(sampler.load) .then { self.view.animateDownload(false) self.view.showAlert(message:…) } .error { … }
ViewModel
Use case No.4
ParseServicefunc loadRandomSequences() -> Promise<[BeatSequence]> { return client .downloadRandomFile() .then(parseData) }
func downloadRandomFile() -> Promise<Data> { return allPFObjectsFromServer() .then(downloadRandomFileFromPFObjects) }
ParseClient
Когда стоит использовать Promises?
• Сложный бекэнд (последовательные/параллельные запросы)
• Много трудоемких операций в бекграунд потоке
• Последовательная анимация
• Использование promise в качестве контейнера
Преимущества Promises
• Декларативно - весь код находится рядом
• Хранит результат для переиспользования
• Легко собирать результаты разных операций
• Удобно обрабатывать ошибки
Недостатки Promises
• Неудобно отлаживать
• Не подходит для непрерывного потока данных
• Неочевидная работа с делегатами
https://github.com/mcrakhman/drumdemo
Recommended