Как использовать свойство @State из Sendable Closure в SwiftUI?

Вопрос или проблема

Здесь hasComplexWave — это логическое значение @State в представлении SwiftUI. Вот код, который его использует:

view.visualEffect { [hasComplexWave] content, proxy in
    content.distortionEffect(
        ShaderLibrary.complexWave(
            .float(startDate.timeIntervalSinceNow),
            .float2(proxy.size),
            .float(0.5),
            .float(8),
            .float(10)
        ),
        maxSampleOffset: CGSize(width: 100, height: 100),
        isEnabled: hasComplexWave
    )
}

Проблема в том, что это вызывает предупреждение в Swift 5 и ошибку в Swift 6, которая выглядит следующим образом:

Свойство основного актора ‘hasComplexWave’ не может быть использовано из замыкания Sendable; это ошибка в режиме языка Swift 6

Как лучше всего исправить эту ошибку?

Вы можете добавить “список захвата” в замыкание, чтобы избежать ссылки на свойство, изолированное в актере, внутри замыкания.

Таким образом, замените …

view.visualEffect { content, proxy in … }

… на:

view.visualEffect { [hasComplexWave] content, proxy in … }

Смотрите Списки захвата и Захват значений из Язык программирования Swift.

Попробуйте добавить @MainActor

view.visualEffect { @MainActor content, proxy in
    content.distortionEffect(
        ShaderLibrary.complexWave(
            .float(startDate.timeIntervalSinceNow),
            .float2(proxy.size),
            .float(0.5),
            .float(8),
            .float(10)
        ),
        maxSampleOffset: CGSize(width: 100, height: 100),
        isEnabled: hasComplexWave
    )
}

Ответ или решение

Чтобы использовать свойство @State из отправляемого замыкания в SwiftUI, необходимо учесть несколько аспектов, связанных с акторной моделью Swift. Ошибка, с которой вы столкнулись, связана с тем, что свойства, определенные как @State, изолированы на главном акторе, и их нельзя напрямую ссылаться из замыканий, которые объявлены как Sendable. Ниже приведены рекомендации по устранению этой проблемы.

Проблема

Ваш код содержит строку, где переменная hasComplexWave, отмеченная как @State, используется в отправляемом замыкании:

view.visualEffect { [hasComplexWave] content, proxy in
    content.distortionEffect(
        ShaderLibrary.complexWave(
            .float(startDate.timeIntervalSinceNow),
            .float2(proxy.size),
            .float(0.5),
            .float(8),
            .float(10)
        ),
        maxSampleOffset: CGSize(width: 100, height: 100),
        isEnabled: hasComplexWave
    )
}

В Swift 6 это вызывает ошибку: "Main actor-isolated property ‘hasComplexWave’ cannot be referenced from a Sendable closure".

Решение

Есть несколько способов, чтобы решить эту проблему:

1. Использование списка захвата (Capture List)

Вы уже сделали шаг в правильном направлении, добавив список захвата:

view.visualEffect { [hasComplexWave] content, proxy in
    content.distortionEffect(
        ShaderLibrary.complexWave(
            .float(startDate.timeIntervalSinceNow),
            .float2(proxy.size),
            .float(0.5),
            .float(8),
            .float(10)
        ),
        maxSampleOffset: CGSize(width: 100, height: 100),
        isEnabled: hasComplexWave
    )
}

Однако такое решение все еще может не сработать, так как список захвата может просто ‘схватить’ ошибочное состояние hasComplexWave.

2. Использование @MainActor

Для исправления ситуации можно добавить атрибут @MainActor в объявление замыкания. Это позволяет явно указывать, что замыкание должно выполняться на главном акторе, обеспечивая безопасный доступ к свойствам, изолированным на этом акторе:

view.visualEffect { @MainActor content, proxy in
    content.distortionEffect(
        ShaderLibrary.complexWave(
            .float(startDate.timeIntervalSinceNow),
            .float2(proxy.size),
            .float(0.5),
            .float(8),
            .float(10)
        ),
        maxSampleOffset: CGSize(width: 100, height: 100),
        isEnabled: hasComplexWave
    )
}

Заключение

Учитывая вышеописанные методы, использование @MainActor в замыкании представляется наиболее эффективным способом избежать проблемы, связанной с акторной изоляцией. Это решение гарантирует корректный доступ к свойствам, отмеченным как @State, что позволяет вашему коду компилироваться и выполняться без ошибок. Например, если вам нужно будет несколько раз использовать hasComplexWave, хорошей практикой будет его объявление на уровне, доступном для вашего замыкания (если контекст позволяет).

Такое внимание к управлению состоянием и изоляции данных в SwiftUI помогает создать более надежное и безопасное приложение, с правильным управлением потоками выполнения и состоянием пользовательского интерфейса.

Оцените материал
Добавить комментарий

Капча загружается...