Я столкнулся с проблемой при попытке изменить свойство static внутри подкласса UIViewController из другого класса, особенно внутри асинхронной функции. Кроме того, я сталкиваюсь с ошибкой
Основное статическое свойство «текст», изолированное от актера, не может быть изменено из неизолированного контекста; это ошибка в Swift 6
Мой код:
class Primary: UIViewController {
static var text = ""
}
class Secondary {
func someAction() async {
Primary.text = "12" // Getting warning in this line!
}
}
В любом случае, использование DispatchQueue.main.async отключит это предупреждение.
Мой вопрос: почему статическое свойство в UIViewController считается изолированным свойством актера? Если оно изолировано, то почему мы не можем использовать await (просто любопытно) для доступа к этому свойству?





Не используйте DispatchQueue, чтобы «обойти» проблему; это всего лишь повязка, и притом плохая. Сделайте то, что говорит вам сообщение об ошибке: изолируйте Secondary от @MainActor:
class Primary: UIViewController {
static var text = ""
}
@MainActor class Secondary {
func someAction() async {
Primary.text = "12"
}
}
или изолируйте рассматриваемый метод до @MainActor:
class Primary: UIViewController {
static var text = ""
}
class Secondary {
@MainActor func someAction() async {
Primary.text = "12"
}
}
Если вы собираетесь выполнять другие асинхронные действия не с главным актером, добавьте в Secondary больше методов, которые не изолированы от актера. Затем они могут вызвать someAction с помощью await:
class Primary: UIViewController {
static var text = ""
}
class Secondary {
func otherAction() async {
await someAction()
}
@MainActor func someAction() async {
Primary.text = "12"
}
}
Вы сказали:
Мой вопрос: почему статическое свойство в
UIViewControllerсчитается изолированным свойством актера?
Если вы посмотрите документацию UIViewController , то там сказано, что весь класс изолирован от главного актера, определенного следующим образом:
@MainActor
class UIViewController : UIResponder
Поскольку UIViewController изолирован от главного актера, все подклассы (а также свойства, методы и т. д. любых подклассов) также изолированы от главного актера.
Если оно изолировано, то почему мы не можем использовать
await(просто любопытно) для доступа к этому свойству?
Сообщение об ошибке немного вводит в заблуждение. Да, часть проблемы в том, что вы пытаетесь выполнить какое-то действие в другом асинхронном контексте (что требует await). Но более глубокая проблема заключается в том, что вы вообще пытаетесь проникнуть внутрь и напрямую обновить свойство, изолированное от актера. Актер несет ответственность за обработку любых изменений своих свойств. Внешние типы не должны иметь возможность напрямую изменять свои свойства.
Вся идея использования акторов состоит в том, что актор должен облегчить все изменения своих собственных свойств. Это делается не только для предотвращения гонок данных, но и для устранения условий гонки в более общем плане. Посмотрите видео WWDC 2021 Защитите изменяемое состояние с помощью Swift-актеров.
Но вы можете предоставить метод для изменения этого свойства:
class Primary: UIViewController {
static var text = ""
static func updateText(_ string: String) {
text = string
}
}
class Secondary {
func someAction() async {
Primary.updateText("12") // Error: Expression is 'async' but is not marked with 'await'
}
}
Теперь, когда мы правильно обновляем это свойство, вы получаете соответствующую ошибку о необходимости await этого обновления. Таким образом, исправленная реализация будет такой:
class Secondary {
func someAction() async {
await Primary.updateText("12") // OK
}
}
Как обсуждается ниже, я считаю, что сама идея изменяемых свойств static подозрительна, поэтому я бы не советовал вышеизложенное. Но я просто пытаюсь проиллюстрировать, как в целом теоретически можно устранить описанную вами ошибку компилятора.
Несколько несвязанных наблюдений:
Очень любопытно иметь static изменяемые свойства. Если бы static был введен просто для того, чтобы предоставить удобный способ, позволяющий Secondary изменять некоторые свойства Primary, я бы настоятельно не советовал использовать этот шаблон.
static следует использовать только там, где вам действительно нужно, чтобы все экземпляры Primary имели общее состояние. Это единственная цель static. Проблема с изменяемыми свойствами static заключается в том, что зависимости между типами скрыты, что приводит к созданию кода, который чрезвычайно сложно отлаживать. Это похоже на оживленную дискуссию о том, почему глобалы являются «злыми». (Смеется.) Зависимости скрыты, а мутирующее состояние трудно контролировать/отлаживать.
Короче говоря, изменяемая статика чрезвычайно необычна для контроллеров представления и, как правило, имеет запах кода.
Идея о том, что один тип проникает и изменяет свойство другого типа, противоречит цели сохранения слабосвязанных типов. Например, Secondary должен предоставлять механизмы общего назначения, позволяющие наблюдать за его свойствами, но не делать никаких предположений относительно Primary. Primary следует просто создать экземпляр Secondary и наблюдать за его свойствами.
Например, с помощью ObservableObject вы можете использовать «Объединить», чтобы наблюдать за изменениями:
import UIKit
import Combine
class Primary: UIViewController {
private let secondary = Secondary()
@IBOutlet weak var textField: UITextField!
private var cancellables: Set<AnyCancellable> = []
override func viewDidLoad() {
super.viewDidLoad()
textField.text = "-1"
secondary
.objectWillChange
.receive(on: DispatchQueue.main)
.sink { [weak self] object in
self?.textField.text = self?.secondary.text
}
.store(in: &cancellables)
}
@IBAction func didTapButton(_ sender: Any) {
Task { await secondary.someAction() }
}
}
@MainActor
class Secondary: ObservableObject, Sendable {
@Published var text = ""
func someAction() async {
text = "42"
}
}
Таким образом, Secondary больше не будет тесно связан с Primary. В приведенном выше примере Primary просто наблюдает за изменениями в Secondary.
Существует множество различных шаблонов (наблюдение, замыкания, делегаты), которые достигают одной и той же цели. Но в идеале мы хотим, чтобы наши типы были слабо связаны. Primary должен использовать интерфейс Secondary, но Secondary не должен делать никаких предположений относительно Primary.
Я считаю, что это должен быть общепринятый ответ, но ответ Роба также очень силен.