EasySwift iOS🍏
2.95K subscribers
274 photos
9 videos
443 links
Все самое интересное в мире iOS разработки 🧑🏻‍💻

Предложить статью или новость: @EasySwiftBot

По всем вопросам обращаться к @itereznikov
Download Telegram
Thread-Safe Classes: GCD vs Actors

⚙️ Многопоточность и гонки данных являются распространенными проблемами в программировании, особенно при работе с общими ресурсами, такими как словари.

➡️ Использование конкурентной очереди и синхронных/асинхронных вызовов позволяет создать потокобезопасный кэш, предотвращая гонки данных и обеспечивая целостность состояния.

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

➡️ Акторы обеспечивают безопасность параллелизма, позволяя только одной задаче одновременно получать доступ к изолированному состоянию, что упрощает синхронизацию.

✔️ Сравнение различных подходов к потокобезопасности показывает, что использование акторов является наиболее современным и безопасным методом для работы с многопоточностью в Swift.

Хорошая статья с кучей примеров, рекомендую к прочтению. 👍
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤‍🔥2
Singletons with Swift Concurrency

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

⚠️ Рекомендуется избегать использование синглтонов и вместо этого использовать паттерн корня компоновки или передавать аргументы в функции.

🔍 Аннотация @MainActor позволяет гарантировать потокобезопасный доступ к синглтону, что упрощает аудит и выявление проблем в коде.

➡️ Среди альтернатив синглтонам рассматриваются кастомные актеры, использование блокировок и аннотация @unchecked Sendable, но каждая из этих опций имеет свои недостатки.

😲 А вы знали, что у Apple есть тоже документация по синглтонам?
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Understanding task cancellation and lifetimes in Swift concurrency

ℹ️ В Swift structured concurrency связывает время жизни асинхронной работы с областью, в которой она была создана, автоматически отменяя задачи при завершении этой области.

➡️ Несструктурированные задачи, созданные с помощью Task { ... }, работают независимо и требуют ручной отмены, в то время как Task.detached выполняется полностью независимо от контекста вызова.

➡️ При работе с долгоживущими задачами, такими как AsyncStream, важно управлять временем жизни задачи, чтобы избежать бесконечного выполнения.

⚠️ Отмена задач в Swift не останавливает их немедленно, а устанавливает флаг отмены, который асинхронные операции могут проверять для корректного завершения.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Combine – швейцарский нож iOS-разработчика. Или нет?

ℹ️ Combine — это фреймворк для работы с асинхронными событиями в декларативном стиле, который упрощает управление потоками данных и избавляет от сложностей, связанных с колбэками.

⚙️ Основные компоненты Combine включают Publisher, Subscriber и Subscription, которые взаимодействуют для передачи и обработки данных.

➡️ Combine активно используется в SwiftUI через аннотации @Published и @ObservedObject, что позволяет автоматически обновлять интерфейс при изменении данных.

⚠️ Важно помнить о сохранении подписок, так как их отсутствие может привести к потере данных, а также учитывать, что @Published не сравнивает значения, что может вызвать лишние обновления.

А вы пользуетесь Combine?
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9👎2
A deep dive into Collections, Sequences, and Iterators in Swift

⚙️ Sequence в Swift — это базовая единица итерации, которая требует от типа, чтобы он предоставлял новый итератор каждый раз при вызове makeIterator().

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

ℹ️ Цикл for … in фактически преобразуется в вызов makeIterator() и последующий вызов next() для получения элементов, что позволяет избежать ошибок при изменении коллекции во время итерации.

💡 Swift Concurrency вводит AsyncSequence и AsyncIteratorProtocol, позволяя использовать асинхронные итераторы с поддержкой приостановки и обработки ошибок.

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

struct Countdown: Sequence {
let start: Int

func makeIterator() -> Iterator {
Iterator(current: start)
}

struct Iterator: IteratorProtocol {
var current: Int

mutating func next() -> Int? {
guard current >= 0 else { return nil }
defer { current -= 1 }
return current
}
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Using MainActor.assumeIsolated to Solve Legacy API Compatibility Issues with Swift 6

😨 Многие устаревшие API Apple не адаптированы к строгой проверке параллелизма Swift 6, что создает сложности для разработчиков при компиляции кода.

💡 Метод MainActor.assumeIsolated позволяет безопасно создавать UIHostingController в синхронном контексте, что решает проблемы с компиляцией при использовании устаревших API в контексте примера из статьи.

public static func assumeIsolated<T>(_ operation: @MainActor () throws -> T, file: StaticString = #fileID, line: UInt = #line) rethrows -> T where T : Sendable


Несмотря на попытки добавить аннотации @MainActor, компилятор Swift 6 продолжает выдавать ошибки, если код не соответствует требованиям изоляции.

А вы применяли эту функцию, кроме как при отладке? 🤔
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Safer Swift: How ~Copyable Prevents Hidden Bugs

Протокол ~Copyable в Swift 5.9 предотвращает неявное копирование объектов, требуя явного управления владением данными.

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

✔️ Добавление ~Copyable к структуре предотвращает компиляцию кода, если происходит попытка использовать объект после его передачи.

🖥 Краткая справка по модификаторам: borrow (временный доступ для чтения), consume (полное владение) и inout (временный доступ для изменения).

struct FileHandleWrapper: ~Copyable {
let handle: UnsafeMutablePointer<FILE>

init(path: String, mode: String) {
guard let file = fopen(path, mode) else {
fatalError("Failed to open file")
}
self.handle = file
}

func write(_ text: String) {
fputs(text, handle)
}

deinit {
print("Closing file handle")
fclose(handle)
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Prompting users to review your app

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

✔️ Используйте RequestReviewAction из StoreKit 2, чтобы запрашивать отзывы у пользователей до трех раз в год, избегая прерывания их работы в приложении.

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

➡️ Используйте ReviewPromptManager для отслеживания, когда последний раз запрашивался отзыв, чтобы избежать повторных запросов для одной и той же версии приложения.

private func presentReview() {
guard ReviewPromptManager.shared.canRequestReview() else { return }

Task {
try? await Task.sleep(for: .seconds(2))
await requestReview()
ReviewPromptManager.shared.markReviewRequested()
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍73🔥2
MainActorMessage & AsyncMessage: Concurrency-safe notifications

🆕 Apple представила новые протоколы MainActorMessage и AsyncMessage, которые обеспечивают потокобезопасные уведомления, доступные с iOS и macOS 26+.

📣 Старый способ наблюдения за уведомлениями вызывает предупреждения о потокобезопасности, даже если они обрабатываются в главном потоке, из-за неявной изоляции.

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

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

struct RecentBuildsChangedMessage: NotificationCenter.AsyncMessage {
typealias Subject = [RecentBuild]

let recentBuilds: Subject
}

extension NotificationCenter.MessageIdentifier where Self == NotificationCenter.BaseMessageIdentifier<RecentBuildsChangedMessage> {

static var recentBuildsChanged: NotificationCenter.BaseMessageIdentifier<RecentBuildsChangedMessage> {
.init()
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍32
How to perform a lightweight migration in Core Data

➡️ Легкая миграция в Core Data позволяет автоматически переносить совместимые изменения в модели без потери данных.

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

⚠️ При использовании NSPersistentContainer легкая миграция активируется автоматически. Для ручной настройки используйте параметры NSMigratePersistentStoresAutomaticallyOption и NSInferMappingModelAutomaticallyOption.

ℹ️ Создайте новую версию модели через Xcode, чтобы избежать ошибок несовместимости при изменении существующей модели.

ℹ️Core Data поддерживает изменения, такие как добавление, переименование или удаление сущностей и атрибутов, а также изменение типов отношений.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
GSoC 2025 Showcase: Improved Code completion for Swift

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

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

🔥 Успешно реализовано извлечение полной документации по запросу и значительная часть поддержки помощи по сигнатурам, что улучшает опыт работы в редакторах, таких как VS Code и Neovim.

Признавайтесь, а вы уже пишите под Swift на VS Code или планируете?
Please open Telegram to view this post
VIEW IN TELEGRAM
2
Building Peer-to-Peer Sessions: Sending and Receiving Data with Multipeer Connectivity

Для начала работы с Multipeer Connectivity необходимо создать объект MCPeerID для идентификации устройства и MCSession для установления канала связи между устройствами.

➡️ Данные отправляются с помощью метода send(_:toPeers:with:), где строка сообщения преобразуется в формат Data с кодировкой UTF-8. Обработка ошибок обязательна для успешной передачи.

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

➡️ Метод session(_:didReceive:fromPeer:) используется для обработки полученных данных, которые преобразуются обратно в строку и добавляются в массив полученных сообщений.

Для приглашения другого устройства в сессию используется метод invitePeer(_:using:), который позволяет отправить приглашение на подключение.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Меня снова спросили за Optional

Очередная база 🤩

➡️Опционалы представляют собой перечисление с двумя кейсами: .none и .some(T), что позволяет создавать универсальные контейнеры для различных типов данных.

✔️ Для инициализации опционалов без использования кейсов необходимо подписать их под протоколы ExpressibleByNilLiteral и ExpressibleByIntegerLiteral, что позволяет использовать nil и литералы напрямую.

⚠️ Наиболее популярный способ распаковки опционалов — это оператор nil-coalescing (??), который позволяет подставить значение по умолчанию, если опционал пустой.

ℹ️ Для реализации сравнения опционалов необходимо сначала реализовать протокол Equatable, а затем Comparable, что позволяет использовать операторы равенства и сравнения.

enum MyOptional<T> {
case none
case some(T)
}

extension MyOptional: ExpressibleByIntegerLiteral where T == Int {
init(integerLiteral value: Int) {
self = .some(value)
}
}

extension MyOptional {

static func ?? (optional: MyOptional<T>, defaultValue: @autoclosure () -> T) -> T {
switch optional {
case .none:
return defaultValue()

case let .some(unwrappedValue):
return unwrappedValue
}
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
К какому компоненту отнести тот или иной класс? Мой опыт разделения функциональности между компонентами

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

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

Устойчивость компонента можно оценить с помощью метрики I, которая рассчитывается как Fan-out ÷ (Fan-in + Fan-out), где значение 0 указывает на максимальную устойчивость.

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

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

На начальных этапах разработки важнее удобство сопровождения, тогда как на более зрелых стадиях акцент смещается на удобство повторного использования компонентов.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍32
Improving the approachability of data-race safety

🖥 Документ из Swift Evolution описывает видение по повышению удобства Swift 6 concurrency, фокусируясь на устранении ложных ошибок data-race safety в последовательном коде и упрощении миграции. Основные цели — сохранить безопасность памяти, сделать базовый concurrency простым и продвинутое — естественным, с тремя этапами: последовательный код, async без параллелизма, затем parallelism для производительности.

Модель Swift 6 предполагает конкурентность по умолчанию, что приводит к ложным срабатываниям ошибок в коде, который на самом деле является однопоточным.

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

➡️ Предлагаются инструменты для упрощения перехода существующих кодовых баз на новые функции конкурентности Swift, включая автоматическую миграцию.

➡️ Вводится концепция изолированных соответствий, позволяющая типам, изолированным к глобальному актеру, реализовывать протоколы без конфликтов с изоляцией.

ℹ️ Хоть документ и не новый, рекомендую с ним ознакомиться - сможете лучше понимать куда в целом движется развитие языка.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Bridging completion handlers to Swift's async/await

🔍 Если хотели провести миграцию с completion handlers на async/await - загляните в статью. Вот пару советов из нее.

✔️ Функция withCheckedContinuation позволяет создать асинхронную функцию, которая обрабатывает результат из API с обработчиком завершения, обеспечивая безопасное возобновление выполнения.

⚠️ Для API, которые могут возвращать ошибки, можно использовать withCheckedThrowingContinuation, что позволяет обрабатывать ошибки с помощью синтаксиса try await.

🛡 withCheckedContinuation и withCheckedThrowingContinuation обеспечивают проверку на наличие множественных вызовов resume, в то время как withUnsafeContinuation и withUnsafeThrowingContinuation не выполняют таких проверок, что делает их менее безопасными.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤‍🔥4👍21
Навигация на SwiftUI: чего не хватает и как исправить

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

⚙️ Для масштабных проектов необходима поддержка различных навигационных компонентов, таких как Bottom Sheet, алерты и диплинки, что требует продуманной архитектуры.

➡️ Использование паттерна Coordinator позволяет разделить логику навигации и отображения, что упрощает поддержку и тестирование приложений.

🖥 Кастомные алерты в SwiftUI требуют сложной логики для отображения и управления, что может привести к ошибкам и усложнению кода.

➡️ Реализация динамических шторок в SwiftUI до iOS 16 была сложной, и для этого часто использовались UIKit-библиотеки, что увеличивало сложность навигации.

➡️ Для сложных приложений с диплинками и кастомными алертами рекомендуется использовать UIKit, в то время как для менее сложных задач можно рассмотреть FlowStacks или NavigationStack.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Секреты на клиенте: как снизить вероятность утечки с нуля до почти нуля

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

📱 Apple предлагает несколько встроенных механизмов защиты, таких как Sandboxing, Code Signing и ASLR, которые помогают изолировать приложения и защищать их от атак.

⚠️ Существует несколько категорий угроз, включая подмену (Spoofing), манипуляцию (Tampering) и утечку информации (Information Disclosure), которые могут угрожать безопасности мобильных приложений.

🔴 Злоумышленники используют инструменты статического и динамического анализа, такие как Hopper и Frida, для декомпиляции приложений и извлечения секретов.

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

⚠️ Типичная атака включает джейлбрейк устройства, доступ к .ipa файлу, декодирование бинарного файла и анализ кода для извлечения секретов.

✔️ Рекомендую к прочтению, как минимум для понимания общих принципов.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤‍🔥3
Core Transferable

Core Transferable предлагает современный подход к доступности типов данных для операций передачи и обмена, включая функции, такие как кнопка Поделиться и перетаскивание. Этот фреймворк доступен на iOS 16.0+, iPadOS 16.0+, macOS 13.0+, tvOS 16.0+, visionOS 1.0+ и watchOS 9.0+

🖥 Протокол Transferable позволяет вашим моделям взаимодействовать с API передачи данных, такими как перетаскивание и копирование, обеспечивая совместимость с системными фреймворками, такими как SwiftUI.

ℹ️ Core Transferable включает несколько встроенных типов представления, таких как CodableRepresentation и FileRepresentation, которые помогают в импорте и экспорте данных.

ℹ️ Пример модели Note демонстрирует, как расширить тип для соответствия протоколу Transferable, включая реализацию различных представлений для передачи данных.

struct Note: Codable {
var text: String
var url: URL


init(url: URL) {
self.url = url
self.text = ""
}
}


extension Note: Transferable {
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .note)
ProxyRepresentation(exporting: \.text)
FileRepresentation(
contentType: .utf8PlainText,
exporting: { note in SentTransferredFile(note.url) },
importing: { received in
let destination = URL(fileURLWithPath: <# ... #>)
try FileManager.default.copyItem(at: received.file, to: destination)
return Self.init(url: destination) })
}
}


extension UTType {
static var note = UTType(exportedAs: "com.example.note")
}
Please open Telegram to view this post
VIEW IN TELEGRAM
❤‍🔥1
Monitoring app performance with MetricKit

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

✔️ Для получения данных о производительности необходимо подписаться на MXMetricManager и обрабатывать MXMetricPayload и MXDiagnosticPayload.

ℹ️ Используйте функции logEvent и logCrash для записи событий и сбоев, что позволяет лучше понять причины проблем с приложением.

💡 Данные, собранные MetricKit, можно легко экспортировать в формате JSON для дальнейшего анализа на сервере.

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

final class AppDelegate: NSObject, UIApplicationDelegate, MXMetricManagerSubscriber {
private var analytics: Analytics?

func applicationDidFinishLaunching(_ application: UIApplication) {
MXMetricManager.shared.add(self)
}

nonisolated func didReceive(_ payloads: [MXMetricPayload]) {
for payload in payloads {
if let exitMetrics = payload.applicationExitMetrics?.backgroundExitData {
analytics?.logEvent(
"performance_abnormal_exit",
value: exitMetrics.cumulativeAbnormalExitCount.formatted()
)

analytics?.logEvent(
"performance_cpu_exit",
value: exitMetrics.cumulativeCPUResourceLimitExitCount.formatted()
)

analytics?.logEvent(
"performance_memory_exit",
value: exitMetrics.cumulativeMemoryPressureExitCount.formatted()
)

analytics?.logEvent(
"performance_oom_exit",
value: exitMetrics.cumulativeMemoryResourceLimitExitCount.formatted()
)
}
}
}

nonisolated func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
if let crashes = payload.crashDiagnostics {
for crash in crashes {
analytics?.logCrash(crash)
}
}
}
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
This media is not supported in your browser
VIEW IN TELEGRAM
Маленький экран — серьёзный вызов!
В VK мобильные разработчики создают опыт, который помещается в карман, но работает на миллионах устройств. Узнайте об их подходах к сложным задачам и ключевых результатах. По ссылке — ролики и даже вакансии!