Отличная лайвкодинг-сессия с подходом UDA на примере #Redax в SwiftUI
LiveCoding: SwiftUI with unidirectional architecture
#watchthis
LiveCoding: SwiftUI with unidirectional architecture
#watchthis
YouTube
LiveCoding: SwiftUI with unidirectional architecture / Алексей Демедецкий
Купить билеты на iOS Ukraine #2, которая пройдет 17 Мая 2021 можно по ссылке:
https://2event.com/events/1863065#/
Подписывайтесь на наши соц сети:
Twitter: https://twitter.com/iOSUkraine
Telegram Channel: https://t.me/iOSUkraine
Telegram Chat: https://t…
https://2event.com/events/1863065#/
Подписывайтесь на наши соц сети:
Twitter: https://twitter.com/iOSUkraine
Telegram Channel: https://t.me/iOSUkraine
Telegram Chat: https://t…
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...
Protect mutable state with Swift actors
https://developer.apple.com/videos/play/wwdc2021/10133/
1️⃣ Обзор на сессию выложу в виде последовательных постов (номер по порядку смотрите в начале поста), так как инфы оч много, а тема достаточно интересная и нетрививальная.
Одна из основных проблем многопоточности - data race/ гонка данных (когда минимум два потока пытаются получить доступ к общему ресурсу и минимум один - доступ на запись).
Рассмотрим код:
В разный момент времени print может вернуть 1, 2; 2,1 и даже 2,2 или 1,1 (если обе таски прочитают 0 или 1 в начальном состоянии).
Проблема гонки потоков относится к классу недетерминированных (которые можно получить строго в определенной последовательности), так как системный планировщик может запускать параллельные таски каждый раз по-разному.
Value семантика позволяет избегать гонки потоков как в следующем примере (Словари и массивы в Swift относятся к value типам):
Но при этом, если мы изменим код сверху в value-семантике (выбрав не
гонка потоков все равно остается, так как на счетчик ссылаются из двух параллельных задач. К тому же, компилятор и вовсе выдаст ошибку. Таким образом, необходим инструмент для синхронизации для одновременного доступа к общему изменяемому state из параллельных задач (до этого момента мы могли юзать Atomic, Locks или серийные очереди).
🧐Actors предоставляют такой инструмент синхронизации. У Actor есть свой state, который изолирован от остальной части программы так, что любой доступ к state происходит через actor, а actor из коробки обеспечивает, что никакой другой код параллельно не получит доступ к state (по сути это инструмент и напоминает lock и серийную очередь).
❗️Actors - новый тип в swift, у них те же возможности, что и у обычных name-типов: у них могут быть свойства, методы, конструкторы, subscripts, могут подписываться под протоколы и расширяться через extensions, но не наследоваться!
❗️Actors относятся к reference типам как классы (потому что главное назначение actors - предоставлять доступ к общему ресурсу)
Таким образом, основной отличительной чертой actors является то, что они изолируют данные от остальной части программы и обеспечивают синхронизированный доступ к этим данным.
Изменив, код выше на:
Мы гарантируем, что доступ к value не будет предоставляться параллельно.
Но при этом, если мы запустим следующий код:
То print нам вернет или 1,2 , или 2,1 (так как очередность тасок не гарантируется), но при этом НЕ будет значения 1,1 или 2,2!
В случае же одновременного доступа к state, как мы можем гарантировать, чтобы другая таска ждала, пока выполнится первая? Для этого в swift появился механизм из коробки для этого: запускать через
Так как таска запускается как async, то другая таска пытающаяся получить доступ к actor, видя что он занят, перейдет в режим suspended и CPU будет выполнять другую работу, поток не будет блокирован.
#watchthis #readthis
https://developer.apple.com/videos/play/wwdc2021/10133/
1️⃣ Обзор на сессию выложу в виде последовательных постов (номер по порядку смотрите в начале поста), так как инфы оч много, а тема достаточно интересная и нетрививальная.
Одна из основных проблем многопоточности - data race/ гонка данных (когда минимум два потока пытаются получить доступ к общему ресурсу и минимум один - доступ на запись).
Рассмотрим код:
class Counter {
var value = 0
func increment() -> Int {
value = value + 1
return value
}
}
let counter: Counter = .init()
Task.detached {
print(counter.increment())
}
Task.detached {
print(counter.increment())
}
В разный момент времени print может вернуть 1, 2; 2,1 и даже 2,2 или 1,1 (если обе таски прочитают 0 или 1 в начальном состоянии).
Проблема гонки потоков относится к классу недетерминированных (которые можно получить строго в определенной последовательности), так как системный планировщик может запускать параллельные таски каждый раз по-разному.
Value семантика позволяет избегать гонки потоков как в следующем примере (Словари и массивы в Swift относятся к value типам):
var array1 = [1, 2]
var array2 = array1
array1.append(3)
array2.append(4)
print(array1) // [1,2,3]
print(array2) // [1,2,4]
Но при этом, если мы изменим код сверху в value-семантике (выбрав не
class
, а struct
):struct Counter {
var value = 0
mutating func increment() -> Int {
value = value + 1
return value
}
}
Var counter: Counter = .init()
Task.detached {
print(counter.increment())
}
Task.detached {
print(counter.increment())
}
гонка потоков все равно остается, так как на счетчик ссылаются из двух параллельных задач. К тому же, компилятор и вовсе выдаст ошибку. Таким образом, необходим инструмент для синхронизации для одновременного доступа к общему изменяемому state из параллельных задач (до этого момента мы могли юзать Atomic, Locks или серийные очереди).
🧐Actors предоставляют такой инструмент синхронизации. У Actor есть свой state, который изолирован от остальной части программы так, что любой доступ к state происходит через actor, а actor из коробки обеспечивает, что никакой другой код параллельно не получит доступ к state (по сути это инструмент и напоминает lock и серийную очередь).
❗️Actors - новый тип в swift, у них те же возможности, что и у обычных name-типов: у них могут быть свойства, методы, конструкторы, subscripts, могут подписываться под протоколы и расширяться через extensions, но не наследоваться!
❗️Actors относятся к reference типам как классы (потому что главное назначение actors - предоставлять доступ к общему ресурсу)
Таким образом, основной отличительной чертой actors является то, что они изолируют данные от остальной части программы и обеспечивают синхронизированный доступ к этим данным.
Изменив, код выше на:
actor Counter {
var value = 0
func increment() -> Int {
value = value + 1
return value
}
}
Мы гарантируем, что доступ к value не будет предоставляться параллельно.
Но при этом, если мы запустим следующий код:
let counter: Counter = .init()
Task.detached {
print(counter.increment())
}
Task.detached {
print(counter.increment())
}
То print нам вернет или 1,2 , или 2,1 (так как очередность тасок не гарантируется), но при этом НЕ будет значения 1,1 или 2,2!
В случае же одновременного доступа к state, как мы можем гарантировать, чтобы другая таска ждала, пока выполнится первая? Для этого в swift появился механизм из коробки для этого: запускать через
await
.Task.detached {
print(await counter.increment())
}
Task.detached {
print(await counter.increment())
}
Так как таска запускается как async, то другая таска пытающаяся получить доступ к actor, видя что он занят, перейдет в режим suspended и CPU будет выполнять другую работу, поток не будет блокирован.
#watchthis #readthis
Modern swift API design.pdf
2.3 MB
Modern Swift API Design
https://developer.apple.com/videos/play/wwdc2019/415/
Отличная презентация про современной подход к дизайну API приложения, поднимаются такие вопросы, как какие типы - value или reference - использовать и где, когда лучше вводить протоколы, а когда дженерики (общий посыл: не надо чрезмерно злоупотреблять протоколами (принцип less code is better), а чаще использовать generic типы), рассказывают про PropertyWrappers (ключевая аннотация при объявление
#watchthis
https://developer.apple.com/videos/play/wwdc2019/415/
Отличная презентация про современной подход к дизайну API приложения, поднимаются такие вопросы, как какие типы - value или reference - использовать и где, когда лучше вводить протоколы, а когда дженерики (общий посыл: не надо чрезмерно злоупотреблять протоколами (принцип less code is better), а чаще использовать generic типы), рассказывают про PropertyWrappers (ключевая аннотация при объявление
@propertyWrapper
) и их роль в SwiftUI.#watchthis
🧭 Быстрая навигация на канале
#readthis - ссылки на статьи, книги и др
#watchthis - ссылки на видео
#howto - воркшопы, обучающие статьи и т п
#getsources - ссылки на проекты с открытым исходным кодом (включая #swiftpm модули)
#trytodo - челенджи, иногда простые, иногда не очень
#groovy - посты с наибольшим количеством шарингов и реакций
#tasty - “посмотри, чтоб вдохновиться”, здесь будут анимации, концепты и т п
#readthis - ссылки на статьи, книги и др
#watchthis - ссылки на видео
#howto - воркшопы, обучающие статьи и т п
#getsources - ссылки на проекты с открытым исходным кодом (включая #swiftpm модули)
#trytodo - челенджи, иногда простые, иногда не очень
#groovy - посты с наибольшим количеством шарингов и реакций
#tasty - “посмотри, чтоб вдохновиться”, здесь будут анимации, концепты и т п
🧭 Quick navigation
#readthis - recommended articles, books, etc
#watchthis - recommended videos, clips, etc
#howto - tutorials, rtfm
#getsources - where the hell are sources? open-source repositories (including my own swift packages #swiftpm), projects
#trytodo - “try to do” challenges, sometimes not easy
#groovy - trending high-rated posts based on statistics (private or public sharing and positive reactions)
#tasty - cool creative features (animations, concepts, etc), might be useful for inspiring developers, designers or PMs
#readthis - recommended articles, books, etc
#watchthis - recommended videos, clips, etc
#howto - tutorials, rtfm
#getsources - where the hell are sources? open-source repositories (including my own swift packages #swiftpm), projects
#trytodo - “try to do” challenges, sometimes not easy
#groovy - trending high-rated posts based on statistics (private or public sharing and positive reactions)
#tasty - cool creative features (animations, concepts, etc), might be useful for inspiring developers, designers or PMs