От меня бегут разработчики
на собеседованиях...
И немного о накрутке опыта
Два заголовка сразу, мощно?
Для нашего приложения Dhamer (транспортное приложение для города Мекка) мы в темпе искали нового iOS-разработчика: проект горел, времени на углубленную проверку по всем фронтам особо не было. Моей задачей было оперативно найти практика, поговорить об опыте, состыковаться и поскорей начать работать – минимум теории, just do iOS.
Кандидат №1:
Кандидат уверенно рассказывал, как на прошлом проекте за полгода полностью переделал навигацию – заменил какие-то запутанные роутеры и координаторы на (естественно) "удобные". Я начал задавать соответствующие вопросы: как устроены ссылки между контроллерами и координаторами, кто кого держит, где используются сильные, а где слабые ссылки. Но в ответ – путаница, неуверенность, противоречия, retain cycle-ы на каждом предположении, это вызвало у меня некоторую настороженность.
Перешли в лайвкодинг. Я попросил набросать базовые классы навигации в его решении, роутер бахнуть, ну или координатор (Ибо реализацию на слух я так и не понял).
Кандидат пишет одну строчку:
Думает 1 минуту… И просто выходит из Zoom. Больше на связь не вышел. "Ну, не по пути"
Кандидат №2:
Буквально следующий собес, минимум теории, обсуждение опыта. Вновь детали не помнит, да и вообще, все по классике, что обсуждать. Уже и не помню, на каком вопросе про опыт, но кандидат без слов ливает с собеседования с концами. "А может токсик на собеседующем?"
Кандидат №3:
Заряженный мидл, готовый сделать с нами новое крутое приложение. Да вот не задача, в резюме у него 3 года опыта над нашим (буквально) приложением Добро.РФ, да и в разделе компания также мы ISS, прямо в hh резюме, я даже не поверил нашему hr-у, пока сам не убедился. К слову, на этапе hr-a его и завернули, ибо с нами он не работал, но пообщаться было бы интересно. Может, поставил бы личный рекорд — три сбежавших кандидата за 1 день.
Может, история была интересней, если бы я нашел этих ребят на условном ОМ (раз такой хайп вокруг них сейчас) или движухе собес-паровозиков, но нет.
Так же не нулевая вероятность, что просто токсик на собеседующем затоксичел, а нормальные ребята пошли дальше.
UPD: По итогу мы сошлись с классными разработчиками, с которыми и сделали Dhamer, о чем в следующем посте ⬇️
на собеседованиях...
И немного о накрутке опыта
Два заголовка сразу, мощно?
Для нашего приложения Dhamer (транспортное приложение для города Мекка) мы в темпе искали нового iOS-разработчика: проект горел, времени на углубленную проверку по всем фронтам особо не было. Моей задачей было оперативно найти практика, поговорить об опыте, состыковаться и поскорей начать работать – минимум теории, just do iOS.
Кандидат №1:
Кандидат уверенно рассказывал, как на прошлом проекте за полгода полностью переделал навигацию – заменил какие-то запутанные роутеры и координаторы на (естественно) "удобные". Я начал задавать соответствующие вопросы: как устроены ссылки между контроллерами и координаторами, кто кого держит, где используются сильные, а где слабые ссылки. Но в ответ – путаница, неуверенность, противоречия, retain cycle-ы на каждом предположении, это вызвало у меня некоторую настороженность.
Перешли в лайвкодинг. Я попросил набросать базовые классы навигации в его решении, роутер бахнуть, ну или координатор (Ибо реализацию на слух я так и не понял).
Кандидат пишет одну строчку:
class Router {Думает 1 минуту… И просто выходит из Zoom. Больше на связь не вышел. "Ну, не по пути"
Кандидат №2:
Буквально следующий собес, минимум теории, обсуждение опыта. Вновь детали не помнит, да и вообще, все по классике, что обсуждать. Уже и не помню, на каком вопросе про опыт, но кандидат без слов ливает с собеседования с концами. "А может токсик на собеседующем?"
Кандидат №3:
Заряженный мидл, готовый сделать с нами новое крутое приложение. Да вот не задача, в резюме у него 3 года опыта над нашим (буквально) приложением Добро.РФ, да и в разделе компания также мы ISS, прямо в hh резюме, я даже не поверил нашему hr-у, пока сам не убедился. К слову, на этапе hr-a его и завернули, ибо с нами он не работал, но пообщаться было бы интересно. Может, поставил бы личный рекорд — три сбежавших кандидата за 1 день.
Может, история была интересней, если бы я нашел этих ребят на условном ОМ (раз такой хайп вокруг них сейчас) или движухе собес-паровозиков, но нет.
Так же не нулевая вероятность, что просто токсик на собеседующем затоксичел, а нормальные ребята пошли дальше.
UPD: По итогу мы сошлись с классными разработчиками, с которыми и сделали Dhamer, о чем в следующем посте ⬇️
50🙉11🌚3👍1💯1
Мы сделали Dhamer и показались на Dubai World Trade Centre
Для нашей команды разработки это уже вторая большая MaaS платформа, до этого был 🇷🇺 Московский Транспорт, теперь 🇸🇦 Dhamer. Надеюсь, что продукты и дальше будут развиваться, и впереди новые города и страны, новые вызовы и фичи, помогающие миллионам людей удобнее пользоваться различными услугами в своем городе.
Спасибо за невероятный опыт всей команде ISS и SpectralSoft, we did it!
Отдельное спасибо нашему СТО Кириллу @issdev, на котором без малого был весь процесс, порой даже и iOS📱
UPD: Часть технических вызовов и их решений на iOS все ждут статей, но я в этом деле дотошный, так что, в лучшем случае скажу: "Не скоро"
Представляем вам новейшую технологию «Мобильность как услуга» (MaaS), разработанную для создания более интеллектуальных и взаимосвязанных транспортных систем. От мультимодальных маршрутов в реальном времени до бесперебойной интеграции автопарка и платежей — мы меняем представление о городской мобильности.
Для нашей команды разработки это уже вторая большая MaaS платформа, до этого был 🇷🇺 Московский Транспорт, теперь 🇸🇦 Dhamer. Надеюсь, что продукты и дальше будут развиваться, и впереди новые города и страны, новые вызовы и фичи, помогающие миллионам людей удобнее пользоваться различными услугами в своем городе.
Спасибо за невероятный опыт всей команде ISS и SpectralSoft, we did it!
Отдельное спасибо нашему СТО Кириллу @issdev, на котором без малого был весь процесс, порой даже и iOS
UPD: Часть технических вызовов и их решений на iOS все ждут статей, но я в этом деле дотошный, так что, в лучшем случае скажу: "Не скоро"
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥9
О деньгах, мы в Forbes Russia
Почитать подробнее — Forbes Стартап-суббота
Деньги. есть.😘
Лично я за новый офис, ну или хотя-бы кресло-массажер
Онлайн-сервис для управления автопарками и оплаты автомобильных платежей «Паркоматика» привлек 55 млн рублей от Equity Club, рассказали Forbes в компании. Средства пойдут на развитие функционала платформы, расширение команды и масштабирование бизнеса в новых регионах. «Паркоматика» включает в себя мобильное приложение для водителей, электронный диспетчер задач и инструмент бизнес-отчетности, в сервис интегрированы более 20 городских автомобильных систем.
...платформу используют 700 предприятий (более 40 000 автомобилей), среди них — «Яндекс Доставка», РЖД, Lamoda и другие. Выручка компании в 2024 году составила 94,1 млн рублей, чистая прибыль — 15,4 млн рублей, следует из СПАРКа.
Почитать подробнее — Forbes Стартап-суббота
Деньги. есть.
Лично я за новый офис, ну или хотя-бы кресло-массажер
Please open Telegram to view this post
VIEW IN TELEGRAM
25 8🔥6🤩2💯1
И - Известность и общая боль Structed Cuncurency
Наш любимый Swift развивается в сторону строгого паралелизма. Однако на реальных проектах и фреймворках с тоннами легаси новый подход языка не всегда успешно накладывается на кодовую базу. Более того, часть архитектурных решений просто несовместимы с новым async видением от Apple.
Например, взять популярную библиотеку Swinject,
Cама идея регистрации объектов синхронно из общего неизолированного метода в лучшем случае небезопасна. А если у вас тысячи регистраций объектов под акторами... Ммм, в скольких случаях init у вас nonisolated? Как долго компилятор и крешрейт выдержат @preconcurrency в вашем коде?
С опытом (и чаще с болью) находятся обходные пути, хаки, которые в умелых руках делают код еще и безопасным.
Одним таким кейсом я недавно поделился в более известном iOS клубе, суть которых можно глянуть тут и тут.
Что ж, возможно, пришло время записать с Львом с канал iOS Makes Me Hate второй Workshop на тему Swift 6 и Structed Cuncurency
Ориентир на конец сентября, так что, уважаемые читатели, "Как только, так сразу"!
Наш любимый Swift развивается в сторону строгого паралелизма. Однако на реальных проектах и фреймворках с тоннами легаси новый подход языка не всегда успешно накладывается на кодовую базу. Более того, часть архитектурных решений просто несовместимы с новым async видением от Apple.
Например, взять популярную библиотеку Swinject,
Cама идея регистрации объектов синхронно из общего неизолированного метода в лучшем случае небезопасна. А если у вас тысячи регистраций объектов под акторами... Ммм, в скольких случаях init у вас nonisolated? Как долго компилятор и крешрейт выдержат @preconcurrency в вашем коде?
С опытом (и чаще с болью) находятся обходные пути, хаки, которые в умелых руках делают код еще и безопасным.
Одним таким кейсом я недавно поделился в более известном iOS клубе, суть которых можно глянуть тут и тут.
Что ж, возможно, пришло время записать с Львом с канал iOS Makes Me Hate второй Workshop на тему Swift 6 и Structed Cuncurency
Ориентир на конец сентября, так что, уважаемые читатели, "Как только, так сразу"!
🔥13🤡4
И так, официально вышла iOS 26
Официально наш как неделю редизайнутый дизайн снова устаревший
П.C. Ну хоть работа будет
А ниже опрос 🔽🔽🔽
Официально наш как неделю редизайнутый дизайн снова устаревший
П.C. Ну хоть работа будет
А ниже опрос 🔽🔽🔽
😁4❤1💔1
Планируете переход в вашем приложении на стеклянный дизайн?
Anonymous Poll
28%
Да, планируем
5%
Уже в разработке
50%
Не планируем
5%
Не разработчик, жду
13%
Не разработчик, не жду
❤2
Законтрибьютил в RouteComposer
RouteСomposer - библиотека для UIKit навигации на основе диплинков для каждого экрана.
С ней нет необходимости использовать паттерн Coordinator или хранить стейт навигации в какой-либо структуре.
В реализации навигации библеотека полагается на то, что доступно из коробки:
- Возможность обходить дерево UIViewController-ов
- Находить нужный UIViewController на основе типа, протокола, контекста
- А если не нашли, то создавать и показывать там, где нам надо
Все, что вам нужно, это описать конфиг для нужного экрана, и затем скормить его роутеру, который переведет вас хоть через все приложение на нужный экран:
Если у вас возникает вопрос — зачем оно надо? Ответ — диплинки.
Если после этого у вас остается вопрос — зачем оно? То, вам, вероятно, не надо.
Для нашей команды RouteComposer стал стандартом и номером 1 для навигации в наших приложениях. А перепробовали мы, наверное, все, от нативного present / push, до координаторов, роутеров, state-based навигации с разной степень "успешности".
И вот, опыт работы на паре проектов и пару тысяч строк в публичном ПР-е, работы с системой типов на протоколах, и теперь
Было:
Стало:
У библиотеки есть неплохой Example, всем советую хотя бы ознакомиться с навигацией на основе поиска и работы по дереву.
Делитесь своим мнением в комментариях ⬇️
RouteСomposer - библиотека для UIKit навигации на основе диплинков для каждого экрана.
С ней нет необходимости использовать паттерн Coordinator или хранить стейт навигации в какой-либо структуре.
В реализации навигации библеотека полагается на то, что доступно из коробки:
- Возможность обходить дерево UIViewController-ов
- Находить нужный UIViewController на основе типа, протокола, контекста
- А если не нашли, то создавать и показывать там, где нам надо
Все, что вам нужно, это описать конфиг для нужного экрана, и затем скормить его роутеру, который переведет вас хоть через все приложение на нужный экран:
let productScreen = StepAssembler<ProductViewController, Any?>() /// Тип и контекст нужного экрана
.finder(.productViewControllerFinder()) /// Как найти нужный экран по дереву навигации
.factory(.productViewControllerFactory()) /// Как создать экран, если не нашли в дереве
.add(LoginInterceptor<UUID>()) /// Доп. логика
.add(ProductViewControllerContextTask()) /// Доп. логика
.add(ProductViewControllerPostTask(analyticsManager: AnalyticsManager.sharedInstance)) /// Пример аналитики
.using(.push) /// Как открыть
.from(.navigationController) /// Откуда открыть
.using(.present) /// Как открыть / создать (navigationController)
.from(.current) /// Откуда открыть / создать (navigationController)
.assemble() /// Каст
Если у вас возникает вопрос — зачем оно надо? Ответ — диплинки.
Если после этого у вас остается вопрос — зачем оно? То, вам, вероятно, не надо.
Для нашей команды RouteComposer стал стандартом и номером 1 для навигации в наших приложениях. А перепробовали мы, наверное, все, от нативного present / push, до координаторов, роутеров, state-based навигации с разной степень "успешности".
И вот, опыт работы на паре проектов и пару тысяч строк в публичном ПР-е, работы с системой типов на протоколах, и теперь
Было:
Swift
let productScreen = StepAssembly(
finder: ClassWithContextFinder<ProductViewController, ProductContext>(),
factory: ClassFactory())
.using(UINavigationController.push())
.from(NavigationControllerStep())
.using(GeneralActions.presentModally())
.from(GeneralStep.current())
.assemble()
Стало:
let productScreen = StepAssembler<ProductViewController, ProductContext>()
.finder(.classWithContextFinder)
.factory(.classFactory)
.using(.push)
.from(.navigationController)
.using(.present)
.from(.current)
.assemble()
У библиотеки есть неплохой Example, всем советую хотя бы ознакомиться с навигацией на основе поиска и работы по дереву.
Делитесь своим мнением в комментариях ⬇️
51🔥14❤1
Готовлю большой материал по Swift Concurrency для воркшопа
Поэтому iOS разработчиков прошу пройти небольшую викторину в следующих опросах:
⬇️⬇️⬇️
Поэтому iOS разработчиков прошу пройти небольшую викторину в следующих опросах:
⬇️⬇️⬇️
В какой строчке будет ошибка компиляции?
@MainActor
class MyOnMainActorClass {}
class MyNonIsolatedClass {}
final class MySendableClass: Sendable {}
actor MyActor {}
struct MySendableStruct: Sendable {}
var myOnMainActorClass = MyOnMainActorClass() // 1
var myNonIsolatedClass = MyNonIsolatedClass() // 2
var mySendableClass = MySendableClass() // 3
var myActor = MyActor() // 4
var mySendableStruct = MySendableStruct() // 5
На какой строке ошибка при компиляции 👆
Anonymous Poll
18%
1
8%
2
14%
3
23%
4
20%
5
37%
Посмотреть ответы
🤡5😁2
#concurrency
Изоляция vs. Синхронизация
Современный Swift (> 5.5) опирается на две ключевые концепции: изоляция и синхронизация.
Часто их путают, хотя они решают одну и ту же проблему — data race, при этом совершенно по разному.
👮🏻♂️ Синхронизация (часто подразумевается, как ручное управление, работает в runtime)
Суть: Координировать потоки так, чтобы только один из них мог получить доступ к общему ресурсу в конкретный момент времени. Доступ возможен с любого потока, но он регулируется вручную.
Инструменты:
🔸 Локи и мьютексы (NSLock, DispatchSemaphore)
🔸 Серийные очереди (DispatchQueue)
🔸 Atomic operations
Пример:
Ответственность: Полностью на разработчике.
Забыли поставить лок или создали deadlock — узнаете только в рантайме 🔞
🛡Изоляция (акторная защита на уровне компилятора)
Суть: Спроектировать код так, чтобы общие изменяемые данные были защищены от одновременного доступа по определению в рамках актора.
Пример:
💸 Цена за безопасность
Но за эту автоматическую защиту есть своя цена. Изоляция делает (почти)невозможным синхронный доступ к своим данным из неизолированной среды. Компилятор заставляет нас явно обозначать точки в коде, где происходит переключение между конкурентными доменами. Код становится асинхронным, что требует порой кардинального изменения в написании кода и пересмотра архитектуры.
Однако о хаках в следующем посте...
🔗 Эти механизмы разные, это почти противоположные философии для решения Data Race.
- Изоляция запрещает совместный доступ к изменяемому состоянию.
- Синхронизация упорядочивает совместный доступ к изменяемому состоянию.
Ссылки:
- proposals/0306-actors
- WWDC21: Protect mutable state with Swift actors | Apple
Изоляция vs. Синхронизация
Современный Swift (> 5.5) опирается на две ключевые концепции: изоляция и синхронизация.
Часто их путают, хотя они решают одну и ту же проблему — data race, при этом совершенно по разному.
👮🏻♂️ Синхронизация (часто подразумевается, как ручное управление, работает в runtime)
Суть: Координировать потоки так, чтобы только один из них мог получить доступ к общему ресурсу в конкретный момент времени. Доступ возможен с любого потока, но он регулируется вручную.
Инструменты:
🔸 Локи и мьютексы (NSLock, DispatchSemaphore)
🔸 Серийные очереди (DispatchQueue)
🔸 Atomic operations
Пример:
final class Counter: @unchecked Sendable {
private var value = 0
private let lock = NSLock()
func increment() {
lock.lock()
defer { lock.unlock() }
value += 1
}
}Ответственность: Полностью на разработчике.
Забыли поставить лок или создали deadlock — узнаете только в рантайме 🔞
🛡Изоляция (акторная защита на уровне компилятора)
Суть: Спроектировать код так, чтобы общие изменяемые данные были защищены от одновременного доступа по определению в рамках актора.
Не путайте изоляцию с Sendable. Если тип Sendable, значит, что его можно безопасно передавать между потоками и акторами. Но это не означает, что его состояние изолировано.
Изоляция акторов — это статический механизм. Правила доступа проверяются компилятором еще до запуска программы. Это не просто "синтаксический сахар" поверх локов, а фундаментальное свойство языка, встроенное на на уровне ABI.
Пример:
@MainActor
var value = 0 // Изолировали на мейн акторе
actor MyActor {
var value = 0 // Изолировали на кастомном акторе
}
💸 Цена за безопасность
Но за эту автоматическую защиту есть своя цена. Изоляция делает (почти)невозможным синхронный доступ к своим данным из неизолированной среды. Компилятор заставляет нас явно обозначать точки в коде, где происходит переключение между конкурентными доменами. Код становится асинхронным, что требует порой кардинального изменения в написании кода и пересмотра архитектуры.
🔗 Эти механизмы разные, это почти противоположные философии для решения Data Race.
- Изоляция запрещает совместный доступ к изменяемому состоянию.
- Синхронизация упорядочивает совместный доступ к изменяемому состоянию.
Я бы хотел сказать, что изоляция актора под капотом использует синхронизацию, но для этого нужно раскрывать Serial Executor, Mailbox актора и прочие его хитрости, ибо ключевой фишкой является не сам механизм синхронизации, а его использование для реализации неблокирующей, асинхронной модели с приостановкой задач. Так что это было бы большим упрощением.
Ссылки:
- proposals/0306-actors
- WWDC21: Protect mutable state with Swift actors | Apple
1🔥10❤3🫡3 2
#concurrency
Взломать актор: Синхронный доступ извне
В прошлом посте я писал, что изоляция акторов заставляет нас писать асинхронный код для доступа к защищенным данным. Это цена за безопасность, которую платит разработчик, что напрямую влияет на дизайн кода и фичей.
И все же, если мы работаем со старым кодом, legacy API или в контексте, который не может быть async? При этом внутри происходит обращение к подакторным данным, то вы, скорее, получите такую ошибку или предупреждение:
Многие источники и без меня рассказывают, как обойти это ограничение, используя
Так что, если ваш код исполняется не на main потоке, то синхронности не добиться? Что ж, мы можем вспомнить пару хаков из
И теперь нам доступен хак по синхронному доступу изолированных данных из неизолированной среды, и да, это работает:
Окей, мы ушли от Data Race, взломали Swift Concurrency (в рамках main актора), и таким образом вы вполне можете фиксить свой код, либо работать с legacy API, которого в iOS с головой.
🛑 Но! Теперь наш код подвержен deadlock-ам. Создать его в связке
Пример хоть и в вакууме, но показывает хрупкость таких решений, как
При проектировании дизайна своего кода, всегда хочется сделать его простым, безопасным и оптимизированным. А синхронный код в разы проще асинхронного во всех аспектах. Поэтому частенько я стараюсь играться с типами, функциями, асинхронщиной(примеры в следующем посте) , и все же с опытом приходишь к основной мысли:
Взломать актор: Синхронный доступ извне
В прошлом посте я писал, что изоляция акторов заставляет нас писать асинхронный код для доступа к защищенным данным. Это цена за безопасность, которую платит разработчик, что напрямую влияет на дизайн кода и фичей.
И все же, если мы работаем со старым кодом, legacy API или в контексте, который не может быть async? При этом внутри происходит обращение к подакторным данным, то вы, скорее, получите такую ошибку или предупреждение:
nonisolated func getDevice() -> String {
// 'UIDevice' изолирован на MainActor
let model = UIDevice.current.model // ⚠️/⛔️ Main actor-isolated class property 'current' can not be referenced from a nonisolated context
let systemVersion = UIDevice.current.systemVersion // Same
return [model, systemVersion].joined(separator: " ")
}Вы все еще можете оставаться на версии компилятора Swift 5, либо минимизировать строгие проверки параллелизма: В настройках проекта (Build Settings) для параметра Strict Concurrency Checking устанавливается значение Minimal (или false для флага -warn-concurrency).
Многие источники и без меня рассказывают, как обойти это ограничение, используя
MainActor.assumeIsolated, забывая сказать, что:If the current context is not running on the actor’s serial executor, or if the actor is a reference to a remote actor, this method will crash with a fatal error (similar to preconditionIsolated()).
Так что, если ваш код исполняется не на main потоке, то синхронности не добиться? Что ж, мы можем вспомнить пару хаков из
GCD, и получить вполне рабочий метод:extension MainActor {
@discardableResult
public static func syncSafe<T: Sendable>(_ action: @MainActor () -> T) -> T {
Thread.isMainThread
? MainActor.assumeIsolated(action)
: DispatchQueue.main.sync(execute: action)
}
}И теперь нам доступен хак по синхронному доступу изолированных данных из неизолированной среды, и да, это работает:
nonisolated func getDevice() -> String {
MainActor.syncSafe { // Безопасно, но с блокировкой
let model = UIDevice.current.model
let systemVersion = UIDevice.current.systemVersion
return [model, systemVersion].joined(separator: " ")
}
}Окей, мы ушли от Data Race, взломали Swift Concurrency (в рамках main актора), и таким образом вы вполне можете фиксить свой код, либо работать с legacy API, которого в iOS с головой.
🛑 Но! Теперь наш код подвержен deadlock-ам. Создать его в связке
MainActor.syncSafe и async/await оказалось сложно (можете попробовать, лучшие ответы выложу), поэтому вот пример с GCD. И явно отследить deadlock на этапе написания кода — задача непростая для большинства:@MainActor
func causeADeadlock() {
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.global().async {
... // Any work
/// Наш метод с `syncSafe` и `DispatchQueue.main.sync(execute: action)`
/// Из-за ожидания и случится deadlock
_ = getDevice()
semaphore.signal()
}
semaphore.wait()
print("Ничего не распечается, deadlock") // <-- Эта строка не выполнится
}
Пример хоть и в вакууме, но показывает хрупкость таких решений, как
MainActor.syncSafe, так что используйте только при крайней необходимости! При проектировании дизайна своего кода, всегда хочется сделать его простым, безопасным и оптимизированным. А синхронный код в разы проще асинхронного во всех аспектах. Поэтому частенько я стараюсь играться с типами, функциями, асинхронщиной
Золотое правило: если можете использовать async/await — используйте. Синхронные "хаки" — это крайняя мера, а не норма.
🔥7✍3❤2🤮1
#concurrency
Игра по правилам компилятора
Компилятор — наш лучший друг, который защищает от data races. Но иногда его строгость заставляет искать обходные пути, особенно при работе с "наследством" из мира
Разберем реальный кейс из моей практики: проксирование делегатов, например,
Целиком проки код можете посмотреть в моем публичном репозитории для DynamicBottomSheet, остановимся на важном в рамках обсуждения:
В Swift 5 многие методы
В Swift 6 эта магия исчезла...
🤔 Первая мысль: "Просто вызову их вместе с
И тут появляются нюансы, о которых вам не рассказывают в блогах, а именно, что
Мы не можем передавать
🪄 Включаем чит-код
Для этого создадим простую обертку, которая маркирует значение как
И получаем:
Что произошло:
- Мы обернули не-Sendable
- Теперь компилятор пропускает наше замыкание в MainActor.syncSafe, так как контейнер помечен как @unchecked Sendable.
- Внутри замыкания мы безопасно извлекаем .value, используя изолированные main актором данные.
🧠 Итог:
- Swift активно меняется, компилятор становится строже, ломая старые подходы с UIKit/NSObject/ObjC.
- Когда Sendable недоступен, @unchecked Sendable — наш безопасный "пропуск" через границы акторов.
- Относитесь к @unchecked Sendable не как к костылю, а как к осознанному хаку: мы берем на себя ответственность за Data Race и deadlock-и.
Игра по правилам компилятора
Компилятор — наш лучший друг, который защищает от data races. Но иногда его строгость заставляет искать обходные пути, особенно при работе с "наследством" из мира
UIKit и ObjC.Разберем реальный кейс из моей практики: проксирование делегатов, например,
UIScrollViewDelegate или UINavigationControllerDelegate. Это частая задача, когда нужно перехватить часть событий, а остальные — перенаправить оригинальному делегату.Целиком проки код можете посмотреть в моем публичном репозитории для DynamicBottomSheet, остановимся на важном в рамках обсуждения:
@MainActor
internal final class UIScrollViewHolder: NSObject, UIScrollViewDelegate {
private weak var originalDelegate: UIScrollViewDelegate?
// MARK: - Forwarding Unhandled Messages
override func responds(to aSelector: Selector!) -> Bool {
return super.responds(to: aSelector) || (originalDelegate?.responds(to: aSelector) ?? false)
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if originalDelegate?.responds(to: aSelector) ?? false {
return originalDelegate
}
return super.forwardingTarget(for: aSelector)
}
}
В Swift 5 многие методы
NSObject, такие как responds(to:) и forwardingTarget(for:), аннотировались как @MainActor, если реализующий тип так же аннотирован @MainActor. И все просто работало.В Swift 6 эта магия исчезла...
🤔 Первая мысль: "Просто вызову их вместе с
MainActor.assumeIsolated или с моим кастомным MainActor.syncSafe из предыдущего поста!"И тут появляются нюансы, о которых вам не рассказывают в блогах, а именно, что
MainActor.assumeIsolated<T> требует T where T : Sendable:// MARK: - Forwarding Unhandled Messages
override func responds(to aSelector: Selector!) -> Bool {
MainActor.syncSafe { // Its ok, Selector is @unchecked Sendable
return super.responds(to: aSelector) || (originalDelegate?.responds(to: aSelector) ?? false)
}
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
MainActor.syncSafe { ⛔️ // Type 'Any' does not conform to the 'Sendable' protocol
if originalDelegate?.responds(to: aSelector) ?? false {
return originalDelegate
}
return super.forwardingTarget(for: aSelector)
}
}
Мы не можем передавать
Any между тасками (хотя тасок как таковых тут нет), и как тут не крути типы, получаем ошибку...🪄 Включаем чит-код
Для этого создадим простую обертку, которая маркирует значение как
@unchecked Sendable локально:public struct UncheckedSendableContainer<T>: @unchecked Sendable {
public let value: T
public init(_ value: T) {
self.value = value
}
}И получаем:
// MARK: - Forwarding Unhandled Messages
override func responds(to aSelector: Selector!) -> Bool {
MainActor.syncSafe { // ✅ Selector is @unchecked Sendable
return super.responds(to: aSelector) || (originalDelegate?.responds(to: aSelector) ?? false)
}
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
let result: UncheckedSendableContainer<Any?> = MainActor.syncSafe {
/// ✅ UncheckedSendableContainer is @unchecked Sendable
if originalDelegate?.responds(to: aSelector) ?? false {
return UncheckedSendableContainer(originalDelegate)
}
let result = super.forwardingTarget(for: aSelector)
return UncheckedSendableContainer(result)
}
return result.value
}
Что произошло:
- Мы обернули не-Sendable
Any в наш UncheckedSendableContainer.- Теперь компилятор пропускает наше замыкание в MainActor.syncSafe, так как контейнер помечен как @unchecked Sendable.
- Внутри замыкания мы безопасно извлекаем .value, используя изолированные main актором данные.
🧠 Итог:
- Swift активно меняется, компилятор становится строже, ломая старые подходы с UIKit/NSObject/ObjC.
- Когда Sendable недоступен, @unchecked Sendable — наш безопасный "пропуск" через границы акторов.
- Относитесь к @unchecked Sendable не как к костылю, а как к осознанному хаку: мы берем на себя ответственность за Data Race и deadlock-и.
🔥9 5✍3🤩2🙈1
This media is not supported in your browser
VIEW IN TELEGRAM
📱PWA заменит мобилку, проверяй!
Вот сколько не говори о платформенных API, производительности или рендеринге…
Да нас за такие баги отпинали бы за офисом🤩
Вот сколько не говори о платформенных API, производительности или рендеринге…
Да нас за такие баги отпинали бы за офисом
Please open Telegram to view this post
VIEW IN TELEGRAM
😁11❤3 2 2💯1