На WWDC25 Apple представила новые API для работы с AttributedString в SwiftUI, позволяющие создавать мощные текстовые редакторы с гибким форматированием.
Готовые стили:
🔹 Поддержка жирного текста, курсива, подчеркивания, зачеркивания.
🔹 Изменение размера шрифта и цвета текста.
Кастомные атрибуты:
🔹 Можно добавлять собственные правила форматирования.
Безопасное редактирование:
🔹 AttributedTextValueConstraint ограничивает допустимые атрибуты.
🔹 TextEditor автоматически применяет заданные правила.
Динамическое обновление:
🔹 SwiftUI корректно обрабатывает изменения текста и выделения.
🔸 Редакторы статей/рецептов (как в демо WWDC).
🔸 Приложения для заметок с расширенным форматированием.
🔸 Чат-боты с поддержкой стилизованного текста.
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍23🔥9🙏7👀1
Ещё недавно компании боролись за разработчиков, а сегодня на одну вакансию — сотни резюме.
Сегодня вакансий меньше, конкуренция выше, а компании стали жестче отбирать кандидатов. Но хорошие офферы никуда не делись — просто теперь нужно действовать умнее.
🔹 Важно иметь минимум 3 года коммерческого опыта (фриланс и пет-проекты считаются, но котируются меньше).
🔹 Цифры и факты вместо общих фраз. Не «оптимизировал приложение», а «снизил потребление памяти на 25%».
🔹 Если есть пробелы в опыте работы, лучше их закрыть фрилансом или пет проектами.
🔹 Проверьте резюме через ChatGPT или hh-аналитику — даже мелкие ошибки могут отсеять вас.
🔹 Приложите портфолио к резюме. Примеры ваших работ позволят оценить ваш опыт.
🔹 Будьте готовы к лайвкодингу. Без умения решать задачи на глазах у интервьюера шансы равны нулю.
🔹 Тренируйте алгоритмы (LeetCode должен решаться на автомате).
🔹 Перед собеседованием обязательно ищите слитые разборы вопросов по компании (телеграм чаты, форумы, статьи в интернете).
Жирные офферы получают специалисты, умеющие продать себя. Разработчиков много, поэтому берут тех, кого видно. Вот несколько советов, как стать заметнее:
🔹 Ведите технический блог (даже 1 статья в месяц — уже очень хорошо).
🔹 Участвуйте в опенсорс-проектах (это новый must-have для middle+).
🔹 Делайте разборы кейсов в LinkedIn (работодатели это проверяют).
🔹 Выступайте публично, на ИТ мероприятиях или онлайн конференциях.
Медийность = доверие. Если о вас знают, шанс получить оффер гораздо выше.
🔹 Рынок работодателя: вакансий меньше, чем кандидатов.
🔹 Жалобы не помогут: либо адаптируешься, либо останешься без оффера.
🔹 Компании ищут готовых специалистов: доучиваться за их счет не больше не выйдет.
🔹 Удалёнка для джунов почти умерла: готовьтесь к гибридному формату.
🔹 Зарплаты стабилизировались: зп 400к для мидлов осталась в 2023 году.
🔹 Слабые отсеиваются: но те, кто вкатывает, продолжают брать топовые предложения.
Да, рынок уже не тот, но офферы всё ещё есть — их просто получают те, кто готовится лучше других и вкладывается в свою экспертность.
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
❤30🙏13👍8👀1
NavigationStack — это новый (с iOS 16+) и более мощный способ организации навигации в SwiftUI. В отличие от NavigationView, он дает полный контроль над стеком экранов и поддерживает сложные сценарии.
🔹 Программное управление стеком экранов (пуш, поп, переход к корню).
🔹 Глубокая навигация по ссылкам (например, app://products/123).
🔹 Кастомные переходы между экранами.
🔹 Поддержка NavigationPath для хетерогенных данных в стеке.
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List {
NavigationLink("Экран 1", value: 1)
NavigationLink("Экран 2", value: "Текст")
}
.navigationDestination(for: Int.self) { value in
Text("Число: \(value)")
}
.navigationDestination(for: String.self) { value in
Text(value)
}
}
}
}
Тип-обертка для управления стеком. Может содержать разные типы данных:
path.append(1)
path.append("ABC")
path.removeLast()
Определяет, какой экран показывать для каждого типа данных:
.navigationDestination(for: Product.self) { product in
ProductView(product: product)
}
Button("Перейти к экрану 3") {
path.append(3)
}
Button("Назад") {
path.removeLast()
}
Button("В корень") {
path.removeLast(path.count)
}
Можно привязать навигацию к URL:
NavigationStack(path: $path) {
}
.onOpenURL { url in
if url.path == "/products/123" {
path.append(Product(id: 123))
}
}
🔸 Для iOS 15 и ниже используйте NavigationView.
🔸 Для модальных окон — sheet или fullScreenCover.
🔸 Не смешивайте NavigationStack и NavigationView.
NavigationStack — это эволюция навигации в SwiftUI. Он идеален для приложений со сложной навигацией, программного управления историей экранов и лубоких ссылок.
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍28🔥12🤝6❤1🫡1 1
This media is not supported in your browser
VIEW IN TELEGRAM
Установите бета-версию macOS Tahoe и Xcode 26, если хотите попробовать эту функцию.
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥29❤12👍8🤯1 1
Universal Links — это удобный способ открывать контент вашего приложения прямо из веб-ссылок. Они обеспечивают более плавный пользовательский опыт и упрощают обмен контентом.
🔹 На вашем сервере должен быть доступен файл apple-app-site-association в формате JSON.
🔹 В нём указываются appID вашего приложения и разрешённые пути (paths).
{
"applinks": {
"details": [
{
"appID": "TEAMID.hardworker.it",
"paths": [
"/shop",
"/card",
"/profile"
]
}
]
}
}
Где appID указывается в формате teamId.bundleId. paths содержит пользовательские пути, которые должны обрабатываться в приложении.
🔹 Добавьте Associated Domains в Capabilities.
🔹 Укажите ваш домен с префиксом applinks:, например:
applinks:your-domain.com
🔹 Откройте ссылки на реальном устройстве (в Notes, Safari, через сообщения или Telegram).
🔹 Иногда требуется переустановка приложения или перезагрузка устройства, чтобы изменения вступили в силу.
Ожидается обработка следующих ссылок:
https://your-domain.com/shop
https://your-domain.com/card
https://your-domain.com/profile
Universal Links делают навигацию удобнее, а ваш сервис — доступнее.
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍27❤9🔥5 2🙏1
Недавно Дэвид Ханссон (создатель Ruby on Rails) в интервью Лексу Фридману раскрыл два ключевых критерия при найме:
🔹 Качество кода в тестовом задании.
🔹 Мотивационное письмо.
Казалось бы — ничего нового. Но давайте разберём, почему именно эти пункты стали критически важными в 2025 году.
С появлением ИИ ассистентов вроде GitHub Copilot и ChatGPT работодатели стали скептически относиться к идеально написанным, но безликим решениям.
🔹 Стиль кода (если он похож на шаблонный код от ИИ — это красный флаг).
🔹 Неочевидные решения (вас обязательно спросят: «Почему выбрали именно это решение?»).
🔹 Коммиты (история изменений показывает ход ваших мыслей).
В 2025 году резюме без сопроводительного письма — как код без комментариев: вроде бы всё понятно, но чего-то не хватает. HR-ы и тимлиды признаются: «Письмо решает, кого пригласить, когда 100+ кандидатов с одинаковым опытом».
🔸 Показывает вашу человечность: ИИ пишет резюме, но не может искренне объяснить, почему вы мечтаете работать в этой компании.
🔸 Раскрывает soft skills: Удалённая работа требует чёткой письменной коммуникации.
🔸 Компенсирует недостатки резюме: Нет коммерческого опыта или пробелы в резюме? Расскажите о причинах и пет проектах.
🔹 Персонализация: общие письма для всех компаний не работаю, нужна привязка к конкретной компании, куда вы откликаетесь.
🔹 Почему вы? Привяжите свой опыт к конкретной боли компании, которую необходимо решить.
🔹 Почему они? Докажите что вы реально заинтересованы в работе в данной компании, знакомы с ее историей, пользуетесь их продуктами и знакомы с технологиям, которые в ней используются.
Хорошее письмо — это ваш голос в тишине. В 2025 году его отсутствие равноценно фразе «Мне всё равно, куда устраиваться».
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
❤20👍16🔥9👀1 1
Swift предлагает удобные протоколы для преобразования объектов в JSON и обратно. Разберём, как это работает.
🔹 Decodable: только декодирование (JSON → объект).
🔹 Encodable: только кодирование (объект → JSON).
🔹 Codable: и то, и другое (typealias для Decodable & Encodable).
struct User: Codable {
var name: String
var age: Int
}
Используем JSONDecoder:
let json = """
{
"name": "Иван",
"age": 30
}
"""
guard let jsonData = json.data(using: .utf8) else { return }
do {
let user = try JSONDecoder().decode(User.self, from: jsonData)
print(user.name) // "Иван"
} catch {
print("Ошибка: \(error)")
}
🔸 Имена свойств должны совпадать с ключами JSON (или использовать CodingKeys).
🔸 Типы данных должны соответствовать.
Используем JSONEncoder:
let user = User(name: "Мария", age: 25)
do {
let jsonData = try JSONEncoder().encode(user)
let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString ?? "")
// {"name":"Мария","age":25}
} catch {
print("Ошибка: \(error)")
}
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // Красивый вывод
encoder.dateEncodingStrategy = .iso8601 // Формат даты
Если имена в JSON не совпадают со свойствами:
struct Product: Codable {
var productName: String
var price: Double
enum CodingKeys: String, CodingKey {
case productName = "product_name_title"
case price
}
}
JSON:
{
"user": {
"name": "Анна",
"age": 28
}
}
Swift:
struct Response: Codable {
var user: User
}
struct User: Codable {
var name: String
var age: Int
}
struct Profile: Codable {
var name: String
var bio: String? // Опциональное поле
}
Если поля нет в JSON, свойство получит значение nil.
Пропускаем ненужные свойства:
struct Settings: Codable {
var theme: String
var fontSize: Int
// Не включаем в Codable
var temporaryFlag: Bool = false
}
Для сложных случаев можно реализовать методы вручную:
struct Custom: Codable {
var id: UUID
var tags: [String]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
tags = try container.decode([String].self, forKey: .tags)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(tags, forKey: .tags)
}
}
🔸 Используйте Codable для большинства задач.
🔸 Настраивайте сложные случаи через CodingKeys.
🔸 Для нестандартных форматов реализуйте методы вручную.
Теперь вы легко сможете работать с любым JSON в Swift!
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
❤26🙏19👍5 1 1
Компания Apple анонсировала новый Retention Messaging API, который поможет разработчикам снизить отток подписчиков. Теперь можно гибко настраивать сообщения для пользователей, которые хотят отменить подписку.
С помощью данного API можно показывать 4 типа сообщений:
🔹 Текстовое: напоминание о преимуществах подписки.
🔹 Текст + изображение: визуальное убеждение.
🔹 Смена тарифа: предложение перейти на другой тарифный план.
🔹 Промо-оффер: скидка или спецусловия.
Сообщения можно адаптировать под локализацию и продукт, а также менять в реальном времени через серверные запросы.
🔸 Пользователь нажимает «Отменить подписку».
🔸 Система запрашивает у вашего сервера подходящее сообщение (или использует стандартное).
🔸 Пользователь видит предложение: оставить подписку, перейти на другой тариф или воспользоваться скидкой.
🔹 Загрузите тексты и изображения в App Store Connect.
🔹 Настройте дефолтные сообщения (текст, либо текст + картинка).
🔹 Реализуйте серверный endpoint для динамического выбора сообщений.
Retention Messaging API — это не просто новая фича, а стратегический инструмент для монетизации. Вместо пассивного наблюдения за оттоком подписчиков вы получаете:
🔸 Контроль над последним touchpoint перед отменой.
🔸 Данные для анализа поведения пользователей.
🔸 Рычаг влияния на ключевое бизнес-метрики.
Теперь ваша реакция на отток — не постфактум, а в момент принятия пользователем решения.
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
❤26🔥19👍8 3
Очень интересный канал про IOS разработку 👉 @error_nil, в основном все в формате видеоуроков, много полезного материала, интересные обсуждения, XCode проекты и тд, советую подписаться 👍
👍10❤4😁3🔥1🤔1
С выходом Swift Testing многие задумались о миграции с XCTest. Но так ли всё гладко, как обещает Apple? Давайте разберём реальные подводные камни и лайфхаки для перехода.
🔹 XCTest: использует XCTestCase + Objective-C runtime.
🔹 Swift Testing: макросы
@Test + Swift ориентированный подход.🔹 XCTest: выполняется последовательно (один за другим).
🔹 Swift Testing: выполняется параллельно (каждый тест в отдельной Task).
Разница в коде:
// XCTest
func testLogin() { XCTAssertEqual(result, expected) }
// Swift Testing
@Test func login() { #expect(result == expected) }
Swift Testing требует, чтобы проверки (
#expect) выполнялись только внутри Task.// Старая схема (XCTest)
func testAsync() {
DispatchQueue.main.async {
XCTAssertEqual(result, 42)
}
}
// Неправильная миграция (упадёт в Swift Testing)
@Test func asyncTest() {
DispatchQueue.main.async {
#expect(result == 42) // Ошибка: вне контекста Task
}
}
// Правильная миграция
@Test func asyncTest() async { // 1. Объявляем async тест
let result = await withCheckedContinuation { continuation in
DispatchQueue.main.async {
continuation.resume(returning: 42) // 2. Возвращаем результат в контекст Task
}
}
#expect(result == 42) // 3. Проверяем УЖЕ внутри Task-контекста
}
Ключевые моменты:
🔸 Тест должен быть async — это автоматически создаёт контекст Task.
🔸 Все асинхронные операции, которые не поддерживают async/await оборачиваются в withCheckedContinuation.
🔸
#expect вызывается после получения результата, но внутри области теста.Параллельное выполнение тестов могут вызвать гонки данных.
actor Counter {
var count = 0
func increment() {
count += 1
}
}
@Test func testA() async {
await Counter.shared.increment()
#expect(await Counter.shared.count == 1) // Может упасть, если testB запустится раньше!
}
@Test func testB() async {
await Counter.shared.increment()
}Использование
@Suite(.serialized) для последовательного выполнения:@Suite(.serialized)
struct CriticalTests {
@Test func testA() {
}
@Test func testB() {
}
}
В XCTest можно было наследовать XCTestCase и автоматически получать все тесты родителя. В Swift Testing так не работает — тесты нужно дублировать.
Что делать:
🔸 Для простых сценариев использовать параметризованные тесты.
🔸 Для сложных, пока оставьте XCTest и мигрируйте постепенно.
На Swift Testing стоит переходить если у вас современный проект, который использует Swift Concurrency и вам необходимо параллельное выполнение тестов.
Мой совет: Начинайте с гибридного подхода — подключайте Swift Testing для новых модулей, а старые тесты оставляйте в XCTest.
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
❤26👍19 3🔥2
Друзья, привет! Давайте поговорим о том, как мы сами иногда мешаем своей карьере в разработке. Я замечал это и на себе, и на других — есть пять типичных ошибок, которые тормозят рост.
«Сначала изучу весь SwiftUI, потом начну писать код» — знакомо?
Вспоминаю, как сам начинал: вместо того чтобы бесконечно изучать теорию, я просто пробовал писать код. Да, он был кривой, да, с ошибками, но это работало! Сейчас многие застревают на этапе «сначала выучу всё идеально, потом начну». Так не бывает. Лучший способ научиться: делать, ошибаться и исправлять.
«Мой друг-джун сказал, что VIPER — это мусор».
Когда я только начинал, один «опытный» знакомый уверял меня, что Swift — это модно только для мажоров, а за Java будущее и он будет использоваться везде вечно. Хорошо, что я не поверил. Часто советы дают те, кто сам ещё мало что понимает. Ищите советов среди тех, кто реально добился результатов в вашей сфере.
«Мой код идеален, это тимлид ничего не понимает!»
Да, неприятно, когда говорят, что твой код плох. Но именно так и растёшь! Я научился воспринимать замечания не как личное оскорбление, а как бесплатный урок. Заводил даже отдельный блокнот, куда записывал все конструктивные замечания — очень помогает не наступать на те же грабли.
«Сегодня устал, завтра начну».
Сколько раз вы откладывали изучение новой технологии, потому что «еще есть время»? Или обещали себе дописать пет проект, но в итоге смотрели сериалы? Я сам через это проходил. Правда в том, что идеального момента не будет никогда. Каждый отложенный месяц — это упущенные возможности, проекты и даже зарплата. Технологии меняются быстрее, чем мы собираемся их изучить.
«Лучше синица в руках, чем журавль в небе. Останусь там, где хоть что-то платят».
🔹 Страх сменить проект: «а если новый окажется хуже».
🔹 Боязнь попросить повышение: «а вдруг откажут».
🔹 Страх откликнуться на интересную вакансию: «там же нужно будет учить новое!».
Я сам больше года не решался сменить работу, потому что был страх неопределенности, хотя все вокруг на рынке зарабатывали больше меня и работали над современными, интересными проектами.
Эти страхи кажутся разумными только в нашей голове. Со стороны они выглядят именно тем, чем и являются — отговорками, которые мешают нам расти.
В IT безопаснее прыгнуть в неизвестность, чем оставаться в зоне комфорта. Каждый раз, когда вы говорите «еще не готов», кто-то другой говорит «да» и получает ваши потенциальные бенефиты.
Главное, что я понял: карьера строится не на гениальных озарениях, а на постоянных итерациях: попробовал, получил фидбек, исправил, повторил. Чем чаще этот цикл, тем быстрее рост.
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
❤26👍16👀2🔥1🗿1 1
🎨 Вышли официальные наборы компонентов дизайна iOS 26 и iPadOS 26.
Все обновленные компоненты и гайды доступны на сайте Apple.
🤔 Что внутри?
🔹 UI Kit (Figma, Sketch).
🔹 App Icon Template (Sketch, Photoshop, Illustrator, Figma).
➡️ Подписаться на канал
Мобильный трудоголик
Все обновленные компоненты и гайды доступны на сайте 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🗿1 1
В Swift существует несколько способов защиты данных от гонки (data races): NSLock, DispatchSemaphore, последовательные очереди (DispatchQueue). Но на WWDC24 компания Apple представила новый Synchronization Framework, включающий современный Mutex – стандартизированную блокировку взаимного исключения.
🔸 Lock: общее название механизмов синхронизации (например, NSLock, os_unfair_lock).
🔸 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.
🔸 Нужна мгновенная синхронная блокировка.
🔸 Работа с не Sendable типами (например NSBezierPath).
🔸 Интеграция с кодом, не поддерживающим Concurrency.
🔹 Доступен с iOS 18 и macOS 15.
🔹 Не заменяет Actor, а дополняет инструментарий для работы с многопоточностью.
Mutex из Synchronization Framework – это современный, безопасный и эффективный способ защиты данных в Swift.
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21🔥11 4❤1🗿1 1
Сегодня мы разберём 5 принципов SOLID, которые спасут ваш код от хаоса.
Это 5 принципов объектно-ориентированного дизайна, которые делают код:
🔸 Гибким к изменениям.
🔸 Понятным для других разработчиков.
🔸 Более тестируемым.
Акроним расшифровывается так:
Один класс — одна зона ответственности. Пример:
// Неправильно. Класс делает все: логика, 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)
}
}
}
Класс открыт для расширения, но закрыт для изменений. Пример:
// Неправильно. При добавлении нового типа ячейки придётся менять метод.
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
}
Подклассы должны заменять родительские классы. Пример:
// Неправильно
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("Плыть!")
}
}
Клиенты не должны зависеть от интерфейсов, которые они не используют. Пример:
// Неправильно
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
}
Зависите от абстракций, а не от конкретных типов. Пример:
// Неправильно. Прямая зависимость от 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
👍18❤14🔥1 1 1
Когда в команде появляется тестировщик, некоторые разработчики думают: «Ну вот, теперь будут придираться к каждой мелочи». Но на самом деле хороший QA — это не враг, а ваш главный союзник в создании качественного приложения и сейчас я расскажу почему.
Мы, разработчики, часто смотрим на код через призму «Как это должно работать», а тестировщики — через «Как это может сломаться».
Пример:
Вы добавили красивый анимационный переход между экранами. Всё работает на iPhone 15 Pro. Но тестировщик проверяет на старом iPhone 8 и обнаруживает, что анимация лагает. Без него этот баг мог бы улететь в прод.
Один пропущенный критический баг может:
🔹 Испортить первый опыт пользователей (и они удалят приложение).
🔹 Привести к отказу от подписки (если что-то сломалось в платежах).
🔹 Потребовать хотфиксов (а это внеурочные работы для разработчиков).
По данным исследований, исправление бага на этапе тестирования может быть в 5-10 раз дешевле, чем после релиза.
Разработчик проверяет «работает ли фича», а тестировщик — «можно ли её сломать».
Пример:
🔹 Приложение падает, если сменить язык системы во время загрузки данных.
🔹 Кнопка «Купить подписку» не нажимается, если нажать её слишком быстро.
🔹 На iPad интерфейс «поехал» из-за неправильных констрейнтов.
Хороший тестировщик не просто ищет баги — он оценивает удобство использования.
Пример:
🔹 «Эта модалка появляется слишком часто — пользователи будут раздражаться»
🔹 «Нет индикатора загрузки — люди думают, что приложение зависло».
🔹 «Текст кнопки слишком длинный и обрезается на маленьких экранах».
Многие стартапы думают: «Мы же agile, сами всё проверим». Но:
🔹 Разработчики не успевают тестировать всё вручную.
🔹 Автоматические тесты не покрывают все сценарии.
🔹 Без QA страдает качество, а плохие отзывы в App Store снижают конверсию.
🔸 Не воспринимайте замечания как личную критику: это про продукт, не про вас.
🔸 Всегда запрашивайте детали: если баг сложно воспроизвести, попросите скриншоты/видео.
🔸 Цените их мнение: иногда они предлагают реально полезные доработки UX.
Тестировщик — это не «лишний человек» в команде, а гарантия качества. Чем раньше он найдёт проблему, тем дешевле её исправить.
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
❤22👍15🔥4🫡1 1
Синглтон (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🤝1 1
Когда View в SwiftUI неожиданно перерисовывается, это может сбивать с толку. Но есть простой способ понять, что именно вызвало обновление.
var body: some View {
let _ = Self._printChanges()
// Ваш код
}
🔸 Какое свойство (
@State, @ObservedObject и т.д.) вызвало обновление View.🔸 Было ли обновление из-за изменения самой вьюхи.
🔸 Была ли смена её идентичности (
@identity).
Text("Привет")
.onAppear {
print("View появилась")
}
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
👍19❤14🔥2 2👀1
В интернете идет обсуждение, что не следует нанимать ярких и известных разработчиков, а следует нанимать тех, кто не хочет роста, не выступает на конфах и молча делает свою работу.
Что скрыто за этим?
Компании заморозили найм и хочет минимизировать сложных сотрудников. Но так ли это выгодно самим разработчикам?
🔹 Сегодня SwiftUI и async/await — стандарт, а GCD уже называют «устаревшим».
🔹 Ценят боевой опыт, который принёс бизнесу деньги, а не пет проекты из 2018 года.
Комфортная зона — это ловушка. Вот что происходит с зарплатой, когда ты отказываешься от роста:
🔹 Если не расти внутри компании, через 2-3 года окажешься ниже рынка.
🔹 Твой текущий стек дешевеет на 15-25% в год, пока рынок требует новых компетенций.
🔹 Сначала ты отказываешься от повышения — «мне и так норм». Потом компания замораживает индексацию — «кризис, потерпите».
🔹 HR давно ведут зарплатные матрицы. Твой «стабильный» грейд через 3 года сравняется с позицией джуниора.
🔹 ИТ — это эскалатор: если не идёшь вверх — скатываешься вниз.
🔹 Компании-лидеры рынка берут тех, кто решает сложные задачи, а не «стабильно тормозит».
🔸 Разработчикам: постоянно прокачивайте экспертизу и смежные навыки.
🔸 Менеджерам: поощряйте рост — иначе через пару лет команда не сможет конкурировать.
Выбор прост: остаться «удобным» и зависеть от настроений менеджмента или расти и диктовать свои условия.
ИТ любит тех, кто не стоит на месте!
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🗿26👍18❤4✍1🤔1👀1
Retain cycle — одна из самых коварных проблем в Swift. Даже опытные разработчики могут не заметить, как создают цикл удержания ссылок, который мешает освобождению памяти. Разберём пример, который выглядит безобидно, но содержит скрытую угрозу.
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 работает бесконечно, удерживая self → deinit никогда не вызовется.
Перенесите guard внутрь цикла, чтобы ссылка освобождалась после каждой итерации:
task = Task { [weak self] in
repeat {
guard let self else { return }
self.performSomeWork() // Сильная ссылка только здесь
} while !Task.isCancelled
}
Если метод не требует обязательного self:
task = Task { [weak self] in
repeat {
self?.performSomeWork() // слабая ссылка
} while !Task.isCancelled
}
Вызовите 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🔥20❤3🙏2 2😁1
Всем привет! Сегодня поделюсь полезным расширением для тех, кто поддерживает iOS 16 в своих проектах. В iOS 17 компания Apple добавила улучшенную версию onChange с доступом к старому значению, но как быть, если нужно поддерживать и более ранние версии?
Решение: backport-реализация, о которой расскажу ниже:
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👍19 2🔥1🙏1
Друзья, привет! Сегодня хочу представить вам свою библиотеку, которая избавит вас от головной боли при работе с новыми модификаторами SwiftUI.
Недавно я наткнулся на удобную Android-библиотеку, которая упрощает работу с разными версиями API и подумал: «Почему бы не сделать что-то подобное для SwiftUI?».
После этого родилась идея разработать инструмент, который избавит вас от бесконечных проверок
#available и сделает код чище:Каждый раз, когда 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
1❤26👍16 4🔥2 2