Мобильная разработка
13.2K subscribers
949 photos
237 videos
3.07K links
Актуальное по мобильной разработке — Android, iOS, кроссплатформа

Разместить рекламу: @tproger_sales_bot
Правила общения: https://tprg.ru/rules
Другие каналы: @tproger_channels
Сайт: https://tprg.ru/site

Регистрация в перечне РКН: https://tprg.ru/oVBP
Download Telegram
Task и Task.detached в Swift: как выбрать задачу для iOS-кода

Обычная Task {} пригодится, когда нужно вызвать async-функцию из синхронного viewDidLoad. Такая задача не становится дочерней: вызвавший её метод может завершиться раньше. При этом она наследует текущий актор, локальные значения задачи и приоритет. Поэтому внутри @MainActor код задачи остаётся в изоляции текущего актора, а ожидание через await не блокирует актор.

Task.detached не наследует ни актор, ни локальные значения задачи. Для доступа к состоянию актора понадобится await. При этом detached-задача не определяет, где исполняется вызываемая async-функция: это задаёт изоляция самой функции.

В статье о задачах Swift разобраны оба варианта с кодом. Практическое правило: начинайте с Task, а Task.detached оставляйте для редкой работы, которую нужно отделить от актора и структурированной конкурентности.
Как защитить доступность iOS-приложения от регрессий

Разметить элементы для VoiceOver недостаточно: после редизайна доступный сценарий может сломаться. В 2ГИС первая версия адаптации со временем именно так и потеряла работоспособность.

Во второй итерации разработчик начал со snapshot-тестов. Они сохраняют текстовый слепок экрана: набор элементов и фразы, которые услышит незрячий пользователь. Если слепок меняется, тест падает до слияния кода. Автор выбрал этот путь вместо UI-тестов, которые выполняются десятки секунд, а причину расхождения здесь видно в тексте.

В статье 2ГИС на Хабре есть рабочий проект и нюансы адаптации: действие Escape, динамический расчёт доступности и укрупнение элементов. Подход пригодится командам с частыми изменениями интерфейса, где ручная регрессия VoiceOver не успевает за разработкой.
Как отделить загрузку данных от экранов на SwiftUI

Если экран сам запускает запрос, хранит результат и переключается между загрузкой, ошибкой и контентом, интерфейс смешивается с сетевой логикой. Swift by Sundell сначала показывает эту схему на ArticleView, а затем выносит загрузку и состояние в модель представления.

Дальше появляются общий LoadingState с четырьмя состояниями, протокол LoadableObject и универсальный AsyncContentView. Контейнер выбирает представление по состоянию источника, а экран только отрисовывает готовые данные.

Материал написан для Swift 5.3 и уже помечен архивным: архитектурный приём полезен, но API стоит сверять с текущим SwiftUI. В статье Swift by Sundell есть код всех трёх вариантов.
👍1
Как убрать нестабильность скриншот-тестов Paparazzi с Lottie

Paparazzi снимает Android-интерфейс через Layoutlib без эмулятора. Layoutlib обычно вызывают из одного потока, а приложение запускает фоновые задачи.

В нестабильном тесте Lottie загружала анимацию в фоне, а затем отправляла задачу через Handler. Одновременно Paparazzi перебирала карту очереди обработчиков. Добавление обработчика во время перебора вызывало ConcurrentModificationException и периодически роняло часть CI-прогона.

Обход для тестов: в методе @Before заменить LottieTask.EXECUTOR исполнителем, который запускает задачу в текущем потоке. Исправление в Layoutlib требует синхронизировать доступ к карте и её спискам. Код воспроизведения и вариант синхронизации есть в подробном разборе.
1
Как акторы Swift защищают состояние от гонок данных

Гонка данных возникает, когда потоки без синхронизации обращаются к области памяти и хотя бы один пишет в неё. Актор изолирует изменяемое состояние: снаружи его методы и свойства доступны через await, а компилятор проверяет синхронизированный доступ. Неизменяемые свойства можно читать напрямую.

Каждый await может приостановить код. Между двумя вызовами актор способен обработать другой запрос, поэтому связанные операции лучше объединять в одном методе. Для методов и вычисляемых свойств, которым нужны только неизменяемые данные, есть nonisolated: их вызывают без ожидания.

В разборе SwiftLee есть код до и после замены очереди с барьером на актор и объяснение изоляции. Граница подхода тоже обозначена: акторы уменьшают риск гонок данных, но состояния гонки всё ещё возможны.
Как Baidu переносила бизнес-логику iOS и Android на Kotlin Multiplatform

В приложении Wonder одинаковые правила отдельно реализовывали на Swift и Kotlin. Изменения приходилось синхронизировать между командами, а корректность проверять дважды. Связанный с логикой интерфейс также мешал повторно использовать код и писать модульные тесты.

Команда начала с общего слоя данных: объединила модели, REST-запросы, разбор JSON и кеш данных по ключу. Нативные интерфейсы iOS и Android сохранили. Позже часть интерфейсной логики перенесли на шаблон MVI для управления состоянием, а настройку модулей упростили собственными плагинами Gradle.

Подключение модуля добавляло 2 МБ из-за встроенной среды выполнения, а отладка общего Kotlin-кода для iOS сводилась к логам и замедлялась по мере роста проекта. Кейс Baidu показывает порядок внедрения и ограничения, которые стоит проверить на пилотном модуле.
Как найти причину утечки памяти в Android с LeakCanary

Утечка возникает, когда приложение сохраняет ссылку на уже ненужный объект. LeakCanary строит leak trace: цепочку сильных ссылок от неудаляемого корня сборщика мусора до удерживаемого объекта. По состоянию и жизненному циклу объектов инструмент сокращает список подозрительных ссылок.

В примере из статьи синглтон Application хранит TextView в списке. TextView через поле mContext удерживает уничтоженную MainActivity. Такая цепочка указывает, где искать причину: View добавили в долгоживущую коллекцию приложения.

В руководстве LeakCanary разложены четыре этапа: получить цепочку, сузить подозрения, найти ссылку, которая вызывает утечку, и убрать её. LeakCanary выполняет первые два этапа, а решение в коде остаётся разработчику.
Было: качаешь альбом через торрент трое суток, боишься выключить компьютер. Стало: включаешь трек в одно нажатие и не думаешь, где он хранится.

Между этими двумя точками — двадцать пять лет, которые SpaceWeb прошёл вместе с рунетом. Анкета к юбилею компании как раз про этот путь.
Как отделить данные от интерфейса во Flutter с помощью репозитория

Репозиторий помещают в слой данных между источником и бизнес-логикой. Он вызывает REST API или базу данных, преобразует JSON в типизированные сущности и отдаёт их приложению. Виджеты не зависят от сетевого клиента и формата ответа, а изменение стороннего API исправляют только в репозитории.

В примере WeatherRepository задаёт получение погоды по городу, а реализация запрашивает OpenWeatherMap и разбирает ответ. В тестах сетевой код заменяют тестовым репозиторием. Абстрактный класс для этого необязателен: в Dart у каждого класса есть неявный интерфейс. Разбор Repository Pattern для Flutter сравнивает абстрактные и конкретные классы и показывает полный код приложения.
🥱32
Как ловить визуальные регрессии в Android Compose с Paparazzi

Скриншот-тест рендерит компонент или экран и сравнивает снимок с эталоном. Paparazzi запускает такую проверку на JVM без устройства и эмулятора. Можно быстро прогнать разные экраны, но библиотека использует LayoutLib и наследует ограничения превью Android Studio.

В разборе показан цикл: подключить плагин, вызвать paparazzi.snapshot в JUnit-тесте, записать эталон через recordPaparazziDebug, затем сравнить кадр командой verifyPaparazziDebug. Отчёт покажет эталон, результат и различия.

Добавляйте проверку в CI как дополнение к другим тестам. Для задуманного изменения эталон перезаписывают; визуальную регрессию исправляют. Если снимков много, автор советует Git LFS, чтобы не раздувать Git-репозиторий.
🎉2
Как использовать @MainActor для интерфейса в Swift

@MainActor привязывает класс, метод, свойство или замыкание к глобальному актору, задачи которого идут в главном потоке. В конкурентном коде так компилятор проверяет контекст обновлений UI вместо ручных вызовов DispatchQueue.main.async.

В Swift 6.2 новые проекты Xcode 26 по умолчанию изолируют код приложения главным актором. Тяжёлые вычисления можно вынести из главного актора через nonisolated или @concurrent, чтобы не задерживать интерфейс.

В режиме Swift 5 остаётся ловушка: синхронный @MainActor-метод из неизолированного контекста может выполниться в потоке вызывающего кода. Swift 6 ловит большинство таких случаев при компиляции. Разбор SwiftLee сравнивает MainActor.run с Task { @MainActor in } и объясняет ограничения assumeIsolated.
👍1
Как собрать переиспользуемую пагинацию во Flutter на Riverpod

Материал разбирает бесконечную ленту на Flutter без отдельного пакета пагинации. Автор строит общий контроллер поверх AsyncNotifier: один тип задаёт элементы списка, второй задаёт индекс страницы. Поэтому индексом может быть не только число, но и дата или строка.

Подключаемая примесь хранит текущий индекс, загружает первую страницу и добавляет следующую к уже полученным элементам. Конкретному экрану остаётся определить начальный индекс, запрос данных и способ вычислить следующий индекс. В интерфейсе NotificationListener отслеживает конец прокрутки и вызывает загрузку.

В руководстве Динко Маринаца есть схема слоёв, реализация поиска и фильтров и полный пример кода. Разбор предполагает знакомство с MVVM и репозиторием.
👎31
Как настроить и обрабатывать диплинки в SwiftUI

Диплинк открывает нужный экран iOS-приложения сразу после перехода по ссылке. В примере схема recipeapp:// принимает действие open-recipe и параметр name, а SwiftUI получает адрес через onOpenURL.

Обработчик сверяет схему, разбирает адрес через URLComponents, проверяет действие и лишь затем читает имя рецепта. Если одну схему зарегистрируют несколько приложений, какое из них получит ссылку, не определено. Для однозначной привязки автор советует Universal Links.

Диплинки пригодятся и для экранов отладки или переключения тестовой среды. Открыть поддерживаемый адрес может кто угодно, поэтому принимайте только известные действия и параметры. В материале SwiftLee показаны настройка URL Types в Xcode, варианты для AppDelegate и SceneDelegate и тестирование ссылок в RocketSim.
💯1
Как безопасно перевести Flutter-код на первичные конструкторы Dart 3.12

В Dart 3.12 появилась экспериментальная поддержка primary constructors, или первичных конструкторов: поля класса и параметры конструктора можно объявить одной записью. В Flutter это сокращает шаблонный код виджетов, но массовая замена требует проверки поведения, а не только синтаксиса.

Попытка поручить миграцию агентам давала разные результаты даже при повторных запусках. Поэтому автор сделал детерминированную утилиту: она читает дерево кода через пакет Dart analyzer и применяет фиксированные правила.

Утилита выполняет три вида преобразований и пропускает код в 31 ситуации, когда безопасность замены нельзя доказать. Она записывает файл только после успешного разбора, а тесты проверяют результат анализатором. В инженерном разборе миграции показано, что именно нужно проверять кроме синтаксиса.
💯1
Как установить рантаймы симуляторов Xcode из терминала

После установки Xcode 27 beta загрузка рантайма iOS у автора зависла на получении сведений. Переустановка Xcode и перезагрузка Mac не помогли, поэтому он перешёл к xcodebuild.

Сначала проверьте активную версию Xcode командой xcode-select -p. Если выбрана не та, переключите её: sudo xcode-select -s /Applications/Xcode-beta.app. Затем запустите xcodebuild -downloadPlatform iOS.

Если команда ищет универсальный рантайм и отвечает No matching downloadable, явно запросите arm64: xcodebuild -downloadPlatform iOS -architectureVariant arm64. Команду для загрузки рантаймов всех платформ и полный вывод ошибки смотрите в статье Донни Уолса.
👍21
Как проверять iOS-приложение из Cursor, Claude Code и Codex

RocketSim 17 выводит поток с iOS Simulator на локальную веб-страницу, которую можно открыть в браузере среды разработки. По команде /rocketsim агент проходит по экранам приложения и проверяет изменения через командную строку.

После прогона симулятор остаётся открыт. Разработчик выбирает элементы интерфейса прицелом, оставляет комментарии и возвращает их агенту в том же рабочем контексте. Слева видна иерархия доступности, снизу указаны рабочее дерево Git и ветка.

Такой цикл сокращает переключения между средой разработки и симулятором, но сохраняет визуальную проверку за человеком. В материале SwiftLee показан весь сценарий и объяснено, как подключить навигацию и проверку к агенту.
👍21
Как Flutter готовит приложения к новым версиям iOS

Между WWDC и стабильным релизом iOS у команды Flutter около трёх месяцев. В 2026 году скрипт на Dart выгрузил стенограммы более 145 сессий, а Gemini оценил их важность и область влияния. К вечеру команда получила разбор всех докладов.

Отдельный кейс в статье посвящён UIScene. Apple предупреждала: после iOS 26 UIKit-приложение, собранное последним SDK без UIScene, не запустится. Полная поддержка UIScene вошла во Flutter 3.41 в феврале 2026 года, до появления бета-версии iOS 27 с обязательной проверкой. Большинству разработчиков достаточно выполнить flutter upgrade; проекты со встраиванием Flutter в нативное приложение требуют ручной миграции, а плагины должны поддерживать UIScene.

В разборе команды Flutter есть и другой полезный кейс: сбой жестов в webview воспроизвели на чистом Swift, после чего Apple исправила его в iOS 26.4.
🤝1
Как безопасно освобождать временные ресурсы с defer в Swift

В Swift блок defer выполняется перед выходом из ближайшей области видимости, которую задаёт пара фигурных скобок. Его можно поставить сразу после создания ресурса: код очистки останется рядом с настройкой, но сработает в конце функции, цикла или вычисляемого свойства.

Для iOS-разработки автор разбирает два сценария. В первом приложение записывает данные во временный файл, а defer удаляет его даже при раннем выходе после неудачного создания XMLParser. Во втором API наблюдения HealthKit требует вызвать обработчик завершения после обработки данных; defer делает это при выходе из итерации.

В статье с примерами кода также показано, как применять приём для снятия блокировок и восстановления изменённого состояния.
👍1👏1
Как управлять кнопками панели инструментов в SwiftUI

Одна и та же панель в SwiftUI ведёт себя по-разному: macOS показывает все действия справа сверху, а iOS часть оставляет вверху, остальные сворачивает в дополнительное меню. Новые API задают, что останется на виду.

visibilityPriority задаёт высокий или низкий приоритет, и SwiftUI сворачивает элементы с низким. ToolbarOverflowMenu помещает группу в свёрнутое меню на всех платформах. topBarPinnedTrailing удерживает действие справа сверху и может свернуть его только при активном поиске, если места не хватает.

toolbarMinimizeBehavior сворачивает панель вкладок, нижнюю, оконную или навигационную панель при прокрутке. Статья с примерами кода показывает все четыре API. Для iOS-команды схема такая: назначить приоритет ключевому действию, второстепенные убрать в меню, затем проверить интерфейс с активным поиском и при прокрутке.
2👏1
Как переносить сложные Android-экраны на Jetpack Compose: опыт TikTok

Команда TikTok переносила отдельные пользовательские сценарии и после каждого этапа сверяла метрики: начала с QR-кода, затем взялась за вход и регистрацию.

При небольших изменениях время загрузки страниц сократилось на 20–30%, а для новых функций и полностью переписанных страниц — на 70–80%. Та же функция на Compose потребовала на 58% меньше кода, чем реализация на View.

Практический нюанс: множество маленьких ComposeView внутри одного ViewHolder добавляло расходы на композицию. Один ComposeView на весь ViewHolder сработал лучше. Для пилота берите один сценарий и измеряйте результат до расширения миграции. В разборе Android Developers Blog есть также пример с ViewPager.
🔥6
Как использовать свайп-действия вне List в SwiftUI

В SwiftUI свайп-действия вроде удаления больше не привязывают интерфейс к List. Для ScrollView с LazyVStack или собственного Layout достаточно оставить swipeActions у строки и добавить контейнеру модификатор swipeActionsContainer().

Он не только включает жесты: следит, чтобы действия были раскрыты лишь у одной строки, и закрывает их при прокрутке или касании снаружи. В List эта логика уже встроена, поэтому отдельный модификатор там не нужен.

Приём пригодится, когда экрану нужна нестандартная компоновка или собственный стиль, но хочется сохранить нативное свайп-взаимодействие. Код для ScrollView и пользовательского Layout автор показывает в статье «Swipe actions outside of List in SwiftUI».
👍1