Мобильный трудоголик – Telegram
Мобильный трудоголик
1.44K subscribers
64 photos
10 videos
289 links
👨‍💻 Пишу простым языком об iOS разработке на Swift и мобильной разработке в целом.
🔹 Вошел в IT задолго до того как это стало мейнстримом.
---
‍Обо мне: https://news.1rj.ru/str/hardworkerIT/3
Чат: @hardworkerChatIT
Канал про разработку и жизнь в ИТ: @itDenisov
Download Telegram
🔢 Эффективная работа с данными в Swift: Sequence и Collection.

Привет! Сегодня поговорим об архитектурных основах Swift, которые формируют фундамент работы с данными в наших приложениях. Речь пойдет о протоколах Sequence и Collection, тех строительных блоках, которые мы используем ежедневно, но чьи глубинные механизмы часто остаются за пределами нашего внимания.

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


Sequence:

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

Когда мы говорим о Sequence, мы имеем в виду контракт: «я могу дать вам итератор, который будет возвращать элементы до тех пор, пока они не закончатся». Именно эта простота делает Sequence идеальным для:

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


Collection:

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

🔵Гарантированную возможность многократной итерации.
🔵Стабильность порядка элементов (где это применимо).
🔵Эффективный доступ по индексам.
🔵Предсказуемую производительность операций.


Механика итерации:

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


for element in collection {
// обработка элемента
}


На самом деле преобразуется в полноценный механизм итерации:


var iterator = collection.makeIterator()
while let element = iterator.next() {
// обработка элемента
}


Такой подход обеспечивает несколько важных преимуществ:

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


Асинхронная революция: AsyncSequence

С приходом Swift Concurrency модель итерации получила развитие в виде AsyncSequence. Этот протокол расширяет знакомые концепции для работы с асинхронными потоками данных:


for try await event in eventStream {
await processEvent(event)
}


AsyncSequence становится особенно важен в контексте:

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


Рекомендации:

Когда выбирать Sequence:

🔵Данные генерируются на лету.
🔵Требуется ленивая загрузка.
🔵Работа с потоками в реальном времени.
🔵Необходим единоразовый доступ к данным.

Когда выбирать Collection:

🔵Необходим многократный доступ к данным.
🔵Важна стабильность порядка.
🔵Требуется эффективный доступ по индексу.
🔵Данные фиксированы или изменяются редко.


Производительность:

Глубокое понимание иерархии протоколов позволяет писать более эффективный код. Компилятор Swift использует знания о соответствии типов этим протоколам для применения агрессивных оптимизаций:

🔵Специализация generic-функций.
🔵Предварительное выделение памяти.
🔵Оптимизация доступа по индексу.
🔵Устранение избыточных проверок.


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


💡 Вывод:

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


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

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


Проблема, которую решает ~Copyable:

Представьте, что у вас есть структура для работы с файлом:


struct FileHandler: ~Copyable {
private var fileDenoscriptor: Int32

init(path: String) {
fileDenoscriptor = open(path, O_RDWR)
}

consuming func close() {
close(fileDenoscriptor)
}
}

// Временный доступ для чтения
func readFromFile(_ handler: borrowing FileHandler) {
let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 1024, alignment: 1)
defer { buffer.deallocate() }

let bytesRead = read(handler.fileDenoscriptor, buffer.baseAddress, 1024)
if bytesRead > 0 {
print("Прочитано \(bytesRead) байт")
}
}

// Передача владения
func takeOwnership(of handler: consuming FileHandler) {
print("Закрываем файл с дескриптором \(handler.fileDenoscriptor)")
handler.close()
}

// Изменение без передачи владения
func modifyFile(_ handler: inout FileHandler) {
let data = "Новые данные".data(using: .utf8)!
_ = data.withUnsafeBytes { buffer in
write(handler.fileDenoscriptor, buffer.baseAddress, buffer.count)
}
print("Данные записаны в файл")
}

let file = FileHandler(path: "data.txt")
readFromFile(file) // borrowing: file остается доступен
modifyFile(&file) // inout: изменяем, но владение не передается
takeOwnership(of: file) // consuming: передача владения, file больше не доступен
// file.close() // Ошибка компиляции! file уже недоступен


Без ~Copyable можно случайно создать копию структуры и получить две независимые переменные, работающие с одним файловым дескриптором. Это может привести к:

🔵Попытке двойного закрытия файла.
🔵Конфликтующим операциям чтения/записи.
🔵Трудноотлавливаемым багам.


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

После добавления ~Copyable компилятор начинает следить за перемещением экземпляров структуры. Теперь нужно явно указывать:

🔵borrow: для временного доступа без передачи владения.
🔵consume: для полной передачи владения.
🔵inout: для временного изменения.


Когда это может быть полезно:

~Copyable особенно пригодится при:

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


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


💡 Вывод:

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
15👍12🔥52🙏1👀1
🔢 Swift Profile Recorder: профилирование Swift-приложений выходит на новый уровень.

Apple продолжает радовать сообщество open-source инициативами, выпустив Swift Profile Recorder - мощный инструмент для профилирования производительности Swift-приложений. Давайте разберемся, что это за инструмент и как он может упростить жизнь разработчикам.


Что такое Swift Profile Recorder:

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


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

🔵Работает в ограниченных средах: не требует root-прав или специальных разрешений.
🔵Кроссплатформенность: поддерживает macOS и Linux.
🔵Простая интеграция: достаточно добавить пакет как зависимость.
🔵Стандартные форматы: поддерживает perf, pprof, flame graphs.
🔵HTTP-интерфейс: сбор данных через простые curl-запросы.


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

Добавление в проект занимает минимум усилий:


// Package.swift
dependencies: [
.package(url: "https://github.com/apple/swift-profile-recorder.git", from: "0.3.0")
]

// В коде приложения
import ProfileRecorderServer

@main
struct MyApp {
func run() async throws {
async let _ = ProfileRecorderServer(
configuration: .parseFromEnvironment()
).runIgnoringFailures(logger: logger)

// Основной код приложения
}
}


Сбор профиля через curl:


curl --unix-socket /tmp/app-samples-12345.sock \
-sd '{"numberOfSamples":1000,"timeInterval":"10ms"}' \
http://localhost/sample > profile.perf



Визуализация результатов:

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

🔵Speedscope - интерактивные flame graphs.
🔵Firefox Profiler - детальный анализ временных линий.
🔵FlameGraph - классические флеймграфы.
🔵Любые инструменты, поддерживающие форматы .perf или .pprof


Отличие от других решений:

В отличие от swift-parca, который использует eBPF и требует специальных привилегий, Swift Profile Recorder работает на уровне приложения. Это делает его идеальным для:

🔵Контейнеризированных сред (Kubernetes, Docker).
🔵Облачных окружений с ограниченными правами.
🔵Быстрого прототипирования и отладки.


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


💡 Вывод:

Swift Profile Recorder - это серьезный шаг в развитии инструментов для Swift-разработки. Он демократизирует доступ к продвинутому профилированию, делая его доступным для проектов любого масштаба.

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🔥115👀21🙏1
🔢 SQLiteData: элегантная альтернатива SwiftData для работы с базой данных.

Всем привет! Сегодня поговорим про библиотеку для работы с данными - SQLiteData, которая сочетает проверенную надежность SQLite с современным Swift-подходом. В основе библиотеки лежит идея предоставить разработчикам привычные инструменты, подобные тем, что есть в SwiftData, но с более предсказуемым поведением.


Модели данных:

Модели данных описываются с помощью структур и перечислений, что делает код интуитивно понятным и соответствующим духу Swift. Это обеспечивает:

🔵Естественную работу с типами данных.
🔵Безопасность на этапе компиляции.
🔵Простоту в сопровождении и расширении.


Работа с запросами:

Для выборки данных предлагаются типобезопасные запросы, которые интегрируются в интерфейс через property wrappers. Важно, что эти обертки работают не только в SwiftUI, но и в любых @Observable моделях, а также в контроллерах UIKit, что обеспечивает универсальность подхода.


Интеграция с CloudKit:

Библиотека включает прямой доступ к возможностям CloudKit, обеспечивая:

🔵Синхронизацию данных между устройствами.
🔵Функцию совместного использования с другими пользователями.

Это избавляет от необходимости самостоятельной интеграции сторонних решений.


Технологическая основа:

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


Пример:


// Модель
@Table
struct Task: Identifiable {
let id: UUID
var noscript: String
var isCompleted: Bool
}

struct TasksView: View {
// Запрос
@FetchAll(Task.where { !$0.isCompleted })
var pendingTasks

var body: some View {
List(pendingTasks) { task in
Text(task.noscript)
}
}
}



🔗 Подробнее по ссылке


💡 Вывод:

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍179🔥42🤔1
Привет! Хочу порекомендовать канал практикующего 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