Discover concurrency in SwiftUI
https://developer.apple.com/videos/play/wwdc2021/10019/
Рассмотрим следующий код:
1. Присвоение значения в переменную items, помеченную как
2. Как только SwiftUI видит событие
Именно такая последовательность гарантируется, так как код запускается на
Предположим, что загрузка фото, происходит слишком долго вследствие слабого соединения, и в этом случае мы хотим “разгрузить” главный поток, чтобы как минимум не было фриза у приложения, для этого мы выносим загрузку в глобальную очередь:
В этом случае, возможна следующая ситуация:
3. Срабатывает событие
4. SwiftUI делает snapshot текущего значения items
5. Само присвоение items происходит уже в новом цикле runloop-а (то есть триггер события
6. SwiftUI сравнивает значения и не видит изменения (так как новое еще не успело записаться в новом цикле, а snapshot еще равен текущему значению)
Чтобы SwiftUI видел изменения, нужно чтобы изменения произошли в следующем порядке:
Чтобы гарантировать такой порядок до Swift 5.5 и iOS 15 (ждем поддержки ниже в Xcode 13.2), необходимо было вернуться на
Чтобы гарантировать, что работа происходит на
Связывать UI теперь можно не через
Выполнение
#watchthis #readthis
https://developer.apple.com/videos/play/wwdc2021/10019/
Рассмотрим следующий код:
class Photos: ObservableObject {
@Published var items: [Photos] = []
func updateItems() {
let fetched = fetchPhotos()
items = fetched
}
}
1. Присвоение значения в переменную items, помеченную как
@Published
, триггерит событие objectWillChange
, сразу после этого в storage @Published
записывается новое значение.2. Как только SwiftUI видит событие
objectWillChange
, происходит snapshot текущего значения items, в следующем runloop-е происходит сравнение сделанного snapshot с текущим значением itemsИменно такая последовательность гарантируется, так как код запускается на
Main actor
.Предположим, что загрузка фото, происходит слишком долго вследствие слабого соединения, и в этом случае мы хотим “разгрузить” главный поток, чтобы как минимум не было фриза у приложения, для этого мы выносим загрузку в глобальную очередь:
class Photos: ObservableObject {
@Published var items: [Photos] = []
func updateItems() {
DispatchQueue.global().async {
let fetched = fetchPhotos()
items = fetched
}
}
}
В этом случае, возможна следующая ситуация:
3. Срабатывает событие
objectWillChange
4. SwiftUI делает snapshot текущего значения items
5. Само присвоение items происходит уже в новом цикле runloop-а (то есть триггер события
objectWillChange
и присвоения items происходит в разных циклах)6. SwiftUI сравнивает значения и не видит изменения (так как новое еще не успело записаться в новом цикле, а snapshot еще равен текущему значению)
Чтобы SwiftUI видел изменения, нужно чтобы изменения произошли в следующем порядке:
objectWillChange
- Изменение состояния - Runloop входит в новый циклЧтобы гарантировать такой порядок до Swift 5.5 и iOS 15 (ждем поддержки ниже в Xcode 13.2), необходимо было вернуться на
Main actor
. Начиная с Swift 5.5 и iOS 15 (начиная с Xcode 13.2, ниже), все становится проще и достаточно юзать просто await
, который продолжит выполнение работы на Main actor
после того, как отработает async
. Это называется “to yield main actor”.
class Photos: ObservableObject {
@Published var items: [Photos] = []
func updateItems() async {
let fetched = await fetchPhotos()
items = fetched
}
}
Чтобы гарантировать, что работа происходит на
Main actor
мы добавляем аннотацию @MainActor
в начало описания класса, это означает, что свойства и метода доступны только из Main actor.Связывать UI теперь можно не через
onAppear { Task {…}}
, а через модификатор .task { async {…} }
, доступный с iOS 15.Выполнение
.task {}
привязано к жизненному циклу View и автоматически отменяется, если жизненный цикл View заканчивается.#watchthis #readthis
Apple Developer
Discover concurrency in SwiftUI - WWDC21 - Videos - Apple Developer
Discover how you can use Swift's concurrency features to build even better SwiftUI apps. We'll show you how concurrent workflows interact...