Как выглядит типичный рабочий день Swift-разработчика в 2025 году
Когда я только начинал изучать Swift, мне казалось, что iOS-разработчики — это такие волшебники, которые с утра до вечера пишут идеальный код под чашечку латте в стильном коворкинге. Реальность, как вы догадываетесь, оказалась прозаичнее, но не менее интересной.
В этой статье я проведу вас через типичный рабочий день Swift разработчика — от утреннего кофе до вечернего пуша в App Store. Вы увидите не только расписание, но и реальные фрагменты кода, с которыми мы сталкиваемся каждый день. Поехали!
09:00 — Утро: Daily Stand-up и планирование
День начинается с короткого стендапа в Zoom или Slack. Мы — команда из 5 iOS-разработчиков, двух бэкендеров и продакта. Каждый говорит по 1-2 минуты: что сделано вчера, что планируется сегодня, есть ли блокеры.
Типичный мой ответ звучит так:
- Вчера: Закончил рефакторинг модуля авторизации на Combine.
- Сегодня: Интегрирую новый экран профиля с SwiftUI, нужно сделать анимацию перехода.
- Блокер: Жду от дизайнера финальные мокапы для тёмной темы.
После стендапа — 15 минут на разбор сообщений в Jira и GitHub. Смотрю, какие PR требуют моего ревью, и читаю комментарии к своим пул-реквестам.
09:45 — Первая глубокая работа: Новый функционал
Утро — лучшее время для сложных задач. Сегодня я пишу модуль Push-уведомлений с deep linking. Вот как выглядит типичная структура класса-обработчика:
import UIKit
import UserNotifications
final class PushNotificationHandler: NSObject, UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
guard let deepLink = DeepLinkParser.parse(userInfo) else {
completionHandler()
return
}
Task { @MainActor in
Coordinator.shared.handle(deepLink)
}
completionHandler()
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
completionHandler([.banner, .sound, .badge])
}
}
Обратите внимание на Task { @MainActor in } — это современный способ форсировать выполнение на главном потоке без старомодного DispatchQueue.main.async. iOS разработчик сейчас активно использует Concurrency из Swift 5.5+.
11:00 — Code Review: Взгляд со стороны
Приходит уведомление от коллеги: «Посмотри PR, пожалуйста». Открываю GitHub, вижу изменения в модели данных. Вот пример того, что я часто комментирую:
// ❌ Плохо: force unwrap
let user = users.first!
// ✅ Хорошо: безопасное извлечение
guard let user = users.first else {
assertionFailure("Список users не должен быть пустым на этом этапе")
return
}
На code review мы обсуждаем не только корректность, но и архитектуру. Сегодня коллега использовал SwiftUI с @ObservableObject, но по нашему код-стайлу мы переходим на @Observable (iOS 17+). Делаю мягкое замечание:
// Рекомендуем переписать на @Observable
@Observable
final class ProfileViewModel {
var name: String = ""
var age: Int = 0
}
12:30 — Обед и переключение контекста
Обед — святое. Выхожу на улицу или готовлю сам. Стараюсь не смотреть в экран — глаза отдыхают. За обедом часто читаю телеграм-каналы по iOS разработке: «Swift by Sundell», «iOS Goodies», «Российский Swift». Это помогает быть в курсе новых фич.
Кстати, сегодня наткнулся на интересную статью про Swift Testing — новый фреймворк, который Apple представила как альтернативу XCTest. Записал себе в заметки попробовать на pet-проекте.
14:00 — Работа с базой данных: Core Data и SwiftData
Вторая половина дня — время для более рутинных, но важных задач. Сегодня нужно добавить кэширование в наш новостной дайджест. Мы используем CoreData с легковесной миграцией, но постепенно переходим на SwiftData (Swift 6).
Вот как выглядит типичный запрос на сохранение:
import CoreData
class NewsCacheManager {
private let context: NSManagedObjectContext
init(context: NSManagedObjectContext) {
self.context = context
}
func saveArticles(_ articles: [ArticleDTO]) throws {
for article in articles {
let cached = CachedArticle(context: context)
cached.id = article.id
cached.title = article.title
cached.url = article.url
cached.cachedAt = Date()
}
if context.hasChanges {
try context.save()
}
}
func fetchCachedArticles() -> [CachedArticle] {
let request = CachedArticle.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "cachedAt", ascending: false)]
request.fetchLimit = 50
do {
return try context.fetch(request)
} catch {
print("Ошибка чтения кэша: \(error)")
return []
}
}
}
Обратите внимание: я всегда передаю NSManagedObjectContext через инициализатор, а не создаю внутри. Это позволяет легко тестировать код и использовать viewContext для UI и backgroundContext для тяжелых операций.
15:30 — Тестирование и отладка
Написал код — напиши тесты. Это правило я выучил после того, как однажды забыл проверить граничные случаи, и приложение упало у пяти тысяч пользователей. Больше не повторяю.
Пишу unit-тест для нашего кэша:
import XCTest
@testable import MyNewsApp
final class NewsCacheManagerTests: XCTestCase {
var sut: NewsCacheManager!
var container: NSPersistentContainer!
override func setUp() {
super.setUp()
container = NSPersistentContainer(name: "NewsModel")
// Используем InMemoryStore для тестов
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { _, error in
XCTAssertNil(error)
}
sut = NewsCacheManager(context: container.viewContext)
}
func testSaveAndFetchArticles() throws {
let articles = [ArticleDTO(id: "1", title: "Тест", url: "https://example.com")]
try sut.saveArticles(articles)
let cached = sut.fetchCachedArticles()
XCTAssertEqual(cached.count, 1)
XCTAssertEqual(cached.first?.title, "Тест")
}
}
Запускаю тесты — зелёные. Чувствую себя уверенно. Теперь можно делать коммит.