Мобильный трудоголик
1.35K subscribers
61 photos
9 videos
266 links
👨‍💻 Пишу простым языком об iOS разработке на Swift и мобильной разработке в целом.
🔹 Вошел в IT задолго до того как это стало мейнстримом.
---
‍Обо мне: https://t.me/hardworkerIT/3
Чат: @hardworkerChatIT
Канал про разработку и жизнь в ИТ: @itDenisov
Download Telegram
Привет! Хочу порекомендовать канал практикующего staff инженера из Т-Банка.

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

➡️ Подписывайтесь на @ios_broadcast
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥4🗿41
🔢 SwiftUI Canvas: революция при работе с графикой в iOS.

В SwiftUI есть мощнейший инструмент для работы с графикой - Canvas API. Этот инструмент кардинально меняет подход к созданию сложных визуальных эффектов и кастомной графики в приложениях.


Проблемы до Canvas:

До появления Canvas разработчики сталкивались с серьезными ограничениями:

🔵Path и Shape: слишком примитивны для сложной графики.
🔵UIKit и Core Graphics: требовали императивного подхода.
🔵Metal: избыточно сложен для большинства задач.
🔵Производительность: кастомные решения часто работали с просадками FPS.

Фактически, для любой нетривиальной графики приходилось покидать комфортную декларативную среду SwiftUI.


Что такое Canvas API:

Canvas - это View в SwiftUI, предоставляющая прямой доступ к низкоуровневому рендерингу:

Canvas { context, size in
// Рисуем эллипс
context.fill(
Path(ellipseIn: CGRect(origin: .zero, size: size)),
with: .color(.blue)
)

// Добавляем текст
context.draw(Text("Hello Canvas"), at: CGPoint(x: size.width/2, y: size.height/2))
}



Ключевые возможности:

🔹 GraphicsContext: сердце Canvas, предоставляет:

🔵Рисование примитивов (линии, фигуры, текст).
🔵Работу с трансформациями и наложениями.
🔵Поддержку градиентов и теней.
🔵Высокопроизводительный рендеринг через Metal.

🔹 Производительность: Canvas работает напрямую с GPU, обеспечивая 60 FPS даже при сложных вычислениях.

🔹 Интеграция с SwiftUI: полная совместимость с анимациями, жестами и системой layout.


Ограничения:

🔵Только с iOS 15+
🔵Требует понимания основ компьютерной графики.
🔵Для сверхсложной 3D-графики все еще нужен Metal.
🔵Отладка может быть сложнее стандартных View.


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


💡 Вывод:

Canvas API - это не просто рядовая фича в SwiftUI, а фундаментальное расширение возможностей фреймворка. Разработчики могут создавать сложную графику и визуальные эффекты, не покидая декларативной парадигмы.

Для проектов, где важна визуальная составляющая: анимированные onboarding-экраны, кастомные графики, интерактивные элементы - Canvas становится обязательным инструментом в арсенале разработчика.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
19👍1262🔥2👀1
Forwarded from Кот Денисова
👨‍💻 Не деплой в пятницу и другие суеверия в ИТ.

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

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


Самые распространенные суеверия в нашей сфере:


Табу на пятничный деплой.

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


Феномен «озарения у доски».

Не можешь найти ошибку? Начни объяснять ее коллеге или просто проговори вслух и решение придет само. Именно из этого ритуала пошел мем с резиновым утенком. Выглядит со стороны очень забавно, но работает!


Проклятие «легкой задачи».

Стоит только сказать на планировании: «Да это за час сделается!», как задача обязательно превратится в многодневный марафон с переписыванием половины кодовой базы. Закон подлости? Или просто недооценка сложности?


Магический перезапуск.

Не работает? Не читай логи, а просто перезапусти! Не собирается? Перезапусти билд! И ведь часто это помогает!


«Не смотри на процесс сборки - упадет!» или «Смотри пристально, иначе сломается!».

Здесь разработчики делятся на два лагеря. Одни уверены: если ты уйдешь от монитора, пока идет билд, все обязательно сломается. Другие ровно наоборот: стоит только начать пристально следить за консолью, как система тут же выдаст ошибку. Парадокс! Каждому свое.


Почему это работает (или кажется, что работает)?

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

Когда мы выполняем ритуальные действия (не смотрим на процесс сборки, перезапускаем без чтения логов пайплайн), мы снижаем тревожность. А спокойный разработчик - это эффективный разработчик.


➡️ Кот Денисова
Please open Telegram to view this post
VIEW IN TELEGRAM
148👍4🔥1🫡1
Мобильный трудоголик pinned «🛫 Дарим три подписки на полгода в приложении Social Poster - автопостинг по социальным сетям. Чтобы принять участие, нужно: 🔹Подписаться на канал. 🔹Нажать кнопку «Участвую!».»
🔢 Эволюция именования переменных и функций в Swift.

Swift 6.2 принес долгожданную фичу - Raw Identifiers, которая ломает привычные ограничения именования переменных, функций и констант. Теперь мы можем использовать символы, которые раньше были под запретом.

До Swift 6.2 названия не могли:

🔵Начинаться с цифры.
🔵Содержать пробелы.
🔵Включать математические символы.

Теперь достаточно заключить такое название в обратные кавычки (`), и ограничения снимаются!


Пример:

Вместо громоздких конструкций с аннотациями:


@Test("Пользователь нажимает кнопку сохранения без заполненных полей")
func testSaveButtonTapWithEmptyFields() {
}


Можно писать лаконично и понятно:


@Test
func `пользователь нажимает кнопку сохранения без заполненных полей`() {
}


Раньше для числовых значений приходилось искать обходные пути:


enum VideoFormat {
case resolution1080p
case resolution4K
case frameRate24
case frameRate60
}


Теперь называем вещи своими именами:


enum VideoFormat {
case `1080p`
case `4K`
case `24fps`
case `60fps`
}


Использование становится интуитивно понятным:


let format: VideoFormat = .`4K`
let frameRate: VideoFormat = .`60fps`



Важные нюансы:

🔵Читаемость и удобство: не переусердствуйте с сложными названиями.
🔵Автодополнение: имена в кавычках работают в автодополнении.
🔵Рефакторинг: инструменты рефакторинга корректно обрабатывают такие идентификаторы.


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


💡 Вывод:

Raw Identifiers - это не просто синтаксический сахар, а мощный инструмент для улучшения читаемости кода. Особенно полезно в тестировании и работе с внешними API, где точность формулировок критически важна.

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
20👍1042👀1
🔢 MainActor.assumeIsolated: спасательный круг для старого кода в Swift 6.

Всем привет! Многие из нас столкнулись с неприятным сюрпризом при обновлении до Swift 6 и новее: старый добрый код внезапно перестал компилироваться. Жесткие проверки конкуренции выявляют проблемы там, где раньше все работало. Особенно болезненно это проявляется при работе с легаси-API от Apple, которые еще не адаптированы под новые требования.


В чем именно проблема:

Возьмем классический пример: создание UIHostingController внутри методов, которые не помечены как @MainActor. Swift 6 видит потенциальную гонку данных и отказывается компилировать такой код:


class CustomAttachmentViewProvider: NSTextAttachmentViewProvider {
override func loadView() {
// Ошибка компиляции в Swift 6 и новее
let hosting = UIHostingController(rootView: MySwiftUIView())
self.view = hosting.view
}
}


Обычные решения не работают:

Многие пытаются решить проблему стандартными способами:

🔵 Добавление @MainActor к классу или методу
🔵 Обертывание в Task { @MainActor in }
🔵 Использование DispatchQueue.main.async

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


Решение: MainActor.assumeIsolated:

Именно здесь на помощь приходит MainActor.assumeIsolated - метод, созданный специально для таких случаев:


class CustomAttachmentViewProvider: NSTextAttachmentViewProvider {
override func loadView() {
let view = MainActor.assumeIsolated {
let hosting = UIHostingController(rootView: MySwiftUIView())
hosting.view.backgroundColor = .clear
return hosting.view
}
self.view = view
}
}



Особенности работы метода:

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


Важные замечания:


🔵 Всегда проверяйте поток: используйте Thread.isMainThread перед вызовом.
🔵 Только для синхронных операций: не подходит для асинхронных задач.
🔵 Избегайте захвата self: используйте статические методы когда возможно.
🔵 Тестируйте тщательно: ошибки приведут к крешам в рантайме.


Подробный пример использования:


class CustomAttachmentViewProvider: NSTextAttachmentViewProvider {
override func loadView() {
view = createViewSafely()
}

private func createViewSafely() -> UIView {
if Thread.isMainThread {
return Self.createHostingView()
} else {
return DispatchQueue.main.sync {
Self.createHostingView()
}
}
}

private static func createHostingView() -> UIView {
MainActor.assumeIsolated {
let hosting = UIHostingController(rootView: MySwiftUIView())
return hosting.view
}
}
}



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


💡 Вывод:

MainActor.assumeIsolated - это не хаки и не костыли, а официально одобренный Apple способ решать проблемы совместимости в переходный период. Он позволяет сохранить работоспособность старого кода соблюдая строгие правила Swift 6.

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17👍115🤯2🙏1👀1
🔢 Системный меню-бар в iPadOS 26 теперь как в macOS.

Привет! С выходом iPadOS 26 работа с системным меню-баром вышла на совершенно новый уровень. Теперь iPad получил полноценное меню в стиле macOS с разделами File, Edit, View и другими, но что еще важнее: Apple наконец-то дала разработчикам полноценный инструментарий для тонкой настройки этого меню. Давайте разберемся, какие возможности открываются и как это изменит подход к разработке интерфейсов.


От хаков к нативным решениям:

Раньше кастомизация системного меню требовала нестандартных решений и обходных путей. Теперь в iPadOS 26 появился UIMainMenuSystem - специализированный подкласс UIMenuSystem, предназначенный именно для работы с главным меню-баром. Доступ к нему осуществляется через синглтон:

let configuration = UIMainMenuSystem.Configuration()
UIMainMenuSystem.shared.setBuildConfiguration(configuration) { menuBuilder in
// Ваша логика кастомизации
menuBuilder.remove(menu: .file)
}



Точечный контроль над структурой меню:

Ключевое нововведение - возможность модифицировать меню динамически, без необходимости пересобирать всю структуру целиком. Новые методы UIMenuBuilder позволяют:

🔵 Заменять элементы: builder.replace(menu: .file, with: customMenu)
🔵 Вставлять пункты в нужную позицию: builder.insertElements([element], afterMenu: .view)
🔵 Удалять ненужное: builder.remove(action: .duplicate)


Упрощенная настройка через Configuration:

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

let configuration = UIMainMenuSystem.Configuration()
configuration.newScenePreference = .removed
configuration.findingConfiguration.style = .search


Это изящная альтернатива ручному удалению элементов через buildHandler.


Гибкая настройка поиска:

Особенно полезным оказалось findingConfiguration. В зависимости от типа приложения можно выбрать один из четырех стилей:

🔵 .search - единый пункт Search вместо Find и Replace
🔵 .nonEditableText - только поиск без замены
🔵 .editableText - полный набор поиска и замены
🔵 .automatic - система выбирает оптимальный вариант


Практические сценарии применения:

Новые возможности особенно полезны для:

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


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


💡 Вывод:

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
1710👍4🔥11
🔢 Core Data миграции: Как безопасно менять модель данных.

Всем привет! Одна из самых критичных задач в iOS-разработке: изменение структуры базы данных без потери пользовательских данных. Представьте: вы выпускаете обновление приложения, а у пользователей пропадают все сохраненные данные. Это катастрофа! К счастью, Core Data предлагает элегантное решение: легкую миграцию. Сейчас разберем, как это работает.


Что такое легкая миграция и когда она возможна:

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


Что не поддерживается в легкой миграции:

🔵Изменение типа существующего атрибута (например, String в Int).

🔵Сложные трансформации данных.

🔵Изменение иерархии наследования сущностей.


Использование легкой миграции:

Для успешного автоматического обновления модели данных необходимо выполнить три последовательных действия:

🔵Активация механизма легкой миграции.

🔵Создание новой версии модели данных.

🔵Внесение изменений в обновленную модель.


Рассмотрим каждый этап подробно:

Активация механизма миграции:

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

Если же вы работаете с собственной реализацией стека Core Data, потребуется явно указать необходимые опции при подключении хранилища:


let persistentStoreCoordinator = NSPersistentStoreCoordinator(
managedObjectModel: dataModel
)

let migrationOptions: [AnyHashable: Any] = [
NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true
]

do {
try persistentStoreCoordinator.addPersistentStore(
ofType: NSSQLiteStoreType,
configurationName: nil,
at: databaseURL,
options: migrationOptions
)
} catch {
// Обработка неудачной миграции
}


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


Создание новой версии модели:

Для корректной миграции системе необходимо видеть как исходную, так и целевую версию модели данных.

Важное правило: никогда не редактируйте непосредственно существующий файл .xcdatamodeld после публикации приложения. Вместо этого:

🔵В Xcode выберите вашу модель данных.

🔵Перейдите в меню Editor -> Add Model Version.

🔵Система создаст копию модели с новым именем.

🔵В панели File Inspector установите созданную версию как текущую (Current).

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


Редактирование обновленной модели:

Вносите изменения исключительно в новую, только что созданную версию модели. Core Data сможет автоматически построить карту преобразований, если изменения соответствуют определенным шаблонам:

🔵Добавление или удаление сущностей, их атрибутов или связей.

🔵Переименование элементов через указание Renaming Identifier в Data Model inspector.

🔵Изменение обязательности атрибутов (с обязательного на необязательный и наоборот) при наличии значения по умолчанию.

🔵Изменение типа связи (например, с «один-к-одному» на «один-ко-многим»).


Проверка возможности автоматической миграции:


let canMigrateAutomatically = try? NSMappingModel.inferredMappingModel(
forSourceModel: previousModelVersion,
destinationModel: updatedModelVersion
) != nil

if canMigrateAutomatically {
// Легкая миграция возможна
} else {
// Требуется ручная (тяжелая) миграция
}


Если метод возвращает nil, это означает, что Core Data не может самостоятельно сопоставить версии моделей.


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


💡 Вывод:

Легкая миграция Core Data - мощный инструмент, который избавляет разработчиков от ручного написания сложных скриптов миграции. При правильном использовании (создание новых версий модели, а не изменение существующих) система надежно перенесет данные пользователей между версиями приложения.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
17👍10🔥52🙏1👀1
👨‍💻 Конец эпохи клонов: Apple ужесточает правила App Store.

Привет! Мобильная экосистема стоит на пороге важных изменений. Apple недавно внесла существенные правки в App Review Guidelines, которые кардинально меняют правила игры для разработчиков. Речь идет не просто о технических требованиях, а о фундаментальном принципе: оригинальность против плагиата.


Контекст проблемы: эпидемия клонов.

Вспомните ситуацию с релизом Sora 2 от OpenAI. Через несколько дней после официального запуска App Store заполонили десятки клонов с похожими названиями, иконками и описаниями. Пользователи путались, разработчики оригинального приложения теряли аудиторию, а экосистема деградировала.


Что именно изменилось в правилах:

Нововведения фокусируются на трех ключевых аспектах:

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

🔵Ужесточение ответственности за имитацию: создание приложений, которые выдают себя за другие сервисы, теперь рассматривается как нарушение Developer Code of Conduct. В EU это может привести к исключению из программы разработчиков.

🔵Призыв к оригинальности: Apple прямо заявляет: «Придумывайте свои идеи. Мы знаем, они у вас есть.» Компания поощряет уникальные решения, а не косметические изменения популярных приложений.


Технические детали обновлений:

Помимо антиплагиатных правил, Apple внесла и другие важные изменения:

🔵Финансовые приложения: ограничение максимальной процентной ставки 36% годовых.

🔵ИИ-сервисы: обязательное информирование о передаче данных сторонним ИИ.

🔵Возрастные ограничения: механизмы верификации возраста для контента 18+

🔵Криптобиржи: добавлены в список строго регулируемых сервисов.


Как подготовиться к новым правилам:

🔵Аудит текущих проектов: проверьте, нет ли в ваших приложениях заимствований, которые могут нарушить новые правила.

🔵Инвестиции в уникальный дизайн: UX/UI должен отражать вашу уникальную ценность, а не копировать успешные паттерны.

🔵Документирование оригинальности: подготовьте материалы, демонстрирующие уникальность вашего решения.

🔵Мониторинг конкурентов: отслеживайте, не нарушают ли ваши конкуренты новые правила в отношении вашего продукта.


🔗 Ссылка на App Review Guidelines


💡 Вывод:

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

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17👀10🤯5🔥22
🔢 Мост между эпохами: как превратить completion handlers в современный async/await.

Swift Concurrency с async/await стал стандартом современной iOS-разработки, но вокруг нас все еще живут тонны кода, написанного на completion handlers. Вместо болезненного рефакторинга всего легаси можно построить элегантный мост между старым и новым кодом. Сегодня разберем, как превратить старый код с устаревшим подходом в красивые async-функции.


Два разных мира асинхронности:

Представьте ситуацию: вы пишете современное приложение на Swift Concurrency, но вам нужно использовать библиотеку или системный API, который работает через completion handlers:


// Старый код (до async/await)
func fetchUserData(userId: Int, completion: @escaping (Result<User, Error>) -> Void) {
// Некоторая асинхронная операция
NetworkService.shared.request(.user(id: userId)) { result in
completion(result)
}
}

// Как мы хотим использовать это в новом коде:
let user = try await fetchUserData(userId: 123)


Решение - Continuations переводчики между эпохами:

Swift предоставляет специальные функции-континуации, которые становятся мостом между двумя моделями:


// Новый async-интерфейс поверх старого API
func fetchUserData(userId: Int) async throws -> User {
return try await withCheckedThrowingContinuation { continuation in
// Вызываем старый метод с completion handler
fetchUserData(userId: userId) { result in
switch result {
case .success(let user):
// Пробуждаем async функцию с результатом
continuation.resume(returning: user)
case .failure(let error):
// Пробуждаем с ошибкой
continuation.resume(throwing: error)
}
}
}
}



Типы континуаций и когда что использовать:

Swift предлагает четыре варианта, каждый для своего случая:

🔵withCheckedContinuation - для API, которые всегда возвращают результат.

🔵withCheckedThrowingContinuation - для API, которые могут завершиться ошибкой.

🔵withUnsafeContinuation и withUnsafeThrowingContinuation - для оптимизации производительности.


Ключевое правило: вызывать resume только один раз.

Самая частая ошибка при работе с континуациями: множественный вызов resume(). Checked-версии помогут отловить это в рантайме:


// Опасный код (может вызвать краш)
func dangerousWrap() async -> String {
await withCheckedContinuation { continuation in
unreliableAPI { value in
continuation.resume(returning: value)
// Если API вызовет completion дважды — будет ошибка
}
}
}

// Безопасная обертка
func safeWrap() async -> String {
await withCheckedContinuation { continuation in
var hasResumed = false
unreliableAPI { value in
guard !hasResumed else { return }
hasResumed = true
continuation.resume(returning: value)
}
}
}



Производительность: Checked vs Unsafe.

🔵Checked continuations добавляют runtime-проверки на двойной вызов resume() - безопаснее, но чуть медленнее.

🔵Unsafe continuations убирают проверки - быстрее, но требуют абсолютной уверенности в корректности кода.


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


💡 Вывод:

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
15👍8🤝522🔥1
Forwarded from Кот Денисова
👨‍💻 Собеседования в ИТ: почему пора менять устаревший подход к отбору кандидатов.

Всем привет! Сегодня поговорим про собеседования в ИТ - это болезненный и несовершенный процессов. Каждая компания изобретает собственный велосипед, состоящий из хаотичных этапов, непрозрачных критериев и субъективных оценок. В результате кандидаты проходят 5 кругов ада, тратя нервы и время, а компании месяцами не могут закрыть вакансии, теряя сильных специалистов из-за выгорания в процессе найма.

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

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


Устранение дублирования.

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


Задачи, приближенные к реальности.

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


Смещение фокуса на архитектурные навыки.

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


Мое мнение:

Эти изменения не просто «снижение планки». Это признак зрелости рынка. Компании наконец-то поняли, что эффективный найм - это не фильтрация через сито с заведомо мелкими ячейками, а поиск специалистов, релевантных реальным задачам.

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


Что ждет нас дальше?

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

🔹Отказываются от стресс-интервью и лайв-кодинга.
🔹Дают тестовые задания с адекватными сроками выполнения.
🔹Предоставляют конструктивную обратную связь независимо от результата.

Кандидаты становятся более разборчивыми и компании вынуждены подстраиваться, делая процесс найма комфортным и предсказуемым.

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


💡 Вывод:

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


➡️ Кот Денисова
Please open Telegram to view this post
VIEW IN TELEGRAM
👍159🔥3👀2🤔11
🔢 Паттерны проектирования в Swift: Абстрактная фабрика.

Привет! Сегодня разберем паттерн Abstract Factory (Абстрактная фабрика) - мощный инструмент для создания семейств связанных объектов без привязки к конкретным типам.


Зачем это нужно:

Представьте, что вы разрабатываете приложение, которое должно работать с разными темами оформления (светлая/темная) или разными базами данных (SQLite/Realm). Abstract Factory позволяет создавать согласованные наборы объектов, которые гарантированно работают вместе.


Как это работает в Swift:

В отличие от Java/C++, в Swift нет абстрактных классов. Но мы можем использовать протоколы для той же цели.

Пример:

Абстрактные товары:

protocol Coffee {
func brew()
}

protocol Tea {
func steep()
}


Конкретные товары:

// Для итальянской кофейни
struct Espresso: Coffee {
func brew() {
print("Варим эспрессо")
}
}

struct ItalianTea: Tea {
func steep() {
print("Завариваем итальянский травяной чай")
}
}

// Для английской кофейни
struct Cappuccino: Coffee {
func brew() {
print("Готовим капучино")
}
}

struct EnglishTea: Tea {
func steep() {
print("Завариваем английский черный чай")
}
}


Абстрактная фабрика:

protocol CafeFactory {
func makeCoffee() -> Coffee
func makeTea() -> Tea
}


Конкретные фабрики:

struct ItalianCafeFactory: CafeFactory {
func makeCoffee() -> Coffee {
Espresso()
}

func makeTea() -> Tea {
ItalianTea()
}
}

struct EnglishCafeFactory: CafeFactory {
func makeCoffee() -> Coffee {
Cappuccino()
}

func makeTea() -> Tea {
EnglishTea()
}
}


Использование фабрики:

final class CafeOrder {
private let factory: CafeFactory

init(factory: CafeFactory) {
self.factory = factory
}

func prepareCoffeeOrder() {
let coffee = factory.makeCoffee()
coffee.brew()
}

func prepareTeaOrder() {
let tea = factory.makeTea()
tea.steep()
}
}

let italianOrder = CafeOrder(factory: ItalianCafeFactory())
italianOrder.prepareCoffeeOrder()
italianOrder.prepareTeaOrder()

let englishOrder = CafeOrder(factory: EnglishCafeFactory())
englishOrder.prepareCoffeeOrder()
englishOrder.prepareTeaOrder()



Преимущества:

🔹 Изоляция кода от конкретных классов.
🔹 Гарантия согласованности объектов.
🔹 Легкое добавление новых вариаций.
🔹 Упрощение тестирования.


Когда не стоит использовать:

🔹 Если у вас только один вариант продуктов.
🔹 Когда семейства объектов часто меняются.
🔹 В простых приложениях без сложной архитектуры.


💡 Вывод:

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
199👍621
🔢 Митап от Apple: Deep dive в производительность SwiftUI.

Всем привет! Месяц назад Apple провела митап с глубоким разбором производительности SwiftUI. Сегодня обсудим самое важное, что вы могли пропустить.

Суть проблемы: SwiftUI не «тормозит» сам по себе. Проблемы начинаются, когда логика обновлений UI выходит из-под контроля. Каждая лишняя перерисовка View - это работа для системы, которая копится и превращается в лаги.


Что важно знать:

🔵Observable - новая базовая модель для состояния. Забудьте о @StateObject для разделяемых данных. Класс с @Observable дает точный контроль: вью обновится только если она читает конкретное измененное свойство. Это резко снижает количество нежелательных перерисовок.

🔵Замыкания во View - это новые зависимости. Передавая ViewBuilder (замыкание), которое захватывает, например self, вы создаете новую зависимость при каждом обновлении родителя. Это заставляет SwiftUI пересчитывать дочернюю вью, даже если ее внешний вид не изменился. Решение: по возможности передавайте уже готовую вью, а не замыкание для ее построения.

🔵Обработчики скролла, анимаций, жестов - это зона повышенного внимания для оптимизации. Прямое изменение @State или запись в Environment в таких местах запускает каскад проверок и перерисовок по всему дереву вьюх. Вместо этого выносите часто меняющиеся данные в отдельные, минимальные по объему вьюхи или передавайте их через Observable‑классы - это ограничит зону обновления только теми компонентами, которые действительно зависят от этих данных.


🔗 Ссылка на митап


💡 Вывод:

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

Как проверить: используйте Instruments с шаблоном SwiftUI. Ваш главный ориентир не общее время выполнения, а столбец Updates. Если видите, что какая-либо вьюха обновляется десятки раз без визуальных изменений - вы нашли точку для немедленной оптимизации.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍18942🙏1
🔢 Управление временем жизни задач в Swift Concurrency.

Привет! Swift Concurrency предоставляет мощные инструменты для работы с асинхронными операциями, но понимание времени жизни задач - ключевой аспект, который часто упускают из виду. Давайте разберемся, как разные типы задач управляют своим жизненным циклом.


Structured Concurrency: автоматическое управление:

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


Примеры structured задач:

🔵async let в асинхронных функциях.
🔵withTaskGroup для параллельного выполнения.
🔵Модификатор .task в SwiftUI.

struct ProfileView: View {
@State private var user: User?

var body: some View {
VStack {
// Задача автоматически отменится при исчезновении View
.task {
user = await loadUserData()
}
}
}
}


Преимущество structured подхода - предсказуемость. Вы можете быть уверены, что при выходе из области видимости все дочерние задачи будут корректно отменены.


Unstructured задачи: ручное управление:

Когда вы создаете задачу через Task {} вне structured контекста, вы берете на себя ответственность за ее жизненный цикл. Такие задачи выполняются независимо и требуют явной отмены.

class DataLoader {
private var loadingTask: Task<User, Error>?

func loadUser() {
loadingTask = Task {
try await fetchUser()
}
}

func cancelLoading() {
loadingTask?.cancel()
}
}


Важно понимать: вызов cancel() не останавливает задачу мгновенно. Он лишь устанавливает флаг isCancelled, который ваша асинхронная логика должна проверять.


Detached задачи: полная независимость:

Task.detached создает полностью изолированную задачу, которая не наследует контекст родителя, ни приоритета, ни актора, ни состояния отмены. Используйте их осторожно, только когда действительно нужна полная независимость от контекста вызова.


Работа с долгоживущими операциями:

Особого внимания требуют задачи, которые могут выполняться бесконечно долго, например, обработка AsyncStream:

class NotificationService {
private var listenerTask: Task<Void, Never>?

func startListening() {
listenerTask = Task {
for await notification in await notificationStream() {
process(notification)
}
}
}

func stopListening() {
listenerTask?.cancel()
}
}


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

Чтобы полностью остановить поток данных, нужно управлять обеими сторонами:

🔵Задачей, которая читает данные (через cancel()).
🔵Источником данных (через соответствующий метод остановки).


Проверка отмены:

Системные API типа URLSession или Task.sleep автоматически проверяют отмену, но в своем коде вам нужно делать это явно:

func processLargeDataset() async throws {
for item in dataset {
try Task.checkCancellation() // Вызывает CancellationError
// или
if Task.isCancelled { return }

await process(item)
}
}



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


💡 Вывод:

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
17👍136🙏2🔥11
🔢 Embedded Swift в Swift 6.3: выход за пределы iOS в мир микроконтроллеров.

Swift продолжает расширять границы применения. После успешного освоения серверной разработки, Linux и Windows, язык делает стратегический шаг в embedded-разработку. Теперь на Swift можно писать не только приложения для iPhone, но и программы для микроконтроллеров, датчиков и специализированных устройств.


Суть Embedded Swift:

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


Основные отличия от стандартного Swift:

🔵Автоматическое управление памятью (ARC) заменяется преимущественно ручным контролем.

🔵Отсутствуют механизмы рефлексии и динамической диспетчеризации.

🔵Стандартная библиотека сокращена до минимального набора.

🔵Запрещены weak и unowned ссылки из-за их накладных расходов.

🔵Сохранены ключевые возможности: структуры, классы, протоколы, дженерики, перечисления.


Пример:


import EmbeddedHal

// Настройка пинов как в embedded-проектах
let ledPin = DigitalOutputPin(pin: 13)
let sensorPin = AnalogInputPin(pin: 0)

@main
struct DeviceController {
static func main() async {
while true {
// Чтение данных с датчика
let sensorValue = sensorPin.read()

// Логика управления на основе показаний
if sensorValue > threshold {
ledPin.write(true)
await Task.sleep(milliseconds: 500)
ledPin.write(false)
}

await Task.sleep(seconds: 1)
}
}
}



Области применения Embedded Swift:

🔵Образовательные проекты: изучение программирования через работу с реальным оборудованием.

🔵Быстрое прототипирование IoT-устройств.

🔵Специализированные embedded-решения, требующие высокоуровневых абстракций.

🔵Разработка для игровых систем вроде консоли Playdate.


Новые языковые возможности для embedded-разработки:


// Прямая работа с разделами памяти
@section(".__DATA, .sensor_data")
@used
var sensorReadings: [Float] = []

// Упрощённая интеграция с C-кодом
@c(readSensorData)
func readSensorData() -> Float {
// Взаимодействие с аппаратным датчиком
return readFromHardware()
}



Сравнение с традиционными подходами:

Язык C/C++ предлагает максимальный контроль и минимальный размер кода, но требует глубокого знания низкоуровневых деталей. Rust обеспечивает безопасность памяти на уровне компилятора, но имеет крутую кривую обучения. Embedded Swift занимает промежуточную позицию - предоставляет более высокоуровневые абстракции, сохраняя приемлемую производительность и размер бинарного файла.


Значение для iOS-разработчиков:

🔵Улучшенная совместимость с C: атрибут @c делает взаимодействие безопаснее и предсказуемее.

🔵Расширенная диагностика: компилятор теперь лучше обнаруживает проблемы, специфичные для embedded-среды.

🔵Усовершенствованные инструменты отладки: LLDB получил улучшенную поддержку embedded-устройств.

🔵Глубокое понимание оптимизаций: опыт работы с ограниченными ресурсами улучшает общие навыки разработки.


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


💡 Вывод:

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

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
21👍11🔥52👏1🙏1
🔢 MainActorMessage и AsyncMessage: безопасные уведомления в Swift Concurrency.

Всем привет! С выходом iOS 26 работа с уведомлениями в Swift Concurrency кардинально меняется. Старый добрый NotificationCenter, который годами служил верой и правдой, теперь выглядит как отголосок из прошлого - он не понимает модель акторов и постоянно генерирует предупреждения компилятора, но у Apple готово элегантное решение: протоколы MainActorMessage и AsyncMessage. Давайте разберемся, как они работают и почему это настоящий прорыв.


Проблемы старого подхода:

Традиционный NotificationCenter сталкивается с двумя фундаментальными проблемами в Swift Concurrency:

🔵Нарушение изоляции акторов: даже при подписке на главной очереди компилятор видит потенциальную гонку данных при вызове методов, помеченных @MainActor

🔵Отсутствие типобезопасности: постоянные приведения типов через as? создают хрупкий код, где ошибки обнаруживаются только в рантайме


MainActorMessage: гарантия выполнения на главном потоке:


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


// Старый подход
NotificationCenter.default.addObserver(
forName: UIApplication.didBecomeActiveNotification,
object: nil,
queue: .main,
using: { [weak self] _ in
self?.updateUI() // Warning: Call to main actor-isolated method
}
)

// Новый подход
private var observationToken: NotificationCenter.ObservationToken?

// Подписываемся и сохраняем token
observationToken = NotificationCenter.default.addObserver(
of: UIApplication.self,
for: .didBecomeActive
) { [weak self] message in
self?.updateUI() // Без предупреждений
}

// Отмена подписки
deinit {
observationToken?.cancel()
}



AsyncMessage: гибкость для фоновых задач:

Для сценариев, где не требуется главный поток, идеально подходит AsyncMessage. Он доставляет уведомления асинхронно, сохраняя при этом строгую типизацию:


struct DataSyncMessage: NotificationCenter.AsyncMessage {
typealias Subject = SyncResult
let result: Subject
}

// Отправка из любого контекста
let message = DataSyncMessage(result: syncResult)
NotificationCenter.default.post(message)



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


💡 Вывод:

Внедрение MainActorMessage и AsyncMessage - это не просто следование трендам, а повышение качества кода. Мы получаем не только технические преимущества в виде компиляторных проверок и строгой типизации, но и совершенно иной уровень уверенности в своем коде.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
17🔥11👍6🙏21👏1
🔢 Параметризованные тесты: скрытые ловушки при переходе на Swift Testing.

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


Подробнее о проблема:


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


@Test(arguments: UserRole.allCases)
func testAccess(role: UserRole) {
#expect(system.hasAccess(role) == true)
}


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

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

🔵Нет проверки граничных случаев и исключительных ситуаций.

🔵Ошибка в условии доступа повлияет на все тесты одновременно, маскируя корневую причину.


Проблема 2 - зависимость от порядка и структуры данных: использование CaseIterable для автоматической генерации тестовых данных создает хрупкие зависимости:


enum PaymentMethod: CaseIterable {
case card, applePay, googlePay // Порядок имеет значение!
}

@Test(arguments: PaymentMethod.allCases)
func testPaymentProcessing(method: PaymentMethod) {
// Тест зависит от порядка элементов в enum
}


Последствия:

🔵Рефакторинг enum (например, алфавитная сортировка) ломает тесты.

🔵Добавление новых кейсов может пройти незамеченным.

🔵Невозможно использовать ассоциированные значения.


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


@Test(arguments: ProductCategory.allCases)
func testPricing(category: ProductCategory) {
if category == .premium {
#expect(calculatePrice(category) >= 1000)
} else {
#expect(calculatePrice(category) < 1000)
}
}


Что здесь происходит:

🔵Тест начинает дублировать бизнес-логику.

🔵Усложняется понимание, что именно проверяется.

🔵Возрастает вероятность ошибок в самом тесте.


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


💡 Вывод:

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

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


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

Добрый день! У каждого из нас были проекты, которые начинались с энтузиазмом, а через месяц превращались в папку «забытые идеи». Существует подход, который помогает доводить дела до конца, даже самые сложные.


Проблема больших целей.

Когда вы ставите цель «сделать приложение для управления задачами», мозг воспринимает это как одну огромную неподъемную задачу. Гораздо эффективнее разбить ее на этапы, каждый из которых дает видимый результат.


Ключевые принципы:

🔹Декомпозиция на мелкие задачи: вместо «разработать таск-трекер» ставим цель «сделать форму для добавления задач». Вместо «разработать сайт» ставим «купить домен для сайта». Каждая задача должна занимать не больше 1-2 дней.

🔹Первый прототип за неделю: самый важный рубеж - получить работающий прототип как можно быстрее. Даже если он будет кривым и с ограниченной функциональностью.

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

🔹Без перфекционизма: не пытайтесь сделать идеально с первого раза. Сделайте удовлетворимый минимум и двигайтесь дальше.


Почему это работает?

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


💡 Вывод:

Главный враг больших проектов не сложность, а потеря мотивации. Когда вы неделями не видите результата, легко забросить все.

Этот подход стоит применять и в работе и в личных проектах. Он особенно полезен джуниорам, которые еще не научились оценивать объем работ.


➡️ Кот Денисова
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17🔥93🙏1👀1