Мобильный трудоголик – Telegram
Мобильный трудоголик
1.44K subscribers
65 photos
10 videos
290 links
Пишу простым языком об iOS разработке на Swift и мобильной разработке в целом.
Обо мне: https://news.1rj.ru/str/hardworkerIT/3
Чат: @hardworkerChatIT
Канал про разработку и жизнь в ИТ: @itDenisov
Вакансии по мобильной разработке: @mobileDevJobs
Download Telegram
🎨 Вышли официальные наборы компонентов дизайна iOS 26 и iPadOS 26.

Все обновленные компоненты и гайды доступны на сайте Apple.


🤔 Что внутри?

🔹 UI Kit (Figma, Sketch).
🔹 App Icon Template (Sketch, Photoshop, Illustrator, Figma).


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥18👍3👀1🗿11
🔢 Современные механизмы блокировок в Swift: Mutex и Synchronization Framework.

В Swift существует несколько способов защиты данных от гонки (data races): NSLock, DispatchSemaphore, последовательные очереди (DispatchQueue). Но на WWDC24 компания Apple представила новый Synchronization Framework, включающий современный Mutex – стандартизированную блокировку взаимного исключения.


🤔 В чем разница между Lock и Mutex?
🔸 Lock: общее название механизмов синхронизации (например, NSLock, os_unfair_lock).
🔸 Mutex: частный случай блокировки, гарантирующий, что только один поток может владеть ресурсом в данный момент.


Ключевая особенность Mutex:
🔹 Строгое владение – разблокировать может только тот поток, который заблокировал.
🔹 Поддержка Sendable, что делает его удобным для Swift Concurrency.


♣️ Пример использования.
Пример защиты счетчика:


final class Counter {
private let count = Mutex<Int>(0) // Обернули значение в Mutex

var currentCount: Int {
count.withLock { $0 } // Безопасное чтение
}

func increment() {
count.withLock { $0 += 1 } // Атомарная операция
}

func decrement() throws {
try count.withLock {
guard $0 > 0 else { throw Error.reachedZero }
$0 -= 1
}
}
}



🆚 Mutex vs Actor.
🔸 Actor: идеален для асинхронного доступа, но требует await.
🔸 Mutex: синхронный, подходит для legacy кода или когда нельзя использовать async/await.


⚠️ Когда выбирать Mutex?
🔸 Нужна мгновенная синхронная блокировка.
🔸 Работа с не Sendable типами (например NSBezierPath).
🔸 Интеграция с кодом, не поддерживающим Concurrency.


💡 Важно:
🔹 Доступен с iOS 18 и macOS 15.
🔹 Не заменяет Actor, а дополняет инструментарий для работы с многопоточностью.

Mutex из Synchronization Framework – это современный, безопасный и эффективный способ защиты данных в Swift.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21🔥1141🗿11
🔢 Принципы SOLID в Swift: полный гид с примерами.

Сегодня мы разберём 5 принципов SOLID, которые спасут ваш код от хаоса.

🤔 Что такое SOLID?

Это 5 принципов объектно-ориентированного дизайна, которые делают код:
🔸 Гибким к изменениям.
🔸 Понятным для других разработчиков.
🔸 Более тестируемым.

Акроним расшифровывается так:

1️⃣ S — Single Responsibility.

Один класс — одна зона ответственности. Пример:

// Неправильно. Класс делает все: логика, UI, сетевые запросы.
class ProfileViewController: UIViewController {
func loadUserData() {
URLSession.shared.dataTask(...) { [weak self] data in
let user = decode(data)
self?.nameLabel.text = user.name
self?.avatarImageView.image = UIImage(data: user.avatarData)
}.resume()
}
}

// Правильно. Разделяем ответственность.
class UserLoader {
func fetchUser() async throws -> User {
// Запросы к бд или серверу и логика обработки данных
}
}

class ProfileViewController: UIViewController {
private let loader = UserLoader()

func updateUI() async {
let user = try? await loader.fetchUser()
if let user {
nameLabel.text = user.name
avatarImageView.image = UIImage(data: user.avatarData)
}
}
}


2️⃣ O — Open-Closed.

Класс открыт для расширения, но закрыт для изменений. Пример:

// Неправильно. При добавлении нового типа ячейки придётся менять метод.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = data[indexPath.row]
if item is Photo {
return PhotoCell()
} else if item is Video {
return VideoCell() // Добавили новое условие
}
}

// Правильно. Один из вариантов правильного решения - решение через протокол. Новый тип ячейки = новая сущность, а не правки в коде.
protocol CellConfigurable {
static var reuseID: String { get }
func configure(with item: Any)
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = data[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: type(of: item).reuseID)
(cell as? CellConfigurable)?.configure(with: item)
return cell
}


3️⃣ L — Liskov Substitution.

Подклассы должны заменять родительские классы. Пример:

// Неправильно
class Bird {
func fly() {
print("Лететь!")
}
}

class Penguin: Bird {
override func fly() {
fatalError("Пингвины не летают!") // Ошибка
}
}

// Правильно
protocol Bird {
func move()
}

class Sparrow: Bird {
func move() {
print("Лететь!")
}
}

class Penguin: Bird {
func move() {
print("Плыть!")
}
}


4️⃣ I — Interface Segregation.

Клиенты не должны зависеть от интерфейсов, которые они не используют. Пример:

// Неправильно
protocol TableViewDelegate {
func didSelectRow(at indexPath: IndexPath)
func heightForRow(at indexPath: IndexPath) -> CGFloat
func willDisplayCell()
// ... 20+ методов
}

// Правильно: разделяем на мелкие протоколы, которые можно реализовывать при необходимости.
protocol TableViewSelectable {
func didSelectRow(at indexPath: IndexPath)
}

protocol TableViewSizable {
func heightForRow(at indexPath: IndexPath) -> CGFloat
}


5️⃣ D — Dependency Inversion.

Зависите от абстракций, а не от конкретных типов. Пример:

// Неправильно. Прямая зависимость от Firebase.
class AnalyticsService {
private let firebase = FirebaseAnalytics()

func track(event: String) {
firebase.log(event)
}
}

// Правильно. Гибкое решение.
protocol AnalyticsEngine {
func log(event: String)
}

class AnalyticsService {
private let engine: AnalyticsEngine // Зависим от протокола

init(engine: AnalyticsEngine) {
self.engine = engine
}

func track(event: String) {
engine.log(event)
}
}



💡 Вывод:
SOLID не просто теория, а практический инструмент для создания профессионального кода. Он требует больше времени на старте, но окупается с лихвой, так как упростит масштабируемость и тестирование проекта, позволит любому разработчику быстрее вникнуть в ваш код. Это как строить дом с фундаментом вместо хижины на песке.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1814🔥111
👨‍💻 Тестировщики в мобильной разработке: почему без них не обойтись даже в стартапах.

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


1️⃣ Разработчик ≠ тестировщик.

Мы, разработчики, часто смотрим на код через призму «Как это должно работать», а тестировщики — через «Как это может сломаться».

Пример:
Вы добавили красивый анимационный переход между экранами. Всё работает на iPhone 15 Pro. Но тестировщик проверяет на старом iPhone 8 и обнаруживает, что анимация лагает. Без него этот баг мог бы улететь в прод.


2️⃣ Они экономят деньги компании.

Один пропущенный критический баг может:
🔹 Испортить первый опыт пользователей (и они удалят приложение).
🔹 Привести к отказу от подписки (если что-то сломалось в платежах).
🔹 Потребовать хотфиксов (а это внеурочные работы для разработчиков).

По данным исследований, исправление бага на этапе тестирования может быть в 5-10 раз дешевле, чем после релиза.


3️⃣ Они видят то, что вы не замечаете.

Разработчик проверяет «работает ли фича», а тестировщик — «можно ли её сломать».

Пример:
🔹 Приложение падает, если сменить язык системы во время загрузки данных.
🔹 Кнопка «Купить подписку» не нажимается, если нажать её слишком быстро.
🔹 На iPad интерфейс «поехал» из-за неправильных констрейнтов.


4️⃣ Они помогают улучшить UX.

Хороший тестировщик не просто ищет баги — он оценивает удобство использования.

Пример:
🔹 «Эта модалка появляется слишком часто — пользователи будут раздражаться»
🔹 «Нет индикатора загрузки — люди думают, что приложение зависло».
🔹 «Текст кнопки слишком длинный и обрезается на маленьких экранах».


5️⃣ Даже в маленькой команде они нужны.

Многие стартапы думают: «Мы же agile, сами всё проверим». Но:
🔹 Разработчики не успевают тестировать всё вручную.
🔹 Автоматические тесты не покрывают все сценарии.
🔹 Без QA страдает качество, а плохие отзывы в App Store снижают конверсию.


⚠️ Как правильно работать с тестировщиками?

🔸 Не воспринимайте замечания как личную критику: это про продукт, не про вас.
🔸 Всегда запрашивайте детали: если баг сложно воспроизвести, попросите скриншоты/видео.
🔸 Цените их мнение: иногда они предлагают реально полезные доработки UX.


💡 Вывод:

Тестировщик — это не «лишний человек» в команде, а гарантия качества. Чем раньше он найдёт проблему, тем дешевле её исправить.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
22👍15🔥4🫡11
🔢 Паттерны проектирования в Swift: Синглтон.

Синглтон (Singleton) — это паттерн проектирования, который гарантирует, что у класса будет только один экземпляр на протяжении всего жизненного цикла приложения, и предоставляет к нему глобальную точку доступа.

Данный паттерн является одним из самых популярных и одновременно спорных паттернов в iOS разработке.

Вот как это работает на практике:
Когда вы создаёте синглтон, вы делаете его конструктор приватным, чтобы предотвратить создание дополнительных экземпляров извне, а единственный экземпляр храните в статическом свойстве. Swift автоматически обеспечивает ленивую инициализацию — объект создаётся только при первом обращении к этому свойству.

Например, если у вас есть класс AppConfig, который управляет настройками приложения, его можно реализовать как синглтон:


final class AppConfig {
static let shared = AppConfig()
private init() {}

var apiKey: String = "12345"
}


Такой подход удобен для объектов, которые должны быть доступны из разных частей программы, но при этом не должны дублироваться — например, для работы с настройками, сетевыми запросами или аналитикой.

В нашем коде есть серьезный недостаток, если в будущем понадобится подменить apiKey для тестов, возникнут сложности. Более гибкий вариант: использование протоколов:


protocol ConfigService {
var apiKey: String { get }
}

final class ProdConfig: ConfigService {
static let shared = ProdConfig()
private init() {}

let apiKey = "prod_12345"
}

class TestConfig: ConfigService {
let apiKey = "test_67890"
}


Теперь в проде используем ProdConfig.shared, а в тестах можем подсунуть объект TestConfig.


⚠️ Главная проблема синглтонов:
Они создают неявные зависимости. Представьте, у вас 20 классов, использующих AppConfig.shared. Вдруг понадобится сделать разные конфиги для разных пользователей. Придется переписывать половину приложения.

Поэтому я использую синглтоны только для логгеров и аналитики. Для всего остального лучше подходит dependency injection, потому что это делает зависимости прозрачными и даёт гибкость в управлении объектами.

♣️ Пример:


protocol APIServiceProtocol {
func fetchData()
}

class APIService: APIServiceProtocol {
func fetchData() {
}
}

class DataManager {
let apiService: APIServiceProtocol

init(apiService: APIServiceProtocol) {
self.apiService = apiService
}

func loadData() {
apiService.fetchData()
}
}



💡 Вывод:
Синглтон — мощный инструмент, но при неправильном использовании может принести больше вреда, чем пользы. Главная его опасность в том, что он создаёт скрытые зависимости, которые со временем превращают код в запутанный клубок. Лучше всего его использовать только для объектов, которые действительно должны существовать в единственном экземпляре.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
17🔥13👍2🤝11
🔢 SwiftUI: как отследить, что вызывает обновление View.

Когда View в SwiftUI неожиданно перерисовывается, это может сбивать с толку. Но есть простой способ понять, что именно вызвало обновление.


♣️ Добавьте в body вашей View:


var body: some View {
let _ = Self._printChanges()
// Ваш код
}


Данный код отобразит в консоли:

🔸 Какое свойство (@State, @ObservedObject и т.д.) вызвало обновление View.
🔸 Было ли обновление из-за изменения самой вьюхи.
🔸 Была ли смена её идентичности (@identity).


⚠️ Альтернативные способы отладки.

1️⃣ onAppear + print.


Text("Привет")
.onAppear {
print("View появилась")
}



2️⃣ Кастомный модификатор для вывода в консоль.


extension View {
func debugPrint(_ text: String) -> Self {
print(text)
return self
}
}

// Использование:
Circle()
.fill(.green)
.debugPrint("Круг отрисован")



💡 Вывод:

🔹 Self._printChanges() — лучший способ понять, почему перерисовывается View.
🔹 onAppear и кастомные модификаторы помогут отследить жизненный цикл вью.

Вот так просто можно понять, почему вьюхи в SwiftUI вдруг решили перерисоваться.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1914🔥22👀1
👨‍💻 Неамбициозные разработчики — удобно, но смертельно для карьеры.

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

Что скрыто за этим?
Компании заморозили найм и хочет минимизировать сложных сотрудников. Но так ли это выгодно самим разработчикам?


Почему стратегия «сиди и не высовывайся» проигрышная.

1️⃣ Знания устаревают — цена на рынке падает.
🔹 Сегодня SwiftUI и async/await — стандарт, а GCD уже называют «устаревшим».
🔹 Ценят боевой опыт, который принёс бизнесу деньги, а не пет проекты из 2018 года.

2️⃣ Зарплата и инфляция навыков.
Комфортная зона — это ловушка. Вот что происходит с зарплатой, когда ты отказываешься от роста:
🔹 Если не расти внутри компании, через 2-3 года окажешься ниже рынка.
🔹 Твой текущий стек дешевеет на 15-25% в год, пока рынок требует новых компетенций.
🔹 Сначала ты отказываешься от повышения — «мне и так норм». Потом компания замораживает индексацию — «кризис, потерпите».
🔹 HR давно ведут зарплатные матрицы. Твой «стабильный» грейд через 3 года сравняется с позицией джуниора.

3️⃣ Невидимость = риск.
🔹 ИТ — это эскалатор: если не идёшь вверх — скатываешься вниз.
🔹 Компании-лидеры рынка берут тех, кто решает сложные задачи, а не «стабильно тормозит».


🤔 Что же делать?
🔸 Разработчикам: постоянно прокачивайте экспертизу и смежные навыки.
🔸 Менеджерам: поощряйте рост — иначе через пару лет команда не сможет конкурировать.


💡 Вывод:
Выбор прост: остаться «удобным» и зависеть от настроений менеджмента или расти и диктовать свои условия.

ИТ любит тех, кто не стоит на месте!


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🗿26👍1841🤔1👀1
🔢 Retain Cycle в Swift: как не попасть в тонкую ловушку.

Retain cycle — одна из самых коварных проблем в Swift. Даже опытные разработчики могут не заметить, как создают цикл удержания ссылок, который мешает освобождению памяти. Разберём пример, который выглядит безобидно, но содержит скрытую угрозу.


⚠️ Что такое Retain cycle?
Retain Cycle (цикл удержания сильных ссылок) в Swift — это ситуация, когда два или более объекта удерживают друг друга через сильные ссылки (strong), не позволяя ARC (Automatic Reference Counting) освободить память. Это приводит к утечкам памяти, так как объекты никогда не освобождаются.


Проблемный код.


class MyClass {
var task: Task<Void, Never>?

init() {
task = Task { [weak self] in
guard let self else { return }

repeat {
self.performSomeWork() // <- Цикл удержания!
} while !Task.isCancelled
}
}

func performSomeWork() {
}

deinit {
print("deinit") // Не выполнится
task?.cancel()
}
}



🤔 Что не так?
🔸 guard let self создаёт сильную ссылку на весь блок Task.
🔸 Цикл repeat работает бесконечно, удерживая selfdeinit никогда не вызовется.


Как исправить?

1️⃣ Изменить область видимости сильной ссылки.
Перенесите guard внутрь цикла, чтобы ссылка освобождалась после каждой итерации:


task = Task { [weak self] in
repeat {
guard let self else { return }
self.performSomeWork() // Сильная ссылка только здесь
} while !Task.isCancelled
}


2️⃣ Использовать слабую ссылку везде.
Если метод не требует обязательного self:


task = Task { [weak self] in
repeat {
self?.performSomeWork() // слабая ссылка
} while !Task.isCancelled
}


3️⃣ Вручную разорвать цикл.
Вызовите cancel() вне deinit, например, при закрытии экрана:


task?.cancel()
task = nil



⚠️ Чего лучше избегать?
Явный захват метода performSomeWork:


task = Task { [performSomeWork] in
repeat {
performSomeWork() // Метод всё равно содержит неявный self
} while !Task.isCancelled
}


Методы экземпляра неявно захватывают self, даже если переданы как замыкание.


🤔 Как отлавливать такие проблемы?
🔸 Тесты: Добавьте проверку на deinit с задержкой.
🔸 Instruments: Используйте Leaks и Allocations для поиска утечки памяти и цикличных ссылок.
🔸 Логирование: Добавьте print в deinit для критичных объектов.


💡 Вывод:
🔹 [weak self] — не панацея. Всегда анализируйте область видимости сильных ссылок.
🔹 Для бесконечных задач (repeat, while) используйте локальный guard или явный cancel.
🔹 Пишите тесты на освобождение памяти — это сэкономит часы отладки.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍32🔥203🙏22😁1
🔢 Бекпорт onChange с oldValue для iOS 16 в SwiftUI.

Всем привет! Сегодня поделюсь полезным расширением для тех, кто поддерживает iOS 16 в своих проектах. В iOS 17 компания Apple добавила улучшенную версию onChange с доступом к старому значению, но как быть, если нужно поддерживать и более ранние версии?

Решение: backport-реализация, о которой расскажу ниже:


♣️ Создадим совместимый вариант valueChanged:


public extension View {
@available(iOS, deprecated: 17.0, message: "Используйте нативный onChange в iOS 17+")
@ViewBuilder func valueChanged<V>(
of value: V,
handler: @escaping (_ previous: V, _ current: V) -> Void
) -> some View where V: Equatable {
if #available(iOS 17, *) {
onChange(of: value, handler)
} else {
onChange(of: value) { [value] newValue in
handler(value, newValue)
}
}
}
}



🤔 Как это работает?

🔸 На iOS 17+ использует нативный onChange.
🔸 На iOS 16 эмулирует поведение через захват текущего значения.
🔸 Сохраняет одинаковый интерфейс для всех версий.


Добавим дополнительную возможность: отслеживание переходов.

Создадим метод для отслеживания смены конкретного значения:


public extension View {
func trackTransition<V>(
of value: V,
from initialValue: V,
action: @escaping () -> Void
) -> some View where V: Equatable {
valueChanged(of: value) { old, new in
if old == initialValue && new != initialValue {
action()
}
}
}
}



♣️ Пример использования:


struct ContentView: View {
@State private var score: Int = 0

var body: some View {
VStack {
Text("Score: \(score)")
Button("Increase") { score += 1 }
}
.valueChanged(of: score) { old, new in
print("Score changed from \(old) to \(new)")
}
.trackTransition(of: score, from: 0) {
print("Score started changing from zero!")
}
}
}



Главное преимущество этого подхода - единый код для всех поддерживаемых версий iOS без дублирования логики.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
28👍192🔥1🙏1
🔢 SwiftUI-Adapter: как поддерживать новые фичи SwiftUI и не терять совместимость.

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

Недавно я наткнулся на удобную Android-библиотеку, которая упрощает работу с разными версиями API и подумал: «Почему бы не сделать что-то подобное для SwiftUI?».

После этого родилась идея разработать инструмент, который избавит вас от бесконечных проверок #available и сделает код чище:

🔗 Ссылка на GitHub


🤔 Зачем это нужно?

Каждый раз, когда Apple выпускает новый модификатор в SwiftUI, нам приходится писать такие конструкции:

if #available(iOS 15.0, macOS 12.0, *) {
YourView()
.badge(5)
} else {
YourView()
}


SwiftUI-Adapter делает эту рутину за вас! Просто используйте единый синтаксис – проверки версий останутся под капотом:

YourView()
.adapter.badge(5)



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

🔹 Не повлияет на производительность: все проверки производятся на этапе компиляции.
🔹 Чистая кодовая база: больше никаких #available в каждом втором файле.
🔹 Простота интеграции: добавляется за пару минут через SPM.
🔹 Открытый исходный код: полная прозрачность, возможность вносить правки и участвовать в развитии.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
126👍164🔥22
👨‍💻 Крах IT-пузыря: как выжить в эпоху ИИ и сокращений.

Минцифры кричит о нехватке кадров, а компании массово режут штаты. ИИ пугает заменой разработчиков. Что на самом деле происходит?


⚠️ Корень проблем.

1️⃣ Пузырь лопнул.
🔹 2020-2022: нанимали «впрок», раздували команды.
🔹 2024: кредиты подорожали, инвесторы потребовали окупаемости.
🔹 Итог: 20% меньше вакансий, 26% больше резюме (данные с hh).

2️⃣ Театр менеджмента.
🔹 Раньше: 50 человек «перерисовывали кнопки», лишь бы были задачи.
🔹 Сейчас: берут только самых эффективных или обращаются к аутсорсу.

3️⃣ ИИ-революция.
🔹 Искусственный интеллект уже способен заменить низкоклассифицированных сотрудников.
🔹 Ожидается что к 2027 году ИИ будем способен полностью заменить сеньоров.


🤔 Как выжить?

1️⃣ Стать незаменимым.
🔹 Осваивать ИИ-инструменты (ChatCPT, Copilot, DeepSeek, Qwen Chat..).
🔹 Прокачивать продуктовое мышление.
🔹 Развивать soft skills.

2️⃣ Адаптироваться.
🔹 Мониторить реально востребованные навыки.
🔹 Развиваться до более высоких и незаменимых позиций.


💡 Вывод:
Рынок вовсе не умер, он повзрослел. Зарабатывать будут те, кто создаёт ценность, а не красивые отчёты.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🗿21👀137👍3🤯2
🔢 Dependency Injection: как правильно внедрять зависимости в SwiftUI.

Сегодня поговорим о Dependency Injection (DI) — мощный подход, который делает ваш код чище, тестируемым и гибким.


⚠️ Что такое Dependency Injection?

Это способ передачи зависимостей извне, вместо создания их внутри класса.

Без использования DI:


class MyViewModel {
private let service = MyService() // Зависимость создаётся внутри
}


С использованием DI:


class MyViewModel {
private let service: MyService

init(service: MyService) { // Зависимость передаётся извне
self.service = service
}
}



🤔 Зачем это нужно?

🔸 Тестируемость: можно подменять зависимости на моки.
🔸 Гибкость: легко менять реализации (например, для разных сборок).
🔸 Чистая архитектура: сущности не знают, как создаются их зависимости.
🔸 Масштабирование: удобное расширение функционала благодаря добавлению зависимостей.


Три способа внедрения зависимостей в SwiftUI:

1️⃣ Через инициализатор (Constructor Injection).


struct MyContentView: View {
let service: MyNetworkService

var body: some View {
Text(service.fetchText())
}
}

MyContentView(
service: MyNetworkService()
)


2️⃣ Через свойства (Property Injection).


class MyViewModel: ObservableObject {
var analytics: MyAnalyticsService? // Можно задать позже
}

let myViewModel = MyViewModel()
myViewModel.analytics = MyAnalyticsService()


3️⃣ Через .environmentObject (Environment).


@main
struct MyApp: App {
let service = MyNetworkService()

var body: some Scene {
WindowGroup {
MyContentView()
.environmentObject(service)
}
}
}

struct MyContentView: View {
@EnvironmentObject var service: MyNetworkService

var body: some View {
Text(service.fetchText())
}
}



💡 Вывод:

Dependency Injection — это не просто модный паттерн, а реальный способ сделать ваш код чище и гибче. Начните с малого — вынесите хотя бы одну зависимость наружу, и вы сразу заметите, насколько проще стало тестировать и менять логику.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
122👍115🔥3
🔢 @ViewBuilder: магия композиции в SwiftUI.

Всем привет! Сегодня разберём @ViewBuilder — один из ключевых механизмов SwiftUI, который делает наш код таким лаконичным и выразительным.


⚠️ Что такое @ViewBuilder?

Это специальный атрибут, который превращает несколько View в одну иерархию. Благодаря ему мы пишем так:

VStack {
Text("Привет")

Button("Нажми меня") {
// Действие
}

Image(systemName: "apple.logo")
}


Вместо такого кошмара:

VStack(content: {
TupleView(
Text("Привет"),

Button("Нажми меня") {
// Действие
},

Image(systemName: "swift")
)
})



🤔 Как это работает?

1️⃣ Автоматическая упаковка:

@ViewBuilder преобразует список вьюх в TupleView (для 2-10 элементов) или Group (если их больше).

2️⃣ Поддержка условий:

Может обрабатывать if, if let, switch:

@ViewBuilder
func header(isLoggedIn: Bool) -> some View {
if isLoggedIn {
Text("Добро пожаловать!")
} else {
Button("Войти") {
// Действие
}
}
}


3️⃣ Ограничения:

🔹 Нельзя использовать for напрямую (нужен ForEach).
🔹 Максимум 10 вьюх в одном блоке (но это ограничение обходится).


Продвинутые фишки:

🔸 Комбинирование с @resultBuilder.

Можно создать свой билдер для специфичных задач:

@resultBuilder
struct GridBuilder {
static func buildBlock(_ items: AnyView...) -> [AnyView] {
items
}
}


🔸 Обход ограничения в 10 вьюх.

Для этого стоит использовать Group:

@ViewBuilder
func megaView() -> some View {
Group {
Text("1")
...
Text("11") // Работает!
}
}



💡 Вывод:

@ViewBuilder — это мощный инструмент SwiftUI, который превращает декларативный код в иерархию View, делая интерфейсы гибкими и читаемыми.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
2218👍2🔥1🫡11
🔢 Result Builders в SwiftUI: как создать свой DSL.

Привет, друзья! В прошлый раз мы разбирали @ViewBuilder, а сегодня углубимся в его основу и поговорим про resultBuilder, который делает магию SwiftUI возможной.


⚠️ Что такое resultBuilder?

Result Builder (ранее назывался function builder) — это механизм, позволяющий:
🔹 Объединять элементы в единую структуру.
🔹 Создавать собственные мини-языки (DSL) прямо в Swift.
🔹 Писать декларативный код, как в SwiftUI.


🤔 Как работает в SwiftUI?

Когда вы пишете:

VStack {
Text("Привет")

Button("Нажми меня") {
// Действие
}
}


Под капотом @ViewBuilder (частный случай Result Builder) превращает это в TupleView.


Создаём свой Result Builder.

Сделаем билдер для объединения строк с отступами:

@resultBuilder
struct ParagraphBuilder {
// Объединяем строки через \n
static func buildBlock(_ components: String...) -> String {
components.map { "- \($0)" }.joined(separator: "\n")
}

// Поддержка if
static func buildOptional(_ component: String?) -> String {
component ?? ""
}
}

// Использование:
@ParagraphBuilder func makeList() -> String {
"Первый пункт"
"Второй пункт"

if true {
"Случайный пункт"
}
}

print(makeList())

/* Выведет:
- Первый пункт
- Второй пункт
- Случайный пункт
*/



🤔 Где ещё применяется?

1️⃣ Кастомные UI-билдеры.

Например, для декларативного описания таблиц:

@TableBuilder func buildTable() -> Table {
Row {
Cell("A1")
Cell("A2")
}
Row {
Cell("B1")
Cell("B2")
}
}


2️⃣ Конфигурация сетевых запросов:

@RequestBuilder func makeRequest() -> HTTPRequest {
Method(.get)
Path("users")
Query("page", "2")
}


3️⃣ Тестовые данные:

@TestDataBuilder func mockUser() -> User {
Name("Иван")
Age(30)

if isPremium {
Tier(.gold)
}
}



Ограничения:

🔸 Нет поддержки for-циклов (нужно реализовывать buildArray).
🔸 Сложная отладка (ошибки компиляции могут быть непонятными).
🔸 Лимит в 10 элементов (как у @ViewBuilder).


💡 Вывод:

Result Builder — это мощный инструмент Swift, позволяющий создавать собственные DSL-синтаксисы для декларативного описания логики. Он лежит в основе @ViewBuilder и открывает возможности для кастомных решений, но требует внимания к ограничениям вроде обработки условий и лимита элементов.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1982🔥1🙏11
👨‍💻 Дисциплина — главный навык разработчика.

В мобильной разработке (да и в IT в целом) недостаточно просто уметь писать код. Без дисциплины даже талантливый разработчик быстро теряет эффективность. Вот почему это так важно:


1️⃣ Дедлайны не ждут.

В разработке релизы требуют четкого планирования:
🔹 Изучение ТЗ.
🔹 Разработка.
🔹 Ревью кода.
🔹 Подготовка сборки для тестирование.
🔹 Тестирование на разных устройствах.
🔹 Отправка релиза на проверку в Apple App Store.

Если откладывать задачи на потом — страдает качество, а команда начинает работать в авральном режиме.


2️⃣ Техдолг копится незаметно.

«Сейчас сделаю костыль, потом перепишу». Проходит месяц и проект превращается в легаси-монстра. Дисциплинированный разработчик:
🔹 Старается писать чистый код с первого раза.
🔹 Рефакторит по мере возможности.
🔹 Не допускает «временных решений», которые становятся постоянными.


3️⃣ Самообучение требует системы.

Swift и экосистема Apple обновляются каждый год. Если учиться урывками:
🔹 Пропускаешь важные изменения (Concurrency, SwiftUI, новых API).
🔹 Отстаешь от рынка (и зарплатных ожиданий).

Рекомендую: выделять 2-3 часа в неделю на изучение нового и вести чек-лист актуальных технологий.


4️⃣ Баги любят хаос.

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


5️⃣ Карьера строится на привычках.

Разработчики, которые постоянно улучшают код, изучают новые технологии и следят за code style быстрее растут до мидлов/сеньоров и получают интересные офферы.


🤔 Как прокачать свою дисциплину?

🔸 Планируйте все свои задачи, в том числе и личные дела.
🔸 Разбивайте любую работу на этапы (не «сделать фичу», а «написать сетевой слой, сверстать UI, написать тесты»).
🔸 Автоматизируйте рутину (SwiftLint, CI/CD, юнит-тесты).


💡 Вывод:
Дисциплина ≠ скука. Это свобода от авралов, легаси-кода и профессионального застоя.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
31🔥12👍3👀11
🐳 Container: Linux-контейнеры на macOS без Docker.

Один из самых интересных анонсов WWDC25Container, новый инструмент для работы с Linux-контейнерами прямо на macOS. Больше не нужен Docker — теперь можно собирать, запускать и деплоить контейнеры нативно, используя Swift и Virtualization.framework.


⚠️ Что умеет Container:

🔸 Собирает OCI-совместимые образы (как Docker).
🔸 Работает на Apple Silicon (M1/M2/M3 и новее).
🔸 Поддерживает кросс-компиляцию (например, под amd64 для Fly.io).
🔸 Интегрируется с удалёнными реестрами (Docker Hub, Fly.io и др.).


Сборка и деплой Vapor-приложения:

1️⃣ Установка.

Установим Container через Homebrew:

brew install container
container system start # Запускаем сервисы



2️⃣ Сборка образа.

Допустим, у нас есть Vapor-приложение. Container умеет читать Dockerfile:

container build --tag my-app --file Dockerfile .



3️⃣ Локальный запуск.


container run my-app


Проверим IP контейнера:


container list



4️⃣ Деплой на Fly.io

Сначала аутентифицируемся:


container registry login registry.fly.io


Собираем образ под amd64 (Fly.io не поддерживает arm):

container build --tag registry.fly.io/my-app:latest --arch amd64 --file Dockerfile .


Пушим и деплоим:

container images push registry.fly.io/my-app:latest
fly deploy --image registry.fly.io/my-app:latest



🤔 Зачем это iOS-разработчику?

🔹 Тестирование: быстро поднимать моки API, базы данных.
🔹 CI/CD: изолированные среды для SwiftLint, Fastlane.
🔹 Воспроизведение багов: запуск специфичных версий окружения.
🔹 Обучение: новички могут развернуть среду за минуты.


Текущие ограничения:

🔸 Только Apple Silicon.
🔸 Долгая сборка под amd64 (~15-20 минут).
🔸 Нет аналога Docker Compose (но уже есть сторонние решения).


💡 Вывод:

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

Container — это не просто замена Docker. Это шаг к универсальности Swift-разработки, где один язык и экосистема работают на всех уровнях — от iOS до бэкенда.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2811🔥3👀11
♠️ XML в мобильной разработке: почему он всё ещё актуален?

Привет, друзья! Сегодня поговорим о XML — формате, который десятилетиями остаётся фундаментом для обмена данными, несмотря на популярность JSON. В мобильной разработке он встречается чаще, чем кажется!


🤔 Где XML используется в iOS/Android?

1️⃣ Манифесты и конфиги:

🔸 AndroidManifest.xml (основной файл конфигурации приложений на Android).
🔸 info.plist (аналог в iOS, хотя формально это не XML, но схожая структура).


2️⃣ Локализация:

В Android строки хранятся в strings.xml — это стандартный подход для поддержки множества языков.


3️⃣ Верстка UI:

В Android XML до сих пор основной язык для разметки экранов (хотя Jetpack Compose постепенно меняет это).


4️⃣ Сетевые запросы:

Некоторые API (особенно в корпоративном секторе) до сих пор используют XML вместо JSON.


5️⃣ Базы данных:

Например, Firebase Realtime Database поддерживает экспорт/импорт данных в XML.


Плюсы XML для мобильных разработчиков:

🔹 Стандартизация: идеален для строгих форматов (например, банковские транзакции).
🔹 Валидация: можно проверить структуру через XSD-схемы.
🔹 Читаемость: вложенность тегов делает данные понятными для человека.
🔹 Поддержка: работает на любом устройстве и ОС без дополнительных библиотек.


Минусы XML:

🔸 Громоздкость: больше символов, чем в JSON.
🔸 Сложность парсинга: требует больше ресурсов (но XMLParser в iOS и XmlPullParser в Android решают эту проблему).
🔸 Не для всего: не подходит для высоконагруженных real-time-приложений.


💡 Вывод:

XML — не «устаревший» формат, а инструмент для конкретных задач. Он остаётся важным форматом в мобильной разработке, особенно для Android-манифестов, локализации и работы с legacy-API. Несмотря на популярность JSON, его строгая структура и валидация делают XML незаменимым в корпоративных и банковских решениях.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍25👀12🗿21🔥1😁1
🔢 Навигация в SwiftUI: от основ до продвинутых техник.

SwiftUI предлагает несколько подходов к организации навигации и с выходом iOS 16 многое изменилось. Разберём ключевые стратегии, которые помогут вам создавать гибкие и поддерживаемые приложения.


1️⃣ Базовая навигация.

Самый простой способ — NavigationLink:

List(customers) { customer in
NavigationLink {
CustomerDetailScreen(customer: customer)
} label: {
Text(customer.name)
}
}


Минус: инициализация CustomerDetailScreen происходит для каждого элемента списка.
Альтернатива: navigationDestination:


List(customers) { customer in
NavigationLink(customer.name, value: customer)
}
.navigationDestination(for: Customer.self) { customer in
CustomerDetailScreen(customer: customer)
}


Плюс: более эффективно, так как обработка навигации централизована.


2️⃣ Динамическая маршрутизация.

Используйте enum для управления сложными сценариями:

enum Route: Hashable {
case dashboard
case detail(Customer)
}

@State private var routes: [Route] = []

NavigationStack(path: $routes) {
Button("Login") {
routes.append(.dashboard) // Программный переход
}
.navigationDestination(for: Route.self) { route in
switch route {
case .dashboard: DashboardView()
case .detail(let customer): CustomerDetailScreen(customer: customer)
}
}
}


🤔 Когда использовать:
🔹 После асинхронных операций (например авторизации).
🔹 Для глубоких ссылок (deeplinks).


3️⃣ Глобальная маршрутизация.

Чтобы избежать передачи routes через всю иерархию, вынесите логику в Observable класс:

@Observable
class Router {
var routes: [Route] = []

func navigate(to route: Route) {
routes.append(route)
}
}

// В корневом View:
.environment(Router())


Преимущества:
🔹 Доступ к навигации из любого места.
🔹 Чистый код без проброса состояний.


4️⃣ Навигация через Environment Values.

Вдохновляемся React и создаём свой navigate:

struct NavigateAction {
let action: (Route) -> Void

func callAsFunction(_ route: Route) {
action(route)
}
}

// Использование:
@Environment(\.navigate) private var navigate

Button("Войти") {
navigate(.dashboard)
}


⚠️ Почему это удобно:
🔸 Интуитивный синтаксис.
🔸 Подходит для сложных сценариев.


5️⃣ Навигация во вкладках (TabView).

Каждая вкладка должна иметь свой NavigationStack:

enum AppScreen: Hashable {
case patients, doctors
}

TabView {
PatientsScreen()
.tabItem {
Label("Пациенты", systemImage: "heart")
}

DoctorsScreen()
.tabItem {
Label("Врачи", systemImage: "stethoscope")
}
}


⚠️ Важно:
🔸 Не используйте общий Router для вкладок.
🔸 Каждый NavigationStack управляет своим стеком.


6️⃣ Модальные окна и навигация.

Модальные окна — это не часть стека навигации. Для них лучше использовать @State или отдельный сервис:

.sheet(isPresented: $showModal) {
ModalView()
}



💡 Вывод:
🔹 Для простых приложений — NavigationLink или navigationDestination.
🔹 Для сложных сценариев — глобальный Router + enum маршруты.
🔹 Для вкладок — отдельные NavigationStack на каждую вкладку.

Главное: не усложняйте архитектуру раньше времени. Начинайте с простого и масштабируйте по мере роста приложения.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍28🔥14421
👨‍💻 Почему хорошего кода недостаточно для успеха в разработке.

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


⚠️ Код ≠ ценность.

Бизнесу нужны результаты, а не строки кода. Можно написать идеальную архитектуру, но если она не решает реальную проблему компании — это пустая трата времени.

Пример из практики:
Команда месяц разрабатывала сложный файловый менеджер для загрузки изображений, а потом выяснилось, что пользователи (админы) просто заливают файлы через FTP. Код был безупречным, но абсолютно бесполезным.


🤔 Что действительно важно?


1️⃣ Понимание бизнес-задачи.

🔹 Прежде чем писать код, спросите: «Зачем это нужно?».
🔹 Если не понимаете цель — уточняйте! Иначе рискуете сделать лишнее.


2️⃣ Приоритеты.

🔹 Бизнесу часто нужен простой и быстрый фикс, а не «идеальная архитектура» которая займет месяцы разработки.
🔹 Решайте сначала то, что влияет на прибыль, а не то, что интересно технически.


3️⃣ Эффективность, а не объем кода.

🔹 Лучший код — тот, который не написан.
🔹 Иногда проще изменить процесс, чем автоматизировать бесполезный функционал.


Как это влияет на карьеру?

Разработчиков ценят не за знание фреймворков, а за:
🔸 Умение решать реальные проблемы.
🔸 Надежность (попадание в сроки).
🔸 Понимание бизнес-метрик.


Хороший код — это лишь инструмент, а настоящая ценность разработчика в умении решать бизнес-задачи. Если ваш код не приносит прибыль, экономит время или улучшает пользовательский опыт, он бесполезен, каким бы элегантным ни был.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍24👀147🔥21
🔢 @MainActor в Swift: как избежать багов с UI.

Сегодня разберём @MainActor — один из самых важных атрибутов для iOS-разработки, который спасает от случайных крашей при обновлении интерфейса.


⚠️ Что такое @MainActor?

🔸 Маркер, гарантирующий выполнение кода в главном потоке.
🔸 Обязательное требование для всех операций с UI в SwiftUI/UIKit.
🔸 Часть системы акторов (Swift Concurrency).


🤔 Как он работает?

Автоматическая диспетчеризация.
Помеченный код всегда выполняется на главном потоке:


@MainActor
func updateUI() {
label.text = "Обновлено!" // Безопасное обновление UI
}


Для классов.
Можно пометить весь класс, если он работает с интерфейсом:


@MainActor
class UserProfileViewModel: ObservableObject {
@Published var name: String = "" // Изменения в главном потоке
}



Ошибки без @MainActor.


Task {
let data = await loadData() // Фоновое выполнение
noscriptLabel.text = data.noscript // Краш: попытка обновить UI не из главного потока
}



Решение:


Task {
let data = await loadData()

await MainActor.run { // Переключение на главный поток
noscriptLabel.text = data.noscript
}
}



♣️ Особенности в SwiftUI.

body уже на главном потоке.
Нет нужды дополнительно маркировать:


struct ContentView: View {
var body: some View { // Автоматически @MainActor
...
}
}


MVVM и ObservableObject.
Лучше явно пометить ViewModel при помощи @MainActor:


@MainActor
class SettingsViewModel: ObservableObject {
@Published var theme: Theme = .light
}



💡 Вывод:

@MainActor — это важный инструмент для безопасной работы с UI, который автоматически гарантирует выполнение кода в главном потоке. Он особенно полезен в SwiftUI и UIKit, предотвращая случайные краши при обновлении интерфейса из фоновых задач.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
25👍1442🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
✍️ Анимируем SF Symbols 7: новые возможности.

С выходом SF Symbols 7 работать с анимацией иконок стало проще, чем когда-либо. Apple добавила мощные API, которые открывают новые возможности для визуальных эффектов.


🔹 Draw On / Draw Off:

Теперь можно анимировать появление и исчезновение символа постепенно, штрих за штрихом. Это создаёт плавные и стильные переходы — идеально для интерактивных элементов интерфейса.

🔹 Progress Draw:

Анимация на основе variableValue позволяет «прорисовывать» символ в зависимости от прогресса. Например, можно визуализировать загрузку, заполнение или поэтапное выполнение задачи.

🔹 Magic Replace:

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

🔹 Gradients:

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


🔗 Подробнее на сайте Apple


💡 Вывод:

Новые фичи в SF Symbols 7 позволяют создавать более динамичные и приятные интерфейсы без сложного кода.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍2311🔥111