SwiftUI dev
1.17K subscribers
87 photos
37 videos
1 file
74 links
Mobile development, SwiftUI, Compose, feel free to reach me: @lexkraev
Download Telegram
SwiftUI dev
4️⃣ Поговорим про взаимодействие акторов с замыканиями. Рассмотрим пример: actor LibraryAccount { let idNumber: Int var booksOnLoan: [Book] = [] } extension LibraryAccount { func readSome(_ book: Book) -> Int { ... } func read() -> Int…
5️⃣ Как устроен актор, Sendable

Типы, которые безопасны для общего параллельного(одновремнного) использования между акторами, называются Sendable.
Тип может быть Sendable, если его значение копируется из одного места в другое, и оба места могут безопасно изменять свои собственные копии этого значения, не мешая друг другу.
Таким образом, по определению value-типы и акторы являются Sendable, а классы могут быть Sendable, если реализованы тщательным образом, например, если класс и все его подклассы содержат только неизменяемые данные или если класс выполняет синхронизацию с блокировкой для обеспечения параллельного доступа, в остальных случаях классы нельзя отнести к Sendable. Функции необязательно являются Sendable, но для случаев, когда являются, вводится новый тип функций с аннотацией @Sendable.
На самом деле, акторы и вообще параллельный код должен общаться посредством Sendable типов, которые защищают код от date race.
Swift в конечном итоге предотвратит совместное использование не-Sendable данных: компилятор выдаст ошибку. Как компилятор это проверяет? Так как Sendable это протокол, то компилятор смотрит удовлетворяет ли тип протоколу. Например, рассмотрим код:

struct Book: Sendable {
var title: String
var authors: [Author]
}


Структура Book может быть Sendable тогда, когда все ее свойства удовлетворяют Sendable, и если Author является, например, классом, то [Author] не является Sendable, а значит и Book таким не является.
Дженерики, при определении являются ли они Sendable, все зависит от их параметров:

struct Pair<T, U> {
var first: T
var second: U
}

extension Pair: Sendable where T: Sendable, U: Sendable {
}


Pair будет Sendable, когда оба аргумента Sendable. Таким образом, и массив типов Sendable является Sendable.

Функции могут быть @Sendable. Это означает, что безопасно передавать значение функции между акторами. Это особенно важно для замыканий, где Sendable ограничивает то, что может сделать замыкание, чтобы предотвратить data race (например, замыкание не может захватить локальную переменную, потому что это позволит data race по этой переменной). И, наконец, синхронное замыкание Sendable не может быть изолировано от актора, потому что это позволило бы запустить код на акторе снаружи.

detached у Task определена следующим образом:
static func detached(operation: @Sendable () async -> Success) -> Task<Success, Never>

Потому и в примере выше:

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())
}


компилятор ругался на захват var counter.

Рассмотрим другой пример:

static func detached(operation: @Sendable () async -> Success) -> Task<Success, Never>

extension LibraryAccount {
func read() -> Int { ... }

func readLater() {
Task.detached {
self.read()
}
}
}


Поскольку замыкание для Task.detached является Sendable, то замыкание не должно быть изолировано от актора. Таким образом, вызов метода self.read() должен быть асинхронным. Преобразуем:

static func detached(operation: @Sendable () async -> Success) -> Task<Success, Never>

extension LibraryAccount {
func read() -> Int { ... }

func readLater() {
Task.detached {
await self.read()
}
}
}


Sendable типы и замыкания помогают поддерживать изоляцию акторов, проверяя, что изменяемое состояние не является общим для акторов и не может быть изменено одновременно.

#readthis
SwiftUI dev
5️⃣ Как устроен актор, Sendable Типы, которые безопасны для общего параллельного(одновремнного) использования между акторами, называются Sendable. Тип может быть Sendable, если его значение копируется из одного места в другое, и оба места могут безопасно…
6️⃣ Main actor
Когда мы работаем над приложением, нам нужно постоянно думать об основном потоке. Именно здесь происходит рендеринг UI, а также где обрабатываются события взаимодействия с пользователем. Тем не менее, если мы постоянно работаем в главном потоке, скажем, загружаем данные, то наш UI начинает фризить, особенно в случаях плохой связи. Такие операции нужно выносить из главного потока, а потом вызывать DispatchQueue.main.async всякий раз, когда есть конкретная операция, которая должна быть выполнена в основном потоке, например, присвоение в @Published.

func checkedOut(_ booksOnLoan: [Book]) {
booksView.checkedOutBooks = booksOnLoan
}

DispatchQueue.main.async {
checkedOut(booksOnLoan)
}


На самом деле, взаимодействие с основным потоком очень похоже на взаимодействие с актором.
И если мы работаем в основном потоке, то можем безопасно получить доступ и обновить state у UI, а если не на главном, то работать с ним нужно асинхронно. Именно так и работают акторы. Main actor - специальный актор, который представляет главный поток.
Код выше преобразовывается:

@MainActor func checkedOut(_ booksOnLoan: [Book]) {
booksView.checkedOutBooks = booksOnLoan
}

await checkedOut(booksOnLoan)

Главный актор отличается от обычного двумя свойствами:
1. Всю свою синхронизация выполняет через main disptach queue. Это означает, что с точки зрения выполнения на главном акторе логику можно заменить на DispatchQueue.main
2. Так как код и данные, которые нужно выполнять на главном потоке, разбросаны повсюду: SwiftUI, UIKit, фрэймворки, то достаточно указать для этого специальную аннотацию @MainActor (и код выполнить асинхронно на главном потоке).
У типов, помеченных @MainActor, члены и подклассы также будут @MainActor. Это удобно тех частей кода, которые должны взаимодействовать с пользовательским интерфейсом, где большинство все должно выполняться в основном потоке.

#readthis
SwiftUI dev
Protect mutable state with Swift actors https://developer.apple.com/videos/play/wwdc2021/10133/ 1️⃣ Обзор на сессию выложу в виде последовательных постов (номер по порядку смотрите в начале поста), так как инфы оч много, а тема достаточно интересная и нетрививальная.…
👨🏻‍💻Правила изоляции акторов:
1. Актор может читать свои собственные свойства или вызывать свои функции (т.е. используя self) синхронно.
2. Актор может обновлять только свои собственные свойства (и может делать это синхронно). Это означает, что вы можете обновлять свойства только с помощью ключевого слова self.
Попытка обновить свойство другого актора приведет к ошибке компилятора.
3. ☝🏼Считывание свойств между участниками или вызовы функций должны происходить асинхронно с использованием ключевого слова await. 🧐Однако перекрестное чтение неизменяемых свойств может происходить синхронно (тех, что объявлены с помощью let).
👍🏻 Весьма годная статья про акторы
https://apptractor.ru/info/articles/actors-swift-5-5.html

Выдержка:
Одно из лучших объяснений для общения в модели акторов выглядит следующим образом:
Представьте, что каждый актор похож на остров, а наша кодовая база — это мир с островами. Каждый остров может общаться с другим островом, отправляя ему сообщения в бутылке. Каждый остров знает, куда отправить сообщение (то есть адрес другого острова), и именно так работает связь между островами.

#readthis