Привет! Многие команды до сих пор внедряют сложные архитектуры вроде VIPER или Clean Architecture, считая их стандартом индустрии. Проблема в том, что эти решения часто не закрывают реальные проблемы проекта, зато увеличивают объем кода, замедляют сборку и усложняют онбординг новых разработчиков. Автор статьи проанализировал 147 проектов и результаты этого анализа выглядят довольно интересно.
Что говорят цифры:
Распределение архитектур среди проектов выглядит так: MVVM лидирует с 41%, MVC на втором месте с 34%, VIPER и Clean Architecture занимают 18%, TCA - 5%.
Главная проблема в том, что 73% проектов используют архитектуры, которые не решают их реальных проблем. Внедрение Clean Architecture в средних командах приводит к росту кода на 143%, замедлению разработки на 67% и увеличению времени онбординга новых разработчиков до трех недель.
Архитектуры и их зоны ответственности:
Как не ошибиться с выбором:
Автор статьи рекомендует придерживаться такой формулы при выборе верной архитектуры:
Лучше сделать нормально на простой архитектуре, чем на коленке на сложной. Архитектура сама по себе не гарантирует успеха. Важнее чистота кода, регулярные ревью и тесты. Сервисы, координаторы и внедрение зависимостей не требуют перехода на сложную архитектуру. Их можно добавлять в проект по мере необходимости, не меняя всю архитектуру. Не гонитесь за модными решениями - решайте реальные проблемы.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
💯14👍10❤7🔥1🙏1 1
Forwarded from Кот Денисова
Пока одни рисуют мрачные картины массовых увольнений и всесильного ИИ, другие уже используют его как рычаг для карьерного роста. Паника о «конце ИТ» - это маркетинг, страх и непонимание того, как на самом деле работает индустрия. История повторяется: каждый новый технологический прорыв вызывал аналогичные предсказания, но вместо исчезновения профессии мы получали её трансформацию. ИИ - не исключение.
Миф о замене - почему ИИ не станет вашим начальником:
Основное заблуждение - что ИИ сможет полностью автономно решать сложные бизнес-задачи. В реальности ИИ, особенно LLM (Large Language Models) - это продвинутый статистический инструмент, который генерирует вероятные последовательности слов на основе обученных данных. Он не понимает контекст, не обладает критическим мышлением и не может заменить человеческую способность к коммуникации, выявлению скрытых потребностей и творческому решению проблем.
Ключевая проблема, которую не решает ИИ: уточнение нечетких требований. Когда клиент говорит «сделайте красиво» или «нужна интеграция», за этим стоят десятки нюансов, которые выявляются только в диалоге. ИИ даст усредненный, шаблонный ответ, но не задаст уточняющих вопросов, не прочитает невербальные сигналы и не поймет бизнес-контекст.
Эволюция, а не революция:
ИИ повторяет этот путь: он становится новым инструментом в арсенале профессионала, а не его заменой. Художник с ИИ создаст больше концептов, копирайтер - больше вариантов текста, а программист - прототипирует быстрее. Но качественный, итоговый результат по-прежнему требует эксперта.
Парадокс спроса - почему автоматизация создает больше рабочих мест:
Логика «один программист с ИИ сделает работу пяти, значит, программистов нужно в пять раз меньше» - ошибочна. Экономика работает иначе:
Проще говоря, ИИ не сокращает количество работы, он увеличивает объем работы, которую бизнес готов и может заказать.
Новая карта компетенций:
В новой реальности ценность смещается:
ИИ не отбирает работу у программистов. Он отбирает работу у тех, кто отказывается меняться. Это естественный эволюционный фильтр.
Вместо того чтобы бояться, стоит спросить себя: вы используете ИИ, чтобы стать в 10 раз продуктивнее? Умеете ли вы делать то, что ИИ не может - понимать людей, принимать решения в условиях неопределенности, мыслить стратегически?
Будущее принадлежит не тем, кто пишет больше строк кода, а тем, кто становится архитектором решений, используя ИИ как мощный, но подконтрольный инструмент.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16❤9🗿3🔥2🙏1🤝1
Всем привет! Сегодня хочу поговорить о Metal шейдерах в SwiftUI. Это код, который выполняется прямо на графическом процессоре и определяет цвет каждого пикселя. В отличие от обычных анимаций (которые работают на уровне вьюх), здесь управление идет попиксельно. Звучит сложно, но разобраться можно довольно быстро. Есть отличная статья на эту тему, предлагаю остановиться на ней подробнее. В ней объясняют основы Metal шейдеров в SwiftUI с простыми и понятными примерами.
Подробнее о шейдерах:
Шейдер выполняется для каждого пикселя на экране. Если у вас прямоугольник 300×300 пикселей, шейдер вызовется 90 000 раз. GPU справляется с этим легко, потому что он создан для параллельных вычислений.
В SwiftUI вы просто говорите «сделай этот прямоугольник синим», и фреймворк сам решает, как закрасить пиксели. С Metal вы сами пишете код, который определяет цвет каждого пикселя. Сложнее, но зато вы контролируете каждый пиксель.
Как это работает в SwiftUI:
Процесс простой:
Никакой ручной связки не нужно - Xcode все делает автоматически.
Первый пример - сплошной цвет:
#include <metal_stdlib>
#include <SwiftUI/SwiftUI.h>
using namespace metal;
[[ stitchable ]] half4 basicColor(float2 position, half4 currentColor) {
return half4(0.2, 0.6, 0.9, 1.0);
}
Функция получает координаты пикселя и текущий цвет, но игнорирует их и возвращает один и тот же синий для всех пикселей. В SwiftUI это применяется одной строкой: .colorEffect(ShaderLibrary.basicColor()).
Второй пример - градиент от позиции:
[[ stitchable ]] half4 gradient(float2 position, half4 currentColor) {
float normalizedX = position.x / 300.0;
float normalizedY = position.y / 300.0;
half r = half(normalizedX);
half g = half(normalizedY);
half b = half(1.0 - normalizedX);
return half4(r, g, b, 1.0);
}
Здесь координаты пикселя нормализуются (приводятся к диапазону 0-1), а затем используются как цветовые компоненты. Красный растет слева направо, зеленый - сверху вниз, синий убывает слева направо. Получается красивый градиент.
Metal шейдеры в SwiftUI - это не магия для избранных, а инструмент, который вполне можно освоить, начиная с простых примеров. Да, придется немного по-другому думать и писать код на C-подобном языке. Но результат того стоит: вы получаете контроль над каждым пикселем и возможность создавать визуальные эффекты, которые невозможно повторить стандартными средствами. А главное - порог входа не такой высокий, как кажется. Достаточно одного работающего примера, чтобы понять принцип и начать экспериментировать.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13 8👍3❤1🙏1
В Swift есть несколько способов работать с ассоциированными значениями enum, и if case let - один из самых полезных, но его синтаксис часто путают. Давайте разберемся, как он работает и в каких случаях помогает писать чище.
Что такое case let и зачем он нужен:
Обычный if проверяет булево условие. Но если у вас enum с ассоциированными значениями, просто проверить тип недостаточно - нужно еще достать эти значения. Для этого есть if case let. Это сокращенная форма switch, когда вас интересует только один кейс.
Синтаксис может показаться непривычным: сначала идет if case let, потом шаблон, потом знак равенства и значение, которое вы проверяете.
Связывание значений:
Самый простой случай - извлечь все ассоциированные значения:
if case let Puppy.mastiff(droolRating, weight) = fido {
print("Рейтинг слюнявости: \(droolRating), вес: \(weight)")
}
Это эквивалентно switch, но без лишнего кода.
Можно связывать значения выборочно, используя let перед каждым:
if case Puppy.mastiff(let droolRating, let weight) = fido {
print("Рейтинг слюнявости: \(droolRating), вес: \(weight)")
}
А можно одним именем связать все значения в кортеж:
if case let Puppy.mastiff(characteristics) = fido {
print(characteristics) // (droolRating, weight)
}
Работа с опционалами:
Если значение, которое вы проверяете, опциональное, можно добавить ? после шаблона. Это синтаксический сахар, который избавляет от лишней вложенности:
if case let Puppy.mastiff(droolRating, weight)? = optionalPuppy {
// выполнится, только если optionalPuppy не nil и это кейс mastiff
}
Когда значения не нужны:
Если вас интересует только сам кейс, а значения не нужны, let можно опустить:
if case Puppy.mastiff = fido {
print("Это мастиф, какая разница какой?)
}
Добавление условий:
С помощью запятой можно добавить дополнительные проверки:
if case let Puppy.mastiff(droolRating, _) = fido, droolRating > 8 {
print("Диван пропал")
}
То же работает с guard:
guard case let Puppy.mastiff(droolRating, _) = fido, droolRating < 10 else {
print("В дом не пущу")
}
if case let - мощный инструмент для работы с enum, который делает код короче и понятнее, чем вложенные switch. Он особенно полезен, когда нужно обработать один конкретный кейс из нескольких, связать его значения и добавить условия. Освоив этот синтаксис, вы перестанете писать лишние switch для простых проверок.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
Многие разработчики привыкли вызывать API прямо в .onAppear. Вроде логично: экран показался - пора грузить данные. В маленьких проектах это работает. Но когда приложение растет, начинаются проблемы: дублирующиеся запросы, утечки памяти, неправильное состояние интерфейса. Давайте разберемся, почему .onAppear - это ловушка и как правильно выполнять загрузку данных с помощью .task и конечного автомата.
В чем проблема .onAppear:
В SwiftUI вью - это легковесные структуры. Они создаются и пересоздаются постоянно. .onAppear срабатывает каждый раз, когда представление становится видимым. Если пользователь открыл экран, вернулся назад и снова зашел - запрос улетит повторно, хотя данные уже есть.
Еще хуже с отменой задач. Когда вы пишете Task { await loadData() } внутри .onAppear, вы создаете неструктурированную задачу. Если пользователь закрыл экран до завершения запроса, задача продолжает висеть в памяти. Результат - утечки, лишний трафик, проблемы с батареей.
Можно добавить ручную отмену в .onDisappear, но это лишний код, который легко забыть. А еще классическая проблема - куча флагов isLoading, error, data. Они независимы, и интерфейс может перейти в противоречивое состояние: например, одновременно крутится лоадер и показывается ошибка.
Что использовать вместо этого:
Вместо трюков с флагами нужно ввести конечный автомат - перечисление, которое описывает все возможные состояния экрана:
Это гарантирует, что в каждый момент времени экран может находиться только в одном состоянии. Никакой путаницы. Бизнес-логику выносим в отдельную ViewModel (класс с
@Observable). В ней один метод, который меняет состояние.Почему .task лучше:
В представлении вместо .onAppear используем модификатор .task потому что он:
.onAppear - это не инструмент для загрузки данных. Это событие жизненного цикла. Используя его для API, вы боретесь с фреймворком: вручную управляете отменой задач, плодите невозможные состояния и рискуете утечками. Правильный подход - конечный автомат на перечислениях и модификатор .task, который берет на себя управление жизненным циклом. Код становится чище, а компилятор сам следит за тем, чтобы вы обработали все состояния.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21💯11 4👀2❤1🤔1
Forwarded from Кот Денисова
Business Insider опубликовал неутешительную новость для тех, кто привык рассылать свое резюме на каждую открытую вакансию. Крупные компании открыто пишут в требованиях: «резюме не нужно, мы их не читаем». Причина банальна - ChatGPT сделал из всех кандидатов идеальных клонов. Рекрутеры тонут в одинаковых документах, а на собеседованиях выясняется, что за красивыми формулировками пустота. Доверие к резюме рухнуло окончательно.
Что пришло на смену:
Цифры говорят сами за себя: 70% работодателей уже перешли на найм, где оценивают только реальные навыки. Дипломы и громкие имена компаний в резюме больше не работают. Платформы подстраиваются, например LinkedIn теперь верифицирует навыки через интеграцию с реальными инструментами, а Indeed запустил мгновенные видеособеседования прямо в момент отклика.
Как теперь искать работу:
Старая схема «разослал сто резюме -> получил три отклика -> выбрал лучший» больше не работает. Теперь нужно заставлять работодателя замечать вас до того, как вы отправите заявку. И вот что реально приносит результат:
Эпоха, когда можно было составить красивое резюме и ждать офферов, закончилась. Рынок перенасыщен, а инструменты подделки стали слишком доступными. Теперь единственный способ выделиться - показывать, что ты умеешь, а не рассказывать об этом. Портфолио, код, экспертные материалы, публичная активность - вот новая валюта на рынке труда. Если вы до сих пор надеетесь, что хорошее резюме откроет все двери, придется переучиваться.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤8🗿3🤯2🙏1👀1
В Swift 6.4 добавили три новых типа: UniqueBox, Ref и MutableRef. Раньше похожие задачи решались либо через классы с их счетчиками ссылок, либо через небезопасные указатели. Новые типы дают больше контроля над памятью и избавляют от лишних накладных расходов, оставаясь при этом в рамках стандартной модели безопасности Swift - без выхода в низкоуровневое программирование и без принудительного использования указателей.
Какая проблема была раньше:
Иногда нужно положить значение в кучу (например если оно большое или нужен стабильный адрес). Самый простой способ - завернуть значение в класс. Но у класса есть побочные эффекты: счетчик ссылок ивозможность случайно создать несколько ссылок на один объект, даже если вы этого не хотели. Получается, что вместо одного хозяина у значения появляются несколько и это создает лишнюю нагрузку на ARC.
UniqueBox - коробка с одним хозяином:
Это тип, который хранит значение в куче, но при этом имеет только одного владельца. Он не копируется автоматически, и у него нет счетчика ссылок. UniqueBox дает стабильный адрес для значения, но без балласта, который несут классы. Полезно для больших структур или когда значение должно быть доступно по стабильному адресу, но не должно иметь лишние ссылки.
Ref - доступ на чтение:
Этот тип дает временный доступ к значению для чтения. Он не владеет значением, а только заимствует его. Ref допускает множественные ссылки на одни и те же данные - читателей может быть сколько угодно. Но пока Ref жив, исходное значение нельзя изменить. Это защита от гонок на уровне компилятора.
MutableRef - доступ на запись:
В отличие от Ref, MutableRef дает эксклюзивный доступ на изменение. Сделать копию MutableRef не выйдет - доступ на изменение всегда один. Пока MutableRef активен, исходное значение заблокировано для любого доступа из других мест.
Когда нужно выделить память на куче или обеспечить стабильный адрес для значения, раньше приходилось использовать классы. Но они приносили с собой счетчик ссылок и неявную возможность множественного доступа к одним и тем же данным. Новые типы решают ту же задачу без этих накладных расходов. А для временного доступа к значению больше не нужны unsafe-указатели - Ref и MutableRef делают это на уровне компилятора, с полной типобезопасностью.
Для повседневной разработки эти типы, скорее всего, не понадобятся. Но если вы пишете производительные коллекции, работаете с буферами или создаете низкоуровневые абстракции - это важный шаг вперед. Swift продолжает развиваться в сторону большей выразительности без компромиссов по безопасности.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥7🤔3❤1 1
Жесты в SwiftUI выглядят просто: повесил .gesture на вью и оно реагирует. Но как только на экране появляются вложенные элементы, системные свайпы или несколько жестов на одной вью - начинается хаос. Вместо красивой анимации вы получаете неправильное поведение. Сегодня разберем три механизма, которые ставят жесты под контроль.
Маска жеста - кто вообще имеет право слышать касание:
По умолчанию жест привязан ровно к той вью, на которую вы его повесили. Если пользователь ткнул в дочернюю вью - родительская вью не реагирует. Но это не всегда удобно. Представьте, что вы хотите перетаскивать всю карточку, даже если палец попал на иконку внутри нее.
Тут вступает GestureMask. Это параметр модификатора .gesture(including:), который определяет, насколько глубоко в иерархии отслеживается ваш жест:
Кто главный на сцене - приоритеты внутри одной вью:
Бывает, что на одной вью висят два жеста. Например, тап и долгое нажатие. По умолчанию SwiftUI пытается распознать оба жеста и вы получаете двойную реакцию.
Решение - highPriorityGesture(_:including:). Этот модификатор говорит: «Этот жест важнее всех остальных на этой вью». Как только он начал распознаваться, остальные отменяются.
Как переспорить системные жесты - свайп назад и другие навязчивые привычки iOS:
Самый болезненный момент. Вы делаете кастомный горизонтальный свайп, а система перехватывает его своим - например, возвратом на предыдущий экран. Пользователь злится, потому что не может сдвинуть вашу карточку.
Модификатор .defersSystemGestures(on:) решает эту проблему. Он говорит системе: «Подожди, сначала пусть моя вью решит, что делать с этим свайпом». Если ваша вью не воспользовалась жестом (например, палец сместился меньше порога), тогда система запускает свой.
GestureMask, highPriorityGesture и defersSystemGestures - нужны только тогда, когда стандартное поведение SwiftUI не справляется. Пока на экране один жест на одну кнопку, все работает и без них. Но как только появляются вложенные вью и конкурирующие жесты, начинается хаос. Здесь главное не переборщить. Не надо лепить .all и высокие приоритеты везде подряд, иначе жесты начнут срабатывать там, где не должны, а пользователь решит, что приложение глючит.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
В этом году Apple наконец дала разработчикам прямой доступ к Apple Intelligence. Не через косвенные API, а полноценно. На WWDC26 показали новый ИИ-стек, пересобрали Xcode под работу с агентами и заодно обновили платформы.
Коротко про изменения в iOS:
iOS 27 поддерживает устройства от iPhone 11 и новее, приложения запускаются быстрее. Появились детские аккаунты с гибкими ограничениями. Siri получила отдельное приложение, больше контекста и Visual Intelligence. Apple Intelligence работает по смешанной схеме: легкие задачи решают модели прямо на устройстве, а для сложных запросов подключается Gemini в облаке. Shortcuts тоже прокачали - теперь не нужно вручную собирать цепочки действий, достаточно описать словами, что вы хотите сделать, и система сама сгенерирует готовый сценарий.
ИИ-инструменты для разработчиков:
Apple представила новые фреймворки для работы с ИИ.
Xcode 27 - агент становится частью IDE:
Самое большое изменение для ежедневной работы. Apple не просто добавила чат с ИИ, а встроила агента в полный цикл разработки - от идеи до поддержки.
Агент работает в несколько этапов:
Изменения от агента применяются только с подтверждением разработчика. Apple не пытается заменить инженера, а забирает рутину: подготовку локализации, поиск дорогих функций, регресс.
Другие изменения в Xcode 27:
WWDC 2026 задала направление: Apple окончательно открыла свой ИИ-стек разработчикам, перевела Siri на App Intents и встроила агентов в Xcode. Теперь не нужно гадать, как подключиться к Apple Intelligence - есть официальные API. Агент в Xcode не заменит разработчика, но возьмет на себя грязную работу. При этом Apple не забыла про производительность SwiftUI, работу с памятью и инструменты отладки. ИИ становится не фичей, а частью инструментария. И чем аккуратнее у вас проект, тем больше вы выиграете от нового Xcode.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12 8🔥4❤3🙏1
Forwarded from Кот Денисова
Когда говорят о вайбкодинге, обычно представляют разработчика, который пишет промпты в ChatGPT и вставляет сгенерированный код, надеясь, что все заработает. Но на самом деле вайбкодинг существовал задолго до появления больших языковых моделей. Просто раньше вместо нейросети был разработчик, а вместо промпта - задача в Jira.
Как это работало всегда:
Схема знакома каждому:
Это и есть вайбкодинг. Код пишется, но никто из менеджеров его не читает. Результат оценивается по ощущениям. Просто раньше генератором кода был только разработчик, а промптом - описание задачи.
Почему это не замечали:
Когда кто-то говорит, что вайбкодинг - это несерьезно, стоит напомнить: десятилетиями вся индустрия работала именно так. Менеджер не знает, как устроен код. Он знает, как должен работать продукт. И оценивает результат по внешним признакам - работает или нет, удобно или нет, быстро или нет.
Разработчик же, получая задачу, часто не знает всех нюансов. Он пишет код, потом правит его по замечаниям, потом правит снова. Пока менеджер не скажет: «Ок, теперь то, что нужно». Это и есть итеративная разработка, в которой код часто становится черным ящиком даже для того, кто его писал.
Вайбкодинг не появился с ChatGPT. Он был всегда. Просто раньше генератором кода был разработчик, а теперь - нейросеть. И когда кто-то с презрением говорит о вайбкодинге, стоит вспомнить, что вся современная разработка во многом построена на том, что результат оценивают по внешним признакам, а не по качеству кода. Это не новость, это индустрия. Просто сейчас это стало заметно.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15💯11👀5🤔3🗿2❤1🔥1👏1
Всем привет! На WWDC26 показали то, о чем разработчики просили с момента появления AsyncImage в iOS 15 - поддержку кэширования.
Что изменилось:
Раньше AsyncImage при каждом появлении вьюхи на экране отправлял запрос в сеть, даже если вы только что загрузили данное изображение. Разработчики выкручивались своими силами: писали кастомные обертки, подключали сторонние библиотеки, вручную сохраняли изображения на диск.
В новой версии достаточно указать политику кэширования в URLRequest:
AsyncImage(request: URLRequest(
url: imageURL,
cachePolicy: .returnCacheDataElseLoad
))
Если картинка уже есть в кэше - она берется из него. Никаких дополнительных запросов.
На WWDC26 наконец-то доделали то, что должно было работать с момента появления AsyncImage. Кэширование - базовая вещь, которую в UIKit завезли еще много лет назад. Все это время разработчикам приходилось решать задачу, которая вообще не должна была существовать. Но теперь эта проблема осталась в прошлом.
Седьмой год SwiftUI, а мы все еще радуемся базовым вещам. Но прогресс есть и это здорово.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
Apple обновила гайдлайны для разработчиков и изменения довольно серьезные. Раньше компания просто отклоняла однотипные приложения из перенасыщенных категорий. Теперь политика ужесточилась: Apple может начать удалять уже существующие приложения, если они не обновляются, не улучшаются или не привлекают пользователей.
Кого коснется новая политика:
В списке категорий, которые Apple считает перегретыми - приложения для обоев, простые таймеры, звуковые эффекты, фонарики, гадания, сервисы знакомств. А также те, которые компания прямо называет низкокачественными и не требующими значительных усилий - игры для выпивки, приложения-имитации смешных звуков и подобные.
Раньше формулировки были более прямолинейными. Apple прямо перечисляла конкретные типы приложений, которые считает низкокачественными. Теперь тон стал жестче. Компания говорит о бездумном копировании существующих популярных приложений. И предупреждает: новые версии таких приложений не примут, а старые могут удалить, если они не предлагают существенно улучшенный пользовательский опыт.
Что грозит разработчикам:
Самый неприятный момент касается не только отдельных приложений. Apple предупредила, что разработчики, которые неоднократно отправляют низкокачественные или клонированные приложения, могут полностью потерять доступ к программе Apple Developer Program. То есть лишиться возможности публиковать что-либо в App Store в принципе.
При этом компания обещает, что процесс будет не внезапным. Существует система уведомлений: разработчикам сообщают, что их приложения устарели или плохо скачиваются, и дают время на улучшение. Только после этого, если ничего не меняется, приложение может быть удалено.
Apple наконец-то взялась за порядок в App Store. Удалять будут не только новые приложения-клоны, но и старые, которые годами не обновлялись и никому не нужны. Под раздачу попадают фонарики, обои, таймеры, гадания и подобные. Разработчикам, которые специализируются на таких приложениях, стоит задуматься. Повторные нарушения могут стоить не только отдельных приложений, но и всего аккаунта. С одной стороны, это ужесточение правил. С другой - давно назревшая чистка, которая пойдет на пользу и пользователям и добросовестным разработчикам.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15👀9🤔3🔥1🤯1
У каждого iOS-разработчика был момент, когда Xcode вдруг начинал вести себя странно: код не менялся, но сборка падала, SwiftUI Preview выдавал ошибку вместо интерфейса, зависимости переставали подключаться. В подобных случаях часто помогает удаление Derived Data.
Раньше приходилось вручную искать папку Derived Data, но в Xcode 27 это можно сделать прямо из выпадающего меню. Мелочь, а для тысяч разработчиков - это целое событие.
Что такое Derived Data:
Derived Data - это папка, куда Xcode складывает все, что генерирует в процессе сборки: скомпилированные модули, индексы, кэши пакетов, логи и прочие промежуточные артефакты. Это ускоряет повторные сборки.
Но у этого подхода есть обратная сторона. Если кэш расходится с актуальным состоянием проекта, Xcode начинает вести себя непредсказуемо.
Причина почти всегда одна: Xcode держится за устаревшее состояние. И единственный способ сбросить его - удалить Derived Data.
Как это работало раньше:
Самый простой способ удалить Derived Data - выполнить в терминале команду:
rm -rf ~/Library/Developer/Xcode/DerivedData
Но это удаляет кэш для всех проектов сразу. На машине, где несколько активных рабочих пространств, это означает лишние пересборки и потерю времени. Можно открыть Finder, найти папку и удалить только нужный проект. Но это каждый раз занимает лишнее время.
Что изменилось в Xcode 27:
В бете появился пункт в меню Product -> Delete Derived Data. Он удаляет кэш только для текущего проекта. Чисто, быстро, безопасно для других проектов.
Для CI и автоматизации по-прежнему есть командная строка:
xcodebuild -workspace App.xcworkspace -scheme App -derivedDataPath .build/DerivedData clean build
Но для локальной разработки новая возможность облегчает жизнь разработчиков.
Новый пункт меню Delete Derived Data в Xcode 27 - это не революция, но очень полезное дополнение. Apple наконец-то официально признала: кэш сборки - это техническая деталь, а не часть проекта. Его можно и нужно сбрасывать, когда он начинает мешать.
Но главное здесь не переборщить. Если вы удаляете Derived Data каждый день, проблема не в кэше. Он всего лишь маскирует реальную причину, которая прячется в конфигурации проекта, зависимостях или процессах сборки. Удаление кэша - это временное решение. Найдите и устраните корень проблемы, и тогда новый пункт меню понадобится вам гораздо реже.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥16👍11❤3🗿1 1
Forwarded from Кот Денисова
Нашел интересную статью, в которой автор размышляет о том, что эффективнее: доделать одну задачу от начала до конца или параллелить несколько. В эпоху, когда агентов можно запускать десятками, этот вопрос становится все более актуальным.
Параллельная работа - это не всегда хорошо:
Автор честно признается: он пробовал запускать несколько агентов одновременно в разных проектах. И называет это ошибкой. Постоянное переключение между контекстами выматывает и снижает эффективность. Его текущая стратегия - работать не более чем с 1–2 проектами одновременно, причем либо близкими по смыслу, либо, наоборот, настолько разными, что они требуют разного типа энергии.
Почему не нужно хвататься за все подряд:
С появлением агентов стало слишком легко сразу же браться за любую идею или запрос. Пришел тикет в поддержку - можно сразу его закрыть, не планируя. Пришла новая идея - запустить агента, не думая. Но это отвлекает от главных целей и высасывает энергию.
Автор предупреждает: новый open-source проект или приложение - это не только радость от запуска, но и месяцы поддержки. Быстрый старт оборачивается долгим финишем, когда на последние 20% работы уходит столько же времени, сколько на первые 80.
Что должно двигать решениями:
Внутренняя мотивация. Если вы горите идеей - делайте. Но не потому, что «могу», и не только ради денег. Стоит браться за то, что вы готовы развивать месяцы или годы. Иначе оно того не стоит.
Возможностей у агентов много, но и рисков не меньше. Автор сравнивает это с Лигой чемпионов: нужны фокус, дисциплина и постоянное самосовершенствование. Не стоит распыляться. Лучше делать меньше, но доводить до конца, чем хвататься за все подряд и не завершать ничего.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🙏6❤3🔥1🤔1
@State в SwiftUI стал macro. Синтаксис не изменился, но поведение стало лучше.На WWDC26 компания Apple перевела
@State с property wrapper на macro. Для разработчиков это значит, что код останется таким же, как и раньше, но внутри все работает иначе. Основное изменение касается ленивой инициализации observable-объектов, хранящихся в @State.Что изменилось:
Раньше
@State был property wrapper. Теперь это attached macro. В документации указано:
@attached(accessor, names: named(init), named(get), named(set))
@attached(peer, names: prefixed(`_`), prefixed(__), prefixed(`$`))
macro State()
На практике синтаксис не меняется. Код, который вы писали раньше, продолжает работать. Разница только в том, как SwiftUI управляет хранением состояния.
Изменения на практике:
Самое важное - ленивая инициализация для observable-классов в
@State.Раньше, если вы писали так:
@Observable
class ViewModel {
var index = 0
init() {
print("Init")
}
}
struct MyView: View {
@State private var viewModel = ViewModel()
var body: some View {
Button("Index: \(viewModel.index)") {
viewModel.index += 1
}
}
}
Инициализатор ViewModel мог вызываться каждый раз, когда SwiftUI пересоздавал структуру вью. Даже если сам объект хранился и не терялся, его инициализация все равно выполнялась. Это приводило к лишней работе, особенно если в инициализаторе были тяжелые операции.
Теперь, с macro версией
@State, объект создается один раз - когда SwiftUI создает хранилище состояния для вью. И больше не пересоздается при каждом обновлении родителя.Когда это особенно заметно:
Это изменение особенно полезно, когда вы храните в
@State тяжелые объекты: модели с подписками, кэшами, сетевыми запросами при инициализации. Раньше приходилось использовать обходные пути - например, хранить опциональный объект и создавать его в .task.
// Раньше приходилось делать так, чтобы избежать повторной инициализации
@State private var viewModel: ViewModel?
var body: some View {
MyView(viewModel: viewModel)
.task {
viewModel = ViewModel()
}
}
Теперь это больше не нужно. Можно просто писать:
@State private var viewModel = ViewModel()
И быть уверенным, что объект создастся только один раз.
Apple сделала
@State macro, чтобы улучшить производительность в типичных сценариях. Синтаксис остался прежним, код не требует изменений, но observable-объекты в @State теперь инициализируются один раз. Это избавляет от лишнего кода и делает поведение более предсказуемым.Переход на macro для такого базового механизма - это важный сигнал. Apple уверена в своей macro-системе и готова переносить на нее ключевые API. А для разработчиков это просто означает, что SwiftUI становится чуть более эффективным и предсказуемым.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15 12❤4🤯2🔥1
Telegram для iOS - это технически сложный продукт. В кодовой базе 2.1 миллиона строк, 700+ модулей, 86% написано на Swift. Проекту больше 13 лет. Но при этом приложение регулярно лагает на флагманских устройствах, открывает по несколько дублей окон и годами крешится при редактировании изображений.
Почему так происходит, если код считается одним из лучших в индустрии?
Ответ звучит неожиданно: разработчики пишут на Swift, но мыслят в парадигме объектно-ориентированного программирования, как будто работают с Java. А Swift для этого не предназначен.
В чем проблема ООП в Swift:
Swift - это язык, архитектура которого заточена под Protocol Oriented Programming. Компилятор умеет оптимизировать код, написанный в этой парадигме: убирать лишние вызовы, встраивать функции, специализировать дженерики. Все это дает производительность, близкую к C.
Но когда разработчики используют классы и иерархии наследования так, как это делают в Java, компилятор не может применить эти оптимизации. Каждый вызов метода идет через виртуальную таблицу, а это в 3-4 раза медленнее прямого вызова.
Цена ООП-подхода в Swift:
На небольших проектах разница незаметна. Но на масштабах Telegram она становится критичной. По оценкам, из-за ООП-подхода приложение теряет от 10% до 25% производительности CPU. Лишние 20-30% памяти уходят на аллокации в куче там, где можно было использовать структуры.
Фреймрейт в ключевых пользовательских сценариях мог бы быть выше на треть. А количество крашей сократилось бы просто потому, что исчезла бы часть неожиданных мутаций общего состояния, которые неизбежны при работе с классами.
Типичные симптомы ООП-мышления в коде Telegram:
В кодовой базе есть классы на 11 тысяч строк. Например, ChatControllerImpl. Это прямой результат парадигмы, которая поощряет концентрацию логики в одном месте.
Постфикс Impl - еще один характерный признак. Это наследие Java 90-х, где один интерфейс равнялся одной реализации. В Swift протокол - это описание способности, а не место в иерархии. Когда у протокола есть ровно одна реализация с суффиксом Impl, это почти всегда симптом: протокол создан по привычке, а не осознанно.
Ссылочный хаос - еще одна проблема. Классы передаются по ссылке и два модуля могут держать один объект, незаметно меняя его состояние. В POP со структурами семантика копирования явная, без неожиданных сайд-эффектов.
Telegram - это огромный и сложный проект. Его разработчики проделали колоссальную работу. Но архитектурный подход, выбранный много лет назад, сегодня тормозит приложение. ООП в Swift - это не просто устаревший стиль. Это потерянная производительность.
Swift дает возможность писать почти так же быстро, как на C, но только если использовать его правильно. Protocol Oriented Programming - это не просто модный термин, а путь к реальной оптимизации. И чем крупнее проект, тем заметнее разница.
Telegram мог бы работать быстрее, потреблять меньше памяти и реже падать. Для этого не нужно переписывать все с нуля. Достаточно начать мыслить в парадигме языка, а не тащить за собой привычки из прошлого.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21❤13🗿5 2🤯1👀1
Forwarded from Flutter & Dart | Мобильный трудоголик
Привет. В последнее время все только и говорят о разработке с помощью ИИ-агентов. Кто-то в восторге, кто-то скептичен. Но мало кто задумывается, как выбор технологий влияет на эффективность работы с LLM. Майкл Томсен, инженер из команды Flutter, выпустил разбор на эту тему. Он объясняет, почему мультиплатформенная архитектура Flutter дает неожиданные преимущества в эпоху агентской разработки. Основная мысль простая: одна кодовая база вместо трех - это не только удобно для людей, но и критически важно при работе с ИИ.
В чем была ценность Flutter раньше:
Еще до эры агентов Flutter решал классическую проблему: вместо трех команд (iOS, Android, веб) можно иметь одну, которая пишет код на Dart и запускает его везде. От 95% до 99% кода в реальных проектах переиспользуется. Это давало:
Что изменилось с приходом ИИ-агентов:
Когда разработкой занимаются агенты, нативный подход (Swift под iOS, Kotlin под Android, JavaScript под веб) начинает пробуксовывать. Агенту нужно сгенерировать одну и ту же логику трижды, на разных языках, с разными нюансами. Это приводит к трем проблемам.
Во-первых, растет потребление токенов. Каждая генерация и каждое исправление умножаются на количество платформ. Во-вторых, возникают отличия в реализации между платформами. Агент может начать галлюцинировать и на одной платформе фича будет работать не так, как на другой. В-третьих, сложнее отлаживать и поддерживать.
Почему Flutter решает эти проблемы:
Flutter предлагает единую кодовую базу на Dart. Агент пишет все один раз, а не три. Токенов тратится меньше, генерация быстрее, логика не расходится.
Кроме того, у Flutter есть архитектурные особенности, которые делают его удобным для агентской разработки. Сильная статическая типизация Dart работает как система самопроверки: если агент сгенерировал не то, компилятор сразу укажет на ошибку. Это снижает количество галлюцинаций.
Flutter был удобен для команд, которые хотят поддерживать несколько платформ без тройного объема работы. В эпоху ИИ-агентов этот подход становится еще более актуальным. Меньше кода - меньше токенов. Одна логика - нет платформенного различия. Сильная типизация - меньше галлюцинаций. Горячая перезагрузка - быстрая проверка.
Для агентской разработки Flutter выглядит прагматичным выбором.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤7🗿4🤔2🫡2🔥1
Раньше в SwiftUI была четкая граница. Если вы хотели реализовать перетаскивание с помощью стандартных средств ваш выбор был List с его onMove. Для LazyVStack, HStack, Grid или кастомных макетов приходилось писать решение вручную. Не было никакого готового функционала для этого.
В iOS 27 это изменилось. SwiftUI получил новый API: reorderable() и reorderContainer. Теперь любой контейнер может поддерживать перетаскивание и перестановку элементов.
Как это работает:
Новый API состоит из двух частей. Первая - reorderable(). Она ставится на динамический контент, который может участвовать в перетаскивании. Чаще всего это ForEach.
ForEach(items) { item in
ItemRow(item: item)
}
.reorderable()
Вторая - reorderContainer(for:). Она определяет родительский контейнер, внутри которого разрешена перестановка.
.reorderContainer(for: Item.self) { difference in
items.apply(difference: difference)
}
Этот подход хорошо ложится в логику SwiftUI. Поведение элемента живет с динамическим контентом. Границы взаимодействия - с контейнером. Разделение четкое и предсказуемое.
Простой пример:
Допустим, у вас есть плейлист в виде кастомного вертикального списка. Раньше пришлось бы реализовывать перетаскивание вручную. Теперь это всего лишь несколько строк кода:
struct PlaylistView: View {
@State private var items: [PlaylistItem] = [...]
var body: some View {
ScrollView {
LazyVStack(alignment: .leading, spacing: 8 ) {
ForEach(items) { item in
Text(item.title)
.padding()
.background(.thinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
.reorderable()
}
.padding()
.reorderContainer(for: PlaylistItem.self) { difference in
items.apply(difference: difference)
}
}
}
}
reorderable() ставится на ForEach, reorderContainer - на LazyVStack. SwiftUI берет на себя всю логику жестов, а вы только обновляете данные в замыкании.
Модель данных остается за вами:
API не сохраняет новый порядок автоматически. Он дает ReorderDifference описание того, что и куда переместили. А вы уже сами решаете, как применить это к модели.
.reorderContainer(for: PlaylistItem.self) { difference in
items.apply(difference: difference)
}
Можно применить apply(difference:) к массиву. А можно обработать вручную, если логика сложнее: проверка прав на перемещение, обновление sortIndex в базе данных, синхронизация с сервером. API не навязывает конкретный способ.
Управление доступностью перетаскивания:
У reorderContainer есть параметр isEnabled. Это удобный способ включать и выключать перестановку без условной логики на уровне жестов.
.reorderContainer(
for: PlaylistItem.self,
isEnabled: isEditing
) { difference in
items.apply(difference: difference)
}
Как только isEditing становится true, reorder активируется. Контейнер по-прежнему управляет областью взаимодействия, а состояние решает, активно ли оно.
SwiftUI в iOS 27 наконец-то избавился от ограничения, которое долго раздражало разработчиков. Теперь перетаскивание работает в любом контейнере, а не только в List. Можно использовать reorderable() и reorderContainer с LazyVStack, HStack, Grid и кастомными макетами. API оставляет контроль над моделью данных за разработчиком, что важно для сложных приложений. И поддерживает перемещение между разными коллекциями.
Для пользователей это означает привычное поведение перетаскивания в любых интерфейсах. Для разработчиков - меньше ручного кода и более предсказуемый API.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15👍12❤4👀1 1
Вместе с выходом новой версии Xcode компания Apple сделала давно ожидаемый шаг - полностью отказалась от поддержки процессоров Intel. Xcode 27 теперь работает исключительно на процессорах Apple Silicon. Для многих разработчиков это не стало неожиданностью, но все равно требует внимания.
Почему Apple отказалась от Intel:
Переход на собственные процессоры Apple начался еще в 2020 году и с тех пор компания последовательно двигалась к полному отказу от Intel. Причин несколько. Процессоры Apple Silicon обеспечивают значительно лучшую производительность на ватт - они быстрее и при этом потребляют меньше энергии. Это особенно заметно при работе с тяжелыми задачами вроде компиляции крупных проектов.
Кроме того, унификация архитектуры позволяет Apple лучше оптимизировать связку железа и софта. В отличие от Intel, где приходилось подстраиваться под чужие процессоры, теперь вся экосистема работает на единой архитектуре. Это дает прирост производительности не только в Xcode, но и во всей системе в целом.
Когда я впервые перешел с MacBook с процессором Intel на MacBook с M1, я был в полном восторге. Ноутбук работал бесшумно, совершенно не грел колени, не включал вентиляторы при работе и работал без подзарядки значительно дольше. При этом он справлялся с компиляцией проектов в разы быстрее. Разница была заметна с первых минут и стало очевидно, что процессоры Apple - это не просто переход от стороннего производителя чипов к собственному производству, а совершенно иной подход к тому, как должен работать современный ноутбук.
Что изменилось в Xcode 27:
Помимо изменения системных требований, Xcode 27 получил несколько заметных улучшений. Проекты теперь открываются быстрее - это результат оптимизаций под Apple Silicon и изменений во внутренней архитектуре IDE.
Настройки синхронизируются через iCloud, что удобно для тех, кто работает на нескольких устройствах. Можно не тратить время на перенос конфигураций между машинами.
Панель инструментов стала полностью настраиваемой. Разработчики могут перестраивать ее под свои рабочие процессы, убирая лишние элементы и добавляя нужные.
Появилась поддержка тем, которые меняют оформление не только редактора кода, но и всего приложения. Это мелочь, но визуально приятная.
Самое заметное изменение - Device Hub, который заменил привычный Simulator. Теперь симуляторы и физические устройства собраны в одном окне. Управление стало удобнее, а переключение между устройствами быстрее.
Apple окончательно завершила переход на собственную архитектуру в инструментах разработки. Xcode 27 - еще один шаг в этом направлении. Для разработчиков это означает более быструю работу, лучшее энергопотребление и доступ к новым возможностям IDE. Но и обязательство обновить железо тем, кто все еще использует технику на Intel.
Переход на Apple Silicon оказался не просто маркетинговым ходом. Разница в производительности и комфорте работы действительно заметна. И Xcode 27 - еще одно подтверждение того, что будущее экосистемы Apple - за собственными процессорами.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17❤9🤯4🔥1👀1
Swift Package Index, сервис, который используют тысячи Swift-разработчиков для поиска пакетов и проверки их совместимости, официально стал частью Apple. Сделка состоялась, но условия не раскрываются. В блоге проекта появилась запись о том, что Swift Package Index присоединился к Apple.
Что такое Swift Package Index:
Это поисковая система и индексатор метаданных для Swift-пакетов с открытым исходным кодом. Разработчики используют Swift Package Index, чтобы находить пакеты, проверять их совместимость с платформами и версиями Swift, смотреть документацию, оценивать активность авторов и состояние пакета в целом.
Одна из главных фишек сервиса - автоматическое тестирование каждого пакета на поддерживаемых платформах и версиях Swift. Это дает уверенность перед тем, как добавить зависимость в проект. Сейчас в индексе уже больше 10 тысяч пакетов. Только за прошлый год было обработано более 3,5 миллиона билдов на совместимость.
Что изменится для разработчиков прямо сейчас:
В официальном сообщении подчеркивают: для разработчиков и авторов пакетов ничего не меняется. Пакеты индексируются как раньше, документация хостится как раньше. Проект остается с открытым исходным кодом. Инженеры Apple будут участвовать в развитии вместе с сообществом.
Так что если вы пользуетесь Swift Package Index сегодня - завтра все будет работать точно так же.
Что изменится в будущем:
Самое интересное - в планах. В сообщении прямо называют направления, над которыми будут работать: подписание пакетов, идентификация авторов, безопасность и надежность экосистемы.
Это может означать, что Swift Package Index постепенно превратится из удобного поисковика в более официальный слой доверия вокруг Swift-зависимостей. Возможен сценарий, когда Xcode позволит искать и добавлять пакеты прямо из IDE, без необходимости вручную вставлять URL репозитория. Управление зависимостями станет проще и удобнее.
Присоединение Swift Package Index к Apple - это хорошая новость для Swift-сообщества. Сервис получит больше ресурсов для развития, оставаясь при этом открытым проектом. А главное - Apple прямо говорит о планах улучшить безопасность и надежность экосистемы пакетов.
Для разработчиков это значит, что в будущем управление зависимостями станет проще, а доверие к пакетам - выше. Пока все остается как есть. Но следующие месяцы должны показать, каким будет новый Swift Package Index под крылом у Apple.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15 10🔥3❤2💯1
На WWDC26 компания Apple показала обновления и для UIKit. Не радикальные, не революционные, но заметные. И главное - они подтверждают: UIKit никуда не уходит. SwiftUI будет на первых ролях, это очевидно. Но UIKit остается надежным фундаментом и Apple продолжает его развивать.
В этом году изменений не так много и почти ничего из добавленного не выглядит эффектно. Но в каком-то смысле это справедливо и для iOS 27 в целом. Основной фокус - Siri и ИИ. А UIKit получил точечные улучшения, которые делают его более гибким и адаптивным.
Навигация и кнопки:
Большая часть изменений коснулась навигационной панели и ее элементов. И здесь явно прослеживается одна тенденция: адаптация под разные размеры экранов и форм-факторы. Интересно, с чем бы это могло быть связано?
Каждый контроллер теперь может влиять на то, будет ли его навигационная панель уменьшаться при прокрутке. Это полезно для экранов с большим количеством контента.
Пример использования:
navigationItem.barMinimizeBehavior = .onScrollDown
navigationItem.barMinimizationSafeAreaAdjustment = .enabled
Также появилась возможность ранжировать кнопки в панели. UIKit теперь знает, какие действия важнее сохранять на экране, когда места становится мало. Адаптивные интерфейсы - iPad, Stage Manager, изменяемые окна - везде, где ширина панели постоянно меняется, это становится критичным.
Пример использования:
saveItem.visibilityPriority = .high
shareItem.visibilityPriority = .standard
Еще одно небольшое, но полезное изменение - возможность убрать стандартный отступ у кнопки на панели инструментов:
colorItem.isPaddingRemoved = true
Сцены, окна и ориентация:
Apple продолжает двигаться в сторону многооконных приложений с изменяемыми размерами. И здесь тоже есть подсказки: все это особенно актуально для грядущих складных устройств.
Теперь можно запросить системное подтверждение перед закрытием сцены. Это полезно для приложений работы с документами, редакторов текста и инструментов для работы, где закрытие окна может стереть несохраненные данные.
Пример использования:
windowScene.closureConfirmation = UISceneClosureConfirmation(
title: “Несохраненные данные",
message: "Закрыть несохраненные данные?",
actions: [...]
)
Из интересного, еще появилась возможность регистрировать неинтерактивную сцену для внешнего дисплея. Вспомогательные экраны для презентаций, панели мониторинга, справочные представления - все это теперь проще интегрировать.
И самое важное - каждая сцена теперь может объявлять собственный набор поддерживаемых ориентаций. Видео-сцена может оставаться горизонтальной, а сцена для просмотра или редактирования поворачиваться как угодно.
Пример использования:
func supportedInterfaceOrientations(
for windowScene: UIWindowScene
) -> UIInterfaceOrientationMask {
.landscape
}
Таббары и сайдбары:
Таббары и сайдбары получили несколько настроек для управления тем, что показывать и чему отдавать главный акцент.
Теперь можно визуально выделить одну вкладку, которая важнее остальных. Подходит для ключевых действий вроде написания сообщения, поиска, корзины или создания нового элемента.
Пример использования:
tabBarController.prominentTabIdentifier = "compose"
Также появилась возможность указать предпочтительное размещение: сайдбар или таббар. Приложения, ориентированные на сайдбар, теперь могут аккуратно сворачиваться в таббар на компактных устройствах или в сценариях со складными экранами.
Пример использования:
tabBarController.mode = .tabSidebar
tabBarController.sidebar.preferredPlacement = .sidebar
Еще несколько лет назад можно было задаваться вопросом, не собирается ли Apple отказаться от поддержки UIKit. Сейчас это выглядит маловероятным. SwiftUI становится лучше каждый год и будет на первом место, но UIKit остается надежным фреймворком и продолжает быть важной частью экосистемы.
Подписаться на канал:
Закрытый канал:
Please open Telegram to view this post
VIEW IN TELEGRAM