YDC — Pizza Powered iOS – Telegram
YDC — Pizza Powered iOS
242 subscribers
67 photos
97 links
Young Da Code 👨‍💻
Первый командный дайджест о мобильной разработке 🍕
Download Telegram
☝️ В модульных iOS-приложениях важно понимать как именно линкуются модули.

Существует 3 типа линковки:
• Static
• Dynamic
• Mergeable

Почему это важно? Тип линковки напрямую влияет на:
• модульность и управляемость архитектуры,
• размер приложения,
• скорость запуска.

Хороший разбор с примерами — тут.

Отдельно отмечу mergeable libraries. Tuist прямо говорит о cost of convenience,
и я с этим полностью согласен:
➡️ mergeable почти всегда сложнее организовать,
➡️ сложнее явно контролировать и дебажить, чем осознанная и прозрачная логика линковки.

Подход который использую я, и который сквозь время доказал свою пригодность и масштабируемость (под бинарные кеши всякие т.д. не страдая качеством):
staticForDeploy — все библиотеки статические, кроме тех, что шарятся с экстеншонами
dynamicForDev — всё динамика для комфортной разработки

Вся логика механизма зашита в Tuist.ProjectDenoscriptionHelpers, дополняется оверрайдами в CocoaPods и SPM.

Итог: быстрее старт приложения в проде, удобство разработки и контролируемая архитектура без магии.
P.S.: Если интересно, чуть больше года назад рассказывал об этом подробнее.

#L #Linkage #iOS

👏
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7
😄 Когда хочется и новый стек трогать и легаси сильно не шатать

Если коротко, то тут статья, а тут макрос, который сгенерирует async/await обертки над старыми callback-based функциями.

Если чуть подробнее, то главная мысль не переписывать всё сразу, а создать мост между старым API и новым с помощью continuations и Swift-макросов, которые генерируют обёртки автоматически.

Если в вашем проекте сетевой слой и другие корные модули написаны через completion handlers, то полный рефакторинг повлечёт большой объём изменений, нагрузку на тестирование и риски для стабильности.

Используя bridge-подход можно включить async/await, сохранив обратную совместимость, поддержать постепенную миграцию и при этом не тормозить развитие продуктовых фич.


func info(
for id: String,
completion: @escaping (NetworkResponse<Response, BaseErrorResponse>) -> Void
) {
let request = Action.getInfo(id: id)
guard var url = pathProvider.createURL(type: request) else {
completion(.failure(.urlNotFound))
return
}

let endPoint = RequestEndpoint(
url: url,
parameters: request.parameters
)

networkClient.request(endpoint: endPoint, responseHandler: completion)
}


Добавляя withCheckedContinuation мы создаем мост между кодом, основанным на коллбэках, и современным миром асинхронности.

Параметр continuation это наша связь с асинхронным контекстом.

Для предоставления результата continuation.resume() нужно вызвать ровно один раз.

Можно вернуть значение через resume(returning:) или ошибки через resume(throwing:), а также resume(with result: sending Result<T,E>) если вы используется тип Swift Result.


func info(
for id: String
) async -> NetworkResponse<Response, BaseErrorResponse> {
return await withCheckedContinuation { continuation in
info(for: id) { result in
continuation.resume(returning: result)
}
}
}


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

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

Плюсы:

↗️ Минимальные изменения в существующих модулях и обратная совместимость.

↗️ Пошаговая миграция: можно переводить отдельные вызовы/фичи.

↗️ Автоматизация через макросы уменьшает рутину и шанс ошибок.

↗️ Команда может продолжать продуктовую разработку параллельно с миграцией.

Минусы?

↘️ async/await обычно используют с throws, что даёт лаконичную обработку do/try/catch. Если старый нетворкинг возвращает Result<Response, Error>, то простая обёртка async -> Result не даёт преимуществ throws, клиенты всё равно будут делать switch/case или разбирать результат вручную.

↘️ resume нужно вызвать ровно один раз. Смешанная логика (ошибка сети + таймаут + cancellation + повтор попытки) повышает риск двойного resume или забытого resume, а это либо краш, либо подвисший await.

↘️ сам withCheckedContinuation имеет небольшой накладной расход и массовое применение без профилирования может дать неожиданный overhead. UnsafeContinuation быстрее, но теряет проверки.

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

📎 Modern Concurrency and Legacy code

#D #Arch #Legacy #Macros

👏
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3💯1
🦾 Новый open-source навык для SwiftUI и AI-агентов

Если вы уже используете AI-агентов для генерации SwiftUI, то наверняка сталкивались с тем, что код получался компилируемым, но не вполне «идентичным» SwiftUI — неэффективные паттерны, странные навигации или обновления состояний. Многие инженеры отмечают, что AI-помощники часто не понимают тонкостей SwiftUI и генерируют почти SwiftUI, который приходится вручную править и рефакторить.

В новой статье от Antoine van der Lee анонсирован SwiftUI Agent Skill open-source навык для AI-агентов, который помогает писать, улучшать и рефакторить SwiftUI-представления по лучшим практикам Apple.

🔍 Что такое Agent Skills?
Это пакеты с подробными инструкциями и контекстом по предметной области (в данном случае SwiftUI), которые AI-агент может «подгружать» и использовать при генерации/анализе кода, вместо того чтобы полагаться только на базовую модель. Такая система снижает количество повторяющихся ошибок и делает выводы агентов более осмысленными.

📌 В Skill есть подробные референсы по:
• оптимизации изображений и layout-паттернам;
• правильной композиции view;
• управлению состоянием и производительности;
• современным API и паттернам навигации.

💡 Это отличный шаг к тому, чтобы AI-агент не просто генерировал SwiftUI, а генерировал его правильно — с учётом архитектурных рекомендаций и практик сообщества.

#L #AI #SwiftUI

👏
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
😏 Хочу разобрать статью про переход на Observation в SwiftUI и напомнить, зачем вообще появился этот механизм и чем он отличается от привычного ObservableObject.

Тема не новая, но на практике до неё доходят не сразу, особенно если в проекте уже есть устоявшийся подход к работе с состоянием.

🔹 Начиная с iOS 17 в SwiftUI доступен фреймворк Observation, который позволяет отслеживать изменения состояния без использования ObservableObject, @Published и Combine.

Основная идея – упростить работу с состоянием и сократить шаблонный код.

🔹 Классический подход с ObservableObject работает, но имеет свои особенности:
• необходимость явно помечать свойства как @Published
• дополнительный синтаксический шум
• риск забыть опубликовать изменения
• привязка к Combine даже в простых сценариях

Observation предлагает более прямой способ описывать наблюдаемое состояние.

🔹 На уровне кода вместо ObservableObject используется атрибут @Observable.

Старый подход:
class Counter: ObservableObject {
@Published var value = 0
}


С Observation:
@Observable
class Counter {
var value = 0
}


🔹 Преимущества такого подхода:
• Нет необходимости писать @Published, objectWillChange и вспомогательные конструкции.
• Меньше риска ошибок, связанных с ручным управлением публикацией изменений.
• Логика наблюдения находится ближе к самой модели.

Пример использования:
@Observable
class TimerModel {
var time: Int = 0

func tick() {
time += 1
}
}


Использование во View:
struct TimerView: View {
@State private var model = TimerModel()

var body: some View {
Text("Time: \(model.time)")
}
}


SwiftUI отслеживает изменения time и обновляет View без дополнительной настройки.

🔹 Где Observation особенно уместен:
• простые ViewModel и модели состояния
• логика, не требующая Combine
• случаи, где ObservableObject использовался только ради обновления UI

Следующие механизмы продолжают работать как раньше:
@State
@Binding
@EnvironmentObject

Observation не заменяет их, а дополняет.

🍕 Observation упрощает работу с состоянием в SwiftUI за счёт уменьшения шаблонного кода и более прямого описания наблюдаемых моделей. Для новых экранов и при постепенном рефакторинге старых ObservableObject это часто более удобный и читаемый вариант.

#R #SwiftUI #Observation
Please open Telegram to view this post
VIEW IN TELEGRAM
32🔥211
🦾 Автоматика подъехала: Опыт Tuist-а, где Codex мигрирует проект на кодген и поддерживает кеширование.
+ Tuist превращает все это дело в skill 🛠️

📦 Миграция Mastodon iOS
🎮 Первый skill — migrate

#L #Tuist #AI

👏
Please open Telegram to view this post
VIEW IN TELEGRAM
84🔥1
🤖 📡 Базовые протоколы клиент-серверного взаимодействия (без обратной связи)

Хочется сделать серию постов по основам CS и system-design.
Если будет отклик, масштабируем в mind-map.
И начать хочется с сетевого взаимодействия.

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

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

📌 Немного про транспорт:
- SOAP — почти всегда поверх HTTP/HTTPS
- REST — по определению строится поверх HTTP
- GraphQL — чаще всего поверх HTTP, подписки через WebSocket (но об этом можно глубже, в другом посте)
- RPC — не обязан использовать HTTP

👉 Поэтому корректнее говорить не «HTTP-протоколы», а API-стили / протоколы поверх разных транспортов.

Небольшая затравка из практики:
В бородатых годах я использовал Apache Thrift на Objective-C
— тогда для меня это был "магический" опыт с RPC:
отличный кодген, строгие контракты, минимум ручной работы.

Это был классический пример RPC-подхода, задолго до хайпа gRPC, и он отлично показывал разницу между:
- «я работаю с ресурсами» (REST)
- и «я вызываю удалённые методы» (RPC)

Детальнее про протоколы:
🔹 SOAP (Simple Object Access Protocol)
Что это: Строгий протокол обмена сообщениями, основанный на XML и формальных контрактах (WSDL).
Транспорт: почти всегда HTTP
Когда применять:
- корпоративные и интеграционные системы
- среды с жёсткими требованиями к контрактам, безопасности и совместимости
Почему:
SOAP — это максимум формализма: строгая схема, строгие правила, минимум свободы.
Цена — высокая сложность и избыточность.
📍 SOAP — это «корпоративный стандарт», а не инструмент скорости.

🔹 REST (Representational State Transfer)
Что это: Архитектурный стиль, завязанный на HTTP, где всё крутится вокруг ресурсов и их состояний.
Транспорт: HTTP.
Когда применять:
- CRUD-сценарии
- публичные API
- системы, где важны кэширование, простота и масштабируемость
Почему: REST максимально использует возможности HTTP: методы, статусы, cache-control.
Он прост, прозрачен и отлично поддерживается инструментами.
📍 REST — дефолтный выбор, если нет веской причины делать иначе.

🔹 GraphQL
Что это: Язык запросов и runtime, где клиент сам описывает, какие данные ему нужны.
Транспорт:
- чаще всего HTTP
- иногда WebSocket (но это уже про real-time)
Когда применять:
- сложные графы данных
- разные клиенты с разными требованиями к payload
- желание сократить количество запросов
Почему: GraphQL снимает проблему overfetching/underfetching, но усложняет сервер и наблюдаемость.
📍 GraphQL — про гибкость клиента, а не про простоту системы.

🔹 RPC (gRPC, Thrift, JSON-RPC)
Что это: Удалённый вызов процедур: клиент вызывает метод, сервер его исполняет.
Транспорт: не привязан к HTTP
- gRPCHTTP
- Thrift — TCP / HTTP / custom transport
Когда применять:
- межсервисное взаимодействие
- высокая производительность
- чёткие контракты и кодогенерация
Почему: RPC ближе к обычному программированию: вызвал метод — получил результат. Это эффективно, но хуже ложится на публичные API и браузный мир.
📍 RPC — лучший выбор для внутренних систем и платформенных API.

#L #Network #HTTP #TCP #REST #SOAP #GraphQL #RPC

👏
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥542
🫡 ⚠️ Warning. Expensive. Experimental.

В Claude Code появилась экспериментальная фича Agent Team.
Что позволяет запускать виртуальную команду ИИ-сессий, которые работают параллельно над одной задачей, берут задачи из общего пула и могут общаться между собой напрямую.
- можно распараллеливать работу по пуллу задач,
- тимейты говорят друг с другом и сами решают, что делать дальше,
- и могут критиковать друг друга,
- полезно для сложных фич, исследований и коворкинга с ИИ.

Но:
💸 дорого — каждый агент работает как отдельная сессия и "жжёт" токены,
🧪 экспериментально — фича отключена по умолчанию, поэтому, наверное, пока стоит осторожно.

#L #AI #Agent #Team

👏
Please open Telegram to view this post
VIEW IN TELEGRAM
21
😏 Снова громкий заголовок – «Combine умер?», решил коротко разобрать статью и понять причину хайпа.

Повод - system prompt внутри Xcode AI, где есть рекомендация избегать Combine и предпочитать async/await. В system prompts Xcode есть строка:

“Avoid using the Combine framework and instead prefer … async and await”

Репозиторий с prompt’ами:
https://github.com/artemnovichkov/xcode-26-system-prompts

Это не документация Apple, а рекомендация для генерации нового кода. Но направление развития технологий читается довольно явно.

Apple уже несколько лет двигает стек в сторону async/await, Task, Actor и AsyncSequence. Они лучше встроены в язык, проще читаются и требует меньше шаблонного кода. Новые API чаще строятся вокруг concurrency-модели.

Combine никуда не делся. Он не deprecated, не удалён из SDK и используется в продакшене. Срочно переписывать существующий код смысла нет, но, очевидно, дефолтный выбор для нового async-кода сместился в сторону Structured Concurrency.

#R #Swift #Concurrency #Combine

👏
Please open Telegram to view this post
VIEW IN TELEGRAM
💯3
Вечернее-занимательное чтение.

Пишет iOS разработчик с опытом, обзор на пост от iOS разработчика с опытом. 😅

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

Как-то после тяжёлой тренировки в зале у меня «поехала» спина.
Итог — несколько месяцев лечения грыжи у очень сильных врачей по резорбции.
Многое узнал про восстановление, но один мотив шёл через всё лечение красной нитью: витамин D в нормальных (иногда ударных) дозах реально ускоряет заживление, снижает воспаление и положительно влияет на самочувствие в целом.


Теперь про пост и занимательные выводы по географии, и почему большинству не хватает витамина D.

• Нью-Йорк ≈ 40,7° северной широты (место жительства автора)
• Москва ≈ 55°
• Астрахань ≈ 46° (моя родина)

Исследование которое дает автор:
Чем дальше от экватора — тем хуже коже удаётся синтезировать витамин D из солнца.

Зимой в Москве UVB почти нет вообще.
В Нью-Йорке солнца больше чем в МСК, сезон длиннее, но зимой дефицит тоже обычное дело.
В Астрахани лучше, но в зимнее время, далеко не идеально.

Отсюда и реальность: в Нью-Йорке есть проблемы, в МСК будет и подавно (с Астраханью сравнивать сложнее, солнца больше, но если верить статье про "усвоение", количество != качество).

Мне врачи рекомендовали ударные дозы витамина D в день, для коррекции дефицита — и судя по всему, это распространённая практика, особенно осенью и зимой.
Имхо, с дозировкой, конечно, лучше не гадать, а сдавать анализы и обсуждать с врачом.

Если коротко:
меньше солнца → меньше витамина D → хуже восстановление, иммунитет и общее самочувствие.
А мы тут живём не на экваторе.

#L #WorkLife #Health #Balance

👏
Please open Telegram to view this post
VIEW IN TELEGRAM
4🔥21
😏 Разберём разницу между some View и AnyView в SwiftUI. Тема не новая, но споры о правильном использовании периодически возвращаются.

Коротко:
some View сохраняет конкретный тип View на этапе компиляции.
AnyView стирает тип и оставляет только «какой-то View» на этапе выполнения.

Когда мы пишем:
var body: some View {
Text("Cowabunga")
}


Мы говорим компилятору:
тип конкретный и известен, но имя типа скрыто.

SwiftUI при этом:
- строит статическое дерево типов
- может оптимизировать diff
- лучше отслеживает identity View
- эффективнее обновляет UI

А с AnyView:
AnyView(Text("Cowabunga"))


Мы теряем информацию о реальном типе и часть работы переносится в runtime.

SwiftUI больше не знает:
- какая структура View внутри
- как оптимизировать обновления

🍕 Классический пример
func content(isLoggedIn: Bool) -> some View {
if isLoggedIn {
return HomeView()
} else {
return LoginView()
}
}


Компилятор ругается, потому что возвращаются разные типы.

Можно быстро пофиксить так:
func content(isLoggedIn: Bool) -> AnyView {
if isLoggedIn {
return AnyView(HomeView())
} else {
return AnyView(LoginView())
}
}


Работает, но ухудшает оптимизации SwiftUI, да и ревью вряд ли пройдет.

😅 Нормальный SwiftUI-подход - использовать ViewBuilder.
@ViewBuilder
func content(isLoggedIn: Bool) -> some View {
if isLoggedIn {
HomeView()
} else {
LoginView()
}
}


SwiftUI создаёт:
ConditionalContent<HomeView, LoginView>


И сохраняет типовую информацию для оптимизаций.

Когда AnyView действительно нужен:
- массив разных View
- runtime injection View
- API, где нельзя использовать generics

Например:
let cells: [AnyView] = [
AnyView(ProfileHeaderView()),
AnyView(SettingsRow(...))
]


🍕 Можно сформулировать простое правило:
Если SwiftUI может знать тип на этапе компиляции → используем some View.
Если тип появляется только во время выполнения → используем AnyView.

AnyView не зло, но его легко начать использовать как костыль. Поэтому имеет смысл ограничивать использование или подсвечивать его линтером.

#R #SwiftUI #Performance #Architecture

👏
Please open Telegram to view this post
VIEW IN TELEGRAM
71
☝️ 📦 Размер приложения — это не просто цифра в App Store

Размер приложения напрямую влияет на:
- конверсию в установку (особенно в регионах с мобильным трафиком),
- время загрузки и first launch,
- обновляемость (пользователь чаще откладывает “тяжёлый” апдейт),
- хранение на устройстве.

Исторически порог в ~200 МБ считался психологическим и сетевым барьером — при крупных загрузках.
App Store, например предлагает переходить на Wi-Fi.
Даже несмотря на изменения лимитов, сам эффект остаётся: чем тяжелее билд, тем выше friction.

Недавно я интегрировал в нативное iOS-приложение Flutter-модуль. И это автоматически поднимает целый пласт инженерных вопросов:

🧩 Flutter inside native: что пришлось анализировать

1️⃣ Dependency resolution
- transitive зависимости (резолв общих нативных SDK)
- влияние flutter-плагинов на iOS/Android-проект
- корректную сборку в release profile

2️⃣ Оптимизацию release-сборки
- оптимизации Flutter.framework и App.framework (термины flutter)
- в том числе удаление неиспользуемой функциональности

3️⃣ Бинаризацию Flutter-модуля
- сборка в xcframework
- кэширование артефактов
- минимизация пересборки нативного проекта
- влияние на CI и локальный DX

Отдельная боль — время сборки. Flutter внутри iOS может существенно увеличить clean и incremental build, если не выстроить правильную стратегию доставки и кэширования.

В целом по тех стеку для оптмизаций flutter проекта, можно тут почитать.

🏗 Но оптимизации — это не только Flutter. Мы давно системно работаем с нативным стеком:

🔹 On-Demand Resources (ODR)
- Подгружаем тяжёлые ассеты по требованию.

🔹 Модуляризация
- изоляция фич
- переиспользование кода
- контроль зависимостей

🔹 Работа с ресурсами
- поиск дубликатов и больших ассетов
- анализ неиспользуемых ассетов
- контроль веса .xcassets
- внутренняя аналитика через скрипты и graphana

🔹 App Thinning
- понимание slicing
- корректная работа с architectures
- оптимизация universal vs device-specific бинарей

🔹 App Store Connect аналитика позволяет контролировать:
- размер IPA
- размер установленного приложения
- вариативность по устройствам

📊 По факту размер — это не разовая оптимизация, а процесс:
- инструменты анализа
- прозрачные метрики
- контроль на CI
- ответственность на уровне архитектуры.

Flutter, натив, ресурсы, сборка — всё это части одной системы.

#L #iOS #AppSize #Flutter

👏
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥42
☝️ В прошлом посте мы говорили про клиент-серверное взаимодействие: API, REST, запрос-ответ.

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

С чего всё началось — TCP и UDP (транспортный уровень):
Когда два компьютера обмениваются данными, они делают это через транспортный протокол.

1️⃣ TCP«передать всё и правильно»
TCP
появился в 70-х. Его цель — гарантировать доставку данных.
- следит за порядком пакетов
- подтверждает получение
- переотправляет потерянные
- регулирует перегрузку сети

Это надёжно. Но за надёжность приходится платить задержками. Передача фрагметов данных - сложный процесс и достоин отдельного поста, на тему. Но, базово, из-за передачи пакетов и их согласования может возникать проблема - head-of-line blocking (если один сегмент потерялся — остальные ждут).

2️⃣ UDP — «передать быстро»
UDP появился примерно тогда же, но философия у него другая. Он просто отправляет пакет.
Без гарантий:
- дошёл ли
- в каком порядке
Зато:
- минимум задержек
- минимум оверхеда
Поэтому UDP любят там, где важнее скорость, чем идеальная надёжность: игры, голос, видео.

3️⃣ Дальше появился HTTP (прикладной уровень)
Когда понадобился стандарт для веба, поверх TCP построили HTTP. GET, POST и т.д. запросы с примитивами вроде header, body и т.д.

👨‍🦳 HTTP/1.1: запрос → ответ.
- каждый запрос зависел от предыдущего
- соединения открывались и закрывались
- производительность была далека от идеала (в т.ч. из-за текстового формата передачи данных)
Но для 90-х этого было достаточно.

👷‍♂️ HTTP/2 — попытка ускорить веб. Спустя почти 20 лет стало ясно, что HTTP/1 не справляется.
- стал бинарным
- добавил мультиплексирование (чтобы решать head-of-line blocking на уровне HTTP)
- позволил нескольким запросам идти параллельно в одном соединении
Это серьёзно ускорило загрузку страниц. Но есть нюанс. HTTP/2 всё ещё работает поверх TCP. А это значит, что на транспортном уровне все еще: если один пакет потерялся — потоки блокируются внутри соединения.

🤖 HTTP/3 отказался от TCP.
Он работает поверх QUIC. QUIC — новая философия транспорта, построеная поверх UDP. Но он добавляет то, чего UDP не умеет сам:
- надёжность
- управление потоками
- шифрование
- мультиплексирование
И делает это без проблем TCP.

Ключевые отличия:
- Нет head-of-line blocking между потоками
- Быстрый handshake (0-RTT)
- Встроенный TLS 1.3
- Лучше переносит смену сети (например, Wi-FiLTE)

То есть QUIC — это фактически современный транспорт, который:
- взял скорость UDP
- добавил надёжность
- встроил безопасность

А HTTP/3 — это просто HTTP-семантика поверх QUIC.

#L #HTTP #TCP #UPD #TLS

👏
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3