Основное статическое свойство «текст», изолированное от актера, не может быть изменено из неизолированного контекста; это ошибка в Swift 6

Я столкнулся с проблемой при попытке изменить свойство 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 (просто любопытно) для доступа к этому свойству?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
433
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Не используйте 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"
    }
}

Я считаю, что это должен быть общепринятый ответ, но ответ Роба также очень силен.

Paulo Mattos 24.06.2024 18:58
Ответ принят как подходящий

Вы сказали:

Мой вопрос: почему статическое свойство в 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 подозрительна, поэтому я бы не советовал вышеизложенное. Но я просто пытаюсь проиллюстрировать, как в целом теоретически можно устранить описанную вами ошибку компилятора.


Несколько несвязанных наблюдений:

  1. Очень любопытно иметь static изменяемые свойства. Если бы static был введен просто для того, чтобы предоставить удобный способ, позволяющий Secondary изменять некоторые свойства Primary, я бы настоятельно не советовал использовать этот шаблон.

    static следует использовать только там, где вам действительно нужно, чтобы все экземпляры Primary имели общее состояние. Это единственная цель static. Проблема с изменяемыми свойствами static заключается в том, что зависимости между типами скрыты, что приводит к созданию кода, который чрезвычайно сложно отлаживать. Это похоже на оживленную дискуссию о том, почему глобалы являются «злыми». (Смеется.) Зависимости скрыты, а мутирующее состояние трудно контролировать/отлаживать.

    Короче говоря, изменяемая статика чрезвычайно необычна для контроллеров представления и, как правило, имеет запах кода.

  2. Идея о том, что один тип проникает и изменяет свойство другого типа, противоречит цели сохранения слабосвязанных типов. Например, 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.

Другие вопросы по теме