Мобильный трудоголик
1.37K subscribers
61 photos
9 videos
266 links
👨‍💻 Пишу простым языком об iOS разработке на Swift и мобильной разработке в целом.
🔹 Вошел в IT задолго до того как это стало мейнстримом.
---
‍Обо мне: https://t.me/hardworkerIT/3
Чат: @hardworkerChatIT
Канал про разработку и жизнь в ИТ: @itDenisov
Download Telegram
🔢 Контекстные меню с превью в SwiftUI: показываем детали до действия.

Контекстные меню в SwiftUI это мощный инструмент для скрытия дополнительных действий и функций. Однако они часто страдают от недостатка контекста: пользователь не всегда понимает, к чему именно относится действие, пока не нажмет на него. Начиная с iOS 16, SwiftUI предлагает решение этой проблемы с помощью модификатора preview, который позволяет показывать детальную информацию прямо в меню, прежде чем пользователь сделает выбор.


Базовый пример - уточнение действия:

Рассмотрим сценарий с просмотром документа. Без preview пользователь видит только действия, но не понимает, какой именно документ будет затронут:


DocumentCard(document: report)
.contextMenu {
Button("Переименовать", systemImage: "pencil") {
rename()
}

Button("Создать копию", systemImage: "doc.on.doc") {
duplicate()
}

Button("Удалить", systemImage: "trash", role: .destructive) {
delete()
}
}


С добавлением preview меню становится самодостаточным:


DocumentCard(document: report)
.contextMenu {
Button("Переименовать", systemImage: "pencil") {
rename()
}

Button("Создать копию", systemImage: "doc.on.doc") {
duplicate()
}

Button("Удалить", systemImage: "trash", role: .destructive) {
delete()
}
} preview: {
// Preview показывает, с каким документом работаем
VStack(alignment: .leading, spacing: 8) {
Text(report.title)
.font(.headline)

HStack {
Label("\(report.pageCount) стр.", systemImage: "doc.text")
Label(report.formattedDate, systemImage: "calendar")
}
.font(.caption)
.foregroundStyle(.secondary)

if let previewText = report.preview {
Text(previewText)
.font(.caption)
.lineLimit(3)
.foregroundStyle(.secondary)
}
}
.padding()
.frame(width: 300)
}



Расширенный сценарий - интерактивный preview:

Модификатор preview поддерживает не только статический контент, но и интерактивные элементы, что открывает дополнительные возможности:


Image(uiImage: selectedPhoto)
.resizable()
.aspectRatio(contentMode: .fit)
.contextMenu {
Button("Редактировать", systemImage: "slider.horizontal.3") {
editPhoto()
}
Button("Поделиться", systemImage: "square.and.arrow.up") {
sharePhoto()
}
Button("Удалить", systemImage: "trash", role: .destructive) {
deletePhoto()
}
} preview: {
// Preview с возможностью масштабирования
ZoomableImageView(image: selectedPhoto)
.frame(height: 300)
.cornerRadius(12)
}



Важные детали:

🔵Preview работает только в контекстных меню, вызванных долгим нажатием.

🔵Размер и layout preview можно гибко настраивать.

🔵Preview поддерживает анимации и переходы внутри себя.

🔵Может содержать любые SwiftUI вью, включая списки, сетки и интерактивные элементы.


Оптимизация производительности:

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


💡 Вывод:

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
17👍12🔥5👏211
🔢 Структуры в Swift: когда скорость копирования становится проблемой.

Структуры в Swift - это основа современной разработки под iOS. Они гарантируют безопасность потоков, предсказуемость и чистоту кода. Однако многие разработчики забывают, что за кажущейся простотой value types скрываются важные компромиссы, особенно когда речь заходит о производительности. В этом посте разберем, как неправильное использование структур может незаметно замедлить ваше приложение, и как этого избежать.

Основной принцип структур - копирование при каждом присваивании или передаче. Это прекрасно работает для простых типов вроде Int, String или небольших struct. Но представьте сценарий, где ваша структура содержит несколько больших массивов данных или вложенные коллекции:


struct AnalyticsReport {
var userEvents: [UserEvent] // 10 000 элементов
var sessionLogs: [SessionLog] // 5 000 элементов
var metadata: [String: Any]
var userId: UUID
var createdAt: Date
}


Каждый раз при передаче такого отчета между функциями Swift создает полную копию всех данных. Если UserEvent и SessionLog - тоже структуры, то происходит глубокое рекурсивное копирование. В таком сценарии затраты памяти и процессорного времени могут быть колоссальными.


Где возникают скрытые копии:

🔵Передача структуры как параметра функции.

🔵Возврат структуры из функции.

🔵Присваивание одной переменной другой.

🔵Помещение структуры в массив или словарь.

🔵Использование в @escaping замыканиях.


Пример опасного сценария:


func processReport(_ report: AnalyticsReport) -> ProcessedReport {
// Здесь уже создана полная копия report
let filtered = report.userEvents.filter { $0.isImportant }
}

// Где-то в коде:
let report = AnalyticsReport(...) // Большой объект
for processor in processors {
let result = processReport(report) // Копия на каждой итерации!
}



Оптимизация Copy-on-Write (COW) - не панацея:

Многие рассчитывают на встроенную оптимизацию Copy-on-Write, но она работает только для стандартных типов (Array, Dictionary, String) и требует определенных условий. Ваши кастомные структуры не получают COW автоматически.


struct LargeData {
var items: [String] // Имеет COW
var customBuffer: UnsafeMutableRawPointer // Копируется всегда
var nestedStruct: AnotherStruct // Копируется всегда
}



Когда стоит рассмотреть переход к классам:

🔵Общие изменяемые состояния: когда несколько объектов должны ссылаться на одни и те же данные.

🔵Большие неизменяемые (immutable) данные: которые часто передаются по системе.

🔵Ресурсоемкие объекты: содержащие файловые дескрипторы, сетевые соединения, изображения.


// Вместо большой структуры:
struct HeavyConfiguration {
var rules: [Rule] // 1000+ правил
var templates: [Template]
var settings: [String: Any]
}

// Можно использовать гибридный подход:
final class ConfigurationStorage {
var rules: [Rule]
var templates: [Template]
var settings: [String: Any]
}

struct LightweightConfiguration {
let id: UUID
let version: String
let storage: ConfigurationStorage // Общая ссылка
}


Практическое правило: Если ваша структура превышает 1-2 КБ в размере или содержит больше 10-15 свойств, задумайтесь об оптимизации. Используйте Instruments и Time Profiler для измерения реального влияния.


🔗 Ссылка на подробную статью


💡 Вывод:

Структуры - мощный инструмент, но их следует использовать осознанно. Слепая вера в то, что «структуры всегда быстрее классов», может привести к обратному результату. Ключ к производительности - понимание стоимости копирования и умение выбирать правильную абстракцию для каждой задачи.

Иногда небольшое количество контролируемых мутабельных состояний через классы дает больше преимуществ, чем повсеместное использование иммутабельных структур. Баланс между безопасностью и производительностью - вот что отличает опытного Swift-разработчика.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍209👀52🔥1🙏1
🔢 Swift Concurrency: как проверить, на каком акторе выполняется ваш код.

Начиная с Swift 6 подход к отладке многопоточных приложений изменился. Вместо вопроса «на каком потоке выполняется код?» теперь нужно спрашивать «на каком акторе я нахожусь?». Это фундаментальный сдвиг, который требует новых инструментов отладки.


Почему Thread.isMainThread больше не актуален:

В Swift 6 при использовании async/await вы получите ошибку компиляции при попытке использовать Thread.isMainThread. Вместо этого система предлагает использовать аннотацию @MainActor. Это не ограничение, а переход к более безопасной модели - акторной изоляции.


Основной инструмент: MainActor.assertIsolated()

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

func updateUI() {
MainActor.assertIsolated("UI нужно обновлять в MainActor!")
titleLabel.text = "Данные загружены"
}


Что делает данный метод:

🔵В debug-сборках: вызывает краш приложения, если код выполняется не на главном акторе.

🔵В release-сборках: ничего не делает.

🔵Позволяет мгновенно обнаруживать ошибки во время разработки.

Когда ошибка должна приводить к крашу в любом случае, используйте MainActor.preconditionIsolated().


Анализ контекста выполнения в Xcode:

Когда происходит ошибка изоляции, в Xcode Debug Navigator отображается:

🔵Queue: com.apple.main-thread (serial) - выполнение на главном акторе.

🔵Queue: com.apple.root.user-initiated-qos.cooperative (concurrent) - выполнение на фоновом акторе.

Эта информация помогает понять текущий контекст выполнения без проверки потоков.


Что такое акторы:

Акторы в Swift - это не просто обертка над потоками. Это полноценная модель программирования, которая обеспечивает:

🔵Сериализацию доступа к данным.

🔵Компиляторные гарантии безопасности.

🔵Автоматическое управление жизненным циклом задач.


🔗 Ссылка на подробную статью


💡 Вывод:

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

Использование MainActor.assertIsolated() и аналогичных методов для кастомных акторов как основных инструменты отладки не только помогает находить ошибки на этапе разработки, но и заставит задуматься о правильном проектировании изоляции данных в вашем приложении.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1452🔥11
🔢 Обратная сторона SwiftUI: почему сложная навигация все еще требует UIKit.

SwiftUI обещал революцию: декларативный синтаксис, живые превью, кроссплатформенность. Но когда речь заходит о построении сложной навигационной архитектуры для приложений, декларативный рай оборачивается императивным адом. Особенно остро это чувствуется в проектах с требовательными дизайнами, глубокими диплинками и кастомными UI-компонентами. Давайте разберем, где SwiftUI показывает свои границы и какие стратегии помогают эти границы расширить.


Фундаментальный разрыв - декларативное состояние vs императивная логика:

Навигация по своей природе императивна: «перейди туда», «вернись обратно», «покажи поверх», «закрой все». SwiftUI пытается описать это через состояние (@State, @Published), но сталкивается с проблемой композиции навигационных действий.

Рассмотрим реальный сценарий: пользователь получает push-уведомление -> должен открыться конкретный экран заказа -> но если пользователь не авторизован, нужно сначала показать экран входа -> после успешной авторизации продолжить исходный переход.

В UIKit это цепочка императивных команд. В SwiftUI возникает парадокс:

🔵Вы добавляете экран логина в состояние навигации.

🔵Но как система узнает, что авторизация завершена?

🔵Как вернуться к исходному «намерению» перейти к заказу после успешного входа?

🔵Где хранить это отложенное намерение, пока показывается логин?

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


Решение - гибридная архитектура:

Вместо попыток заставить SwiftUI делать то, для чего он не предназначен, эффективнее признать: навигация - это системная, платформозависимая задача. И использовать правильный инструмент для каждой части:

🔵Ядро навигации на UIKit: UINavigationController, модальные презентации, кастомные переходы.

🔵Контент экранов на SwiftUI: UIHostingController с SwiftUI вьюхами.

🔵Координаторы на Swift: для бизнес-логики переходов.


// Координатор на Swift управляет UIKit навигацией
class OrderCoordinator {
private let navigationController: UINavigationController

func showOrder(id: String, context: NavigationContext) {
if !context.isAuthenticated {
showAuth { [weak self] success in
if success { self?.showOrder(id: id, context: context) }
}
return
}

let swiftUIView = OrderDetailView(orderId: id)
let hostingController = UIHostingController(rootView: swiftUIView)
navigationController.pushViewController(hostingController, animated: true)
}
}



Почему это работает лучше:

🔵Полный контроль над анимациями и completion handlers.

🔵Единое состояние навигации через UINavigationController.viewControllers.

🔵Кастомные презентации через UIPresentationController.

🔵Глубокая интеграция с системными жестами.


Что SwiftUI делает хорошо:

🔵Быстрое прототипирование простых навигационных сценариев.

🔵NavigationStack для линейных потоков без кастомных компонентов.

🔵Вьюхи внутри экранов (где декларативный подход действительно силен).


🔗 Ссылка на подробную статью


💡 Вывод:

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥16👍95🤯1🙏1👀1
🔢 Как писать стабильные тесты для Swift Concurrency кода.

Тестирование многопоточного кода в Swift всегда было не без проблем. С появлением Swift Concurrency ситуация стала лучше, но не идеальной. Самые проблемные - плавающие тесты, которые то проходят, то падают без видимых причин. Корень проблемы в недетерминированном выполнении асинхронных операций, особенно при использовании Task {}. Сейчас разберем, как заставить такие тесты работать предсказуемо.


Почему обычные тесты с Task не работают:

Когда мы создаем Task внутри синхронного метода, тест не ждет его завершения:

// Плавающий тест
func testAsyncOperation() {
let service = DataService()
service.startProcessing() // Внутри создается Task

XCTAssertTrue(service.isCompleted) // Может упасть
}


Тест завершится раньше, чем Task выполнится, потому что Task {} запускает асинхронную операцию, которая живет своей жизнью.


Решение - абстракция над Task:

Ключевая идея - создать протокол TaskProvider, который инкапсулирует создание задач:

protocol TaskProvider {
func task<T>(priority: TaskPriority?, operation: @escaping () async -> T) -> Task<T, Never>
}

struct DefaultTaskProvider: TaskProvider {
func task<T>(priority: TaskPriority?, operation: @escaping () async -> T) -> Task<T, Never> {
Task(priority: priority, operation: operation)
}
}

// Мок для тестов
class MockTaskProvider: TaskProvider {
private var tasks: [Task<Void, Never>] = []

func task<T>(priority: TaskPriority?, operation: @escaping () async -> T) -> Task<T, Never> {
let task = Task(priority: priority) {
await operation()
}

tasks.append(task as! Task<Void, Never>)
return task
}

// Ждем завершения всех созданных задач
func waitForAllTasks() async {
for task in tasks {
await task.value
}
}
}



Использование в коде:

Меняем зависимость в нашем сервисе:

class DataService {
private let taskProvider: TaskProvider

init(taskProvider: TaskProvider = DefaultTaskProvider()) {
self.taskProvider = taskProvider
}

func startProcessing() {
taskProvider.task(priority: .medium) {
// Долгая асинхронная операция
await self.processData()
}
}
}



Стабильный тест:

Теперь тест может дождаться выполнения всех задач:

func testAsyncOperation() async {
let mockProvider = MockTaskProvider()
let service = DataService(taskProvider: mockProvider)

service.startProcessing()
await mockProvider.waitForAllTasks() // Ждем завершения

XCTAssertTrue(service.isCompleted) // Стабильно проходит
}



Преимущества подхода:


🔵Контроль приоритетов: можно проверять, что задачи создаются с правильными приоритетами.

🔵Отслеживание создания: можно вести лог созданных задач для assertions.

🔵Изоляция тестов: каждый тест работает с изолированным моком.

🔵Поддержка detached задач: паттерн легко расширяется для Task.detached.


Важное замечание:

Этот подход не заменяет async/await тесты, а дополняет их. Для кода, который уже использует async функции, лучше тестировать через await. Но для legacy-кода или ситуаций, где Task создается внутри синхронных методов, этот паттерн незаменим.


🔗 Ссылка на подробную статью


💡 Вывод:

Тестирование многопоточного кода требует особого подхода и абстракция TaskProvider предоставляет его. Она превращает плавающие тесты в детерминированные, давая полный контроль над выполнением асинхронных операций. Через инверсию зависимостей сохраняется чистота архитектуры, а случайные падения в CI/CD становятся историей.

Особенно ценен этот паттерн при работе с legacy-кодом, где синхронные и асинхронные вызовы соседствуют в процессе миграции на Swift Concurrency.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
16👍11🔥41🙏1🤝1
Forwarded from Кот Денисова
👨‍💻 Долгосрочная стратегия в ИТ: почему нельзя гнаться за деньгами.

Сейчас многие в ИТ стремятся к быстрым деньгам: переходят в компании, где платят больше, но не дают развития. Кажется, это логичный выбор, но на самом деле это тупиковый путь.


Как работает карьерный рост в ИТ:

Есть естественная последовательность, которую нельзя нарушать:

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


Если попытаться прыгнуть на четвертый этап, минуя предыдущие, ничего не получится. Без знаний и навыков высокие зарплаты недолговечны.


Плохой пример:

Представьте разработчика, который в 25 лет выбрал компанию, предлагавшую зарплату на 40% выше рыночной. Проект казался стабильным, задачи комфортными. Но через три года оказалось, что:

🔹Он продолжает верстать простые экраны и решать легкие проблемы.
🔹Технологический стек в проекте застыл на уровне 2019 года.
🔹Коллеги-однокурсники, начавшие со стажировок в технологичных компаниях, уже выросли до тимлидов и архитекторов.
🔹Его собственные навыки устарели, а на рынке требуются совсем другие компетенции.

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


Важно для развития:

🔹Выбирать проекты с современным стеком технологий.
🔹Работать с сильными коллегами, у которых можно учиться.
🔹Не бояться сложных задач, даже если за них платят меньше.


💡 Вывод:

Высокая зарплата в ИТ - это следствие экспертизы, а не ее причина. Сначала станьте ценным специалистом, а финансовый успех придет естественным образом.


➡️ Кот Денисова
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1714😁6🔥2🗿2👀11
🔢 ARC в Swift: как работает автоматическое управление памятью.

Всем привет! Сегодня разберем одну из фундаментальных тем в iOS-разработке: управление памятью с помощью ARC. Понимание этого механизма критически важно для создания стабильных приложений без утечек памяти.


Что такое ARC:

Automatic Reference Counting - это система автоматического подсчета ссылок, встроенная в Swift. В отличие от ручного управления памятью, ARC самостоятельно отслеживает, когда объекты больше не нужны, и освобождает занимаемую ими память.

Пример:


class User {
let name: String

init(name: String) {
self.name = name
}
}

var user1: User? = User(name: "Иван") // Счетчик ссылок: 1
var user2: User? = user1 // Счетчик ссылок: 2
user1 = nil // Счетчик ссылок: 1
user2 = nil // Счетчик ссылок: 0, память освобождена



Главная проблема: циклы сильных ссылок.

Самая распространенная ошибка: создание retain cycles, когда два объекта держат друг друга сильными ссылками и не могут быть освобождены.

Пример:


class Profile {
var settings: Settings?
}

class Settings {
var profile: Profile?
}

let profile = Profile() // Счетчик ссылок: 1
let settings = Settings() // Счетчик ссылок: 1

profile.settings = settings // Счетчик ссылок: 2, Profile держит Settings
settings.profile = profile // Счетчик ссылок: 2, Settings держит Profile

// Оба объекта держат друг-друга и не будут освобождены!



Решение: weak и unowned ссылки.

Weak и unowned ссылки не увеличивают счетчик ссылок объекта.

Weak ссылки:


class Profile {
weak var settings: Settings?
}

class Settings {
weak var profile: Profile?
}

let profile = Profile() // Счетчик ссылок: 1
let settings = Settings() // Счетчик ссылок: 1

profile.settings = settings // Счетчик ссылок не увеличивается
settings.profile = profile // Счетчик ссылок не увеличивается

// Теперь объекты могут быть освобождены



Unowned ссылки:


class CreditCard {
unowned let owner: Customer

init(owner: Customer) {
self.owner = owner
}
}

class Customer {
let name: String

init(name: String) {
self.name = name
}
}

let customer = Customer(name: "Artem") // Счетчик ссылок: 1
let card = CreditCard(owner: customer) // Счетчик ссылок: 1 (не изменился!)

// Теперь объекты могут быть освобождены



Разница между weak и unowned:

Weak:

🔵Не увеличивает счетчик ссылок.
🔵Автоматически становится nil при удалении объекта.
🔵Безопасны в использовании.

Unowned:

🔵Не увеличивает счетчик ссылок.
🔵Не становятся nil (вызовут краш при обращении к удаленному объекту).
🔵Используются, когда объект гарантированно существует.


🔗 Ссылка на подробную статью


💡 Вывод:

ARC автоматизирует управление памятью, но требует от разработчика понимания работы ссылок. Ключевое правило: weak и unowned ссылки не увеличивают счетчик ссылок, что позволяет разрывать циклы зависимостей. Используйте weak для безопасных связей и unowned только когда уверены в жизненном цикле объектов.

Про внутреннюю механику работы weak-ссылок через Side Tables подробно поговорим в будущих постах.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
19👍114🙏1🤝1
🔢 Как диагностировать тормозящие List и ForEach в SwiftUI.

Работа со списками в SwiftUI часто превращается в борьбу с производительностью. Казалось бы, простой List или ForEach начинает лагать, приложение потребляет неоправданно много ресурсов, а причина остается неочевидной. Стандартные инструменты профилирования не всегда показывают корень проблемы, особенно когда дело касается динамического создания вьюх. Однако в SwiftUI существует малоизвестный параметр, который может стать вашим первым шагом в поиске проблемы.


Причина возникновения проблемы:

Проблема производительности в List и ForEach часто возникает из-за динамического количества вьюх. Когда SwiftUI не может предсказать, сколько элементов будет отрисовано, или когда структура списка динамически меняется, фреймфорк вынужден использовать более медленные пути рендеринга. Эти «медленные пути» могут существенно влиять на плавность скролла и общую отзывчивость интерфейса.


Параметр -LogForEachSlowPath:

Диагностический параметр -LogForEachSlowPath YES, передается как аргумент запуска приложения, заставляет SwiftUI логировать предупреждения каждый раз, когда обнаруживается неоптимальный сценарий работы с контейнерами списков. Это не флаг производительности, а исключительно инструмент диагностики, он не ускоряет приложение, но показывает, где именно возникают проблемы.


Как это работает:

Когда SwiftUI встречает ForEach или аналогичный контейнер, который производит неконстантное количество View (например, зависит от вычисляемого в рантайме значения), он переключается на менее эффективный режим работы. Активация флага -LogForEachSlowPath включает внутреннее логирование, которое сообщает о каждом таком переходе, указывая конкретное место в коде.


Важно понимать:

Этот параметр лишь первый шаг диагностики. Он показывает симптомы, но не лечит болезнь. Частые причины проблемы:

🔵Динамическое вычисление количества элементов внутри ForEach.

🔵Использование нестабильных идентификаторов (id:).

🔵Сложная логика внутри замыканий ForEach.

🔵Частые изменения структуры данных во время скролла.


💡 Вывод:

Диагностика производительности в SwiftUI требует системного подхода, и параметр -LogForForEachSlowPath - это ценный инструмент в арсенале разработчика. Он позволяет быстро локализовать проблемные участки кода, которые используют неоптимальные пути отрисовки. Однако важно помнить, что это лишь инструмент выявления симптомов, настоящее решение требует анализа архитектуры, стабилизации данных и оптимизации самих вьюх.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
2014👍7🔥21🙏1
🔢 Не создавайте Actor без причины: как избежать архитектурной ошибки.

С появлением Swift Concurrency многие разработчики стали добавлять actor везде, где видят асинхронность. Это как использовать танк для поездки в магазин за хлебом: мощно, но бессмысленно и создает больше проблем, чем решает.

Вот простой и эффективный чек-лист, который позволит определить, нужно использовать actor или нет:


У вас есть состояние, которое не является Sendable?

Sendable - это маркер того, что тип безопасен для использования в разных параллельных контекстах. Если все ваши данные (структуры, простые классы) уже соответствуют Sendable, то первое и важное основание для использования actor просто отсутствует.

🔵Пример не Sendable: класс с изменяемым (var) свойством, которое является ссылочным типом (например, другим не-Sendable классом) и не защищен блокировками.

🔵Пример Sendable: структура (struct) только с let свойствами простых типов (Int, String) или других Sendable типов.

Если у вас либо нет состояния, либо оно уже потокобезопасно (Sendable). actor для его защиты избыточен.


Операции с этим состоянием должны быть атомарными?

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

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

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

Если ваши операции можно разбить на независимые шаги или они не требуют такой строгой изоляции, возможно, хватит обычной очереди (DispatchQueue) или async/await с корректным проектированием.


Эти операции не могут быть выполнены на уже существующем actor (например @MainActor)?

Часто проблема решается не созданием нового изолированного острова actor, а правильным использованием существующих.

🔵Проверьте: может ли ваша логика быть изолирована к @MainActor, если она касается UI? Или может ее стоит вынести в @concurrent функцию (Swift 6.2+), чтобы просто запустить в параллельном пуле потоков?

🔵Классическая ошибка: создавать отдельный actor только для того, чтобы выгрузить работу с основного потока. Для этого есть async let, Task.detached или аннотация @concurrent.

Вы не исчерпали возможности существующих механизмов изоляции. Новый actor добавит накладные расходы на переключение контекстов без реальной необходимости.


🔗 Ссылка на подробную статью


💡 Вывод:

Actor - это специальный и достаточно ресурсоемкий механизм, созданный для решения конкретной проблемы: строгой изоляции разделяемого изменяемого состояния. Его не следует применять автоматически для любой фоновой работы.

Прежде чем его использовать, необходимо честно ответить на три ключевых вопроса: есть ли у вас состояние, не являющееся потокобезопасным (Sendable), требуют ли операции с ним атомарности и действительно ли эту работу невозможно выполнить в рамках уже существующего контекста изоляции. Только утвердительные ответы на все три пункта оправдывают введение actor. Во всех остальных сценариях стоит отдавать предпочтение более легким инструментам, таким как async/await, проектирование с Sendable типами, классические DispatchQueue или новая аннотация @concurrent.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
19👍107🔥2👏1🙏1
🍏 🏋️ 🪟 Мое приложение для планирования и концентрации - TaskFocus.

В начале 2022 года я решил разработать приложение TaskFocus - планировщик задач со встроенным фокусированием. Мне нужен был единый инструмент, который бы работал на всех моих устройствах и помогал не только фокусироваться на задачах, но и учитывать затраченное на них время.

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


Разработка:

Изначально разработка планировалась под iOS, но для охвата всех пользователей я принял решение выбрать кроссплатформенный фреймворк Flutter. Это позволило выпустить приложение одновременно на iOS, Android, macOS и Windows. Серверную часть для синхронизации данных также разрабатывал самостоятельно на PHP.


Ключевые возможности:

🔵Задач с настройкой отображения списка.
🔵Возможность добавления заметок к задаче.
🔵Удобный поиск по дате и тексту задач или заметок.
🔵Режим фокусировки с выбором фоновых звуков для концентрации.
🔵Детальная статистика по выполнению задач и затраченному времени.
🔵Синхронизация данных и настроек между устройствами.
🔵Экспорт задач и статистики в Excel.
🔵Выбор темы оформления.
🔵Возможность подключения премиум подписки.


📊 На данный момент приложение имеет более 19 тысяч скачиваний в магазинах приложений.

📱 📱 📱 Приложение доступно в магазинах приложений


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
13👍8🔥4👏1👀1
🔢 Swift: как defer наконец-то подружился с асинхронностью.

Иногда самые полезные изменения в языке - не громкие нововведения, а устранение небольших, но раздражающих неудобств. Те, что заставляют писать лишний код, нарушать логику или искать обходные пути. Именно таким изменением стало принятие SE-0493, которое разрешает использовать await внутри defer. На первый взгляд это техническая деталь. На деле - значимое упрощение для написания чистого и надежного асинхронного кода.

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


Что изменилось:

Теперь в async-функции вы можете писать так:


func loadData() async throws {
let resource = try await acquireResource()
defer {
await resource.release()
}
try await work(with: resource)
}



Как это работает:

Ключевая механика defer сохраняется, но теперь с поддержкой асинхронности:

🔵Гарантия выполнения: блок defer выполнится при любом выходе из функции (успех, ошибка, возврат).

🔵Ожидание завершения: если внутри defer есть await, функция дождется его завершения, прежде чем вернуть управление.

🔵Ограничение: использовать await в defer можно только внутри async-контекста.


Почему это важно:

Раньше для асинхронной очистки приходилось либо дублировать код на всех путях выхода, либо использовать Task { }, который не гарантировал завершения операции. Теперь очистка ресурсов (закрытие сетевых соединений, сброс состояния, отмена операций) становится такой же простой и надежной, как и в синхронном коде.


🔗 Ссылка на документацию


💡 Вывод:

Принятие SE-0493 - это пример зрелой эволюции языка. Вместо введения сложных новых концепций, Swift устраняет конкретное, давно назревшее несоответствие между синхронными конструкциями языка и его асинхронной парадигмой. Это изменение делает асинхронный код не только безопаснее, но и чище.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1913🔥42👏1🙏1👀1
This media is not supported in your browser
VIEW IN TELEGRAM
🔢 iOS 26: официальный способ прикрепить плавающие View над TabBar.

Многие годы разработчики iOS испытывали смешанные чувства при виде таких интерфейсов, как мини-плеер в приложении Apple Music или постоянная панель действий в Podcasts. С одной стороны - это удобный и интуитивный паттерн, с другой - его реализация всегда была головной болью, требующей неочевидных трюков с safeAreaInsets и ручной подгонкой размеров. С выходом iOS 26 эта эпоха подошла к концу. Apple официально представила API для создания нижних аксессуаров в UITabBarController, и это одно из тех изменений, которое кардинально упрощает жизнь.


Прощайте, костыли. Здравствуй, декларативность:

До iOS 26 интеграция любого статичного или плавающего элемента поверх таббара была упражнением в нетривиальной геометрии. Разработчики были вынуждены вручную управлять additionalSafeAreaInsets, отслеживать повороты устройства, адаптировать layout под разные состояния навигации (например, появление клавиатуры) и гарантировать, что кастомная вью не перекроет таббар и его элементы. Новый API bottomAccessory решает эту проблему радикально простым и элегантным способом. Все что требуется - это создать экземпляр UITabAccessory, передав ему ваше кастомное представление, и установить его в свойство контроллера.

let miniPlayerView = MiniPlayerView() // Кастомная вью
let accessory = UITabAccessory(contentView: miniPlayerView)
tabBarController.bottomAccessory = accessory


Система берет на себя всю ответственность за позиционирование, анимации при появлении/скрытии (через метод setBottomAccessory(_:animated:)) и корректное взаимодействие с жестами. Это переход от императивного «как это разместить» к декларативному «что я хочу показать».


Гармония с поведением таббара - новый уровень интеграции:

Инновация не ограничивается простым добавлением вью. Apple обеспечила глубокую интеграцию аксессуара с обновленным поведением самого UITabBar. Теперь таббар может автоматически сворачиваться в компактный вид (например, при скролле контента), и аксессуар реагирует на это изменение согласованно. Поведением управляет свойство tabBarMinimizeBehavior, которое предлагает гибкие опции: от автоматического решения системой до явных триггеров вроде скролла вниз или вверх.


Адаптивный интерфейс через tabAccessoryEnvironment:

Одной из самых тонких проблем при создании подобных элементов была адаптация их внешнего вида к разным состояниям. Нужно ли показывать полный заголовок трека или только иконку? Как изменить layout при компактном таббаре? Для этого Apple ввела новый trait - UITabAccessory.Environment. Теперь вью может запросить у traitCollection текущее окружение (.regular, .inline, .none) и кардинально изменить свой вид или внутреннюю композицию.


contentLayoutGuide - надежный фундамент для контента:


Чтобы окончательно устранить ручные расчеты отступов, Apple добавила в UITabBarController новый UILayoutGuide - contentLayoutGuide. Привязка контента основного вью-контроллера к этому guide, автоматически гарантирует, что он всегда будет располагаться в корректной области: выше системных элементов нижней части интерфейса. Этот guide динамически обновляет свои размеры при изменениях состояния таббара, появлении клавиатуры или изменении ориентации.


🔗 Ссылка на подробную статью


💡 Вывод:

Введение bottomAccessory в iOS 26 - это не просто добавление еще одного свойства в API. Это значительный шаг вперед в философии UI-разработки под iOS. Apple признает популярные пользовательские паттерны и предоставляет для них первоклассные, системные инструменты, заменяя годы накопленных хаков и обходных приемов. Это снижает порог входа для создания сложных интерфейсов, повышает стабильность приложений и позволяет разработчикам сосредоточиться на логике и дизайне, а не на борьбе с фреймворком.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1710👍31🙏1👀1
🔨 Xcode - это не среда разработки, это квест на выживание.

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


Иллюзия контроля:

Современная разработка строится на предсказуемости: четких ошибках, логичном рабочем процессе, последовательной документации. Xcode систематически нарушает этот контракт. Возьмем классическую ошибку SwiftUI: «The compiler is unable to type-check this expression in reasonable time». Это не ошибка в вашем коде- это капитуляция системы. Компилятор не говорит, где проблема; он предлагает вам угадывать, разбивая выражения наугад. Это эквивалент того, как если бы строительный кран останавливался со словами «что-то тяжело», не указывая на проблемную балку.


Архитектура как наследие:

Погружение в структуру проекта - это путешествие в прошлое. Файл project.pbxproj - это не просто конфигурация, это артефакт эпохи, когда понятия «человеческий формат» считались роскошью. Merge-конфликты в этом файле - это квест, где наградой становится возможность просто открыть проект. Существование инструментов вроде xcodegen - не доказательство гибкости экосистемы, а молчаливое признание провала.


Церемония подписи кода:

Каждый раз при запуске сборки начинается один и тот же ритуал. Система безопасности macOS требует пароль для связки ключей, чтобы подписать приложение вашим сертификатом. Но вместо одного запроса вы получаете целую серию одинаковых окон: одно для сертификата, другое для приватного ключа, третье для профиля. Они наслаиваются друг на друга, затемняя экран, и вы вынуждены по несколько раз подряд вводить один и тот же пароль.


Документация как мираж:

Попытка следовать официальным руководствам - это часто путь через зеркало. Sandbox-аккаунт для тестирования покупок должен появляться в настройках симулятора, но его нет. Вы вводите пароль снова и получаете загадочное «Password reuse not available for account». Форумы разработчиков разделены: половина утверждает, что в симуляторе это не работает, другая половина - что работает. Правда где-то посередине, но ее приходится устанавливать методом проб и ошибок, а не чтением документации.


Закрытость как система:

Баг-трекер Apple - это черный ящик. Отправив отчет, вы не получаете обратной связи, не видите обсуждений, не можете узнать, воспроизводится ли проблема у других. Это создает вакуум, где каждый разработчик вынужден в одиночку бороться с проблемами, которые могут быть системными. Экосистема, где тысячи профессионалов тратят время на повторное открытие одних и тех же багов - это не экосистема, а лабиринт без карты.


Монополия на инструменты:

Отсутствие реальных альтернатив - ключевой момент. AppCode от JetBrains был похоронен, а настройка Neovim с xcode-build-server остается увлечением для энтузиастов. CLI-инструменты плохо документированы, что делает автоматизацию и CI/CD не естественным процессом, а подвигом. Fastlane существует не для расширения возможностей, а для преодоления фундаментальных недостатков.


🔗 Ссылка на подробную статью


💡 Вывод:

Xcode - это не просто IDE. Это культурный код, который формирует мышление разработчика. Он учит не углубляться в суть проблем, а выполнять ритуалы: перезагрузить, очистить Derived Data, пересоздать проект. Опасность такого подхода в том, что он формирует поколение разработчиков, которые воспринимают непредсказуемость как норму. Apple создала выдающиеся продукты, но инструменты для их создания остаются парадоксом: они одновременно и мост и барьер.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
20👍13👀5🤔2🤯1🤝1
Forwarded from Кот Денисова
👨‍💻 Почему место жительства разработчика влияет на карьеру сильнее, чем кажется.

Распространенное мнение, что ИТ - это сфера, в которой можно строить карьеру из любой точки мира, лишь отчасти соответствует действительности. Несмотря на рост популярности удаленной работы, физическое местоположение продолжает играть значительную роль в карьерном пути разработчика. Вот ключевые причины, почему это так:


Концентрация возможностей в крупных центрах.

Крупные города и технологические кластеры остаются центрами притяжения ИТ-индустрии. Здесь сосредоточены штаб-квартиры компаний, венчурные фонды, исследовательские центры и специализированные образовательные учреждения. Такая концентрация создает среду, где проще найти работу, соответствующую растущим амбициям и квалификации. Кроме того, высокая конкуренция среди работодателей в таких регионах часто приводит к более выгодным условиям труда и уровню дохода.


Роль профессионального сообщества и нетворкинга.

Карьерный рост в ИТ редко происходит изолированно. Значительная часть возможностей возникает благодаря профессиональным связям, как слабым, так и сильным. Участие в конференциях, митапах и воркшопах позволяет не только быть в курсе последних трендов, но и устанавливать контакты, которые в будущем могут трансформироваться в предложения о сотрудничестве, партнерстве или трудоустройстве. В удаленном формате такие спонтанные, но важные взаимодействия происходят значительно реже.


Доступ к уникальным проектам и инновациям.

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


Влияние на формирование личного бренда.

Хотя цифровое присутствие - важная часть современной профессиональной репутации, офлайн-активность по-прежнему имеет вес. Публичные выступления, участие в жюри хакатонов или менторство в локальных образовательных программах усиливают узнаваемость и авторитет специалиста внутри профессионального сообщества. В условиях большого города такие возможности встречаются чаще и имеют более широкий охват.


💡 Вывод:

Несмотря на то, что удаленная работа расширила границы возможного для ИТ-специалистов, географический фактор продолжает влиять на траекторию карьеры. Осознанный выбор места жительства с учетом развитости местной технологической среды может стать стратегическим решением, открывающим доступ к уникальным возможностям для профессионального роста.


➡️ Кот Денисова
Please open Telegram to view this post
VIEW IN TELEGRAM
👍179👀4🗿2🔥1🤯1
🔢 Захват ссылок в Swift: когда [weak self] становится ловушкой.

Всем привет! Наткнулся на интересную статью об отладке памяти в iOS. Автор разбирает реальный кейс, где мелкий UI-баг оказался верхушкой айсберга серьезной проблемы.

С чего все началось:

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


Расследование по шагам:

Автор методично искал причину. Сначала: классические print-ы по всему стеку вызовов. Оказалось, что обработчик deep link вызывался дважды, и что важнее: от двух разных экземпляров координатора в памяти.

Ключевая зацепка: проблема воспроизводилась только после выхода и повторного входа в аккаунт. Это намекало, что старые объекты не убирались из памяти.


Масштаб проблемы:

Используя Memory Graph Debugger, автор обнаружил шокирующую вещь: в системе оставались жить целые копии части приложения, со всеми координаторами, сервисами и вью-моделями. Каждый перелогин добавлял новый слой в память.


Найденный виновник:

Проблема скрывалась в, казалось бы, корректном коде:


.sink(receiveValue: {
Task { [weak self] in // weak здесь не спасает!
// логика
}
})


Ошибка была в том, что [weak self] стоял во внутреннем замыкании Task, в то время как внешнее замыкание .sink захватывало self сильно по умолчанию.


Решение в одну строку:

Исправление оказалось минимальным, но критически важным:


.sink(receiveValue: { [weak self] in // weak должен быть здесь!
Task {
// логика
}
})



💡 Вывод:

Эта история наглядно показывает, как механическая, но невнимательная расстановка [weak self] может создать иллюзию безопасности, скрывая реальную утечку памяти. Мы часто действуем по шаблону, добавляя слабый захват в асинхронные блоки, забывая, что корень проблемы может находиться уровнем выше - в замыкании, которое инициирует эту асинхронную операцию. Ключевой урок здесь в том, что борьба с retain cycles требует не ритуала, а понимания полного контекста захвата, особенно при работе с цепочками и вложенными замыканиями.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20115🤝2👍1🙏1
🔨 Xcodebuild, Swift Build и Swift-Build: как устроен конвейер сборки в Swift.

Когда вы запускаете сборку проекта, под капотом запускается не один инструмент, а целый конвейер. Многие разработчики воспринимают сборку как нечто монолитное, но в современной экосистеме Swift она разделена на четкие слои абстракции. Понимание того, как xcodebuild, swift build и swift-build взаимодействуют - это ключ к эффективной отладке сложных проблем со сборкой, созданию кастомных инструментов и глубокому пониманию того, как ваши проекты превращаются в исполняемый код.


Абстрактный сердцевинный движок: swift-build как единый планировщик задач:

В основе всего лежит swift-build - низкоуровневый движок, построенный на llbuild. Его задача - не понимать Swift, Xcode-проекты или манифесты пакетов. Его задача - эффективно планировать и исполнять граф зависимостей задач. Он работает с абстрактным промежуточным представлением проекта - PIF (Project Intermediate Format). PIF - это, по сути, универсальный язык, на котором можно описать любую задачу сборки: компиляцию .swift файла, линковку библиотеки, копирование ресурсов. swift-build получает этот граф задач и решает, что, когда и как выполнять, оптимизируя параллелизацию и инкрементальные сборки.


Трансляторы - как swift build и xcodebuild говорят с движком:

Здесь начинается специализация. Ни swift build (система сборки Swift Package Manager), ни xcodebuild не компилируют код сами. Они - трансляторы или фронтенды.

🔵swift build (SwiftPM): его мир - это Package.swift. Он парсит манимуфест, анализирует зависимости, разрешает версии и строит граф таргетов специфичный для пакетов. Затем он транслирует этот граф в универсальный PIF и передает его на выполнение движку swift-build с флагом --build-system=swiftbuild.

🔵xcodebuild: его вселенная - это .xcodeproj с кучей настроек (Build Settings), схем (Schemes) и конфигураций. Он считывает этот комплексный проект, разрешает все переменные, обрабатывает зависимости (включая те же Swift-пакеты через XCFrameworks) и транслирует всю эту информацию в тот же самый PIF. Затем он передает PIF тому же самому движку swift-build.


Ключевое преимущество - разделение ответственности и совместимость:

Польза от такого гениального решения:

🔵Единый кэш и инкрементальность: независимо от того, собираете ли вы пакет через SwiftPM или проект через Xcode, движок swift-build один. Это значит, что кэш объектов (DerivedData) и механизм инкрементальной сборки унифицированы и работают согласованно.

🔵Снижение сложности: движку сборки не нужно знать сотни Build Settings Xcode. Ему говорят: «скомпилируй этот файл с этими флагами». А xcodebuild уже сам разбирается, как из настроек SWIFT_OPTIMIZATION_LEVEL и OTHER_SWIFT_FLAGS сформировать итоговый вызов компилятора.

🔵Возможность для сторонних инструментов: такие проекты, как Tuist или XcodeGen, используют эту же схему. Они не изобретают свой компилятор. Они генерируют .xcodeproj (или даже сразу PIF), а затем используют стандартный xcodebuild или напрямую swift-build для сборки. Это обеспечивает стабильность и совместимость.


Где прячется swiftc? Роль компилятора в этой цепочке:

Компилятор swiftc - это просто еще одна задача в графе, который строит движок swift-build. Когда движок видит в PIF задачу типа «скомпилировать Swift-файл», он вызывает swiftc с конкретным набором аргументов, которые были подготовлены для него фронтендом (xcodebuild или swift build). swiftc не имеет состояния между вызовами и ничего не знает о проекте в целом, он просто компилирует то, что ему передали.


💡 Вывод:

Сборка в экосистеме Swift - это не монолит, а пайплайн с четкими интерфейсами: фронтенд (xcodebuild/swift build) отвечает за понимание формата проекта и генерацию абстрактного плана, а бэкенд (swift-build) - за оптимальное исполнение этого плана через вызов низкоуровневых инструментов вроде swiftc.

Такое разделение позволяет Apple независимо развивать удобные инструменты для разработчиков (Xcode, SwiftPM) и высокопроизводительный движок сборки.


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

В iOS-разработке есть один недооцененный инструмент, который дает доступ к метрикам, обычно скрытым от нас. Речь о MetricKit - фреймворке Apple для сбора системной диагностики с устройств пользователей. Это не просто альтернатива краш-репортам, а принципиально другой уровень понимания того, как приложение ведет себя в реальных условиях.

Стандартные инструменты показывают, что упало. MetricKit показывает, почему это произошло. Не только краши, но и зависания, энергопотребление, временя запуска и десятком других, системных параметров. Это данные, которые ОС собирает сама, без участия пользователя и с минимальным влиянием на производительность.


Особенности:

MetricKit раскрывает три слоя проблем, которые иначе остаются невидимыми:

🔵Системные завершения приложения: вместо общего «приложение закрыто» вы получаете конкретную причину: нехватка памяти, превышение лимита CPU, Out Of Memory (OOM) или фоновая активность. Это позволяет отличать баги от системных ограничений.

🔵Ресурсные метрики в контексте: энергопотребление разбивается по компонентам (CPU, дисплей, сеть), I/O операции показываются с объемами и временем выполнения, а зависания детектируются автоматически с полным стек-трейсом.

🔵Агрегированные данные для трендов: MetricKit не сыплет событиями, он присылает агрегированные отчеты раз в 24 часа. Это идеально для анализа трендов: как изменилось время запуска после обновления, выросло ли энергопотребление новой фичи, снизилось ли количество OOM после оптимизации.


🔗 Ссылка на подробную статью


💡 Вывод:

MetricKit меняет подход к качеству приложений с реактивного на проактивный. Вместо того чтобы ждать жалоб из App Store, вы видите проблемы до того, как они станут массовыми. Это инструмент для команд, которые хотят не просто исправлять баги, а понимать фундаментальные причины проблем производительности.

Главное преимущество - объективность. Вы получаете не субъективные оценки, а точные системные метрики. Это позволяет принимать архитектурные решения на основе данных, а не предположений. Для серьезных проектов внедрение MetricKit должно быть не опцией, а стандартом разработки.


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1610🔥43🤔1🙏1🤝1
Forwarded from Кот Денисова
👨‍💻 Инфляция должностей: как компании нас обманывают.

Инфляция должностей в ИТ - это когда разработчикам дают громкие названия позиций без реального роста ответственности и зарплаты. Например:

🔹Джуниора называют миддлом.
🔹Миддла повышают до сеньора.
🔹Но зарплата и реальные обязанности остаются прежними.


Почему компании так делают:

В аутсорсе это была стандартная практика:

🔹Джуниору с зарплатой 100к в год дают звание миддл.
🔹Продают клиенту как миддла за 200к.
🔹Разницу в 100к компания забирает себе.


Такая практика вредит всем:

🔹Разработчикам: они не растут профессионально, довольствуясь красивым титулом.
🔹Компаниям: их репутация падает, когда обман раскрывается.
🔹Рынку: настоящие специалисты теряются среди раздутых резюме.


Что с этим делать:

🔹Реально оценивать свой уровень, а не гнаться за громкими званиями.
🔹При смене работы требовать не только высокий грейд, но и соответствующие обязанности и зарплату.
🔹Не верить красивым обещаниям быстрого карьерного роста.


💡 Вывод:

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

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


➡️ Кот Денисова
Please open Telegram to view this post
VIEW IN TELEGRAM
👍166🤯3🔥1🙏1
🎄 Друзья, с наступающим Новым годом!

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

Я очень благодарен за ваше внимание и активность. Сейчас я ухожу на заслуженные выходные, но в январе вернусь с новыми идеями.


Желаю вам в 2026 году:

🎉 Освоить технологии, которые сделают вас ценнее на рынке.

🎉 Найти проекты, от которых горят глаза.

🎉 Получать не только опыт, но и достойное вознаграждение.

🎉 И никогда не переставать удивляться возможностям кода.


Пусть новый год будет полон открытий и достижений!
С Новым годом! 🎉
Please open Telegram to view this post
VIEW IN TELEGRAM
39👍21🙏4🔥1👏1
🔨 Не все краши одинаковы. Системный подход к отладке падений iOS-приложений.

Всем привет! Сегодня разберем системный подход к работе с крашами iOS-приложений. Частая ошибка - пытаться сразу лезть в код, не поняв, какой именно сбой произошел. Это как лечить температуру, не зная, грипп это или воспаление легких. Каждый тип краша - это сигнал от системы, который указывает на определенный класс проблем. Когда приложение падает, операционная система отправляет сигнал (signal) о причине прекращения работы. Этот сигнал - ключ к быстрой диагностике.


Шесть основных сигналов и методы их исправления:


🔵Сигнал 1: EXC_BAD_ACCESS:

Приложение попыталось обратиться к участку памяти, который уже освобожден или никогда не принадлежал ему. Причиной могут быть объекты, на которые остались ссылки после их удаления (dangling pointers), некорректное использование weak / unowned, ошибки в коде на C / Objective-C. Включите Zombie Objects в схеме запуска Xcode. Этот режим превращает освобожденные объекты в «зомби» и логирует попытки доступа к ним, четко указывая на проблемный объект. Также используйте Address Sanitizer для выявления более тонких ошибок работы с памятью.


🔵Сигнал 2: SIGSEGV:

Попытка чтения или записи в область памяти, доступ к которой запрещен. Низкоуровневая и опасная ошибка. Причиной может быть работа с Unsafe указателями в Swift, ошибки в нативном коде (C / C++ / Obj-C), порча памяти (memory corruption). Тщательно рецензируйте весь код, использующий UnsafeRawPointer и аналоги. Запустите приложение с Address Sanitizer. Установите символьные точки останова на функции работы с памятью.


🔵Сигнал 3: EXC_CRASH / SIGABRT:

Приложение само вызвало аварийное завершение. Это самый прозрачный тип краша. Причиной может быть срабатывание fatalError(), принудительное извлечение опционала (!), неудачное выполнение assert() или precondition, ошибки валидации в Core Data. Внимательно читайте сообщение в консоли, оно часто напрямую указывает на причину. Изучите верхушку стек-трейса, чтобы найти строку вашего кода, которая инициировала остановку. Замените force unwrap на безопасные конструкции (if let, guard let).


🔵Сигнал 4: SIGTRAP / SIGILL:

Выполнение недопустимой инструкции процессора или срабатывание контрольной точки (trap). Причиной может быть срабатывание assertionFailure в релизной сборке, дебаг-проверки, которые сработали не в той среде, ошибки в условиях precondition. Проверьте, не активны ли строгие проверки (assert) в релизной сборке. Сравните поведение Debug и Release версий. Убедитесь, что все предположения о данных, защищенные precondition, корректны.


🔵Сигнал 5: Out of Memory, OOM:

Приложение превысило лимит памяти, выделенный системой и было завершено. Причиной может быть кэширование больших изображений без ограничений, утечки памяти (retain cycles), удержание в памяти огромных массивов данных. Запустите Memory Graph Debugger (кнопка с тремя кругами в Xcode) для визуализации графа объектов и поиска циклов сильных ссылок. Используйте Allocations Instrument для отслеживания роста потребления памяти во времени. Внедряйте инструменты для снижения давления памяти (например, NSCache вместо словаря для изображений).


🔵Сигнал 6: Watchdog Termination, код 0x8BADF00D:

Система усыпила приложение за блокировку главного потока более чем на несколько секунд. Причиной могут быть синхронные сетевые запросы на главном потоке, декодирование большого JSON, тяжелые вычисления в SwiftUI.body. Используйте инструментарий Time Profiler для записи и анализа активности потоков. Найдите операции, блокирующие main thread. Выносите длительные задачи с Main Actor с помощью Task.detached или фоновых очередей.


💡 Вывод:

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


➡️ Подписаться на канал
Мобильный трудоголик
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21105🔥2🙏1
🔢 @Observable в SwiftUI: почему это больше, чем замена ObservableObject.

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


Философский сдвиг - от ручного объявления к автоматическому обнаружению изменений:

Ключевое различие между двумя подходами лежит в парадигме. ObservableObject требует от вас явного объявления намерений. Вы помечаете класс протоколом, а каждое свойство, которое должно публиковать изменения - модификатором @Published. Это императивный подход: «Я, разработчик, говорю системе, за чем именно нужно следить».

@Observable реализует противоположную, декларативную философию. Вы просто помечаете класс макросом @Observable. Система на этапе компиляции анализирует ваш код и автоматически определяет, какие свойства могут изменяться и требуют наблюдения. Вы декларируете: «Это класс с наблюдаемым состоянием», а система сама решает, как за этим эффективно следить.


Технические отличия - Runtime vs Compile-time наблюдение:

Этот философский сдвиг имеет прямые технические последствия для производительности и поведения.

🔵ObservableObject (Runtime Observation): работает через механизм Combine и объект ObservableObjectPublisher. Когда изменяется свойство с @Published, оно через objectWillChange.send() асинхронно уведомляет всех подписчиков (ваши View) о том, что что-то в этом объекте поменялось. View затем вынуждены пересчитать свое тело, чтобы понять, изменились ли конкретные данные, которые они отображают. Это приводит к потенциально избыточным перерисовкам.

🔵@Observable (Compile-time Observation): макрос раскрывается в код, который реализует гранулярное отслеживание доступа. SwiftUI на этапе компиляции строит точные связи между конкретными свойствами модели и конкретными View, которые их используют. Когда свойство изменяется, система точно знает, какие именно View зависят от этого конкретного свойства и обновляет только их. Это устраняет лишние перерисовки и повышает производительность.


🔗 Ссылка на документацию Apple


💡 Вывод:

Появление @Observable - это не просто замена одного модификатора другим. Это шаг SwiftUI к более зрелой, эффективной и декларативной модели реактивности. Он переносит нагрузку с разработчика (которому больше не нужно вручную расставлять флажки для системы) на компилятор, который может оптимизировать наблюдение за состоянием с недоступной ранее точностью.

ObservableObject выполнил свою историческую роль, заложив основы реактивного программирования в SwiftUI. @Observable - это его логическое и технологическое развитие, предлагающее тот же результат с меньшими усилиями и большей эффективностью.


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