Мобильный трудоголик
1.64K subscribers
123 photos
10 videos
432 links
Пишу простым языком об iOS разработке на Swift и мобильной разработке в целом.
Обо мне: https://t.me/hardworkerIT/3
Чат: @hardworkerChatIT
Канал про разработку и жизнь в ИТ: @itDenisov
Вакансии по мобильной разработке: @mobileDevJobs
Download Telegram
🔢 Новый стандарт Apple: async/await вместо Combine.

В файле репозитория, который содержит системные подсказки и документацию из Xcode 26.3 явно указано: «Избегайте использования фреймворка Combine, вместо этого предпочтительнее использовать async/await версии API». Это заметил Артем Новичков и сообщил в своем посте.

Это не приговор, а четкий сигнал от Apple о приоритетах для нового кода. Combine - зрелый и мощный фреймворк, но для большинства современных задач (сетевые запросы, работа с файлами, обновленные системные API) async/await предлагает более простую, читаемую и интегрированную в язык модель.


Что это означает:

🔵Новые проекты: логично стартовать с async/await как основного инструмента для асинхронности.

🔵Существующие проекты на Combine: не нужно срочно все переписывать. Combine остается отличным выбором для сложной реактивной логики UI или работы с легаси.

🔵Общая тенденция: Apple планомерно обновляет свои API, добавляя async/await альтернативы. Со временем это станет стандартом де-факто для новых разработок.


💡 Вывод:

Combine остается рабочим инструментом, но для всех новых разработок без сомнений выбирайте async/await - это официальный вектор развития от Apple.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
17🤯11👍8🔥4❤2👀2🫡2
🔢 Обратная миграция: почему крупные проекты смотрят в сторону UIKit.

Последние несколько лет SwiftUI позиционировался как будущее разработки под Apple-экосистему. Однако в 2024-2025 годах наметился парадоксальный тренд: все больше разработчиков и компаний сознательно возвращаются к классическому стеку - UIKit. Это не просто ностальгия по старым методам, а стратегическое переосмысление выбора инструментов в новых условиях.


Смена приоритетов - от скорости написания к скорости чтения и контролю:

Изначальное преимущество SwiftUI было очевидно: декларативный синтаксис радикально ускорял создание интерфейсов. Разработчик описывал, что должно быть, а система решала, как это отрисовать. Это был прорыв для стартапов и быстрого прототипирования.

Однако с повсеместным внедрением ИИ-инструментов (Copilot, Cursor, Claude Code) парадигма сместилась. Писать код становится проще, а читать и поддерживать - сложнее. И здесь UIKit с его императивным, явным стилем выигрывает.

🔵Прямолинейность UIKit: архитектура MVC/MVVM задает четкую структуру. Вы точно знаете, где искать логику жестов (UIGestureRecognizer во viewDidLoad), где обновляется layout (viewDidLayoutSubviews), и как данные попадают в представление. Это предсказуемо и легко читается как человеком, так и ИИ-ассистентом.

🔵Магия SwiftUI: мощные property wrappers (@State, @Binding, @EnvironmentObject) и модификаторы создают сложные, неочевидные связи данных. Чтобы понять поток информации в нетривиальном экране, часто приходится «разматывать» цепочки зависимостей, что замедляет ревью и рефакторинг.


Архитектура как скелет приложения:

SwiftUI поощряет минималистичные архитектуры вроде Model-View (MV), где бизнес-логика, состояние и представление часто перемешаны в одном файле. Это отлично для простых экранов, но превращается в кошмар для сложных, долгоживущих проектов.

UIKit, со своей «многословной» архитектурой, вынуждает к дисциплине. Разделение ответственности между ViewController, Model и Router/Coordinator создает каркас, который:

🔵Масштабируется. Новые разработчики быстрее входят в проект, понимая стандартные места для разных типов кода.

🔵Облегчает работу ИИ. Четкий запрос вроде «добавь обработку свайпа в делегат коллекции в CollectionViewController» дает более точный и интегрируемый результат.

🔵Дает полный контроль. Прямой доступ к жизненному циклу view (viewWillAppear, viewDidDisappear), нативным делегатам (UIScrollViewDelegate, UICollectionViewDelegate) и методам анимации (UIView.animate) остается незаменимым для создания сложных взаимодействий.


🔗 Ссылка на подробную статью


💡 Вывод:

Выбор между UIKit и SwiftUI больше не является линейным путем «от старого к новому». Это стратегическое решение, основанное на контексте проекта:

🔵Выбирайте SwiftUI, если: вы создаете MVP, простые кроссплатформенные приложения (с SwiftUI на macOS/watchOS) или внутренние инструменты, где скорость создания важнее тонкой настройки и долгосрочной поддержки.

🔵Выбирайте UIKit, если: вы строите большое, сложное приложение с требованием к нативной производительности, кастомными, сложными взаимодействиями, долгим циклом жизни и командой, где читаемость и поддерживаемость кода - приоритет.

SwiftUI не умирает, он находит свою нишу. Но ренессанс UIKit доказывает, что проверенные, явные и контролируемые технологии часто побеждают в долгосрочной перспективе, особенно когда на сцену выходят инструменты, меняющие саму природу программирования с «написания» на «чтение и композицию».


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1913❤4🔥1🤔1👀1
🔢 От хаоса к порядку: Swift делает поведение nonisolated функций предсказуемым.

Swift Concurrency продолжает эволюционировать, и одно из ключевых изменений - пересмотр логики работы nonisolated асинхронных функций. Сейчас они ведут себя противоречиво: синхронные nonisolated методы выполняются в контексте вызывающей стороны (например на акторе), а асинхронные - всегда переключаются на внешний executor, что вызывает неожиданные ошибки типов и усложняет архитектуру.


Суть проблемы - разрыв между ожиданием и реальностью:

Когда вы вызываете nonisolated асинхронный метод из актора, компилятор вынужден применять строгие Sendable-проверки ко всем аргументам, даже если метод по своей природе не должен покидать контекст актора. Это приводит к ложным ошибкам, особенно в библиотечном коде, где автор API может не предусмотреть такой сценарий. Даже стандартная библиотека Swift Concurrency сталкивалась с этой проблемой.


Решение - явное управление изоляцией через новые модификаторы:

Предложение SE-0461 вводит два новых инструмента для точного контроля:

🔵nonisolated(nonsending) - явно указывает, что асинхронная функция должна выполняться в контексте вызывающего актора (унаследовать его изоляцию). Это поведение становится предсказуемым и аналогичным синхронным функциям.

🔵@concurrent - явно указывает, что функция должна переключаться на внешний executor (это старое поведение по умолчанию). Используется в случаях, когда необходимо гарантированно выйти из текущего актора для выполнения работы, например, для избежания его блокировки или для фоновых операций.

class NotSendable {
nonisolated(nonsending)
func performAsync() async {
// Работает в контексте вызывающего актора
}
}

actor MyActor {
let item = NotSendable()

func call() async {
item.performSync() // OK
await item.performAsync() // Теперь тоже OK, ошибки типов нет!
}
}



Макрос #isolation и работа с замыканиями:

Для продвинутых сценариев, таких как создание оберток (например withResource), расширяется функциональность макроса #isolation. Теперь он может корректно передавать контекст изоляции в nonisolated(nonsending) функции, что делает написание высокоуровневых асинхронных API значительно проще и безопаснее.

Важное уточнение: неструктурированные задачи (Task { }), созданные внутри таких функций, по-прежнему не наследуют изоляцию, что соответствует принципам предсказуемости.


Практический смысл - баланс между безопасностью и удобством:

Это изменение - не просто синтаксический сахар. Оно решает фундаментальную проблему: делает поведение nonisolated функций последовательным и интуитивно понятным. Разработчики получают явный контроль:

🔵Хотите, чтобы функция работала в том же контексте и не требовала Sendable? Используйте nonisolated(nonsending).

🔵Нужно гарантированно переключиться для параллельной работы? Явно укажите @concurrent.


💡 Вывод:

Swift продолжает движение к модели, где безопасность от гонок данных достигается не через неявные, сложные правила, а через явные и понятные инструменты, дающие разработчику полный контроль над изоляцией. SE-0461 закрывает один из последних существенных пробелов в этой картине, делая асинхронное программирование в Swift более последовательным, предсказуемым и, как следствие - доступным для написания сложных, но корректных приложений.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥168👀4❤1👍1🤔1
🔢 Swift: профессиональные методы отладки сложного кода.

Отладка - это не просто процесс поиска ошибок. Для опытного разработчика это системное исследование, где каждый баг становится ясной логической задачей. Разница между новичком, который часами ставит print, и сеньором, который за минуты находит корень проблемы, часто заключается не в опыте, а в знании правильных инструментов и методологии. Давайте разберем неочевидные, но мощные возможности Swift, которые меняют подход к диагностике.


Трюк с контекстом - почему ваши логи должны быть умнее print:

Стандартный print оставляет вас в неведении: где именно сработал этот вывод? Вместо этого используйте встроенные литералы компилятора для автоматического контекста:


func debugLog(
_ message: String,
file: String = #file,
function: String = #function,
line: Int = #line
) {
let fileName = URL(fileURLWithPath: file).lastPathComponent
print("[\(fileName):\(line)] \(function) — \(message)")
}


Такой подход сразу дает ответы на три ключевых вопроса: где (файл и строка), в чем (функция) и что (сообщение) произошло. Это особенно критично в асинхронном коде, где стек вызовов может быть обрезан.


Зеркало в runtime - когда нужно увидеть то, что скрыто:

Swift - статически типизированный язык, но это не значит, что мы не можем исследовать объекты в рантайме. Mirror - ваш портал для интроспекции. Допустим, у вас сложная ViewModel с десятком @Published свойств, и вы не понимаете, какое именно вызывает неожиданное обновление UI:


func inspectObject(_ object: Any) {
let mirror = Mirror(reflecting: object)
for child in mirror.children {
print("\(child.label ?? "Unknown"): \(type(of: child.value)) = \(child.value)")
}
}


Вы можете пройтись по любому объекту, будь то ваша ViewModel, состояние навигации или даже сложная конфигурация сервиса. Это незаменимо при анализе утечек памяти (когда нужно понять, что именно удерживает сильную ссылку) или при отладке сложных сериализаций.


dump() против print - анатомия сложных структур:

Когда print выдает что-то вроде User(address: __lldb_expr_123.Address(street: "...", ...)), пора переходить к dump(). Эта функция рекурсивно обходит всю структуру объекта, показывая его реальное дерево:


struct Project {
let title: String
let contributors: [Developer]
let settings: Configuration
}

let activeProject = fetchCurrentProject()
dump(activeProject) // Полное дерево со всеми вложенными объектами


Вы увидите не просто «есть массив разработчиков», а каждого разработчика со всеми его полями, и каждое поле конфигурации. Это спасение при работе с Core Data (исследование графа объектов), сложными JSON-ответами API или глубокими иерархиями UIView.


CustomDebugStringConvertible - создание отладочной документации:

Реализация CustomDebugStringConvertible - это не про красивый вывод. Это про создание специализированного отладочного представления для ваших типов, которое показывает именно ту информацию, которая важна для диагностики:


class NetworkRequest: CustomDebugStringConvertible {
let id: UUID
let endpoint: URL
var state: State
var metrics: [Metric]

var debugDescription: String {
"""
Request \(id.uuidString.prefix(8))...
to \(endpoint.path)
is \(state.rawValue)
last metric: \(metrics.last?.description ?? "none")
"""
}
}


Теперь, логгируя или используя po в LLDB, вы мгновенно видите статус запроса, а не просто его адрес в памяти. Это особенно эффективно для массивов или словарей таких объектов.


🔗 Ссылка на подробную статью


💡 Вывод:

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20❤10👍42✍1🙏1
🔢 Тихий режим для push-уведомлений: обход системных ограничений iOS.

Отключить звук уведомлений - кажется, одна из самых простых пользовательских настроек. Но для iOS-разработчика эта кнопка превращается в сложную архитектурную задачу. Почему? Потому что звук push-уведомления контролируется не приложением, а серверным payload, который iOS исполняет как приказ. Пользователь ждет тишины, а система послушно проигрывает звук из этого самого payload. Разрыв между ожиданием и реальностью можно устранить, но для этого потребуется нестандартный подход, затрагивающий саму цепочку доставки уведомления.


Архитектурная головоломка и ее решение:

Основная сложность заключается в разделенности процессов. Основное приложение и сервис, обрабатывающий входящие пуши (Notification Service Extension), работают в изолированных песочницах. Они не имеют прямого доступа к памяти друг друга. Стандартный UserDefaults здесь бесполезен.

Ключ к решению - App Groups. Это технология, позволяющая выделить область общей памяти, доступную для нескольких компонентов одного приложения (основной таргет и экстеншены). Настройка происходит через добавление кастомной capability в оба таргета и использование идентичного suiteName для инициализации UserDefaults(suiteName:).


Сердце системы - Notification Service Extension:

Этот extension - единственная точка в системе iOS, где можно программно модифицировать контент удаленного уведомления до его показа пользователю. Он активируется системой при получении пуша, имеет строго ограниченное время на выполнение (порядка 30 секунд) и должен вызвать contentHandler. Логика работы экстеншена:

🔵Получить входящий контент (UNNotificationRequest).

🔵Проверить в общих UserDefaults (через App Group), активен ли звук в настройках приложения.

🔵Если звук отключен - удалить свойство .sound из UNMutableNotificationContent.

🔵Передать модифицированный контент в contentHandler.

Именно этот шаг позволяет обмануть систему, заставив ее показать уведомление с визуальной частью, но без звукового сопровождения, даже если сервер изначально его отправил.


Синхронизация состояния - менеджер настроек:

В основном приложении необходим менеджер, который будет записывать значение настройки звука одновременно в стандартные UserDefaults (для быстрого доступа в самом приложении) и в общие UserDefaults App Group (для доступа extension). Это гарантирует консистентность данных независимо от того, запущено ли основное приложение в момент прихода пуша.


Обработка в foreground - делегат центра уведомлений:

Extension работает только для уведомлений, пришедших, когда приложение в фоне или заблокировано. Для случая, когда приложение активно, модификацию необходимо проводить в методе userNotificationCenter(_:willPresent:withCompletionHandler:). Здесь, основываясь на тех же настройках, мы определяем, передавать ли в completionHandler опцию .sound.


Критически важные детали реализации:

🔵Членство в таргетах (Target Membership): файлы, которые должны быть доступны и основному приложению, и extension (например кастомный звуковой файл или GoogleService-Info.plist для Firebase), должны быть отмечены галочками в обоих таргетах.

🔵Точность идентификатора App Group: строка-идентификатор в .entitlements файлах должна быть абсолютно идентичной в основном таргете и в таргете extension. Чувствительна к регистру.

🔵Ограничение времени: код в didReceive extension должен быть максимально эффективным. Если обработка не успевает, система вызовет serviceExtensionTimeWillExpire, где нужно отдать хотя бы исходный контент.


🔗 Ссылка на подробную статью


💡 Вывод:

Реализация пользовательского управления звуком уведомлений - это блестящий пример того, как понимание архитектурных возможностей iOS позволяет создавать кастомный UX вопреки ограничениям стандартных API. Решение элегантно обходит главное препятствие: отсутствие возможности напрямую влиять на серверный payload.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
❤18👍9🔥3🤔1🙏11
🔢 Как работает UUID() в Swift и почему он никогда не повторяется.

Когда вы пишете let id = UUID(), кажется, что это просто генерация случайного набора символов. На деле Swift запускает целый конвейер: собирает случайные данные из множества аппаратных датчиков: от колебаний напряжения в процессоре до микрозадержек при доступе к памяти, превращает их в непредсказуемые числа через сложные криптоалгоритмы и упаковывает результат в формат по международному стандарту. И все это происходит быстрее, чем вы успеваете моргнуть.


Три стратегии, одна цель:


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

🔵Версия 1 (таймстемп + MAC). Самый старый подход. Берет текущее время с точностью до 100 наносекунд, добавляет MAC-адрес сетевой карты и номер такта на случай отката времени. Гарантия уникальности абсолютная, но есть нюанс: MAC-адрес светит железо, на котором сгенерирован UUID. Для распределенных систем это допустимо, для пользовательских приложений - потенциальная угроза приватности.

🔵Версия 4 (полностью случайная). Это то, что делает стандартный UUID() в Swift. Никакого времени, никакого железа - только 122 бита криптостойкой случайности. Шесть бит зарезервировано под версию и вариант, остальное - энтропия. Вероятность коллизии настолько мала, что ее можно игнорировать даже при генерации миллиардов идентификаторов в секунду.

🔵Версия 7 (время + случайность). Новейший стандарт, который пока не встроен в Swift напрямую, но уже активно обсуждается. Берет миллисекундный timestamp и добивает случайными битами. Главное преимущество - монотонность: такие UUID можно сортировать по времени создания, что критично для баз данных и событийных логов.


Откуда берется «достаточно случайно»:

Случайность бывает разная. arc4random() - быстрая, но предсказуемая, если знать состояние генератора. UUID требует другого уровня - CSPRNG (Cryptographically Secure PseudoRandom Number Generator). В Apple-экосистеме за это отвечает Security.framework, который собирает энтропию буквально из всего:

🔵Квантовый шум транзисторов CPU (тепловые флуктуации электронов).

🔵Джиттер тактовых частот между ядрами.

🔵Вариации времени доступа к RAM и кешу.

🔵Тайминги нажатий на экран (для iOS) или движений мыши (для macOS).

🔵Шум сенсоров (акселерометр, гироскоп, тепловые датчики).

🔵Сетевые пакеты и их интервалы.

Все это смешивается в системном пуле энтропии, через который прогоняется криптопримитив (например, ChaCha20 или Fortuna), и только потом отдается в UUID().


Почему это не тормозит:

Несмотря на сложность, генерация UUID в Swift - одна из самых быстрых операций. На современном железе: 2-5 миллионов идентификаторов в секунду. Секрет в том, что пул энтропии поддерживается постоянно, а CSPRNG просто выдает из него уже готовые случайные байты. Тяжелая работа (сбор физического шума) происходит в фоне, а ваш UUID() забирает готовый результат.


🔗 Ссылка на подробную статью


💡 Вывод:

UUID в Swift - это не просто рандомайзер. Это прослойка между вашим кодом и физическими процессами внутри чипа, которые фундаментально непредсказуемы. Каждый вызов UUID() - маленькое чудо инженерной мысли, где квантовая механика встречается с RFC-стандартами, а результат умещается в 36 символов. Шанс, что два UUID совпадут, настолько мал, что вы скорее встретите медведя в метро, чем поймаете коллизию.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1610🔥4🤔1🙏1👀1
Forwarded from Кот Денисова
👨‍💻 Карьера junior-разработчика: что изменилось с приходом ИИ.

Всем привет! Ситуация на рынке junior-вакансий сегодня действительно непростая. Если раньше компании охотно брали начинающих разработчиков на вырост, то сейчас подход кардинально изменился. Давайте разберемся, что происходит и как адаптироваться к новым реалиям.


Почему компании стали реже нанимать джунов:

Основная причина: перераспределение задач внутри команд. Там, где раньше требовались усилия нескольких специалистов разного уровня, теперь часто справляется один опытный разработчик с ИИ-инструментами.

Типичные джуновские задачи, которые теперь автоматизируются:

🔹Верстка стандартных компонентов.
🔹Написание шаблонного кода.
🔹Разработка простого API.
🔹Багфиксы очевидных ошибок.


ИИ как катализатор изменений:


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

Сейчас один мидл разработчик с ИИ может выполнять объем работы, для которого раньше требовалась связка мидл + два джуна.


Новые требования к начинающим специалистам:

Сейчас требования на позицию junior-разработчика еще недавно являлись требованиями на позицию middle-разработчика и включают:

🔹Умение работать в команде без постоянного контроля.
🔹Способность самостоятельно разбираться в сложных задачах.
🔹Понимание архитектурных принципов.
🔹Навыки работы с ИИ-инструментами.


Что делать начинающим разработчикам:

Прокачивать автономность:

🔹Учитесь самостоятельно находить решения.
🔹Развивайте навыки декомпозиции задач.
🔹Практикуйтесь в code review.

Осваивать ИИ-инструменты:

🔹Изучайте эффективные промпты.
🔹Освойте работу с Copilot, Cursor, ChatGPT.
🔹Учитесь проверять и рефакторить код, сгенерированный ИИ.

Фокусироваться на качестве кода:

🔹Вместо количества решенных задач — акцент на качестве решений
🔹Изучайте лучшие практики и паттерны проектирования.
🔹Развивайте архитектурное мышление.


💡 Вывод:

Современный junior-разработчик - это не просто человек, который пишет код. Это специалист, который умеет эффективно работать в связке с ИИ-инструментами, быстро обучается и способен брать ответственность за свои решения. Те, кто адаптируется к новым условиям, получат серьезное конкурентное преимущество.


➡️ Кот Денисова
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🔥6👀2🗿2❤1
👣 Flutter 3.41: стабильность, модульность и подготовка к будущему

Google выпустил Flutter 3.41 - релиз, который выглядит как плановый апдейт, но на самом деле закладывает архитектурные изменения на годы вперед. 868 коммитов от 145 контрибьюторов, но главное не в количестве, а в направлении.


Прозрачность разработки:

Впервые Flutter вводит публичные release-окна на весь 2026 год. Теперь каждый знает точные даты заморозки веток: 3.44 выйдет в мае, 3.47 в августе, 3.50 в ноябре. Для команд, которые зависят от стабильности фреймворка, это снимает огромный пласт неопределенности. Больше не нужно гадать, попадет ли фича в ближайший релиз - календарь открыт.


Материалы и Cupertino уходят в отдельные пакеты:

Это ключевое изменение, которое многие недооценят. Material и Cupertino больше не будут привязаны к монолитному циклу релиза Flutter. Их обновления смогут выходить независимо, в любое время. Для разработчиков это означает две вещи: во-первых, вы сможете получать новые дизайн-системы (вроде Material 3 Expressive или Liquid Glass) не дожидаясь квартального обновления движка. Во-вторых, если вы застряли на старой версии Flutter из-за легаси, вы все равно сможете обновить визуальную часть отдельно. Фреймворк становится конструктором, а не монолитом.


iOS: UIScene по умолчанию и чистый blur:

Flutter окончательно прощается с наследием AppDelegate. Поддержка UIScene включена по умолчанию - это было требование Apple для будущих версий iOS, и теперь оно выполнено. Параллельно Impeller получил улучшенный рендеринг размытия: исчезли цветные ореолы по краям, которые раньше портили впечатление от BackdropFilter. CupertinoSheet обзавелся нативным drag-хендлом - мелочь, но именно из таких мелочей складывается ощущение «родного» интерфейса.


Android: подготовка к AGP 9 с осторожностью:

Важный нюанс: обновляться на Android Gradle Plugin 9 пока нельзя. Поддержка заморожена до аудита обратной совместимости. Но новые плагины уже по умолчанию генерируются на Kotlin DSL - индустрия движется, и Flutter движется с ней. Плюс появилась возможность точечно исключать ассеты для конкретных платформ: тяжелые десктопные текстуры больше не придется тащить в мобильную сборку.


Графика: синхронные текстуры и 128-битные float:

Для тех, кто работает с кастомными шейдерами, релиз принес две важные вещи. Первая - синхронное декодирование текстур. Раньше создание текстуры для шейдера могло «уронить» кадр, теперь это делается в том же фрейме через decodeImageFromPixelsSync. Вторая - поддержка 128-битных float-текстур. Это про LUT для цветокоррекции, про SDF-шейдеры и про фотофильтры на GPU. Технический потолок поднят.


🔗 Ссылка на подробное описание релиза


💡 Вывод:

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

Выделение Material и Cupertino в независимые пакеты ломает многолетнюю монолитность - теперь дизайн-системы могут эволюционировать со скоростью индустрии, а не со скоростью движка.

Flutter перестает быть просто инструментом для быстрого старта и становится платформой, на которой можно строить сложные, долгоживущие проекты - с предсказуемостью, гибкостью и уважением к обратной совместимости.


➡️ Flutter & Dart | Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍6🙏2❤1👀1
🔢 SwiftUI: разница между some View и AnyView о которой важно знать.

В SwiftUI постоянно встречаются два похожих, но работающих по-разному подхода: some View и AnyView. Для новичка разница незаметна: и там и там возвращается View. Но для SwiftUI это две совершенно разные истории, которые влияют на то, как фреймворк будет обновлять интерфейс и потреблять ресурсы.


Что скрывается за some View:

Когда вы объявляете var body: some View, происходит нечто похожее на контракт с компилятором. Вы обещаете, что свойство будет возвращать значение конкретного типа, соответствующего протоколу View, но сам тип остается неназванным. Компилятор это устраивает, он самостоятельно определит тип на основе того, что вы написали внутри.

Дальше вступает SwiftUI. Получив от компилятора точную информацию о типах всех вложенных элементов, фреймворк выстраивает статическое представление интерфейса. Это как если бы SwiftUI заранее знал полную схему экрана со всеми ячейками, текстами и изображениями. Благодаря этому при изменении данных он может быстро понять, какая именно часть экрана требует перерисовки, и обновить только ее.


Как работает AnyView:

AnyView работает иначе. Он стирает тип и оставляет от конкретной вьюхи только факт, что она соответствует протоколу View. Для SwiftUI это означает потерю информации: он больше не знает, что внутри: текст, изображение или целый стек. Все, что остается - черный ящик.

Типичная ситуация, когда хочется использовать AnyView: возврат разных типов в зависимости от условия:


func content(isLoggedIn: Bool) -> some View {
if isLoggedIn {
return HomeView()
} else {
return LoginView()
}
}


Компилятору это не понравится: типы разные, а some View требует один конкретный. Самый простой, но неправильный способ починить: обернуть оба варианта в AnyView. Код скомпилируется, но SwiftUI потеряет информацию о структуре и не сможет нормально оптимизировать обновления.


Правильный подход - ViewBuilder:

Вместо ручного возврата и стирания типов SwiftUI предлагает использовать @ViewBuilder. Этот атрибут превращает набор View в единое выражение с типом ConditionalContent:


@ViewBuilder
func content(isLoggedIn: Bool) -> some View {
if isLoggedIn {
HomeView()
} else {
LoginView()
}
}


Тип остается конкретным и известным компилятору, просто он умеет описывать условную структуру. Все преимущества оптимизации сохраняются.


Когда AnyView все-таки нужен:

Есть ситуации, где без стирания типа не обойтись:

🔵Массив вьюх разного типа.

🔵Инъекция вьюхи во время выполнения, когда тип заранее неизвестен.

🔵API, где нельзя использовать дженерики.

Например, список разнородных ячеек, которые приходят из конфигурации:


let cells: [AnyView] = [
AnyView(ProfileCell()),
AnyView(SettingsCell()),
AnyView(NotificationCell())
]


Но даже здесь стоит подумать: возможно, можно обойтись перечислением с associated value или каким-то другим дизайном, сохраняющим типы.


Простое правило:

Если SwiftUI может узнать тип на этапе компиляции - используйте some View. Если тип становится известен только во время выполнения - используйте AnyView.

AnyView сам по себе не зло. Зло - использовать его как костыль, чтобы быстро починить ошибку компиляции, не задумываясь о последствиях для производительности.


🔗 Ссылка на подробную статью


💡 Вывод:

Разница между some View и AnyView - это разница между статической типизацией с возможностью оптимизации и динамической диспетчеризацией с потерей производительности. SwiftUI спроектирован так, чтобы максимально использовать информацию о типах на этапе компиляции. Любое стирание типов заставляет фреймворк работать вслепую, что рано или поздно скажется на плавности интерфейса. Хорошая привычка - всегда сначала пробовать some View и только в крайнем случае опускаться до AnyView, четко понимая цену такого решения.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
17👍10❤3🔥1🙏1
🔢 Почему .xcstrings ведет себя в Swift Packages не так, как в основном приложении.

Формат .xcstrings вышел достаточно давно, но его поведение в Swift Packages до сих пор удивляет даже опытных разработчиков. В основном приложении все просто: добавил файл, вызвал String(localized:) и строка подхватилась. В пакете та же схема может не сработать. Строки возвращают ключи вместо перевода, вообще ничего не возвращают или требуют писать bundle: .module в каждом вызове. А если вы рассчитывали на автогенерацию статических переменных, то ее просто нет.


Что такое .xcstrings и при чем тут версии iOS:

.xcstrings - это формат файлов локализации, который пришел на смену старым .strings и .stringsdict. Один файл вместо десятка, визуальный редактор, поддержка плюральных форм. Формат появился в Xcode 15 и не привязан к версии iOS - его можно использовать в проектах, которые работают на iOS 13 и выше.

Но когда этот файл попадает в Swift Package, могут возникнуть проблемы.


Первая проблема - поиск бандла:

В основном приложении String(localized:) автоматически находит нужные ресурсы. В пакете - нет. Там файлы локализации лежат в бандле самого пакета, и системе нужно явно сказать, где искать. Самый распространенный способ - писать String(localized: "key", bundle: .module). Это работает, но раздражает, когда локализации много.

Если поднять минимальную версию iOS в пакете до 16, проблема уходит. Начиная с iOS 16 String(localized:) в пакетах по умолчанию смотрит в правильный бандл. Без лишних параметров.


Вторая особенность - автогенерация статических ключей:

Xcode умеет создавать статические переменные для всех ключей локализации. Вместо "profile_title" вы пишете LocalizedStringResource.profileTitle. Удобно, безопасно, с автодополнением.

Но эта фича завязана на версию iOS. Если минимальная версия пакета - iOS 15 или ниже, Xcode просто не предложит сгенерировать статические ключи. Они становятся доступны только при iOS 16 или новее.


💡 Вывод:

Работа с .xcstrings в Swift Packages накладывает определенные ограничения, которые важно учитывать при выборе минимальной поддерживаемой версии iOS. Начиная с iOS 16 локализация работает предсказуемо и удобно: статические ключи генерируются автоматически, а бандл подхватывается без лишних указаний. На более старых версиях требуются дополнительные действия: ручное добавление bundle: .module и самостоятельное создание ключей. Это не делает локализацию невозможной, но добавляет рутины и повышает риск ошибок. Выбор версии определяет не столько саму возможность использовать .xcstrings, сколько уровень комфорта при работе с ними.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16🔥8❤3🙏1👀11
📱 PWA на iOS: почему веб-приложения на iPhone до сих пор чувствуют себя чужими.

Когда речь заходит о Progressive Web Apps, заказчики часто рисуют красивую картину: один код, все платформы, никаких магазинов приложений. Но как только дело доходит до реального запуска на iOS, эта картина начинает трещать по швам. Не потому что технология плохая, а потому что платформа, на которой она работает, просто не заинтересована в вашем успехе.


Главный фильтр - как PWA попадает на iPhone:

В 2026 году большинство пользователей iOS за пределами Евросоюза по-прежнему живут в мире, где Safari - единственный браузер с полным доступом к системным API. Формально Apple разрешает устанавливать PWA на домашний экран. Неформально - делает все, чтобы пользователь об этом забыл.

Никакого нативного баннера с кнопкой «Установить». Вместо этого - стрелочка в меню «Поделиться» и надежда, что пользователь догадается. Конверсия в установку на iOS ниже в разы просто потому, что процесс спрятан глубже, чем хотелось бы.


Семь дней тишины - как iOS стирает ваши данные:

Самая неприятная особенность Safari - Intelligent Tracking Prevention. Она задумывалась как защита приватности, но для PWA работает как медленный убийца. Если пользователь не заходил на ваш сайт семь дней, браузер может просто удалить все данные: localStorage, IndexedDB, кэш сервис-воркера.

Вы потратили недели на офлайн-функционал? Пользователь вернулся после отпуска и приложение встречает его чистым листом. Никаких предупреждений, никаких настроек. Просто молчаливая очистка.


Пуш-уведомления - работают, но с условием:

Web Push на iOS наконец-то стабилизировался. Но есть нюанс: подписка работает только если PWA уже добавлено на домашний экран. Пользователь просто зашел на сайт в Safari - пуши не получит, даже если согласился.

Это убивает сценарии, где вы хотите прогревать аудиторию до установки. Сначала установка, потом пуш-маркетинг. Последовательность не меняется.


Глубокие ссылки - бесшовность только на словах:

Ссылки, ведущие в PWA - тема отдельной боли. Формально механизм работает: можно настроить ассоциацию домена через файл на сервере, и установленное PWA будет открывать ссылки из почты или мессенджеров.

Но на практике iOS часто упрямо открывает Safari, показывая сверху маленькую плашку «Открыть в приложении». Пользователь должен заметить ее и нажать. Никакого автоматического редиректа, как на Android. Это не баг, это сознательное решение Apple сделать опыт чуть менее удобным.


Когда PWA все-таки можно использовать:

Несмотря на все ограничения, есть сценарии, где веб-приложения остаются разумным выбором:

🔵Внутренние инструменты компании. Обновления без App Store, доступ с любого устройства.

🔵Ивент-приложения. Живут неделю, не требуют установки через магазин.

🔵Сервисы с редким использованием. Раз в месяц оплатить счет - не повод качать сотни мегабайт.


Когда PWA превращается в проблему:

🔵Фоновая геолокация. Как только экран гаснет, доступ к координатам теряется.

🔵Сложная работа с камерой. WebRTC в Safari греется и вылетает.

🔵Биометрия. WebAuthn работает, но выглядит как попап на сайте, а не нативный диалог FaceID.

🔵Офлайн с большими данными. Риск, что iOS почистит кэш, слишком велик.


🔗 Ссылка на подробную статью


💡 Вывод:

PWA на iOS в 2026 году - это технология, которая существует вопреки платформе, а не благодаря ей. Каждая фича требует обходных путей, каждый сценарий - проверки на настроение Safari. Apple построила экосистему, где веб-приложения могут работать, но никогда не будут работать слишком хорошо. Если вы выбираете этот путь, готовьтесь к тому, что значительная часть усилий уйдет не на функциональность, а на борьбу с ограничениями, которые намеренно не документированы и не исправляются годами.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18🤯10❤62🤔1
🔢 Как не закопать производительность в глубоких иерархиях SwiftUI.

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


Как SwiftUI на самом деле обновляет экран:

Когда в вашей View меняется @State или другое отслеживаемое свойство, SwiftUI перезапускает вычисление body и сравнивает результат с предыдущим. Только те части интерфейса, где обнаружились различия, отправляются на рендеринг. Остальные игнорируются.

Этот механизм (диффинг) работает быстро, но у него есть цена: чем больше примитивных вьюх в body, тем больше работы алгоритму. И если вы написали одну гигантскую структуру на 200 строк, каждое изменение состояния заставит SwiftUI пройти по всей иерархии и сравнить каждый Text, каждый Image и каждую кнопку.


Главный враг - монолитное body:

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

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


Верный способ - отдельные структуры:

Единственный способ создать настоящие границы перерисовки - выносить части интерфейса в отдельные структуры, реализующие протокол View. Когда SwiftUI видит вложенную структуру, он сначала проверяет, изменились ли ее входные параметры. Если нет - body этой структуры даже не вызывается. Диффинг останавливается на этом уровне.


struct Counter: View {
@State private var value = 0
var body: some View {
VStack {
Button("Увеличить: \(value)") { value += 1 }
SubView()
}
}
}

struct SubView: View {
var body: some View {
Text("Данная структура не зависит от value")
}
}


В этом примере нажатие на кнопку перезапустит body у Counter, но SubView останется нетронутым. SwiftUI видит, что его параметры не изменились, и пропускает вычисления.


Коварство замыканий и Equatable:

Есть один случай, когда даже отдельные структуры могут перерисовываться без необходимости: если в них передаются замыкания. SwiftUI не умеет сравнивать замыкания по содержимому, поэтому может считать, что каждое новое замыкание - это изменение.

В таких ситуациях помогает явное указание, что вьюха поддерживает Equatable. Добавьте модификатор .equatable() и реализуйте метод ==, где явно опишите, какие поля должны влиять на перерисовку. Обратите внимание: просто подписать структуру под Equatable недостаточно, нужно еще сказать SwiftUI использовать эту информацию через .equatable().


Как отлавливать лишние перерисовки:

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


🔗 Ссылка на подробную статью


💡 Вывод:

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18🔥115❤1🙏1🤝1
Forwarded from Кот Денисова
👨‍💻 Поиск работы в 2025 году: почему отправка резюме стала игрой в лотерею.

Здравствуйте! Если вы отправляли резюме в последние полгода и получали только автоматические ответы - вы не одиноки. Рынок ИТ труда 2025 года напоминает сломанный механизм: сотни откликов на вакансию, ИИ-скрининг, который отсекает даже сильных кандидатов, и рекрутеры, тонущие в море одинаковых резюме. Давайте разберемся, что пошло не так и как в этой системе выжить.


Цифры, которые пугают:

Согласно данным на популярных платформах, в 2025 году на одну ИТ-вакансию приходит в среднем 250 заявок. Это втрое больше, чем в 2018 году. Шансы получить оффер после отправки резюме составляют примерно 0,4%. Для популярных позиций (типа Middle iOS/Android разработчика) этот показатель еще ниже.


Что пошло не так:

🔹Технологии упростили подачу отклика до абсурда: кнопка для отклика на вакансию на популярных сервисах по поиску работы превратили процесс в спам. Разработчик за вечер может разослать 50+ заявок, не вчитываясь в требования.

🔹ИИ-ассистенты создали индустрию клонов: ChatGPT и аналоги генерируют «идеальные» резюме и сопроводительные письма. Теперь все кандидаты говорят одними и теми же шаблонными фразами.

🔹ATS-системы не справляются с потоком: автоматические системы скрининга, созданные для помощи рекрутерам, теперь стали проблемой. Они отсекают до 75% резюме по формальным критериям, часто теряя сильных кандидатов.

🔹Рекрутеры в состоянии перманентного выгорания: на одного ИТ-рекрутера приходится в среднем 500 активных заявок в месяц. Физически невозможно качественно обработать такой объем.


Что не так с подходом найма:

Старая модель (до 2020):

Отправить 10 заявок -> 3 ответа -> 1 оффер

Новая модель (2025):

Отправить 100 заявок -> 5 ответов -> 0 офферов


Эффект «конгестии»:

Нобелевский лауреат Элвин Рот называет это явление «конгестией» (congestion) - это когда рынок становится настолько перегруженным, что перестает функционировать. В ИТ это выглядит так:

🔹Кандидаты шлют больше заявок «на удачу».

🔹Рекрутеры просматривают только первые 10-20 резюме.

🔹Сильные специалисты теряются в общем потоке.

🔹Компании жалуются, что не могут найти хороших разработчиков.


Что действительно работает в 2025:

🔹Качество вместо количества: 10 точечных, персонализированных заявок лучше, чем 100 шаблонных.

🔹Сеть контактов: знакомства и связи дают в 5-10 раз больше шансов на собеседование.

🔹Прямые обращения: найти HR менеджера компании и написать ему лично.

🔹Экспертность вместо шаблонов: публикуйте кейсы, ведите блог, выступайте на митапах.


💡 Вывод:

Рынок ИТ-труда не умер, он трансформировался в нечто новое. Старые правила больше не работают. Рассылка сотни одинаковых резюме - это теперь не стратегия, а самообман.

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


➡️ Кот Денисова
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17🔥10🙏5❤1👀1🗿1
👾 Агенты пишут, агенты проводят ревью, CI собирает: новая реальность iOS-разработки.

Разработка с ИИ-агентами перестала быть экспериментом и превратилась в рабочий инструмент. Но когда код пишется автоматически, встает вопрос качества и того, как выкатывать такие изменения в прод. Если агент может создать пул-реквест, почему бы не настроить пайплайн, который сам соберет билд, протестирует его и отправит в TestFlight? Разбираем по шагам, как выстроить такую систему.


Настройка правил для агента:

Все начинается с файла agents.md в корне проекта. Это документ, где вы описываете, как агент должен писать код. Не абстрактно, а конкретно: отступы, предпочтения между SwiftUI и UIKit, работа с сетью, архитектурные паттерны.

Чем детальнее правила, тем меньше шансов, что агент создаст что-то неожиданное. Видите, что он постоянно лепит новые сетевые слои вместо использования существующего APIClient? Добавляете правило и следующий код будет использовать правильные абстракции.

Это живой документ. В идеале он пополняется с каждой итерацией, когда вы замечаете, что агент делает что-то не так.


Планирование перед кодом:

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

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


Автоматическое ревью:

Когда код написан, агент создает пул-реквест. И здесь в игру вступает второй агент - ревьюер. В Cursor, например, есть встроенный BugBot, который проверяет пул-реквесты на логические ошибки, краевые случаи и случайные изменения.

Он замечает то, что вы можете пропустить при беглом просмотре. Например, если в коде остались дебажные проперти, которые нигде не используются - BugBot их удалит. Если возможна гонка состояний при старте тренировки - предложит защиту. Это как второй взгляд, только автоматический и всегда доступный.


CI как второй уровень защиты:

Даже если агент прогнал тесты локально, полезно иметь еще один слой проверки. Bitrise (или Xcode Cloud, или GitHub Actions) запускает тесты на каждый пул-реквест. Если что-то сломалось - статус красный, мержить нельзя.

Отдельный пайплайн собирает релиз при мерже в основную ветку. Тесты прогоняются снова (на всякий случай), потом архив, подпись и отправка в App Store Connect. Все автоматически, без участия человека.


Почему тесты становятся критичными:

С агентами тесты превращаются в контракт. Они описывают ожидаемое поведение, и если агент его нарушает - тесты падают. Это единственный способ гарантировать, что автоматически сгенерированный код делает именно то, что нужно.


TestFlight как финальная точка:

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

Это замыкает цикл: идея -> промпт -> код -> ревью -> тесты -> сборка -> установка на устройстве. И все это без необходимости открывать Xcode.


🔗 Ссылка на подробную статью


💡 Вывод:

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16🔥10🤔5🤯2👀1🗿1
🔢 Скрытые возможности Task в Swift.

Когда говорят о параллелизме в Swift Concurrency, первая ассоциация - Task { }. Удобно, знакомо, работает. Но если смотреть на задачу только как на способ утащить тяжелые вычисления в фон, легко пропустить главное: Task - это универсальный инструмент, который умеет быть и будущим результатом, и синхронизатором, и асинхронным контекстом. Просто большинство используют лишь малую часть его возможностей.


Важные возможностей задачи:

🔵Когда вы сохраняете дескриптор задачи в переменную и передаете ее в другую часть приложения, вы отдаете не сам процесс, а контракт: «рано или поздно здесь появится значение». Именно так работают futures и promises в других языках, но в Swift это встроено в базовый тип. Вы можете ждать результат в любом месте, на любом потоке, без дополнительных примитивов синхронизации.

🔵Задачи потокобезопасны. Не в том смысле, что код внутри них защищен от гонок, а в том, что доступ к состоянию самой задачи (выполнена, отменена, есть результат) атомарен. Это значит, что одна задача может служить точкой согласования для нескольких независимых асинхронных операций. Первая дошла до результата - остальные просто ждут его на await value. Никаких DispatchGroup, никаких семафоров. Только задача и ее значение.

🔵Completion-хендлеры, делегаты, колбэки SwiftUI - все они синхронные. Но внутри них часто нужно вызвать асинхронную функцию. Task дает возможность сделать это без хаков: он создает асинхронный контекст внутри синхронного. При этом важно различать намерения. Task.detached раньше использовали для гарантированного фона, но он сбрасывает @TaskLocal - неочевидное и часто нежелательное поведение. В Swift 6.2 появился более чистый способ: Task { @concurrent in }. Семантика та же, но локальный контекст сохраняется.


Почему это важно:

Пока вы мыслите задачами как «замыканиями, которые бегут в фоне», вы пишете больше кода, чем нужно. Каждый раз, когда требуется общий результат или координация, вы тянете в проект Actor, Publisher или внешнюю очередь. Но часто достаточно просто передать дескриптор задачи.

Речь не об оптимизации. Это смещение фокуса: вместо «как распараллелить» нужно думать «как описать зависимость». Task не про потоки, а про значения, которые появятся в будущем. И этот будущий факт можно передавать, как любую другую сущность языка.


🔗 Ссылка на подробную статью


💡 Вывод:

Task в Swift - пример хорошей абстракции, которая скрывает сложность за простым интерфейсом. Ее можно использовать как фоновый исполнитель, как будущий результат, как точку синхронизации и как вход в асинхронность. И все это - один тип, без подключения дополнительных библиотек. Чем лучше вы понимаете эти роли, тем меньше кода пишете и тем меньше состояний нужно синхронизировать вручную.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
19🔥12👍5❤1🙏1👀1
🔢 SwiftUI Agent Skill: правила написания качественного SwiftUI-кода с помощью ИИ.


Если вы хоть раз пробовали генерировать SwiftUI-код через нейросети, то знаете эту боль: агент упорно пишет onChange с одним параметром, лепит GeometryReader там, где можно обойтись без него, и создает вложенные скроллы, которые потом загадочно тормозят на проде. Исправлять одно и то же в каждом промпте - занятие для мазохистов. Хорошая новость: теперь можно не исправлять, а научить агента с первого раза писать правильно.


Skill вместо бесконечных правок:

Раньше все пытались запихнуть правила в AGENTS.md. Работало, но плохо: файл раздувался, агент его читал по диагонали, а новые правила добавлялись только после очередного провала. Подход со skills работает иначе. Это отдельная инструкция, которую агент может подгружать осознанно, когда понимает, что задача касается SwiftUI.

В открытом репозитории SwiftUI-Agent-Skill собраны лучшие практики, которые обычно всплывают только после код-ревью или уже в проде. Там не абстрактные «пиши хорошо», а конкретные правила:

🔹Как правильно использовать onChange, чтобы не ловить бесконечные обновления.

🔹Когда GeometryReader действительно нужен, а когда лучше обойтись.

🔹Почему вложенные скроллы - это боль и как их избегать.

🔹Как работать с изображениями, чтобы не забить память.

🔹Паттерны для навигации, списков и модальных окон.

🔹Производительность - место, где агенты чаще всего косячат.


Что это дает на практике:

Когда вы просто просите агента написать экран, он может выдать что-то рабочее, но с кучей мелких косяков. Если же подключить скилл, агент сначала анализирует код и подсвечивает проблемные места еще до того, как вы их увидите. Например:

🔹«Ты тут два скролла вложил - будет лагать, давай переделаем»

🔹«У тебя три onAppear дублируют логику, лучше вынести в одно место»

🔹«Этот GeometryReader можно заменить на простой Spacer, будет проще»

То есть скилл работает не как набор запретов, а как ревьюер, который объясняет, почему так делать не стоит и как сделать лучше.


Почему это актуально:

Сейчас уже не вопрос «использовать ИИ или нет». Вопрос в том, как заставить его писать код, который не придется переписывать вручную. Если вы просто генерируете и надеетесь на лучшее, технический долг будет расти быстрее, чем фичи. Если же научить агента правильным паттернам с первого раза, можно реально ускорить разработку без потери качества.

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


🔗 Ссылка на подробную статью


💡 Вывод:

Skill для SwiftUI - это способ перестать тратить время на однотипные правки и объяснения агенту, почему его код не пойдет в прод. Один раз настроили правила и дальше агент сам пишет так, как будто прошел десяток код-ревью. Если вы активно используете Cursor или Claude для генерации интерфейсов, этот скилл сэкономит вам часы нервотрепки и сделает код чище. А если накопили свои грабли - можете добавить их в репу, чтобы другим тоже было меньше боли.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
21👍12🔥5❤1🤔1👀1
Forwarded from Кот Денисова
👨‍💻 Декомпозиция против хаоса: почему тестирование по частям выигрывает у одного финального теста.

Привет! Сегодня хочу поделиться болью, которая знакома многим из вас. Не так давно я работал в одной команде, где существовало железное правило: «Никакого тестирования подзадач! Тестируем только финальную реализацию всей объемной задачи». ПМ наотрез отказывался что-либо менять. «Мы всегда так работали, и все было хорошо» - вот его главный аргумент.

Мне этот подход категорически не нравится и сегодня я подробно разберу, почему инкрементальное тестирование каждой подзадачи - это спасение для команды и ментального здоровья разработчика.


Существует два подхода к работе:

🔹Тестирование всей задачи целиком: пока разработчик разрабатывает объемную фичу, вся команда ждет. После завершения разработки фичи он передает ее на тестирование.

🔹Инкрементальное тестирование: задача дробится на логические части. Каждая часть выполняется в своей ветке, проходит код-ревью и тестирование, и только потом вливается в основную ветку. Это позволяет получить рабочий, протестированный кусок функциональности и только потом двигаться дальше.


Почему «сделать все, потом протестировать» - это провальная стратегия:

Когда вы сталкиваетесь с сопротивлением, важно аргументировать свою позицию. Вот какие аргументы я приводил:

🔹Это создает бюрократического монстра: представьте: вы сделали большую задачу, отдали на тестирование, и QA находит 10 багов. На каждый баг заводится отдельная задача, создается новый MR, его снова нужно тестировать, ревьюить, мержить. Это горы лишней работы. В то время как при поэтапном тестировании каждая подзадача точечно тестируется и дорабатывается в своей ветке. Не нужно заводить кучу багов - фидбэк дорабатывается сразу, в рамках той же ветки и задачи.

🔹Это убивает команду и тормозит разработку: пока разработчик пишет свою монолитную фичу, остальные члены команды не могут работать с его кодом. Они заблокированы. Когда он наконец делает MR, ревью превращается в кошмар: проверить 1000+ строк кода за раз гораздо сложнее. В итоге - ошибки пропускаются, а процесс замедляется.

🔹Это дороже и больнее исправлять: это главный аргумент. Если на первых этапах разработка пошла по неверному пути, при поэтапном подходе это выяснится быстро. Вы потратили день на подзадачу и вам вернули ее на доработку. В монолитном подходе вы можете потратить две недели, чтобы в итоге услышать: «Все сделано не так, нужно переделывать». Команда либо делает костыли, что убивает качество кода, либо тратит огромные ресурсы на переделку.


💡 Вывод:

Инкрементальное тестирование - это переход от хаоса, когда все откладывается на последний момент, к предсказуемому и управляемому качеству на каждом этапе. Если ваша команда до сих пор работает по принципу «сделал целиком - передал в тест», самое время начать этот разговор. Результат, уверен, приятно удивит всех участников процесса.


➡️ Кот Денисова
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17❤8🔥4🤔1🙏1👀1
🔨 Состоялся релиз Xcode 26.3 с поддержкой ИИ-агентов.

Apple выпустила Xcode 26.3 и это не просто очередное обновление с исправлением багов. В среду разработки официально завезли поддержку агентов Claude и ChatGPT, которые реально могут работать с проектом: читают файлы, правят код, запускают сборку и даже смотрят SwiftUI Preview. Итеративно, с обратной связью, без необходимости переключаться между десятком приложений.


Что изменилось:

Раньше ИИ в Xcode был просто умной подсказкой - написал запрос, получил кусок кода, вставил руками, проверил. Сейчас агенты получили доступ ко всему проекту. Они могут прочитать структуру, понять зависимости, внести изменения и проверить, скомпилировался ли проект. Если нет - попробовать еще раз. Это уже не помощник, а полноценный участник процесса, который может сам закрывать задачи.

Claude Agent и OpenAI Codex теперь работают как внутренняя интеграция. Вы даете задачу, агент сам идет в файлы, сам правит, сам проверяет. Для тех, кто пробовал Claude Code из командной строки, ощущения будут знакомыми, только теперь все внутри Xcode и не нужно ничего дополнительно настраивать.


Как настроить:

В настройках Xcode (Settings -> Intelligence) можно выбрать провайдера: Anthropic или OpenAI. Можно либо авторизироваться через аккаунт, либо просто вставляете API-ключ. Для ChatGPT есть даже бесплатный базовый режим без аккаунта, но с лимитами. Отдельно Apple выложила инструкцию по настройке, все достаточно прозрачно.


MCP и внешние агенты:

Xcode 26.3 также поддерживает Model Context Protocol. Если коротко, это способ дать внешним инструментам доступ к проекту. Можно подключить что-то свое, не из списка официальных провайдеров и работать через CLI с возможностью видеть превью. Для тех, кто уже завязан на кастомные агенты, это возможность не ломать существующие процессы.


💡 Вывод:

Xcode 26.3 делает шаг в сторону агентной разработки и это логично. Сторонние решения вроде Cursor или Claude Code уже показали, что такой подход работает. Теперь Apple забирает это внутрь экосистемы. Для тех, кто привык работать в Xcode и не хочет прыгать по приложениям, это большой плюс. Для остальных - как минимум повод присмотреться, потому что через год-два это будет стандартом.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18🔥106❤3🙏1🫡1
🍏 iOS 26.4: откат эксперимента с поиском.

Apple выпустила бета версию iOS 26.4, в которой тихо поправила два системных приложения. Поиск в App Store и Games вернулся к привычному виду: строка вверху экрана, иконка поиска в нижнем таб-баре. Именно так это работало в iOS 18, пока в 26 версии дизайнеры не решили поэкспериментировать.


Что поменялось:

В предыдущих версиях поиск в App Store был вынесен в отдельную вкладку. Казалось бы, логично - нажал на иконку поиска и ищешь. Но на практике это ломало привычную навигацию. Таб-бар всегда был про разделы: сегодня это главная, игры, приложения, обновления. А поиск вдруг стал не действием, а отдельной секцией.

В iOS 26.4 все вернули на круги своя. Вкладка «Поиск» снова интегрирована в нижнюю панель вкладок, а поле поиска вернулось в верхнюю часть экрана. Это отражает структуру, существовавшую до редизайна, представленного в iOS 26.

Та же логика теперь применяется и к приложению Games. Его поиск следует знакомой схеме: поле вверху, точка входа интегрирована в панель вкладок. Предыдущий эксперимент, когда поиск был отделен и расположен по-другому, был отменен в этих двух приложениях.


Почему это важно:

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


Обратная связь работает:

Хорошо, что Apple умеет признавать ошибки и откатывать неудачные решения. iOS 26 прожила с этой фичей достаточно долго, но в итоге возобладал здравый смысл. Интересно, коснется ли эта волна других приложений. Музыка, подкасты, ТВ - там тоже есть поиск и пока он везде устроен по-разному. Было бы логично привести все к единому знаменателю.


🔗 Ссылка на источник


💡 Вывод:

Возврат поиска в App Store и Games - это не просто «сделали как раньше». Это сигнал, что Apple пересматривает свои UX-решения и не боится отменять то, что не сработало. Для разработчиков это хороший урок: иногда лучше оставаться в рамках знакомых паттернов, чем гнаться за новизной. Пользователям все равно, насколько креативно вы спроектировали навигацию. Им важно, чтобы интерфейс был предсказуемым и не заставлял искать поиск по всему экрану.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥18👍12👀5👏1🤯1🫡1
🔨 Как найти и исправить зависания интерфейса в iOS.

Зависания интерфейса - один из самых раздражающих багов для пользователя и сложных для локализации для разработчика. Приложение внешне работает, но периодически замирает, не реагируя на тапы. Частая, но ошибочная реакция - грешить на слабый девайс или тяжелые анимации. В реальности корень проблемы почти всегда кроется в неправильной работе с потоками, а точнее - в блокировке главного (main) потока. Современные инструменты вроде Instruments позволяют не гадать, а точно определить, что именно и когда остановило отрисовку UI.


Определяем тип проблемы:

В Instruments открываем запись с шаблоном «Hangs» или «Time Profiler». Находим момент зависания и смотрим на загрузку CPU главного потока.

🔵CPU высокий: поток чем-то занят (тяжелые вычисления).

🔵CPU низкий: поток заблокирован и ждет (сеть, диск, lock).

Это сразу сужает круг поиска.


Включаем Thread State Trace:

Добавляем инструмент Thread State Trace и перезапускаем запись. Он покажет график состояния потоков. Нас интересует момент, когда главный поток из «Running» переходит в «Blocked».


Смотрим Call Stack:

В точке блокировки открываем стек вызовов (Call Stack). Он укажет на конкретный метод, который вызвал проблему. Часто это:

🔵Синхронная сетевая загрузка (Data(contentsOf:)).

🔵Тяжелая синхронная операция в init() вью.

🔵Блокировка мьютексом или семафором.

🔵Синхронная работа с Core Data или файлами.


Пример:

Была View со списком товаров. При скролле интерфейс замирал. Thread State Trace показал блокировку в ProductCell.init. В инициализаторе был синхронный вызов функции, которая загружала и обрабатывала превью изображения.

Решение: вынес загрузку в onAppear с использованием Task:

struct ProductCell: View {
@State private var image: UIImage?
let product: Product

var body: some View {
HStack {
if let image = image {
Image(uiImage: image)
}
Text(product.name)
}
.onAppear {
Task {
image = await loadImageAsync(from: product.imageURL)
}
}
}
}



Профилактика:

🔵Не делайте тяжелой работы в инициализаторах View.

🔵Все операции с сетью, диском или долгие вычисления - только в фоне (async/await, DispatchQueue).

🔵Регулярно прогоняйте приложение под Instruments, особенно после добавления нового функционала.


🔗 Ссылка на подробную статью


💡 Вывод:

Диагностика и устранение зависаний - это не магия, а технический процесс: записываем профиль, смотрим на состояние потока, находим проблемный метод в стеке вызовов. Инструменты дают всю нужную информацию. Главное не забывать, что основное правило для главного потока одно: он только для UI. Все остальное - в фоновые очереди.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18❤10🔥3✍1🙏1👀1
🔢 Как порядок параметров может влиять на размер структуры в Swift.

Когда создаешь структуру в Swift, интуитивно кажется, что ее размер должен равняться сумме размеров параметров. Int - 8 байт, String - 16, Bool - 1. Сложил и получил 25. Но на практике компилятор может добавить несколько скрытых байт, и тот же набор полей внезапно займет уже 32 байта. Все дело в выравнивании и пустых вставках между полями.


Как процессор читает память:

Процессор не работает с байтами по одному. Ему удобнее читать данные блоками, размер которых зависит от архитектуры. На 64-битных системах это обычно 8 байт. И важное условие: адрес, по которому лежит значение, должен быть кратен размеру этого значения. Int должен начинаться с адреса, кратного 8. String в Swift занимает 16 байт, но его внутренняя структура такова, что он также требует выравнивания на 8 байт. Bool может лежать где угодно.

Компилятор об этом знает и автоматически добавляет пустые байты (padding) между полями, чтобы соблюсти выравнивание. Из-за этого порядок объявления полей влияет на итоговый размер структуры.


Два примера - два размера:

Возьмем структуру с тремя полями разных типов:


struct First {
let flag: Bool // 1 байт
let name: String // 16 байт
let count: Int. // 8 байт
}


Поля идут так: Bool (1 байт), затем String (16 байт), но чтобы String начался с адреса, кратного 8, компилятор добавляет 7 пустых байт после Bool. Затем идет Int (8 байт), который уже сам по себе выровнен. Сумма: 1 + 7 + 16 + 8 = 32 байта. Конечный адрес структуры тоже должен быть выровнен по максимальному требованию (8 байт), у нас он уже кратен 8 и остается без изменений.

А теперь поменяем порядок:


struct Second {
let name: String // 16 байт
let count: Int. // 8 байт
let flag: Bool // 1 байт
}


String и Int идут первыми - оба с правильным выравниванием, никакого padding между ними не нужно. Bool в конце занимает 1 байт. Итог: 16 + 8 + 1 = 25 байт. Но теперь конечный адрес структуры не кратен 8, поэтому компилятор добавит ещё 7 байт в конец, чтобы выровнять структуру в памяти. Финальный размер - 32 байта.

Вроде бы оба варианта дали 32 байта? Да, но если добавить еще одно маленькое поле, разница станет заметной:


struct Third {
let flag: Bool // 1 байт
let name: String // 16 байт
let flag2: Bool // 1 байт
let count: Int. // 8 байт
}


Здесь после первого Bool добавляется 7 байт padding, затем String, потом flag2 (1 байт) и перед Int снова нужно добавить 7 байт padding. Итого: 1 + 7 + 16 + 1 + 7 + 8 = 40 байт.

А если сгруппировать правильно:


struct Fourth {
let name: String // 16 байт
let count: Int. // 8 байт
let flag: Bool // 1 байт
let flag2: Bool // 1 байт
}


Все крупные поля идут в начале, мелкие - в конце. Padding нужен только в конце для выравнивания всей структуры: 16 + 8 + 1 + 1 = 26 байт, плюс 6 байт в конце = 32 байта. Экономия 8 байт на каждой структуре.


Почему это важно:

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

Кроме того, понимание выравнивания помогает при оптимизации: иногда достаточно просто переставить поля местами, чтобы структура похудела без потери функциональности. String, Int, Double требуют выравнивания по 8 байт, Bool и мелкие enum - по 1. Группируйте большие поля вместе, маленькие - в конец.


🔗 Ссылка на подробную статью


💡 Вывод:

Размер структуры в Swift зависит не только от типов полей, но и от порядка их объявления. Компилятор добавляет невидимые байты, чтобы данные лежали в памяти так, как удобно процессору. Знание этих правил позволяет писать более эффективный код, особенно когда речь идет о больших массивах структур. Иногда простая перестановка полей экономит мегабайты памяти без единой строчки дополнительной логики.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
16👍6🔥2🤯2👀2❤1