EasySwift iOS🍏
3.03K subscribers
269 photos
8 videos
394 links
Все самое интересное в мире iOS разработки 🧑🏻‍💻

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

По всем вопросам обращаться к @itereznikov
Download Telegram
How to inspect .ipa files and secure your iOS app from common mistakes

ℹ️ Интересная статья для тех, кто не знал, что можно достать путем анализа .ipa файла приложения.

🤫 Автор выделяет три основных правила для обеспечения базовой безопасности информации.

➡️ Правило 1: Защита данных

Не добавляйте чувствительные данные в Info.plist, так как этот файл не зашифрован и доступен для чтения.

➡️ Правило 2: Избегайте тестовых данных

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

➡️ Правило 3: Защита строковых литералов

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

🔍 Для более глубокого анализа используйте специальные инструменты, такие как IDA pro или Hopper, которые позволяют дизасемблировать приложение и изучить внутренние структуры и не только.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Swift Reduce: Combining elements into a single value

💡 Метод reduce в Swift позволяет объединять элементы коллекции в одно значение, что делает его полезным для преобразования массивов в словари или суммирования чисел.

⚠️ Метод reduce имеет сложность O(n) и может использоваться с вариантом reduce(into:), который более эффективен, избегая копирования аккумулятора на каждой итерации.

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

let animals = ["Dog", "Cat", "Dog", "Bird"]
let counts = animals.reduce(into: [:]) { result, animal in
result[animal, default: 0] += 1
}
// counts == ["Dog": 2, "Cat": 1, "Bird": 1]
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Modern URL construction in Swift

➡️ Swift 5.9 представил макросы, которые позволяют компилировать и валидировать статические URL-строки, улучшая безопасность кода.

➡️ С введением новых API в iOS 16, можно легко создавать динамические URL без опциональных значений, используя методы appending.

✏️ Современные API для создания URL упрощают код, уменьшают вероятность ошибок и позволяют работать с URL более структурированно.

@freestanding(expression)
public macro staticURL(_ value: StaticString) -> URL = #externalMacro(
module: "StaticURLMacros",
type: "StaticURLMacro"
)

public struct StaticURLMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
// Verify that a string literal was passed, and extract
// the first segment. We can be sure that only one
// segment exists, since we're only accepting static
// strings (which cannot have any dynamic components):
guard let argument = node.arguments.first?.expression,
let literal = argument.as(StringLiteralExprSyntax.self),
case .stringSegment(let segment) = literal.segments.first
else {
throw StaticURLMacroError.notAStringLiteral
}

// Verify that the passed string is indeed a valid URL:
guard URL(string: segment.content.text) != nil else {
throw StaticURLMacroError.invalidURL
}

// Generate the code required to construct a URL value
// for the passed string at runtime:
return "Foundation.URL(string: \(argument))!"
}
}

let url = #staticURL("https://swiftbysundell.com")
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
libdispatch efficiency tips

👀 Нашел тут довольно старую, но актуальную подборку советов по работе с GCD. Вот некоторые из них:

➡️ Создавайте несколько долгоживущих и четко определенных очередей, чтобы использовать их как контексты выполнения в вашем приложении. Обычно достаточно 3-4 очередей.

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

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

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

➡️ Проводите реальные тесты производительности вашего продукта, чтобы убедиться, что изменения действительно улучшают скорость, а не ухудшают.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
This media is not supported in your browser
VIEW IN TELEGRAM
Пятничный Vibe-coding

👀 Решил попробовать этот ваш вайб кодинг

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

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

Но это намного лучше и быстрее, чем писать все изначально с 0 🔥

P.S. Я использовал Cursor и плагин для фигмы
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥3
Building Type‑Safe, High‑Performance SwiftData / Core Data Models

🔍 В статье приводятся советы, как сделать модели для CoreData/SwiftData типо-безопасными и эффективными.

Вот некоторые моменты:

➡️ В отличие от SwiftData, CoreData требует использования NSNumber для опциональных числовых свойств, что нарушает стиль Swift. Решение включает использование вычисляемых свойств для обеспечения безопасности типов.

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

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

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

extension DataContent {
// Public array interface
public var optionSelections: [Int] {
get { optionIDsNumber.toArray() }
set { optionIDsNumber = optionIDsToInt64(optionIDs: newValue) }
}

// Stored as a bitmask
private var optionIDsNumber: Int64
}

func optionIDsToInt64(optionIDs: [Int]) -> Int64 {
var result: Int64 = 0
for id in optionIDs where id >= 0 && id <= 63 {
// Set the bit corresponding to each ID
result |= (1 << id)
}
return result
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍32
How a Single Line Of Code Could Brick Your iPhone

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

🔔 Уязвимость в Darwin notifications позволяет любому процессу на iOS отправлять уведомления без специальных привилегий.

Вот эта строка:

notify_post("com.apple.MobileSync.BackupAgent.RestoreStarted")


+ виджет и fatalError() окирпичивали телефон. 🆒

🗓 Первоначальный отчет о проблеме был отправлен в Apple 26 июня 2024 года, а уязвимость была исправлена в iOS 18.3 с присвоением CVE-2025-24091.

💵 За это автор получил вознаграждение в размере 17.5 к $.

✔️ Теперь для отправки чувствительных уведомлений Darwin требуется наличие ограниченных прав, что предотвращает несанкционированные действия со стороны приложений.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍1
Swift Error Handling Done Right: Overcoming the Objective-C Error Legacy

👀 Описываешь ошибки в коде, которые конформят Error протокол, и ожидаешь получить внятное объяснение при выбрасывании, но вместо этого:
"The operation couldn't be completed. (YourApp.YourError error 0.)"

enum NetworkError: Error {
case noConnectionToServer
case parsingFailed

var localizedDescription: String {
switch self {
case .noConnectionToServer:
return "No connection to the server."
case .parsingFailed:
return "Data parsing failed."
}
}
}

// Using the error
do {
throw NetworkError.noConnectionToServer
} catch {
print("Error message: \(error.localizedDescription)")
// Expected: "No connection to the server."
// Actual: "The operation couldn't be completed. (AppName.NetworkError error 0.)"
}


Ну было, да? 🤦‍♂️

Если ответ положительный, то вам нужно прочитать эту статью.

Коротко:

✔️ Swift предлагает использовать протокол LocalizedError для обработки ошибок, но он имеет недостатки, такие как необязательные свойства и неясные названия.

💡 Автор предлагает свой протокол Throwable, который требует единственного обязательного свойства userFriendlyMessage, что упрощает создание понятных сообщений об ошибках.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍42
Руководство по использованию unsafe в Swift

ℹ️ В Swift существуют различные способы работы с небезопасными операциями, включая прямое использование указателей и встроенные обёртки, такие как UnsafePointer и UnsafeMutablePointer.

⚠️ Использование небезопасных механизмов оправдано при взаимодействии с C API, для оптимизации производительности, низкоуровневого программирования и работы с Objective-C.

⚙️ Важно минимизировать область использования unsafe, применять конструкции withUnsafe для безопасного доступа к указателям и тщательно документировать код для предотвращения утечек памяти.

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

import Foundation

let count = 4
// Выделяем память для 4 байтов
let rawPointer = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: MemoryLayout<UInt8>.alignment)

// Инициализируем память
for i in 0..<count {
rawPointer.storeBytes(of: UInt8(i), toByteOffset: i, as: UInt8.self)
}

// Чтение данных через UnsafeRawPointer
let immutableRawPointer = UnsafeRawPointer(rawPointer)
for i in 0..<count {
let byte = immutableRawPointer.load(fromByteOffset: i, as: UInt8.self)
print("Байт \(i): \(byte)")
}

// Освобождаем память
rawPointer.deallocate()
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Bridging interfaces with the Adapter pattern in Swift

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

✏️ В примере показано, как адаптировать интерфейс стороннего SDK с использованием протокола SearchService, чтобы обеспечить совместимость с существующим кодом.

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

protocol SearchService {
func search(query: String) async throws -> [SearchResult]
}

class ThirdPartySearch {
func runSearch(term: String, completion: @escaping ([Any]) -> Void) {
// ...
}
}


class ThirdPartySearchAdapter: SearchService {
private let thirdPartySearch = ThirdPartySearch()

func search(query: String) async throws -> [SearchResult] {
try await withCheckedThrowingContinuation { continuation in
thirdPartySearch.runSearch(term: query) { rawResults in
let results = rawResults.compactMap { $0 as? SearchResult }
continuation.resume(returning: results)
}
}
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Custom subscripts in Swift explained with code examples

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

⚙️ Custom subscripts
можно определить, как методы, и они могут принимать несколько параметров, что позволяет улучшить читаемость кода.

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

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

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

extension ImageCache {
subscript(url: URL) -> UIImage? {
get {
imageStore[url]
}
set {
imageStore[url] = newValue
}
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4👎1
Regular Expressions in Swift

⚙️ В Swift регулярные выражения реализованы через класс NSRegularExpression из Foundation, который позволяет создавать шаблоны и искать совпадения в строках. Для удобства можно расширить этот класс, чтобы упростить создание и проверку выражений, например, добавить метод matches(), который проверяет наличие совпадений в строке.

⚠️ Однако стоит помнить, что чрезмерное использование регулярных выражений в больших текстах может негативно сказаться на производительности приложения. С выходом Swift 5.7 появился более современный и удобный инструмент - Swift Regex с лаконичным синтаксисом и билдерами регулярных выражений, который значительно упрощает работу и повышает читаемость кода по сравнению с NSRegularExpression

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

// matching a url
let link = Reference(URL.self)

let linkRB = Regex {Capture (as:link){.url()}}
Please open Telegram to view this post
VIEW IN TELEGRAM
5
From 180 cm to 5′ 11″: A Complete Guide to Swift Measurement

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

✏️ API Measurement в Swift позволяет безопасно и удобно работать с единицами измерения, обеспечивая автоматическую конвертацию и форматирование значений.

➡️ Можно выполнять математические операции с объектами Measurement, если они принадлежат одной категории единиц, что упрощает работу с данными.

➡️ Метод formatted() позволяет легко преобразовывать Measurement в удобочитаемый текст с учетом локализации, что делает его полезным для международных приложений.

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

let heightMeasurement = Measurement<UnitLength>(value: 180, unit: .centimeters)

// Division with a scalar
let half = heightMeasurement / 2
print(half.value) // 90.0
print(half) // "90.0 cm"

// Addition: different units can be added; the result is in the category’s base unit
let h180cm = Measurement<UnitLength>(value: 180, unit: .centimeters)
let h1m = Measurement<UnitLength>(value: 1, unit: .meters)
let totalHeight = h180cm + h1m // 2.8 m (base unit of UnitLength is metres)
print(totalHeight) // "2.8 m"

// Comparison with automatic unit handling
h180cm < h1m // false (180 cm ≮ 1 m)

// Ranges respect units too
let range = h1m ... h180cm
range.contains(Measurement(value: 6, unit: .feet)) // false (~1.83 m is outside)
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10
Изоляция с помощью глобальных акторов в Swift Concurrency: варианты на примере @MainActor

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

⚠️ Ошибки, такие как разделение соответствия протоколу и реализации (например, через расширение), могут привести к потере ожидаемой изоляции, несмотря на наличие аннотаций @MainActor.

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

⚙️ Выбор подхода к изоляции зависит от контекста: для взаимодействия с UI лучше использовать полную изоляцию, а для повышения производительности — частичную.

@MainActor
protocol IUpdate {
func update(date: Date) async
}

@Observable
final class LS {
var date: Date = .now
}

// Здесь изоляцию @MainActor получит только update

extension LS: IUpdate {
func update(date: Date) async {
await internalUpdate(date: date)
}
// А здесь изоляция уже не применится

private func internalUpdate(date: Date) async {
self.date = date
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Обсуждем кейсы на митапе Яндекса по мобильной разработке

Я.Субботник — большой митап для мобильных разработчиков. В этот раз кроме докладов участников ждёт практический разрбор кейсов на PeerLab.

PeerLab — камерная встреча с экспертами Яндекса. Для неё мы отобрали актуальные темы из разработки и карьеры. Предложить кейс для обсуждения может каждый участник — приносите их в форму регистрации и приходите на обсуждение!

В Москве точно обсудим:

🔸Kotlin Multiplatform
🔸Карьерное развитие
🔸Платформенные команды
🔸AI в разработке

А в Питере:

T-Shape разработчик
🔸Тестирование
🔸AI в разработке

➡️ Регистрируйтесь и ищите список экспертов-участников дискуссии на сайте
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2👎1
Protecting mutable state with Mutex in Swift

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

➡️ Акторы в Swift удобны для асинхронного кода, но могут усложнить код, тогда как Mutex подходит для быстрого синхронного доступа к данным.

ℹ️ Пример класса Counter показывает, как использовать Mutex для безопасного увеличения и уменьшения счетчика, обеспечивая защиту от гонок данных.

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

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

class Counter {
private let mutex = Mutex(0)

func increment() {
mutex.withLock { count in
count += 1
}
}

func decrement() {
mutex.withLock { count in
count -= 1
}
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
When the Swift Debugger Lies: The Hidden Cost of Compiler Optimizations

👀 Интересный кейс произошел у автора при отладке приложения: перед выходом из функции массив оказался пустой.

func getUserListForAdmin() async -> NetworkResponse<[OtherUser]> {
async let type4Request = RestClient.shared.request(Constants.Network.ENDPOINT_USERS, parameters: ["userType": 4])
async let type2Request = RestClient.shared.request(Constants.Network.ENDPOINT_USERS, parameters: ["userType": 2])

let (result4, result2) = await (type4Request, type2Request)

switch (result4, result2) {
case (.success(let users4), .success(let users2)):
for user in users4 {
user.canSeeThisUsersRequests = users2.contains(where: { $0.id == user.id })
}
return .success(users4) // At this breakpoint, users2 appears EMPTY 🤯
case (.error(let error), _), (_, .error(let error)):
return .error(error)
}
}


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

💡 Чтобы сохранить переменные, такие как users2, используйте withExtendedLifetime(users2) или извлеките их из switch-выражения заранее, чтобы избежать их удаления.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Exploring concurrency changes in Swift 6.2

ℹ️ Swift 6.2 вводит два основных изменения в параллельности: новый флаг по умолчанию nonisolated(nonsending) и выполнение кода на главном акторе по умолчанию с помощью настройки defaultIsolation.

⚠️ Флаг nonisolated(nonsending) позволяет асинхронным функциям наследовать контекст актора вызывающего кода, что уменьшает количество ошибок компиляции и упрощает работу с параллельностью.

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

❗️ Рекомендуется включить оба новых флага для упрощения работы с параллельностью, однако важно избегать избыточного использования @concurrent, чтобы не усложнять код.

actor SomeGenerator {
// not allowed
@concurrent
func randomID() async throws -> UUID {
return UUID()
}

// allowed
@concurrent
nonisolated func randomID() async throws -> UUID {
return UUID()
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
3👍2
Swift Enums vs Structs - Picking the Best Tool for the Job

Очередная статья про выбор между Enum и структурами (классы остались за бортом 🫠)

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

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

⚙️ Можно комбинировать Enums и Structs для достижения наилучших результатов, используя каждый инструмент в зависимости от конкретной ситуации.

💡 Выбирайте Enums для управления состоянием с фиксированным набором опций и Structs для хранения сложных состояний с несколькими свойствами.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3