Всем привет! Многие из нас столкнулись с неприятным сюрпризом при обновлении до Swift 6 и новее: старый добрый код внезапно перестал компилироваться. Жесткие проверки конкуренции выявляют проблемы там, где раньше все работало. Особенно болезненно это проявляется при работе с легаси-API от Apple, которые еще не адаптированы под новые требования.
В чем именно проблема:
Возьмем классический пример: создание UIHostingController внутри методов, которые не помечены как
@MainActor. Swift 6 видит потенциальную гонку данных и отказывается компилировать такой код:
class CustomAttachmentViewProvider: NSTextAttachmentViewProvider {
override func loadView() {
// Ошибка компиляции в Swift 6 и новее
let hosting = UIHostingController(rootView: MySwiftUIView())
self.view = hosting.view
}
}
Обычные решения не работают:
Многие пытаются решить проблему стандартными способами:
🔵 Добавление
@MainActor к классу или методу🔵 Обертывание в Task {
@MainActor in }🔵 Использование DispatchQueue.main.async
Но эти подходы либо не решают проблему полностью, либо создают новые сложности с передачей данных между контекстами.
Решение: MainActor.assumeIsolated:
Именно здесь на помощь приходит MainActor.assumeIsolated - метод, созданный специально для таких случаев:
class CustomAttachmentViewProvider: NSTextAttachmentViewProvider {
override func loadView() {
let view = MainActor.assumeIsolated {
let hosting = UIHostingController(rootView: MySwiftUIView())
hosting.view.backgroundColor = .clear
return hosting.view
}
self.view = view
}
}
Особенности работы метода:
🔵 Синхронное выполнение: код внутри замыкания выполняется немедленно.
🔵 Проверка изоляции: если вызов не на главном потоке, приложение упадет.
🔵 Возврат значений: можно безопасно возвращать Sendable-типы.
Важные замечания:
🔵 Всегда проверяйте поток: используйте Thread.isMainThread перед вызовом.
🔵 Только для синхронных операций: не подходит для асинхронных задач.
🔵 Избегайте захвата self: используйте статические методы когда возможно.
🔵 Тестируйте тщательно: ошибки приведут к крешам в рантайме.
Подробный пример использования:
class CustomAttachmentViewProvider: NSTextAttachmentViewProvider {
override func loadView() {
view = createViewSafely()
}
private func createViewSafely() -> UIView {
if Thread.isMainThread {
return Self.createHostingView()
} else {
return DispatchQueue.main.sync {
Self.createHostingView()
}
}
}
private static func createHostingView() -> UIView {
MainActor.assumeIsolated {
let hosting = UIHostingController(rootView: MySwiftUIView())
return hosting.view
}
}
}
🔗 Ссылка на подробную статью
💡 Вывод:
MainActor.assumeIsolated - это не хаки и не костыли, а официально одобренный Apple способ решать проблемы совместимости в переходный период. Он позволяет сохранить работоспособность старого кода соблюдая строгие правила Swift 6.
Главное - использовать этот инструмент осознанно: проверять выполнение на главном потоке, тестировать краевые случаи и постепенно мигрировать на полностью безопасные подходы. Временные решения должны оставаться временными, даже если они очень эффективны.
➡️ Подписаться на канал
Мобильный трудоголик