5. Global Actor
Мы уже с вами изучили как работает Actor, для чего он нужен. Но скорее всего вы уже слышали про MainActor. Так вот он реализован он немного по-другому. У него гораздо больше ответственности и нагрузки на него, но не потому что он отвечает за главный поток, а потому что он является глобальным(Global)
Определение¶
Global Actor в Swift — это механизм, который позволяет обеспечить безопасный и синхронизированный доступ к глобальным данным и функциям в многопоточной среде, используя модель акторов для контроля доступа. Если говорить тезисно:
- Это синглтон
- Умеет шарить своего исполнителя между вызовами
- Умеет шарить изоляцию между всеми вызовами
- Глобальный Actor можно применять к классам, методам, функциям, замыканиям
Примеры¶
@globalActor
struct MyGlobalActor: GlobalActor {
static var shared = MyActor()
private init() { }
}
@MyGlobalActor
func globalFunction() {
// Действия, безопасно выполняемые в MyGlobalActor
}
1) Отнаследоваться от GlobalActor
2) Добавить аннотацию @globalActor
3) Заприватить инициализатор, чтобы доступ был как к синглтону
Зачем нужен global Actor?¶
- Выполнять произвольные методы в коде последовательно
- Связать произвольные методы с определённым потоком
- Вынести определённые методы из общего исполнителя
Приведём ещё примеров:
@globalActor
private actor SharedActor: GlobalActor {
static let shared = SharedActor()
private init() {}
}
Task.detached { @SharedActor in
for _ in 0..<10 {
print("one")
}
}
Task.detached { @SharedActor in
for _ in 0..<10 {
print("two")
}
}
MainActor¶
MainActor - это обычный глобальный эктор, все задачи которые им изолируются - попадают на главный поток. Все задачи на таком экторе имеют самый высокий приоритет.
Task.detached { @MainActor in
for _ in 0..<10 {
print("one")
}
}
Task.detached { @MainActor in
for _ in 0..<10 {
print("two")
}
}
Task.detached { @MainActor in
/// контекст главного исполнителя
let value = await fetchValueFromStorage() /// -> уходим из MainActor
/// Снова возвращаемся к контексту главного исполнителя
}
Task.detached {
let value = await fetchValueFromStorage()
await MainActor.run {
/// запускаем задачи на MainActor
}
}
final class MyClass {
@MainActor
func callMainActorMethod () {
print(Thread.isMainThread ? "Main Thread" : "Background Thread")
}
}
struct Example {
func execute() {
DispatchQueue.global(qos: .background).async {
let myClass = MyClass()
myClass.callMainActorMethod()
}
}
}
let example = Example()
example.example()
Ответ: Хоть метод и помечен @MainActor и кажется, что он будет исполняться на главном потоке. Но это не так, на самом деле напечатается Background Thread. Здесь проблема вызвана именно сочетанием кода GCD и Structured Concurrency. Почему так происходит и где теряется контекст - обсудим в статьях более сложного уровня.