Все специализации

iOS-разработчик: вопросы на собеседовании

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

Run loop - это фундаментальный механизм в iOS, который управляет входящими событиями и планированием задач. Он представляет собой бесконечный цикл, который ожидает ввода (событий) и запускает соответствующие обработчики. Каждый поток, включая главный поток пользовательского интерфейса, имеет свой собственный run loop.

Основные компоненты run loop:

Источники ввода (Input Sources): Генерируют асинхронные события, например, касания экрана, сетевые ответы, срабатывание таймеров.

Таймеры (Timers): Генерируют синхронные события в заданный момент времени.

Наблюдатели (Run Loop Observers): Принимают уведомления о различных активностях run loop (например, когда run loop собирается перейти в спящий режим или обрабатывает событие).

Режимы работы (Run Loop Modes):

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

defaultMode: Режим по умолчанию, используется для большинства задач.

trackingMode: Используется при отслеживании пользовательского ввода (например, скроллинг).

Работа run loop:

Run loop переходит в спящий режим, ожидая событий из источников ввода или срабатывания таймеров.

При поступлении события или срабатывании таймера run loop просыпается.

Run loop обрабатывает событие или вызывает соответствующую функцию таймера.

Если есть наблюдатели, run loop может отправлять им уведомления о текущем статусе.

После завершения обработки run loop может снова перейти в спящий режим или продолжить обработку других событий.

Значение run loop для iOS-разработки:

Отзывчивость пользовательского интерфейса (UI Responsiveness): Главный run loop отвечает за обработку событий UI. Если его блокировать (выполняя долгие операции в главном потоке), UI перестает реагировать на действия пользователя.

Многозадачность (Concurrency): Хотя run loop работает в рамках одного потока, наличие run loop в каждом потоке позволяет управлять тем, как поток обрабатывает задачи и события.

Планирование задач (Task Scheduling): Таймеры позволяют планировать выполнение задач через определенные промежутки времени или в конкретный момент.

Пример использования run loop (вручную, на второстепенном потоке):

Обычно run loop на главном потоке управляется системой автоматически. На второстепенных потоках его можно запустить вручную, например, если поток должен ожидать сетевых событий.

viewDidUnload вызывается после того, как представление контроллера выгружено из памяти, обычно из-за нехватки ресурсов. В этом методе мы освобождаем (nil или deallocate) IBOutlet, которые хранят ссылки на элементы пользовательского интерфейса, чтобы разорвать циклы сильных ссылок и позволить памяти, занимаемой этими элементами, быть освобожденной. Это предотвращает утечки памяти.

Важно отметить, что с iOS 6 и более поздними версиями метод viewDidUnload упразднен и больше не вызывается. Управление памятью для представлений теперь автоматизировано с использованием Weak References (@IBOutlet weak) и Automatic Reference Counting (ARC). IBOutlet'ы, помеченные как weak, автоматически становятся nil, когда соответствующий объект представления выгружается из памяти.

Поэтому в современном iOS-разработке явное освобождение IBOutlet'ов в viewDidUnload не требуется и не имеет эффекта.

Диспетчеризация для методов, реализованных в расширениях, зависит от типа расширения и характера вызываемого метода:

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

Статическая диспетчеризация (Static Dispatch): Используется для методов, определенных в расширениях к структурам и перечислениям. Вызов таких функций определяется на этапе компиляции.

Смешанная диспетчеризация (Witness Table): Для расширений к протоколам с реализацией по умолчанию (default implementation). Если тип, соответствующий протоколу, уже имплементирует метод, будет использована его реализация (виртуальная диспетчеризация). В противном случае будет вызвана реализация из расширения через Witness Table (аналогично статической диспетчеризации, но для протоколов).

Пример:

Диспетчеризация в расширениях к классам также может быть явно указана с помощью атрибута @objc и @nonobjc для взаимодействия с Objective-C рантаймом и управления виртуальной таблицей.

SAITable — это специализированная структура данных или таблица, используемая в контексте оптимизации SQL-запросов и индексирования. Обычно SAITable применяется для хранения и быстрого поиска информации, связанной с индексами или статистикой таблиц в базе данных.

На практике SAITable помогает ускорить выполнение запросов за счёт эффективного доступа к данным и оптимизации плана выполнения запросов.

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

Поскольку термин SAITable не является стандартным в iOS-разработке, скорее всего, он относится к внутренним механизмам или специализированным библиотекам для работы с SQL и оптимизацией запросов.

HEAD: Запрашивает заголовки, которые были бы получены с помощью метода GET, но без тела ответа. Полезен для проверки существования ресурса или получения метаинформации. Идемпотентный и безопасный.

GET: Запрашивает представление указанного ресурса. Используется для извлечения данных. Идемпотентный и безопасный.

POST: Отправляет данные для обработки указанному ресурсу. Данные включаются в тело запроса. Используется для создания нового ресурса или отправки данных формы. Небезопасный и не идемпотентный (повторные запросы могут приводить к созданию дубликатов).

PUT: Отправляет данные для обновления или создания ресурса по указанному URI. Если ресурс не существует, сервер может его создать. Данные включаются в тело запроса. Идемпотентный (повторные запросы не меняют состояние сервера после первого успешного запроса). Небезопасный.

Ключевые отличия:

Безопасный означает, что запрос не изменяет состояние сервера.

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

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

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

Пример добавления представлений:

Порядок в массиве subviews родительского представления отражает порядок расположения представлений по оси Z. Первый элемент массива находится в самом низу, последний - на самом верху.

Использовать метод hitTest: UIView, в который встроен этот слой, или проверить принадлежность точки касания фрейму слоя методом containsPoint: или hitTest:.

Пример определения прикосновения к слою внутри touchesBegan:

В Swift для работы с многопоточностью используются GCD (Grand Central Dispatch) и Operation Queues. Они предоставляют абстракции над низкоуровневыми потоками.

В рамках GCD существуют следующие основные типы очередей:

Serial Queues ( Последовательные очереди):

Задачи выполняются по порядку, одна за другой.

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

Могут быть главной (main) очередью или пользовательской (custom) последовательной очередью.

Concurrent Queues (Параллельные очереди):

Задачи могут выполняться одновременно.

Не гарантируют порядок выполнения задач.

Используют доступные потоки операционной системы.

Могут быть глобальными (global) или пользовательскими (custom) параллельными очередями.

Operation Queues:

Строятся поверх GCD.

Представляют собой более высокоуровневую абстракцию для управления задачами (Operation).

Могут работать как последовательно, так и параллельно, в зависимости от свойства maxConcurrentOperationCount.

Поддерживают зависимости между операциями.

Примеры создания очередей:

Таблица, сравнивающая Serial и Concurrent Queues:

Система управления памятью в iOS основана на Automatic Reference Counting (ARC), а не на традиционном сборщике мусора. ARC автоматически управляет жизненным циклом объектов путем подсчета Strong ссылок на них. Когда счетчик ссылок объекта становится равным нулю, ARC деаллоцирует память, занимаемую этим объектом.

Ключевые моменты:

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

ARC: Компилятор вставляет необходимый код для управления счетчиками ссылок во время компиляции.

Strong ссылки: Увеличивают счетчик ссылок объекта, предотвращая его деаллокацию.

Weak ссылки: Не увеличивают счетчик ссылок. Становятся nil, когда объект деаллоцируется. Используются для предотвращения циклов сильных ссылок.

Unowned ссылки: Не увеличивают счетчик ссылок и не становятся nil. Используются, когда известно, что ссылка будет существовать дольше, чем объект на который она ссылается. Небезопасные, если объект может быть деаллоцирован до того, как ссылка перестанет использоваться.

Пример Strong, Weak, Unowned ссылок:

В данном примере apartment является Strong ссылкой на Apartment, а tenant — Weak ссылкой на Person для избежания цикла сильных ссылок. Без weak оба объекта остались бы в памяти, даже если бы john и unit4A стали nil.

Существует три основных типа диспетчеризации методов в Swift:

Прямая диспетчеризация (Direct Dispatch): Самая быстрая. Компилятор точно знает, какой метод вызвать во время компиляции. Используется по умолчанию для структур, классов с final-методами, приватных методов.

struct MyStruct {

func directMethod() { // Прямая диспетчеризация

print("Direct")

}

}

Диспетчеризация по таблице (Table Dispatch): Используется для методов классов (не final). Каждый класс имеет таблицу виртуальных методов (vtable), где каждый метод имеет уникальный индекс. Компилятор обращается к vtable во время выполнения, чтобы найти нужную реализацию метода. Позволяет полиморфизм.

class MyClass {

func tableMethod() { // Диспетчеризация по таблице

print("Table")

}

}

class MySubClass: MyClass {

override func tableMethod() { // Переопределенный метод, также использует таблицу

print("Table override")

}

}

Диспетчеризация через сообщения (Message Dispatch): Самая гибкая, но самая медленная. Используется для методов objc (помеченных @objc dynamic). Система Objective-C во время выполнения ищет реализацию метода по его имени (селектору).

@objc class MyObjcClass: NSObject {

@objc dynamic func messageMethod() { // Диспетчеризация через сообщения (для ObjC, динамическая)

print("Message")

}

}

MVM (Model-View-ViewModel) и MVP (Model-View-Presenter) — паттерны проектирования пользовательского интерфейса.

Отличия:

Связывание:

MVM использует двустороннее связывание данных (data binding) между View и ViewModel. Изменения в ViewModel автоматически отображаются в View, и наоборот.

MVP обычно использует одностороннее связывание: Presenter обновляет View, а View уведомляет Presenter о событиях.

Ответственность View:

В MVM View — "глупая" (passive), ее основная задача — отображение данных из ViewModel и отправка пользовательских действий обратно в ViewModel. Логика представления полностью находится в ViewModel.

В MVP View — также "глупая", но Presenter полностью управляет ее обновлением и реакцией на события. View информирует Presenter о событиях, и Presenter решает, как на них отреагировать и как обновить View.

Тестируемость:

ViewModel в MVM легко тестируется, так как не зависит от UIKit/AppKit.

Presenter в MVP также хорошо тестируется, но его зависимость от абстрактного интерфейса View может требовать мокирования.

Зависимости:

MVM: View зависит от ViewModel, ViewModel зависит от Model.

MVP: View зависит от Presenter, Presenter зависит от Model. View и Presenter связаны интерфейсами.

Координация:

В MVM View вызывает команды (commands) или методы в ViewModel в ответ на пользовательские действия.

В MVP View вызывает методы в Presenter через интерфейс, а Presenter вызывает методы для обновления View также через интерфейс.

Пример MVM (схематично):

Пример MVP (схематично):

PreferenceKey — это протокол в SwiftUI, который позволяет передавать значения "снизу вверх" по дереву представлений. Он используется для сбора информации от дочерних представлений и доставки ее родительским, которые могут использовать эту информацию для изменения своего поведения или внешнего вида.

Ключевые особенности PreferenceKey:

Протокол: Нужно реализовать протокол PreferenceKey, определяя тип значения и метод для комбинирования значений.

associatedtype Value: Определяет тип данных, который будет передаваться.

static func reduce(value: inout Value, nextValue: () -> Value): Этот метод вызывается SwiftUI при сборе значений от нескольких дочерних представлений. Вы должны определить, как объединить текущее значение (value) с новым (nextValue()).

Передача значений: Дочерние представления используют модификатор .preference(key:value:) для установки значения для конкретного PreferenceKey.

Чтение значений: Родительские представления используют модификатор .onPreferenceChange(_:perform:) для получения уведомлений об изменении значения для конкретного PreferenceKey.

Пример:

Создание PreferenceKey для определения высоты дочернего представления:

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

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

Свойства типа (Type Properties): Хранят значения, общие для всех экземпляров типа.

Методы типа (Type Methods): Выполняют функции, связанные с самим типом.

Пример:

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

static члены хранятся в памяти один раз для всего типа.

Capture List используется в замыканиях (closures) для явного указания, как переменные из окружающей области видимости должны быть захвачены.

Без Capture List замыкание автоматически захватывает переменные либо по ссылке (для классов, функций), либо по значению (для структур, перечислений, кортежей).

Capture List позволяет управлять этим поведением, помогая предотвратить сильные циклы ссылок и сделать замыкание более предсказуемым.

Синтаксис Capture List:

captureListItem может быть:

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

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

variableName: Захватывает переменную по значению (хотя для экземпляров классов это все равно будет ссылка на объект, а не копия объекта).

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

Предотвращение сильного цикла ссылок с weak:

class Person {

let name: String

var apartment: Apartment?

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

deinit { print("\(name) is being deinitialized") }

}

class Apartment {

let unit: String

var tenant: Person?

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

deinit { print("Apartment \(unit) is being deinitialized") }

}

var john: Person? = Person(name: "John")

var unit4A: Apartment? = Apartment(unit: "4A")

john!.apartment = unit4A

unit4A!.tenant = john

// Without capture list, this can create a strong reference cycle:

// lazy var greeting: () -> String = {

// return "Hello, I'm \(self.name)." // self strongly captured

// }

extension Person {

// With capture list, using weak self to break the cycle

lazy var greeting: () -> String = { [weak self] in

guard let self = self else { return "Hello, I'm no longer here." }

return "Hello, I'm \(self.name)."

}

}

print(john!.greeting())

john = nil // Now both Person and Apartment can be deinitialized

unit4A = nil

Использование unowned когда известен жизненный цикл:

class Customer {

let name: String

deinit { print("\(name) is being deinitialized (Customer)") }

var card: CreditCard?

}

class CreditCard {

let number: Int

unowned let customer: Customer // Unowned reference back to customer

init(number: Int, customer: Customer) {

self.number = number

self.customer = customer

}

deinit { print("Card #\(number) is being deinitialized (CreditCard)") }

// Closure that should not outlive the customer

var processPayment: () -> String = { [unowned self] in

return "Processing payment for card #\(self.number) owned by \(self.customer.name)"

}

}

var alice: Customer? = Customer(name: "Alice")

alice!.card = CreditCard(number: 1234_5678_9012_3456, customer: alice!)

print(alice!.card!.processPayment())

alice = nil // Both Customer and CreditCard are deinitialized

Захват по значению (для Int, String, structs и т.д.):

var count = 0

let closure = { [count] in

print("Initial count was \(count)") // Captures the value 0

}

count = 10

closure() // Output: Initial count was 0

Capture List объявляется между открывающей фигурной скобкой { и параметрами замыкания parameters. Variables в Capture List инициализируются когда замыкание создается. For reference types captured without weak or unowned, this means the captured variable holds a strong reference to the object at the time of closure creation.

Правильный выбор между weak и unowned зависит от ожидаемого жизненного цикла объектов. weak используется, когда захваченный объект может стать nil во время жизни замыкания. unowned используется, когда захваченный объект гарантированно будет существовать пока замыкание используется (т.е. имеет такой же или более длительный жизненный цикл).

Асинхронность и многопоточность связаны, но не идентичны.

Многопоточность - это способность выполнять несколько частей программы конкурентно/параллельно, используя несколько потоков выполнения.

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

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

Пример:

Многопоточность: Два разных потока одновременно выполняют независимые вычисления.

Асинхронность: Запрос к сети отправлен, главный поток продолжает обрабатывать UI, а когда ответ приходит, специальный код обрабатывает результат. Этот запрос может выполняться в фоновом потоке.

Отслеживать статус задачи в DispatchWorkItem не всегда обязательно, но в ряде случаев это полезно. DispatchWorkItem предоставляет свойство isCancelled, которое позволяет проверить, была ли задача отменена, а также метод notify(queue:execute:) для получения уведомления о завершении.

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

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

Массив в Swift является структурой.

Наследником класса UIWindow является класс UIView.

AVFoundation — это фреймворк для работы с аудиовизуальными медиа, предоставляющий низкоуровневые API для воспроизведения, записи, редактирования и обработки аудио и видео.

Input (часто в контексте UIResponder) — это система обработки пользовательских событий ввода, таких как касания, жесты, события с клавиатуры и дистанционного управления. Это часть UIKit/AppKit и не связана напрямую с медиаконтентом.

Можно использовать несколько подходов:

Добавление отступов к содержимому кнопки:

В UIEdgeInsets contentEdgeInsets кнопки можно задать положительные значения, увеличивая тем самым видимую область, но не меняя размер самого UIButton. Это не увеличивает область нажатия, только визуальный размер контента.

Изменение размера самого UIButton:

Через frame:button.frame = CGRect(x: button.frame.origin.x - 10,

y: button.frame.origin.y - 10,

width: button.frame.size.width + 20,

height: button.frame.size.height + 20)

Через Auto Layout, добавив отступы к констрейнтам или установив явные размеры больше минимально необходимых.

Создание пользовательского подкласса UIButton и переопределение point(inside:with:):

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

import UIKit

class IncreasedTapAreaButton: UIButton {

// Отступы для увеличения области нажатия

let tapAreaInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {

// Получаем область внутри которой будет обрабатываться касание c учетом отступов

let increasedArea = bounds.inset(by: tapAreaInsets)

// Проверяем, находится ли точка касания внутри этой увеличенной области

return increasedArea.contains(point)

}

}

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

Использование прозрачного UIView поверх или под кнопкой:

Можно разместить более крупный прозрачный UIView и добавить к нему жест (tap gesture recognizer). Обработчик жеста будет срабатывать, а в его действии можно вызвать событие, эквивалентное нажатию кнопки.

// Создаем прозрачный View

let tapView = UIView()

tapView.backgroundColor = .clear

// Добавляем View на экран, размещая его вокруг кнопки с нужными отступами

// ... (с помощью Auto Layout или фреймов)

// Добавляем распознаватель жестов к прозрачному View

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))

tapView.addGestureRecognizer(tapGesture)

@objc func handleTap(_ sender: UITapGestureRecognizer) {

// Ваш код обработки нажатия, например, вызов действия кнопки

button.sendActions(for: .touchUpInside)

}

Наилучшим подходом с точки зрения чистоты и прямого решения задачи является переопределение point(inside:with:) в подклассе UIButton.

Расширение службы уведомлений (Notification Service Extension) — это небольшое исполняемое дополнение, встроенное в ваше iOS-приложение. Оно позволяет изменить внешний вид контента удаленного push-уведомления перед его отображением пользователю.

Основные функции:

Модификация контента: Изменение заголовка, подзаголовка, текста или добавление вложений (изображений, видео) к уведомлению.

Decrypting Encrypted Content: Расшифровка зашифрованных данных, отправленных вместе с уведомлением, перед их отображением пользователю.

Rich Notifications: В сочетании с расширением контента уведомлений (Notification Content Extension), позволяет создавать кастомные интерфейсы для отображения уведомлений.

Жизненный цикл:

При получении удаленного уведомления с ключом mutable-content: 1, система инициирует запуск расширения службы уведомлений.

Реализуется метод didReceive(_:withContentHandler:), в котором происходит обработка и модификация уведомления.

Измененный контент передается в completion handler contentHandler.

Система отображает модифицированное уведомление.

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

Кратковременное выполнение (обычно около 30 секунд).

Ограниченный доступ к ресурсам системы.

Не может выполнять длительные фоновые задачи.

Пример базовой реализации:

dSYM (Debug Symbols) — это файл, генерируемый при компиляции iOS-приложения, который содержит отладочные символы. Он связывает адреса в скомпилированном (оптимизированном) бинарном коде с именами функций, переменных и строк исходного кода.

Основные цели использования dSYM:

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

Отладка: dSYM необходим для полноценной отладки приложения, особенно при использовании символических точек останова.

Анализ производительности: Инструменты профилирования используют dSYM для связывания данных о производительности с соответствующими участками кода.

Каждый бинарный файл (приложение .app, фреймворк, библиотека) имеет свой уникальный UUID. Соответствующий dSYM-файл (содержащий символы для этого бинарного файла) также имеет тот же UUID. При анализе крэш-репорта система или инструменты (например, Xcode Organizer, Crashlytics, Sentry) используют UUID из крэш-репорта для поиска нужного dSYM-файла и выполнения десимволизации.

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

Run loop связан с потоком (thread), а не с очередью (queue). У каждого основного потока (main thread) есть свой run loop, который создается автоматически. У фоновых потоков его нет по умолчанию, но при необходимости его можно создать и запустить. Очереди (GCD) не имеют собственных run loop'ов; они управляют выполнением задач на основе пула потоков (иногда используя main thread).

Main Thread: Имеет RunLoop, созданный автоматически. Он обрабатывает события UI, таймеры и другие асинхронные операции.

Background Threads: Могут иметь RunLoop при необходимости (например, для управления таймерами или портами), но его нужно инициализировать и запустить вручную.

GCD Queues: Не используют RunLoop напрямую. Они ставят задачи в очередь, которые затем выполняются на доступных потоках из пула.

Dispatch Group — это механизм во фреймворке Grand Central Dispatch (GCD), позволяющий объединять задачи и получать уведомление, когда все задачи в группе завершены.

Основные методы:

enter(): Увеличивает счетчик задач в группе. Вызывается перед началом асинхронной задачи.

leave(): Уменьшает счетчик задач в группе. Вызывается после завершения асинхронной задачи.

notify(queue:execute:): Регистрирует блок кода, который будет выполнен на указанной очереди после того, как счетчик задач в группе достигнет нуля.

wait(): Блокирует текущий поток до тех пор, пока счетчик задач в группе не достигнет нуля. Следует использовать с осторожностью, чтобы избежать блокировки основного потока.

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

Независимый менеджер зависимостей для проектов на Swift, Objective-C. Управляет библиотеками через .podspec файлы и Podfile.

Установка:

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

Пример Podfile:

После редактирования Podfile:

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

Упрощает интеграцию библиотек.

Разрешает конфликты зависимостей.

Обновляет библиотеки одной командой.

Недостатки:

Может увеличивать время сборки.

Добавляет абстракцию над стандартными настройками Xcode.

Autorelease Pool — это механизм в Objective-C и Swift (через взаимодействие с рантаймом Objective-C) для управления памятью с использованием подсчета ссылок (ARC). Он позволяет откладывать освобождение объектов до достижения конца области видимости пула или до явного вызова drain.

Объекты, помещенные в Autorelease Pool (например, через вызов метода, возвращающего autoreleased объект), не освобождаются немедленно после потери последней сильной ссылки. Вместо этого они добавляются в пул и будут освобождены, когда пул будет очищен (drained).

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

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

Многократное создание временных объектов внутри цикла.

Работа с некоторыми API фреймворков, которые возвращают autoreleased объекты.

В современный Swift с ARC, Autorelease Pool используется реже напрямую, так как компилятор более эффективно управляет временем жизни объектов. Однако он все еще существует на уровне рантайма и может явно создаваться и использоваться при необходимости, например, для оптимизации производительности в циклах с большим количеством временных объектов или при взаимодействии с legacy Objective-C кодом.

Явное создание пула в Objective-C:

Явное создание пула в Swift:

Стек (Stack) и куча (Heap) — это две области памяти, используемые для хранения данных в программах.

Стек:

Используется для хранения локальных переменных и адресов возврата функций.

Работает по принципу LIFO (Last In, First Out - последний пришел, первый вышел).

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

Быстрее кучи из-за простоты управления и локальности данных.

Размер фиксирован или ограничен, может произойти переполнение стека (stack overflow).

Куча:

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

Управление памятью вручную (через malloc/free в C/C++) или с помощью автоматического управления памятью (ARC/Garbage Collection/Ownership в Swift/Rust).

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

Размер более гибкий и ограничен только доступной системной памятью.

Может произойти утечка памяти (memory leak), если выделенная память не освобождается.

Сравнение:

В Swift, Value Types (например, struct, enum, базовые типы like Int, Bool) обычно хранятся на стеке, а Reference Types (например, class, closure) — на куче. Переменные, даже ссылочных типов, могут храниться на стеке при определенных оптимизациях компилятора (escape analysis).

Для обнаружения и устранения утечек памяти в iOS-проектах я использую следующие подходы:

Инструмент Instruments (Leaks, Allocations) — позволяет отследить объекты, которые не освобождаются, и найти места утечек.

Профилирование с помощью Xcode Memory Graph Debugger — визуализирует граф объектов в памяти, помогает найти циклические ссылки.

Анализ кода на наличие retain cycles — особенно важно проверять замыкания (closures) и делегаты, использовать weak/unowned ссылки.

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

Пример устранения retain cycle в замыкании:

Такой подход предотвращает удержание self внутри closure, что помогает избежать утечки памяти.

GCD (Grand Central Dispatch) — это технология низкоуровневой параллельности от Apple, построенная на базе C, но с удобными обертками на Swift и Objective-C. Она предоставляет механизм управления очередями задач (work items) и их асинхронного или синхронного выполнения на доступных процессорных ядрах.

Основные концепции GCD:

Dispatch Queues: Очереди для выполнения задач. Бывают двух типов:

Serial Queues: Задачи выполняются последовательно одна за другой. Позволяют избежать состояний гонки при доступе к общим ресурсам.

Concurrent Queues: Задачи выполняются параллельно (но порядок их запуска и завершения не гарантирован). Используются для выполнения независимых задач, которые могут выполняться одновременно.

Main Queue: Специальная последовательная очередь, связанная с основным потоком приложения. Используется для обновления UI. Все задачи, связанные с UI, должны выполняться именно в этой очереди.

Work Items (Closures/Blocks): Блоки кода, представляющие собой отдельную задачу для выполнения.

Dispatch Group: Позволяет объединить группу задач и получить уведомление, когда они все завершены.

Dispatch Semaphores: Примитив синхронизации для управления доступом к ограниченному количеству ресурсов.

Использование в iOS-разработке:

Выполнение тяжелых операций в фоне: Перенос задач, таких как сетевые запросы, обработка изображений, работа с базами данных, с main queue на фоновые очереди (например, глобальные concurrent queues) для предотвращения блокировки UI.

// Выполнение асинхронной задачи в глобальной параллельной очереди

DispatchQueue.global(qos: .userInitiated).async {

// Тяжелая операция

let result = performHeavyOperation()

// Обновление UI на главном потоке

DispatchQueue.main.sync {

updateUI(with: result)

}

}

Обновление UI: Всегда выполнять операции по изменению пользовательского интерфейса на main queue.

DispatchQueue.main.async {

self.label.text = "Обновлено!"

}

Сериализация доступа к данным: Использование пользовательских последовательных очередей для синхронизированного доступа к общим изменяемым ресурсам.

// Пример сериальной очереди для синхронизированного доступа

let dataAccessQueue = DispatchQueue(label: "com.myapp.dataaccess")

func updateSharedData(newValue: Int) {

dataAccessQueue.async {

// Изменение общих данных безопасно

sharedData = newValue

}

}

Группировка задач: Ожидание завершения нескольких асинхронных задач перед выполнением следующего шага.

// Группа задач

let group = DispatchGroup()

group.enter() // Начало задачи 1

performAsyncOperation1 {

print("Задача 1 завершена")

group.leave()

}

group.enter() // Начало задачи 2

performAsyncOperation2 {

print("Задача 2 завершена")

group.leave()

}

// Уведомление по завершению всех задач в группе

group.notify(queue: .main) {

print("Все задачи завершены, обновляем UI")

// Обновление UI

}

Отложенное выполнение: Выполнение кода через определенный промежуток времени.

// Отложенное выполнение через 2 секунды

DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {

print("Запущено через 2 секунды")

}

GCD предоставляет более высокоуровневый и эффективный подход к управлению потоками по сравнению с традиционными потоками (например, Thread), делегируя управление пулом потоков системе и позволяя эффективно использовать многоядерные процессоры.

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

В Swift существуют три основных типа диспетчеризации:

Статическая (Static or Direct Dispatch): Самый быстрый тип. Компилятор точно знает, какой метод будет вызван, на этапе компиляции. Нет накладных расходов на поиск метода. Применяется для:

Структур (struct).

Перечислений (enum).

Методов в классах, помеченных как final или private (если нет @objc и не используется динамическая диспетчеризация через Objective-C runtime).

Глобальных и статических функций.

Расширений (extension).

Структур (struct).

Табличная (Table Dispatch): Используется для классов. Для каждого класса создается виртуальная таблица (vtable), содержащая указатели на реализации методов. При вызове метода происходит поиск указателя в таблице. Есть небольшие накладные расходы. Применяется для:

Методов экземпляров классов (class methods), не помеченных как final или private.

Свойств вычисляемых свойств (computed properties).

class Animal {

func speak() { // Table Dispatch

print("...")

}

}

class Dog: Animal {

override func speak() { // Переопределенный метод, также Table Dispatch

print("Woof")

}

}

Динамическая (Dynamic Dispatch): Самая медленная. Разрешение метода происходит во время выполнения через Objective-C runtime. Используется для взаимодействия с Objective-C. Имеет значительные накладные расходы. Применяется для:

Методов, помеченных как @objc dynamic.

Свойств, помеченных как @objc dynamic.

Key-Value Observing (KVO).

Key-Value Coding (KVC).

class SomeClass: NSObject {

@objc dynamic func performAction() { // Dynamic Dispatch

print("Performing action")

}

}

Сводная таблица:

Выбор типа диспетчеризации важен для оптимизации производительности. Swift по умолчанию выбирает наиболее производительный тип, позволяющий сохранить гибкость полиморфизма там, где это необходимо (используя табличную диспетчеризацию для классов). Использование final или private может помочь компилятору применять статическую диспетчеризацию.

Swift использует как типы значений, так и типы ссылок для коллекций, однако стандартные коллекции (Array, Dictionary, Set) ведут себя как типы значений.

При копировании экземпляра Array, Dictionary или Set происходит копирование ссылок на элементы, но сама структура коллекции — это значение. Это означает, что изменения в одной копии не затрагивают другую, если только не изменяются сами элементы (если они являются типами ссылок).

Пример:

Если элементы коллекции — типы ссылок, то при копировании коллекции копируются ссылки на те же самые объекты.

Это поведение "copy-on-write" (копирование при записи) оптимизировано: фактическое копирование данных коллекции происходит только тогда, когда одна из ее копий модифицируется. Это повышает производительность, особенно при передаче коллекций в функции.

Сложность метапрограммирования и reflection по сравнению с некоторыми другими языками. Отсутствие стандартных удобных механизмов для кодогенерации. Повышенные требования к стабильности ABI для сторонних библиотек. Не всегда очевидное поведение системы ошибок Result.

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

В типичных архитектурных паттернах для iOS (MVC, MVVM, VIPER):

MVC (Model-View-Controller): Часто бизнес-логика рассредоточена между Model (ответственна за данные и их обработку) и Controller (координирует взаимодействие Model и View). Это может приводить к "толстым" контроллерам.

MVVM (Model-View-ViewModel): Бизнес-логика преимущественно хранится в ViewModel. ViewModel содержит презентационную логику и бизнес-логику, не связанную напрямую с UI. View привязывается к ViewModel.

VIPER (View-Interactor-Presenter-Entity-Router): Бизнес-логика сосредоточена в Interactor. Interactor содержит основные правила приложения и операции, не связанные с презентацией данных. Presenter управляет Interactor и форматирует данные для View. Entity представляет данные. Router отвечает за навигацию.

Кроме того, в более сложных архитектурах могут присутствовать отдельные слои или модули, специально выделенные для хранения бизнес-логики:

Domain Layer: Независимый от UI и инфраструктуры слой, содержащий основные сущности, правила и операции бизнеса.

Service Objects: Классы, выполняющие определенную бизнес-операцию или набор операций.

Use Cases (или Interactors): Специфические реализации бизнес-сценариев, часто используемые в чистой архитектуре или VIPER.

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

Swift поддерживает семантику копирования (value semantics) и семантику ссылки (reference semantics).

Семантика копирования (Value Semantics):

При присваивании или передаче в функцию создается копия значения.

Изменение копии не влияет на оригинал.

Используется для структур (struct), перечислений (enum) и кортежей (tuple).

Гарантирует предсказуемое поведение и потокобезопасность при работе с неизменяемыми данными.

struct Point {

var x: Int

var y: Int

}

var p1 = Point(x: 1, y: 2)

var p2 = p1 // Происходит копирование

p2.x = 10 // Изменяем копию

print(p1.x) // Выведет 1, оригинал не изменился

Семантика ссылки (Reference Semantics):

При присваивании или передаче в функцию передается ссылка на один и тот же объект в памяти.

Изменение объекта через любую ссылку влияет на все остальные ссылки на этот же объект.

Используется для классов (class).

Позволяет использовать наследование и полиморфизм.

class Circle {

var radius: Double

init(radius: Double) {

self.radius = radius

}

}

var c1 = Circle(radius: 5.0)

var c2 = c1 // Происходит копирование ссылки

c2.radius = 10.0 // Изменяем объект по ссылке

print(c1.radius) // Выведет 10.0, оригинал изменился

LazyVStack

LazyHStack

LazyVGrid

LazyHGrid

Сборщик мусора (Garbage Collector) - это форма автоматического управления памятью, которая работает в фоновом режиме и освобождает память, выделенную под объекты, которые больше не используются программой. Он определяет недостижимые объекты и делает выделенную ими память доступной для повторного использования.

В iOS/macOS разработке с использованием Objective-C и Swift, вместо традиционного сборщика мусора используется Automatic Reference Counting (ARC). Хотя это и не прямое GC, ARC выполняет аналогичную функцию, автоматизируя управление памятью.

Принципы работы GC (для контекста, хотя не применимо к ARC):

Tracing (Трассировка): GC начинает с набора "корневых" объектов (например, глобальные переменные, текущие стековые фреймы). Он затем обходит граф объектов, начиная с корней, помечая все достижимые объекты. Все остальные объекты считаются недостижимыми.

Compaction (Компактирование): В некоторых реализациях GC может перемещать достижимые объекты в памяти, чтобы исключить фрагментацию.

Sweeping (Очистка): Недостижимые объекты удаляются, и занимаемая ими память возвращается в пул свободной памяти.

ARC в Swift/Objective-C:

ARC работает на этапе компиляции и автоматически добавляет код (retain, release, autorelease - в Objective-C; увеличение/уменьшение счетчика ссылок в Swift) для отслеживания количества сильных ссылок на каждый экземпляр класса. Когда количество сильных ссылок на объект становится равным нулю, память, занимаемая объектом, освобождается.

Отличия ARC от традиционного GC:

Время работы: ARC работает во время компиляции и выполнения, встраивая код управления памятью. Традиционный GC работает в фоне во время выполнения, в отдельном потоке или в приостановках.

Предсказуемость: Время освобождения памяти в ARC более предсказуемо, так как происходит сразу после того, как счетчик ссылок достигает нуля. В GC время освобождения памяти может варьироваться.

Циклы сильных ссылок: ARC не может автоматически разрешать циклы сильных ссылок (strong reference cycles). Для их предотвращения используются слабые (weak) или бесхозные (unowned) ссылки. Традиционный GC зачастую умеет обнаруживать и разрывать такие циклы.

Нагрузка: ARC распределяет нагрузку по управлению памятью по всему времени выполнения. Традиционный GC может вызывать "паузы" (stop-the-world), когда он активно собирает мусор.

Преимущества ARC (относительно ручного управления памятью):

Снижение ошибок: Значительно уменьшает количество ошибок, связанных с утечками памяти (memory leaks) и двойным освобождением памяти (double free).

Удобство: Разработчику не нужно вручную управлять счетчиком ссылок.

Преимущества традиционного GC:

Обработка циклических ссылок: Могут автоматически разрешать циклические ссылки.

Сложность реализации: Может быть проще в реализации для очень динамических языков.

На iOS и macOS используется ARC как основной механизм управления памятью для объектов классов (значимые типы, как структуры и перечисления, управляются стеком или содержатся в куче как часть объекта класса).

UIKit — основной фреймворк для построения пользовательских интерфейсов на платформах iOS, tvOS и watchOS. Он предоставляет набор готовых элементов управления, механизмы обработки событий, управления жизненным циклом приложений и других инструментов для создания интерактивных интерфейсов.

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

Элементы управления: Кнопки, текстовые поля, таблицы, коллекции и другие стандартные UI-компоненты.

Обработка событий: Механизмы отслеживания взаимодействий пользователя (тапы, жесты и т.д.).

Архитектура MVC/MVVM: Поддержка разделения логики приложения на модель, представление и контроллер/модель представления.

Управление жизненным циклом: API для обработки различных состояний приложения (запуск, фон, завершение).

Анимация: Инструменты для создания плавных переходов и визуальных эффектов.

Автолейаут: Декларативный способ определения правил расположения UI-элементов.

Поддержка локализации и доступности.

UIKit является фундаментом для большинства нативных приложений iOS, хотя в последние годы набирает популярность SwiftUI как альтернатива для декларативного построения UI.

Singleton нарушает принципы SOLID и усложняет тестирование:

Нарушение принципа единственной ответственности (SRP): Класс одновременно отвечает за свою логику и за управление своим жизненным циклом (создание и доступ к единственному экземпляру).

Нарушение принципа открытости/закрытости (OCP): Расширение функционала Singleton-класса может быть затруднено без изменения его кода.

Нарушение принципа подстановки Барбары Лисков (LSP): Подтипы Singleton-класса могут не удовлетворять контрактам базового типа из-за особенностей реализации Singleton.

Нарушение принципа инверсии зависимостей (DIP): Модули зависят от конкретной реализации Singleton, а не от абстракций. Из-за этого становится сложно подменить Singleton-объект на mock или stub для тестирования.

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

Скрытые зависимости: Использование Singleton скрывает зависимости между модулями, поскольку они не передаются явно.

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

Сложность утилизации: Управление жизненным циклом Singleton-объекта и его утилизация могут быть неочевидными.

Вместо Singleton часто предпочтительнее использовать внедрение зависимостей (Dependency Injection) или Service Locator для управления жизненным циклом объектов и их доступа.

Пример типичной реализации Singleton на Swift, который демонстрирует упомянутые проблемы:

Типы значения (Value Types) и ссылочные типы (Reference Types) отличаются способом хранения и передачи данных.

Типы значения:

Присваивание создает независимую копию данных.

Данные хранятся напрямую в месте объявления или в стеке (для локальных переменных).

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

Ссылочные типы:

Присваивание создает новую ссылку на те же данные.

Данные хранятся в куче, а ссылка на них - в месте объявления или в стеке.

Классы являются ссылочными типами.

Основные отличия в таблице:

Выбор между типами зависит от задачи: для представления простых независимых данных предпочтительны Value Types, для объектов с общей изменяемой сущностью и наследованием - Reference Types (классы). Использование Value Types может улучшить производительность за счет снижения нагрузки на кучу и упрощения управления памятью.

Анимации в UIKit — это визуальные изменения свойств UI-элементов (например, позиция, размер, цвет, прозрачность) с течением времени, создающие иллюзию движения и динамики.

Основные способы реализации анимаций в UIKit:

UIView.animate(withDuration:animations:): Простейший способ для анимирования стандартных свойств UIView.

// Анимация смены центра и прозрачности UIView

UIView.animate(withDuration: 0.5) {

myView.center = newCenter

myView.alpha = 0.0

}

UIView.animate(withDuration:delay:options:animations:completion:): Более гибкий метод с параметрами задержки, кривой анимации и блоком завершения.

// Анимация с задержкой и опцией повторения

UIView.animate(withDuration: 0.8, delay: 0.2, options: [.repeat, .autoreverse]) {

myView.backgroundColor = .red

} completion: { finished in

// Действия после завершения (если не повторяется бесконечно)

}

Основные опции анимации (UIView.AnimationOptions):

.curveEaseInOut

.curveEaseIn

.curveEaseOut

.curveLinear

.repeat

.autoreverse

.allowUserInteraction

.curveEaseInOut

.curveEaseIn

.curveEaseOut

.curveLinear

.repeat

.autoreverse

Constraint based animations: Анимирование изменений констрейнтов с помощью layoutIfNeeded().

// Изменяем констрейнт

myViewHeightConstraint.constant = 200

// Анимируем изменение макета

UIView.animate(withDuration: 0.3) {

self.view.layoutIfNeeded()

}

View transition animations: Переходы между различными представлениями (transition(from:to:duration:options:completion:) или внутри контейнера).

// Пример с transition

UIView.transition(from: oldView, to: newView, duration: 0.5, options: [.transitionFlipFromLeft]) { finished in

// Действия после перехода

}

Core Animation (CALayer): Более низкоуровневый и мощный фреймворк для анимации слоев. Позволяет анимировать свойства CALayer (например, position, bounds, opacity, трансформации).

// Простая анимация изменения opacity CALayer

let animation = CABasicAnimation(keyPath: "opacity")

animation.fromValue = 1.0

animation.toValue = 0.0

animation.duration = 1.0

myView.layer.add(animation, forKey: "fadeAnimation")

Анимации с использованием UIStackView: Автоматическая анимация изменений расположения элементов.

// Добавление представления в UIStackView с анимацией

stackView.addArrangedSubview(newView)

stackView.layoutIfNeeded()

}

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

Объект внутри класса (inner object) привязан к экземпляру внешнего класса и существует только в его жизненном цикле. Компаньон объекта (companion object) существует как синглтон, не привязанный к экземпляру класса, и может быть доступен напрямую через имя класса.

Основные различия:

Привязка к экземпляру: inner object привязан к экземпляру внешнего класса, companion object не привязан.

Доступ: к inner object можно получить доступ только через экземпляр внешнего класса, к companion object - напрямую через имя класса.

Состояние: inner object может иметь состояние, специфичное для экземпляра, companion object - общее для всего класса.

Использование: inner object часто используется для реализации шаблона State или для группировки связанных объектов внутри экземпляра. companion object используется для фабричных методов, констант или статических членов класса.

Пример:

Соответствие паттерна задаче.

Сложность реализации и поддержки.

Совместимость с существующей архитектурой проекта.

Влияние на производительность.

Простота понимания для других разработчиков.

Наличие готовых решений или библиотек.

Обертка свойства (property wrapper) в Swift — это специальный тип, который позволяет инкапсулировать логику доступа и хранения значения свойства. Она помогает переиспользовать код, связанный с валидацией, кэшированием, синхронизацией и другими аспектами управления свойствами.

Обертка объявляется с помощью атрибута @propertyWrapper и содержит, как минимум, свойство wrappedValue, которое хранит значение. При использовании обертки к свойству автоматически применяется эта логика.

Пример простой обертки, которая ограничивает значение числа в диапазоне от 0 до 100:

Таким образом, property wrapper упрощает декларативное управление поведением свойств и улучшает читаемость кода.

Механизм обработки нажатий в iOS основан на цепочке респондеров (Responder Chain).

Этапы:

Жест (нажатие) распознается фреймворком (UIKit или SwiftUI).

Система ищет UIView под точкой нажатия. Для этого используется метод hitTest(_:with:) в порядке, обратном иерархии представлений (сначала дочерние, затем родительские).

hitTest(_:with:) возвращает самое глубокое подпредставление, которое может обработать событие (т.е. isUserInteractionEnabled равно true и точка находится внутри фрейма). Если такого представления нет, возвращается nil.

Если hitTest(_:with:) вернул представление (называемое "первый респондер"), система отправляет ему событие касания через метод touchesBegan(_:with:).

Если первый респондер не обрабатывает событие (например, не реализует соответствующий метод или вызывает super), событие передается следующему респондеру в цепочке:

Для UIView, следующий респондер — это его суперпредставление (superview).

Для корневого представления контроллера (UIViewController.view), следующий респондер — это его viewController.

Для контроллера представления (UIViewController), следующий респондер — это его представление модала (presenter) или splitViewController, navigationController, tabBarController (в зависимости от контекста).

Для UIWindow, следующий респондер — это объект UIApplication.

Для UIApplication, следующий респондер — это аппликационный делегат (AppDelegate).

Этот процесс продолжается до тех пор, пока событие не будет обработано (метод не вызовет super) или не достигнет конца цепочки (обычно AppDelegate).

Ключевые методы:

hitTest(_:with:): Метод UIView для определения представления, находящегося под точкой касания.

point(inside:with:): Метод UIView, вызываемый методом hitTest, для проверки, находится ли точка внутри фрейма представления.

touchesBegan(_:with:), touchesMoved(_:with:), touchesEnded(_:with:), touchesCancelled(_:with:): Методы UIResponder для обработки жизненного цикла касаний.

Цепочка респондеров (пример):

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

Свойства Compression Resistance Priority:

Каждый Dimension (Horizontal, Vertical) имеет свой приоритет.

Диапазон значений: 1 (самый низкий) до 1000 (самый высокий, UILayoutPriorityRequired).

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

Приоритет 1000 гарантирует, что вью не будет сжато ниже егоintrinsic content size при любых обстоятельствах, если это возможно.

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

Label с динамическим текстом: Если у вас есть несколько UILabel, которые могут отображать текст разной длины, использование приоритетов помогает определить, какой лейбл будет сжат первым, если они не поместятся на экране.

Кнопки: Чтобы текст на кнопке не обрезался, можно установить высокий приоритет Compression Resistance Priority для её UILabel.

Сложные макеты: В сложных Auto Layout макетах приоритеты используются для разрешения неоднозначностей при нехватке пространства.

Пример: Два UILabel в горизонтальном стеке.

Связь с Content Hugging Priority:

Content Hugging Priority: Определяет, насколько неохотно вью будет увеличиваться от своего intrinsic content size при избытке свободного пространства.

Высокий Content Hugging Priority = вью "обнимает" свой контент и не расширяется.

Высокий Compression Resistance Priority = вью "сопротивляется" сжатию и не уменьшается.

Эти два свойства работают вместе, чтобы помочь Auto Layout правильно распределить пространство между вью.

Capture list в Swift используется для явного управления захватом переменных замыканиями (closures). Это позволяет избежать сильных циклов ссылок, особенно при работе с self в замыканиях, которые используются внутри классов. Основные типы захвата: weak и unowned.

Синтаксис capture list:

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

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

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

В iOS потоки можно запустить несколькими способами:

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

Самый низкоуровневый способ, напрямую работающий с потоками операционной системы.

Требует ручного управления жизненным циклом потока.

// Пример использования Thread

let myThread = Thread {

// Код, выполняемый в новом потоке

print("Поток запущен: \(Thread.current)")

}

myThread.start() // запускаем поток

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

Более высокоуровневый абстракция над потоками. Позволяет организовывать операции и управлять их зависимостями.

Потоки управляются автоматически.

// Пример использования OperationQueue

let operationQueue = OperationQueue()

operationQueue.addOperation {

// Код, выполняемый в фоновом потоке OperationQueue

print("Операция выполнена в OperationQueue: \(Thread.current)")

}

Использование Grand Central Dispatch (GCD):

Мощный, простой в использовании механизм для выполнения задач асинхронно и параллельно.

Работает с очередями (queues) вместо явных потоков. Система сама управляет пулом потоков.

// Пример использования GCD для выполнения в фоновой очереди

DispatchQueue.global(qos: .userInitiated).async {

// Код, выполняемый в фоне

print("GCD выполнил задачу в фоне: \(Thread.current)")

// Выполнение кода в главном потоке (UI thread)

DispatchQueue.main.async {

// Код для обновления UI

print("GCD выполнил задачу в главном потоке: \(Thread.current)")

}

}

Использование Task (в контексте Concurrency):

Современный способ выполнения асинхронного кода, представленный в Swift 5.5.

Позволяет легко писать асинхронный код с использованием async/await.

// Пример использования Task

Task {

// Код, выполняемый асинхронно

print("Task выполнился асинхронно: \(Thread.current)")

}

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

UIView — это основной строительный блок пользовательского интерфейса в iOS. Он является подклассом NSResponder и обрабатывает события касания, управляет подпредставлениями, обрабатывает компоновку и отрисовку содержимого.

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

UIView существует, чтобы предоставить высокоуровневую абстракцию для взаимодействия с пользователем и управления визуальным представлением, которое обеспечивает CALayer. UIView делегирует большинство своих задач отрисовки связанному с ним CALayer, но добавляет функциональность по обработке событий, управлению иерархией подпредставлений и поддержке Auto Layout/Constraint-based Layout.

Вкратце: CALayer отвечает за визуальное представление, а UIView добавляет интерактивность и управление иерархией.

Массивы, списки, стеки, очереди, деревья, графы, хеш-таблицы.

Массив: Коллекция элементов одного типа, хранящихся в смежных ячейках памяти. Доступ по индексу.

Список: Последовательная коллекция элементов. Могут быть односвязными, двухсвязными. Добавление/удаление в середине эффективнее, чем в массиве.

Стек: Структура LIFO (Last-In, First-Out). Операции: push (добавить), pop (удалить из конца), peek (посмотреть верхний элемент).

Очередь: Структура FIFO (First-In, First-Out). Операции: enqueue (добавить в конец), dequeue (удалить из начала), peek (посмотреть первый элемент).

Дерево: Иерархическая структура данных. Корень, узлы, потомки. Пример: двоичное дерево поиска.

Граф: Набор вершин (узлов), соединенных ребрами. Может быть ориентированным или неориентированным, взвешенным или невзвешенным.

Хеш-таблица: Коллекция пар ключ-значение. Использует хеш-функцию для быстрого доступа (в среднем). Возможны коллизии.

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

Массивы: Array

Списки: нет встроенного класса, реализуются на основе массивов или пользовательских структур

Стеки/Очереди: реализуются на основе массивов или списков

Деревья: используются во фреймворках, например, в UI (иерархия представлений)

Графы: для представления связей, например сетевых графов

Хеш-таблицы: Dictionary, Set

Enum (перечисление) — это тип, определяющий группу связанных значений. Raw value — это предустановленное значение (например, Int, String, Double), которое можно связать с каждым элементом перечисления. Associated value — это значение, которое может быть добавлено к конкретному элементу перечисления для хранения дополнительной информации, не являющейся частью типа raw value.

Основные отличия:

Presenter (MVP):

Отвечает за логику представления и обновление View. Держит сильную ссылку на View.

Получает данные от Model, форматирует их и передает на View.

Тестируется более изолированно, чем ViewModel (VIPER Presenter может быть сложнее).

ViewModel (MVVM):

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

Инкапсулирует логику представления, которая не связана напрямую с изменением UI в ответ на действия пользователя (например, форматирование данных).

Observable/bindable свойства ViewModel триггерят обновление UI через связывание данных.

Легко тестируется без зависимости от UIKit/AppKit.

Ключевые отличия:

Async/await в Swift построен на основе structured concurrency и использует Dispatch/Global Actors. Под капотом работают следующие механизмы:

Continuation Style: Асинхронные функции компилируются в стиль продолжений. Вместо немедленного возврата значения, управление передается в рантайм Swift, который сохраняет текущее состояние функции (локальные переменные, указатель исполнения). Когда асинхронная операция завершается, рантайм возобновляет выполнение функции с сохраненного состояния.

Task: Каждое вызов async функции создает или присоединяется к структурированной Task. Task представляет единицу работы и формирует иерархию. Родительская Task ожидает завершения дочерних Task.

Job: Task разбивается на более мелкие единицы работы, называемые Job. Job представляет собой фрагмент кода, который может быть выполнен на исполнителе (Executor).

Executor: Исполнитель отвечает за запуск Job. В стандартной библиотеке используются глобальные DispatchQueue как исполнители для большинства асинхронных задач. Специфические контексты (например, MainActor) имеют свои специализированные исполнители.

Suspension Points: await является точкой приостановки (suspension point). При достижении await, функция приостанавливается, управление передается вызывающему коду или исполнителю, а текущий Job завершается. Рантайм Swift сохраняет состояние функции.

Resumption: Когда асинхронная операция, на которую ожидал await, завершается, рантайм Swift создает новый Job для продолжения выполнения приостановленной функции. Этот Job ставится в очередь на исполнитель.

Cancellation: Structured concurrency поддерживает иерархическую отмену. Отмена родительской Task автоматически отменяет все ее дочерние Task. Асинхронные операции могут проверять статус отмены и реагировать соответствующим образом.

Вот упрощенный пример компиляции async функции:

Await по сути является синтаксическим сахаром над управлением Continuation и планированием Job на исполнителе. Приостановка не блокирует поток напрямую, а позволяет исполнителю выполнять другие Job.

Употребление UIView вместо CALayer для взаимодействия пользователя:

Обработка событий: UIView является частью иерархии Responder Chain, позволяя обрабатывать пользовательские взаимодействия (тач, жесты и т.д.). CALayer напрямую этого не делает.

Иерархия: UIView строит иерархию представлений (view heirarchy), которая более высокоуровневая и интуитивно понятная для построения пользовательского интерфейса, чем иерархия слоев.

Анимации: Хотя CALayer поддерживает Core Animation, UIView предоставляет более простые и высокоуровневые механизмы анимации, такие как UIView animate.

Автоматические ограничения: UIView работает с Auto Layout для построения адаптивных интерфейсов, в то время как CALayer оперирует только фреймами и трансформациями.

Доступность: UIView участвует в системе доступности (Accessibility).

draw(_:) метод: UIView предоставляет метод draw(_:) для пользовательской отрисовки, который управляется системой и легче в использовании, чем делегирование отрисовки слою.

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

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

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

Улучшение отзывчивости UI (например, выполнение длительных операций в фоновом потоке).

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

Упрощение организации асинхронных операций.

Недостатки:

Сложности в синхронизации доступа к общим ресурсам (гонки данных).

Накладные расходы на создание и управление потоками.

Отладка многопоточных приложений более сложна.

В iOS для работы с потоками часто используются:

Thread (низкоуровневый API)

Grand Central Dispatch (GCD) (высокоуровневый, основанный на очередях)

Operations (высокоуровневый, объектно-ориентированный абстракция над GCD)

Пример создания потока с помощью Thread:

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

В iOS у UIView есть два важных свойства для работы с координатами — frame и bounds.

frame — это прямоугольник, который описывает положение и размер в системе координат супервью (родительского вида). То есть frame.origin — это координаты в родительском пространстве.

bounds — это прямоугольник, описывающий внутренние координаты самого вида. Обычно bounds.origin равен (0,0), а размер совпадает с размером frame.size. bounds определяет, как содержимое вида располагается внутри него.

Пример:

Если у UIView есть frame с origin (50, 100) и size (200, 100), то он расположен на 50 по X и 100 по Y относительно родителя. Его bounds обычно будет origin (0,0) и size (200,100), то есть внутренние координаты вида.

Таким образом:

frame — позиция и размер относительно родителя.

bounds — координаты и размер внутри самого вида.

Состояние user interaction enabled false на уровне UIView в iOS означает, что данный UIView и его подпредставления не могут получать события касания.

Ситуации, когда это может возникнуть и повлиять на обработку событий Vue-компонентами, встроенными, например, через WKWebView:

Наложение прозрачного представления: Если поверх WKWebView наложено другое представление с isUserInteractionEnabled = true и оно полностью или частично перекрывает область WKWebView, события касания будут перехватываться верхним представлением и Vue-компоненты в WKWebView их не получат.

Отключение взаимодействия для WKWebView: Свойство isUserInteractionEnabled самого WKWebView установлено в false. Это полностью отключает обработку касаний внутри веб-представления.

Отключение взаимодействия для родительского представления WKWebView: Если WKWebView является дочерним представлением некоторого UIView, и у этого родительского UIView установлено isUserInteractionEnabled = false, то все его дочерние представления, включая WKWebView, также не будут получать события касания.

Модальные представления или алерт-контроллеры: Отображение модального представления (UIViewController.present) или системного алерт-контроллера поверх основного представления, содержащего WKWebView, перехватывает все события касания до тех пор, пока модальное представление или алерт не будет закрыт.

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

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

Ошибки в Auto Layout: Неправильные настройки Auto Layout могут привести к тому, что WKWebView будет невидим (0 размер, скрыт за другим представлением) или его фрейм будет за пределами экрана, что фактически делает его неактивным для взаимодействия пользователя.

В контексте Vue, работающего внутри WKWebView, все эти iOS-specific причины могут привести к тому, что события touchstart, touchend, click и т.д., которые необходимы для работы Vue-директив (v-on:click, @tap в Quasar/Ionic Vue) или обработки событий напрямую, просто не достигнут JavaScript-кода в WKWebView. Отладка в такой ситуации требует проверки иерархии представлений в Xcode (ViewController Hierarchy Debugger) и состояния свойства isUserInteractionEnabled на различных уровнях.

var объявляет переменную, значение которой может быть изменено. let объявляет константу, значение которой нельзя изменить после инициализации.

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

При хранении даты (Date) в памяти, чаще всего используется унифицированное представление, например, количество секунд с некоторой опорной точки (Epoch), занимающее фиксированный, сравнительно небольшой объем памяти (обычно 64 бита для TimeInterval).

При хранении изображения (UIImage), в памяти обычно хранится его пиксельное представление. Объем памяти зависит от размеров изображения (ширина * высота) и формата пикселей (количество байт на пиксель, например, 4 байта для RGBA). Дополнительно может потребоваться память для декомпрессии изображения, если оно было сжато (например, JPEG).

Основные отличия:

Тип данных: Дата - числовое или структурированное представление момента времени. Изображение - двумерный массив пиксельных данных.

Объем памяти: Дата занимает фиксированный, небольшой объем. Объем изображения зависит от его размеров и формата, и может быть значительно больше.

Представление: Дата - абстрактное значение. Изображение - набор конкретных цветовых данных для каждого пикселя.

Операции: К дате применяются арифметические операции (прибавление/вычитание интервалов). К изображению - операции обработки пикселей (масштабирование, фильтры).

Нет, Core Data не изолирует контекст разработчика в смысле полной изоляции данных или объектов между различными потоками или экземплярами NSManagedObjectContext.

Каждый NSManagedObjectContext представляет собой "блокнот" для работы с данными из persistent store. Объекты (NSManagedObject) могут быть зарегистрированы в одном или нескольких контекстах.

Изоляция контекстов реализуется следующим образом:

Data consistency: Каждый контекст следит за изменениями в своих зарегистрированных объектах. Изменения в одном контексте не сразу видны в другом, пока они не будут сохранены (save()) и другие контексты не "получат" эти изменения (например, через уведомления NSManagedObjectContextDidSaveNotification или механизм automaticallyMergesChangesFromParent).

Thread Concurrency: NSManagedObjectContext не является потокобезопасным. Доступ к контексту и его объектам должен осуществляться только из того потока или очереди, на которой был создан контекст. Core Data предоставляет механизмы для работы с контекстами в разных потоках:

mainQueueConcurrencyType: Контекст, привязанный к главной очереди. Используется для UI.

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

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

Если использовать один и тот же объект NSManagedObject в разных потоках без proper Context-Thread affinity, это приведет к крашам. Для безопасной передачи объектов между контекстами используются NSManagedObjectID.

Таким образом, Core Data обеспечивает конкурентный доступ с изоляцией на уровне контекста, а не на уровне данных или объектов, требованием работы с контекстом в его "родном" потоке/очереди. Разработчик сам отвечает за правильное управление потоками и синхронизацией между контекстами.

Механизм synchronized в Java основан на концепции мониторов (monitor objects). Каждому объекту в Java ассоциирован монитор.

Когда поток входит в синхронизированный блок или метод, он пытается захватить монитор соответствующего объекта. Если монитор свободен, поток захватывает его и продолжает выполнение. Если монитор занят другим потоком, текущий поток блокируется и переходит в состояние ожидания, пока монитор не освободится.

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

Внутри монитора реализованы:

Взаимное исключение (Mutual Exclusion): Только один поток может владеть монитором объекта в один момент времени. Это гарантирует, что только один поток может выполнять синхронизированный код для данного объекта.

Ожидание и уведомление (Waiting and Notification): Монитор включает в себя набор связанных методов wait(), notify() и notifyAll(). Эти методы позволяют потокам, владеющим монитором, временно освободить его и перейти в состояние ожидания, а затем быть уведомленными другими потоками о возможности продолжить работу.

Реализация под капотом включает в себя:

monitorenter инcтрукция: Выполняется при входе в синхронизированный блок или метод. Пытается захватить монитор объекта.

monitorexit инcтрукция: Выполняется при выходе из синхронизированного блока или метода (как в случае нормального завершения, так и при исключении). Освобождает монитор объекта.

Эти инструкции являются частью байт-кода Java и обрабатываются виртуальной машиной Java (JVM). JVM использует нативные механизмы операционной системы (например, mutexes, semaphores) для управления мониторами и блокировкой/разблокировкой потоков.

Захват монитора может быть реализован на уровне объекта (для instance methods и blocks) или на уровне класса (для static methods и blocks), используя объект Class как блокировку.

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

Таблица методов класса Object, связанных с мониторами:

Подсчет ссылок происходит в момент выполнения кода (runtime).

Множество (Set) быстрее для проверки включения элементов.

Объяснение:

Массив (Array): Проверка на включение элемента в массив занимает время O(n) в среднем, где n — количество элементов. Для проверки, входит ли одна коллекция в другую, потребуется n итераций, каждая из которых — O(m), где m — размер второй коллекции. Итого O(n*m).

Множество (Set): Проверка на включение элемента в множество занимает время О(1) в среднем. Для проверки, входит ли одна коллекция в другую, потребуется n итераций, каждая из которых — O(1). Итого O(n).

Пример с множеством:

Создать Set из первой коллекции.

Итерировать по второй коллекции, проверяя каждый элемент на вхождение в созданный Set.

UIView

UIViewController

UIApplication

UIWindow

Сериализация — это процесс преобразования объекта в поток байтов для сохранения или передачи данных. Обратный процесс — десериализация.

Компарация (сравнение) — это процесс определения отношений между двумя или более объектами (равны, меньше, больше).

Примеры:

Сериализация в JSON:

Компарация объектов:

Таблица различий:

Редактор кода: SwiftUI и UIKit для разработки пользовательских интерфейсов, написание логики на Swift.

Интерфейс Builder: Создание и компоновка UI-элементов в сторибордах и XIB-файлах.

Отладчик (Debugger): Простанова точек останова, просмотр значений переменных, анализ стека вызовов для выявления и исправления ошибок.

Инструменты профилирования (Instruments): Анализ производительности приложения (потребление памяти, ЦПУ, батареи), выявление узких мест.

Симулятор и Устройства: Тестирование приложения на различных виртуальных и физических устройствах.

Контроль версий (Git Integration): Управление изменениями кода, работа с репозиториями (GitHub/GitLab/Bitbucket).

Test Navigator: Запуск и управление модульными (Unit Tests) и UI-тестами.

Asset Catalogs: Управление изображениями, цветами, шрифтами и другими ресурсами.

Core Data Editor: Моделирование данных и управление схемами при использовании Core Data.

Source Control Navigator: Просмотр истории коммитов, управление ветками.

Localization: Добавление и управление локализованными строками и ресурсами.

Scheme Editor: Настройка параметров сборки и запуска приложения для различных конфигураций (Debug, Release).

Build Settings: Настройка параметров компиляции, линковки и подписи кода.

Preview в SwiftUI: Интерактивный предпросмотр UI-элементов.

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

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

Уменьшение утечек памяти: Автоматизация процесса освобождения памяти снижает вероятность ошибок, связанных с забытыми release или dealloc.

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

Упрощение кода: Разработчику не нужно вручную заниматься управлением памятью, что делает код чище и понятнее.

Несмотря на автоматизацию, при использовании ARC важно понимать концепции сильных и слабых ссылок, чтобы избежать циклов сильных ссылок (retain cycles).

Пример сильной ссылки:

Пример использования слабой ссылки для разрыва цикла:

ARC применяется к объектам классов. Структуры и перечисления являются типами-значениями и не подвергаются автоматическому управлению памятью посредством ARC.

В стандартном iOS приложении для каждого потока (thread) выделяется свой стек. Таким образом, количество стеков равно количеству потоков в приложении.

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

Итого:

Стеки: Количество равно количеству потоков.

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

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

Пример:

Типы данных определяют набор возможных значений и операции, которые можно над ними выполнять. В Swift они делятся на Value Types и Reference Types.

Value Types (типы-значения):

Значения копируются при присваивании или передаче в функцию.

Каждая переменная или константа хранит собственную независимую копию данных.

Изменения одной копии не влияют на другие.

Примеры Value Types:

Структуры (struct)

Перечисления (enum)

Кортежи (tuple)

Базовые типы: Int, Double, Bool, String, массивы (Array), словари (Dictionary), множества (Set).

Reference Types (ссылочные типы):

Значения не копируются. При присваивании или передаче

в функцию передается ссылка на один и тот же экземпляр данных в памяти.

Несколько переменных или констант могут ссылаться на один и тот же экземпляр.

Изменения через одну ссылку видны через все другие ссылки.

Примеры Reference Types:

Классы (class)

Функции (func)

Замыкания (closure)

Таблица с основными типами:

Выбор между Value и Reference Type зависит от задачи: Value Types предпочтительнее для мелких данных и обеспечения потокобезопасности, Reference Types - для сложных объектов, требующих общего состояния и наследования.

iOS использует иерархическую структуру памяти с несколькими типами:

ОЗУ (Оперативная Память):

Быстрый, энергозависимый тип памяти.

Используется для хранения активно работающих приложений и данных.

Управляется операционной системой и механизмом ARC (Automatic Reference Counting) для объектов Swift/Objective-C.

При нехватке памяти система может выгружать (терминировать) наименее активные приложения.

Встроенная Память (NAND Flash):

Медленнее ОЗУ, но энергонезависимая.

Используется для хранения операционной системы, приложений, пользовательских данных (фото, видео, документы).

Имеет ограниченный ресурс по количеству циклов перезаписи.

Кэш-память (L1, L2, L3):

Очень быстрая память на уровне процессора.

Используется для хранения часто используемых инструкций и данных для ускорения доступа к ОЗУ.

ARC (Automatic Reference Counting)

Механизм управления памятью в Swift и Objective-C, который автоматически отслеживает и управляет ссылками на объекты.

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

Предотвращает утечки памяти, но требует внимательности при работе с циклическими сильными ссылками (strong reference cycles), которые можно разрешать с помощью weak или unowned ссылок.

Virtual Memory

iOS использует виртуальную память, позволяя программам использовать адресное пространство большее, чем физически доступная ОЗУ.

Страницы памяти могут быть перенесены между ОЗУ и файловой системой (хотя в iOS это происходит ограниченно, в основном через очистку неиспользуемых страниц, а не полноценный свопинг на диск как в macOS).

Mechanisms like "compressed memory" can compress pages instead of writing them to storage.

Управление Памятью в Приложении

Разработчик в основном работает с памятью через ARC.

Важно избегать утечек памяти, правильно используя weak и unowned.

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

Инструменты вроде Xcode Instruments (Allocations, Leaks) используются для профилирования потребления памяти и поиска утечек.

Пример использования weak для предотвращения цикла сильных ссылок:

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

Отмена реализована через флаг внутри DispatchWorkItem. Механизм отмены коопративный: DispatchWorkItem сам должен регулярно проверять свой статус отмены.

При вызове метода cancel() у DispatchWorkItem, устанавливается флаг отмены. Фактическое прерывание выполнения происходит только в том случае, если внутри блока кода DispatchWorkItem выполняется проверка этого флага с помощью свойства isCancelled.

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

Ключевые моменты:

Немедленная отмена не гарантируется; зависит от частоты проверки isCancelled внутри блока.

Отмененный DispatchWorkItem может быть поставлен в очередь, но его блок не будет выполнен, если isCancelled будет проверен до начала выполнения.

Вызов cancel() на уже выполненном или отмененном DispatchWorkItem не имеет эффекта.

Свойства:

SOLID — это набор пяти принципов объектно-ориентированного проектирования, направленных на создание более гибкого, поддерживаемого и масштабируемого кода.

Single Responsibility Principle (SRP): Класс должен иметь только одну причину для изменения.

Open/Closed Principle (OCP): Программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации.

Liskov Substitution Principle (LSP): Подтипы должны быть заменяемыми своими базовыми типами без нарушения корректности программы.

Interface Segregation Principle (ISP): Клиенты не должны зависеть от интерфейсов, которые они не используют.

Dependency Inversion Principle (DIP): Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Применение в разработке:

SRP: Разделение функциональности на мелкие, специализированные классы (например, отдельный класс для работы с сетью, отдельный для работы с базой данных).

OCP: Использование протоколов (interfaces) и абстрактных классов для расширения поведения без изменения существующего кода (например, реализация стратегий).

// Пример OCP с протоколом

protocol PaymentStrategy {

func processPayment(amount: Double)

}

class CreditCardPayment: PaymentStrategy {

func processPayment(amount: Double) {

// Логика оплаты кредитной картой

}

}

class PaypalPayment: PaymentStrategy {

// Логика оплаты через PayPal

}

}

class PaymentProcessor {

let strategy: PaymentStrategy

init(strategy: PaymentStrategy) {

self.strategy = strategy

}

func makePayment(amount: Double) {

strategy.processPayment(amount: amount)

}

}

LSP: Гарантия, что наследующие классы не нарушают ожидаемого поведения базового класса (например, при работе с коллекциями).

// Пример, нарушающий LSP

class Bird {

func fly() { print("Летит") }

}

class Ostrich: Bird {

// Переопределяем метод fly, который для страуса не имеет смысла этого действия

// Нарушает ожидание пользователя Bird о том, что он может летать.

override func fly() { fatalError("Страус не летает") }

}

let birds: [Bird] = [Bird(), Bird(), Ostrich()]

// При итерации по массиву можно получить сбой при вызове fly() у Ostrich

for bird in birds {

bird.fly()

}

ISP: Создание мелких, специфичных протоколов вместо одного большого (например, отдельный протокол для загрузки данных, отдельный для отображения).

// Пример ISP

protocol DataLoading {

func loadData()

}

protocol DataDisplaying {

func displayData()

}

class ViewController: DataLoading, DataDisplaying {

func loadData() {

// Загрузка данных

}

func displayData() {

// Отображение данных

}

}

class DataLoader: DataLoading {

func loadData() {

// Только загрузка данных

}

}

DIP: Использование dependency injection для инверсии зависимостей (например, передача зависимости через конструктор).

// Пример DIP

protocol NetworkService {

func fetchData()

}

class APIService: NetworkService {

func fetchData() {

// Реализация получения данных из API

}

}

class DataManager {

let networkService: NetworkService // Зависимость от абстракции

init(networkService: NetworkService) {

self.networkService = networkService

}

func getData() {

networkService.fetchData()

}

}

// Внедрение зависимости

let api = APIService()

let manager = DataManager(networkService: api)

manager.getData()

Свойства Content Hugging Priority и Content Compression Resistance Priority в Auto Layout определяют, как view должны реагировать, когда их содержимое либо хочет занимать меньше места, чем доступно, либо больше.

Content Hugging Priority: Определяет, насколько сильно view "обнимает" свое содержимое. Чем выше приоритет, тем меньше view будет стремиться растягиваться, чтобы заполнить пустое пространство.

Content Compression Resistance Priority: Определяет, насколько view устойчива к сжатию. Чем выше приоритет, тем менее view будет стремиться уменьшаться, если ее содержимому требуется больше места, чем доступно.

Эти свойства используются для разрешения конфликтов в Auto Layout, когда у view нет явно заданных размеров или они противоречат размерам содержимого.

Пример:

UILabel с длинным текстом и высоким Content Hugging Priority не будет растягиваться, чтобы заполнить контейнер, если текста мало. Если текста много, а соседний view имеет более низкий Content Compression Resistance Priority, то соседний view будет сжат.

UIImageView с высоким Content Hugging Priority не будет растягивать изображение меньшего размера, чтобы заполнить доступное пространство.

defaultLow, defaultHigh, required - стандартные значения приоритетов. Также можно использовать другие значения от 1 до 1000. required всегда равен 1000.

Использование этих свойств позволяет Auto Layout принимать решения о размере view на основе их содержимого и приоритетов, обеспечивая гибкое и адаптивное расположение элементов интерфейса.

Добавить URL изображения.

Создать URLSessionTask для загрузки данных.

В completionHandler задачи преобразовать Data в UIImage.

Обновить UIImageView на главном потоке.

TestFlight используется для бета-тестирования iOS, tvOS, watchOS и macOS приложений среди ограниченного числа пользователей перед их публичным релизом в App Store.

Основные функции:

Распространение бета-сборок: Позволяет легко делиться тестовыми версиями приложения с внутренними (члены команды App Store Connect) и внешними тестировщиками.

Сбор отзывов: Тестировщики могут отправлять отзывы напрямую разработчику, включая снимки экрана с аннотациями.

Аналитика использования: Предоставляет данные о сбоях, времени использования и других метриках для оценки стабильности и производительности.

Управление тестировщиками: Позволяет приглашать, удалять и управлять группами тестировщиков.

Поддержка различных версий: Можно тестировать несколько версий одной сборки или разные сборки одновременно.

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

Опционалы в Swift представляют собой тип, который может содержать либо значение типа T (завернутое значение), либо nil, означающее отсутствие значения.

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

Optional Binding (if let, guard let): Безопасное извлечение значения из опционала.

if let: Извлекает значение и присваивает его временной константе или переменной, если опционал содержит значение. Блок кода выполняется только в этом случае.let optionalString: String? = "Hello"

if let unwrappedString = optionalString {

// unwrappedString имеет тип String

}

guard let: Извлекает значение. Если опционал nil, выполняется блок else (обычно для выхода из текущего scope). Если значение успешно извлечено, оно доступно после guard в текущем scope.func processString(_ optionalString: String?) {

guard let unwrappedString = optionalString else {

return // Выход из функции

}

// unwrappedString типа String доступен здесь

}

}

}

}

Optional Chaining (?): Позволяет вызывать свойства, методы и индексы на опционале. Если опционал nil на любом этапе цепочки, вся цепочка прерывается, и результат имеет тип опционала, содержащего nil.

class Person {

var job: Job?

}

class Job {

var salary: Double?

}

let person: Person? = Person()

person?.job = Job()

let salary = person?.job?.salary // salary имеет тип Double?

Forced Unwrapping (!): Небезопасное извлечение значения. Приводит к ошибке времени выполнения (краш), если опционал содержит nil. Следует использовать с большой осторожностью, только когда точно известно, что опционал не nil.

let requiredString: String! = "Must be present" // Неявно извлекаемый опционал

let unwrappedString = requiredString! // Принудительное извлечение

let dangerousOptional: String? = nil

// let crash = dangerousOptional! // Приведет к ошибке

Nil-Coalescing Operator (??): Предоставляет значение по умолчанию, если опционал nil.

let optionalValue: Int? = nil

let definiteValue = optionalValue ?? 0 // definiteValue будет 0

Implicitly Unwrapped Optionals (!): Опционалы, которые автоматически извлекаются при использовании. Объявляются с помощью !. Следует использовать, когда значение будет гарантированно присвоено до первого использования, но не обязательно при инициализации (например, IBOutlets). При доступе, когда значение nil, происходит ошибка времени выполнения.

var button: UIButton! // Предполагается, что будет инициализирован в viewDidLoad

// button.setTitle("Tap me", for: .normal) // Автоматическое извлечение при использовании

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

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

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

Слабые ссылки (weak references) в Swift используются для предотвращения циклов сильных ссылок (strong reference cycles), которые могут привести к утечкам памяти. В системе автоматического подсчёта ссылок (ARC) объекты удерживаются в памяти, пока на них есть сильные ссылки. Если два объекта ссылаются друг на друга сильными ссылками, они никогда не будут освобождены.

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

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

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

Использовать синхронизацию потоков доступа к общим ресурсам.

Основные подходы:

Mutexes (Мьютексы): Блокируют доступ к ресурсу для других потоков, пока один поток его использует.

NSLock

os_unfair_lock

pthread_mutex_t

NSLock

os_unfair_lock

pthread_mutex_t

Serial Queues (Последовательные очереди): Исполняют задачи одну за другой, гарантируя, что одновременно только один блок кода имеет доступ к ресурсу.

DispatchQueue.main

DispatchQueue.global() с атрибутом .serial

DispatchQueue.main

Reader-Writer Locks (Блокировка читатель-писатель): Позволяют множеству потоков читать ресурс одновременно, но только одному потоку писать в него.

DispatchQueue с барьерами (.barrier) для записи и синхронным/асинхронным доступом для чтения.

Atomic Operations (Атомарные операции): Операции, которые гарантированно выполняются целиком без прерывания другим потоком. Используются для простых типов данных (счетчики, флаги).

OSAtomicIncrement, OSAtomicDecrement (Устаревшие, но концепция актуальна)

C++11 <atomic>

C++11 <atomic>

Value Types (Типы значений): Передача копий структур/перечислений вместо ссылок на классы может помочь избежать проблем с гонкой данных, если данные не разделяются напрямую между потоками в изменяемом виде.

Выбор метода зависит от специфики задачи и требуемого уровня параллелизма. Для большинства задач в iOS предпочтительны DispatchQueue (Serial и Concurrent с барьерами) из-за легкости использования и интеграции с Grand Central Dispatch (GCD).

Content Hugging Priority управляет сопротивлением элемента растяжению, чтобы вместить больше контента, чем его внутренний размер. Более высокий приоритет означает, что элемент менее склонен увеличиваться при добавлении контента. Это полезно для предотвращения нежелательного расширения представлений с фиксированным размером или предпочтительным соотношением сторон в Auto Layout.

Пример: Использование Hugging Priority для двух UILabel в горизонтальном UIStackView.

Сравнение с Content Compression Resistance Priority:

Эти два приоритета работают в Auto Layout для разрешения конфликтов, когда внутренний размер контента элемента не соответствует доступному пространству, определяемому констрейнтами. Hugging определяет, какой элемент будет увеличиваться при наличии избыточного пространства или контента, а Compression Resistance определяет, какой элемент будет сжиматься при недостатке пространства.

Проблема переиспользования ячейки (cell reuse) в UITableView и UICollectionView связана с тем, что при прокрутке таблицы/коллекции система переиспользует ячейки, которые вышли за пределы видимой области, для отображения нового контента. Если перед использованием ячейку не подготовить должным образом, она может отображать старые данные или некорректное состояние.

Решение заключается в следующем:

Регистрация ячейки: Перед использованием ячейки ее необходимо зарегистрировать для переиспользования по определенному идентификатору.

// Регистрация для UITableView

tableView.register(MyCustomCell.self, forCellReuseIdentifier: "MyCellIdentifier")

// Регистрация для UICollectionView

collectionView.register(MyCustomCell.self, forCellWithReuseIdentifier: "MyCellIdentifier")

Это позволяет системе эффективно управлять пулом переиспользуемых ячеек.

Получение переиспользуемой ячейки: В методах tableView(_:cellForRowAt:) или collectionView(_:cellForItemAt:) необходимо запросить переиспользуемую ячейку по зарегистрированному идентификатору.

// Для UITableView

guard let cell = tableView.dequeueReusableCell(withIdentifier: "MyCellIdentifier", for: indexPath) as? MyCustomCell else {

fatalError("Failed to dequeue MyCustomCell") // Используем гарантированное получение с проверкой типа

}

// Для UICollectionView

guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCellIdentifier", for: indexPath) as? MyCustomCell else {

}

Методы dequeueReusableCell(withIdentifier:for:) и dequeueReusableCell(withReuseIdentifier:for:) либо возвращают существующую переиспользуемую ячейку из пула, либо создают новую, если пул пуст.

Настройка ячейки: После получения ячейки необходимо установить корректные данные для текущего indexPath (индекса строки/элемента). Это ключевой шаг для решения проблемы переиспользования.

// В методе cellForRowAt или cellForItemAt

let data = dataArray[indexPath.row] // Пример получения данных из массива

cell.configure(with: data) // Вызов метода настройки ячейки с нужными данными

Метод prepareForReuse(): Переопределение метода prepareForReuse() в кастомном классе ячейки позволяет очищать или сбрасывать состояние ячейки перед тем, как она будет добавлена в пул для переиспользования. Это полезно для сброса изображений, состояний переключателей, отмены асинхронных операций и т.д.

// Внутри класса MyCustomCell

override func prepareForReuse() {

super.prepareForReuse()

// Сброс UI элементов

imageView.image = nil

titleLabel.text = nil

// Отмена асинхронных задач (например, загрузки изображения)

cancelAsyncTask()

// Сброс состояний (например, выбранности)

someSwitch.isOn = false

}

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

Важно всегда настраивать все аспекты ячейки (текстовые поля, изображения, цвета, выбранное состояние и т.д.) на основе данных для текущего indexPath, а не полагаться на предыдущее состояние переиспользованной ячейки.

Work item в терминологии Dispatch (Grand Central Dispatch - GCD) представляет собой единицу работы, которую необходимо выполнить в очереди. Это может быть либо closure (блок кода), либо функция. GCD управляет выполнением этих work items, распределяя их по доступным потокам.

@State

@Binding

@EnvironmentObject

@ObservedObject

@StateObject

Эти property wrapper'ы не объявляют семантику ссылки в традиционном понимании (как указатель в C++), но позволяют работать с данными, представленными в SwiftUI как "ссылки" или общие mutable источники истины. Они управляют жизненным циклом данных и обеспечивают автоматическое обновление UI при их изменении.

Например:

@State: Управляет локальным состоянием внутри View. Изменение @State переменной приводит к перерисовке View.struct MyView: View {

@State private var isActive = false

var body: some View {

Button("Toggle") {

isActive.toggle()

}

}

}

@Binding: Позволяет создать двухстороннюю привязку к @State или другому источнику данных. Изменения в одном месте отражаются в другом.struct ParentView: View {

@State private var value = 0

VStack {

Text("Value: \(value)")

ChildView(count: $value) // $ создает Binding

}

}

}

struct ChildView: View {

@Binding var count: Int // Получаем Binding

Button("Increment") {

count += 1 // Изменение Binding

}

}

}

@ObservedObject: Используется для ссылочных типов (классов), которые реализуют протокол ObservableObject. Изменение помеченных @Published свойств в этом объекте вызывает обновление View.import Combine

class Counter: ObservableObject {

@Published var count = 0

}

struct ContentView: View {

@ObservedObject var counter = Counter() // Ссылочный тип

VStack {

Text("Count: \(counter.count)")

counter.count += 1

}

}

}

}

@StateObject: Похож на @ObservedObject, но предназначен для создания экземпляра ObservableObject внутри View и управления его жизненным циклом. View становится владельцем объекта.import Combine

class TimerData: ObservableObject {

@Published var timerSeconds = 0

init() {

Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in

self.timerSeconds += 1

}

}

}

struct TimerView: View {

@StateObject var timerData = TimerData() // Создание и владение объектом

Text("Time: \(timerData.timerSeconds)")

}

}

@EnvironmentObject: Позволяет передавать экземпляры ObservableObject в иерархии View, не передавая их через инициализаторы каждого View.import Combine

class UserSettings: ObservableObject {

@Published var loggedIn = false

}

struct AppView: View {

@StateObject var settings = UserSettings()

ContentView()

.environmentObject(settings) // Внедрение в окружение

}

}

@EnvironmentObject var settings: UserSettings // Доступ из окружения

Toggle("Logged In", isOn: $settings.loggedIn)

}

}

PreferenceKey - это протокол во фреймворке SwiftUI, позволяющий дочерним View передавать данные своим родительским View в иерархии представлений.

Они используются для определения значений, которые распространяются вверх по дереву представлений. Родительские View могут считывать эти значения с помощью модификатора .preference(key:value:) или реактивно реагировать на их изменения с помощью .onPreferenceChange(key:_:).

Основные компоненты:

PreferenceKey протокол: Определяет тип передаваемого значения (Value) и требует реализации статической функции reduce(value:nextValue:), которая объединяет несколько значений одного ключа в одно. Это необходимо, если несколько дочерних Views передают значение для одного и того же ключа.

Value: Тип данных, который передается (например, CGFloat, Int, String, пользовательские структуры).

reduce(value:nextValue:): Статический метод, который вызывается для объединения значений одного ключа, поступающих от разных дочерних View. Например, для CGFloat можно использовать max или min, для [CGFloat] - объединение массивов.

Пример использования: Получение размера дочернего View.

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

В настройках системы, к которым можно получить доступ через интерфейс UIUserInterfaceStyle в UIKit или preferredColorScheme в SwiftUI.

VIP (View, Interactor, Presenter) - это архитектурный шаблон, представляющий собой вариант Clean Architecture, применяемый в iOS-разработке для разделения ответственности и улучшения тестируемости кода. Основой является однонаправленный поток данных между компонентами.

Компоненты VIP:

View: Отображает данные и отправляет действия пользователя (user actions) Интерэктору. View (UIView, UIViewController или просто протокол) не содержит бизнес-логики.

Interactor: Содержит бизнес-логику. Получает действия от View, выполняет необходимые операции (например, обращение к данным, выполнение расчетов) и отправляет результат Презентеру. Не зависит от UI.

Presenter: Получает данные от Интерэктора, форматирует их для отображения и отправляет View. Не содержит бизнес-логики, отвечает только за представление данных.

Цикл взаимодействия:

Пользователь совершает действие в View.

View отправляет "запрос" (request - struct/enum) Интерэктору.

Интерэктор обрабатывает запрос, выполняет бизнес-логику и отправляет "ответ" (response - struct/enum) Презентеру.

Презентер получает ответ, форматирует данные в "ViewModel" (struct/enum) и отправляет ее View.

View получает ViewModel и обновляет UI.

Дополнительные компоненты (опционально):

Router: Управляет навигацией между VIP-модулями (сценами). Обычно вызывается Интерэктором или Презентером.

Worker: Компонент Интерэктора, отвечающий за выполнение конкретных задач (например, сетевые запросы, работа с Core Data).

Основное преимущество VIP:

Разделение ответственности: Четкое разделение между UI, бизнес-логикой и логикой представления.

Тестируемость: Легко тестировать каждый компонент отдельно, мокая зависимости.

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

Пример структуры папок для VIP модуля (UserScene):

Пример взаимодействия (загрузка данных пользователя):

Жизненный цикл контроллера вида (UIViewController) в iOS представляет собой последовательность событий, происходящих от его создания до уничтожения. Основные этапы:

Инициализация:

init?(coder aDecoder: NSCoder): Инициализация из Storyboard/Nib.

init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?): Инициализация из Nib-файла или программно без Nib.

loadView(): Загрузка или создание корневого представления контроллера. Если используется Storyboard/Nib, вызывается автоматически. При программном создании представления нужно переопределить.

Загрузка представления:

viewDidLoad(): Вызывается после загрузки представления контроллера в память. Подходит для инициализационных настроек, которые не требуют доступа к geometries (размерам).

Появление представления:

viewWillAppear(_ animated: Bool): Вызывается непосредственно перед тем, как представление контроллера собирается стать видимым. Подходит для обновления данных, которые могут измениться между появлениями.

viewDidAppear(_ animated: Bool): Вызывается после того, как представление контроллера стало видимым. Подходит для запуска анимаций или других задач, которые должны выполняться только после полного отображения.

Исчезновение представления:

viewWillDisappear(_ animated: Bool): Вызывается непосредственно перед тем, как представление контроллера собирается стать невидимым. Подходит для сохранения состояния или прекращения активностей.

viewDidDisappear(_ animated: Bool): Вызывается после того, как представление контроллера стало невидимым. Подходит для освобождения ресурсов или остановки процессов, которые не нужны в фоне.

Изменения макета (Layout):

viewWillLayoutSubviews(): Вызывается перед тем, как представления контроллера изменят свои размеры.

viewDidLayoutSubviews(): Вызывается после того, как представления контроллера изменили свои размеры. Подходит для окончательной настройки размеров и позиций элементов.

Изменения трейтов (Trait CollectionDidChange):

traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?): Вызывается при изменении характеристик окружения, таких как размер класса, плотность экрана, светлый/темный режим и т.д.

Освобождение памяти (Deinitialization):

deinit: Вызывается непосредственно перед освобождением объекта контроллера из памяти. Используется для очистки ресурсов, отписки от уведомлений и т.д.

Важные моменты:

Методы loadView, viewDidLoad, viewDidAppear, viewWillDisappear, viewDidDisappear, deinit вызываются единожды за жизненный цикл объекта контроллера.

Методы viewWillAppear и viewDidAppear могут вызываться несколько раз, например, при навигации вперед и назад.

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

Замедление работы приложения.

Падение приложения из-за нехватки доступной памяти (OOM - Out Of Memory).

Некорректное поведение приложения из-за доступа к освобожденной памяти (use-after-free).

Повышенное энергопотребление устройства.

Можно использовать ключевые слова weak или unowned перед объявлением поля протокольного типа.

Важно отметить, что протокол должен быть ограничен классами (: AnyObject), чтобы можно было использовать слабую или несобственную ссылку. Это гарантирует, что тип, реализующий протокол, является ссылочным типом, а не типом значения.

Для поля протокольного типа weak применяется в том случае, когда объект, на который ссылается поле, может быть освобожден раньше, чем объект, содержащий эту ссылку. Если объект, на который ссылается поле, освобождается, поле автоматически становится nil.

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

В большинстве случаев для делегатов и других подобных паттернов используется weak.

Ключевые отличия коллекций Swift (Array, Dictionary, Set) от таковых в других языках (например, Java, C#, Objective-C) заключаются в следующем:

Типобезопасность и вывод типов: Коллекции Swift типобезопасны по своей сути. Тип elementos определяется при инициализации или добавлении первого elemento. Compile-time проверка предотвращает ошибки типа на этапе выполнения.

var numbers: [Int] = [1, 2, 3] // Явная типизация

var strings = ["a", "b", "c"] // Вывод типов

// numbers.append("hello") // Ошибка компиляции: Cannot convert value of type 'String' to expected argument type 'Int'

В языках с less строгой системой типов (например, Objective-C без аннотаций) типы элементов могут быть @id, что требует проверок типа во время выполнения.

Value Types (Структуры): Array, Dictionary и Set в Swift являются структурами (value types), а не классами (reference types). При присваивании или передаче в функцию создается копия коллекции.

var arrayA = [1, 2, 3]

var arrayB = arrayA // Копия коллекции

arrayB.append(4)

print(arrayA) // Вывод: [1, 2, 3]

print(arrayB) // Вывод: [1, 2, 3, 4]

В большинстве других языков коллекции являются классами, и присваивание создает ссылку на ту же коллекцию. Это важное отличие для управления памятью и предсказуемости поведения. Swift использует оптимизацию "copy-on-write" для структур коллекций, что делает копирование эффективным, если только коллекция не модифицируется.

Опциональность элементов: Коллекции Swift могут содержать опциональные типы, что явно указывает на возможность отсутствия элемента.

var optionalNumbers: [Int?] = [1, nil, 3]

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

Мощные функциональные методы: Swift предоставляет богатый набор методов для работы с коллекциями в функциональном стиле: map, filter, reduce, forEach, compactMap и другие.

let numbers = [1, 2, 3, 4, 5]

let doubled = numbers.map { $0 * 2 } // [2, 4, 6, 8, 10]

let even = numbers.filter { $0 % 2 == 0 } // [2, 4]

let sum = numbers.reduce(0, +) // 15

Хотя многие языки тоже имеют эти возможности, в Swift они интегрированы болееseamlessly и являются частью стандартной библиотеки.

Протоколы и Расширения: Коллекции Swift соответствуют ряду протоколов (Collection, Sequence, MutableCollection, RandomAccessCollection и т.д.), что позволяет создавать generic функции и расширения, работающие с любыми типами коллекций, реализующими эти протоколы.

extension Collection {

func printElements() {

for element in self {

print(element)

}

}

}

let myArray = [10, 20, 30]

myArray.printElements() // Работает для Array

let mySet: Set = [1, 2, 3]

mySet.printElements() // Работает для Set

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

Индексация и Доступ: Индексация в Swift безопасна. Попытка доступа по невалидному индексу вызывает ошибку времени выполнения (range out of bounds).

Важно отметить, что хотя в Objective-C также есть коллекции (NSArray, NSDictionary, NSSet), они являются классами, основанными на NSObject, не типобезопасны по умолчанию (до появления Generic на уровне компилятора) и не являются value types. Swift Collections предлагают более современный, безопасный и производительный подход.

Утечка памяти (memory leak) — это ситуация, когда выделенная память больше не используется программой, но остается недоступной для сборщика мусора (или системы управления памятью), поскольку на нее продолжают ссылаться объекты. В iOS это происходит из-за циклических ссылок между объектами, которые используют Automatic Reference Counting (ARC).

Как возникает:

ARC в iOS автоматически управляет памятью, освобождая объекты, когда на них нет активных ссылок. Утечка возникает, когда два или более объекта ссылаются друг на друга сильными (по умолчанию) ссылками, создавая замкнутый цикл. В этом случае счетчик ссылок каждого объекта никогда не достигает нуля, даже если на эту группу объектов нет внешних ссылок. ARC не может освободить эти объекты.

Пример:

В данном примере parent сильно ссылается на child, а child сильно ссылается на parent. Это создает цикл. Когда функция createLeak завершается, локальные переменные parent и child выходят из области видимости, но сильные ссылки между экземплярами Parent и Child остаются, предотвращая их освобождение ARC.

Для предотвращения утечек памяти в iOS используются слабые (weak) и бесхозные (unowned) ссылки, которые не увеличивают счетчик ссылок объекта.

weak: Используется, когда срок жизни объектов может быть разным. Слабая ссылка может стать nil, если объект, на который она ссылается, будет освобожден.

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

Да, приходилось.

Тестирование WebSocket включает проверку установления и поддержания двунаправленного соединения, корректной отправки и получения сообщений, обработки ошибок (разрыва соединения, неверных данных) и производительности при высокой нагрузке.

Методы и инструменты:

Ручное тестирование: Использование инструментов разработчика в браузерах или специализированных клиентов (например, Postman, Paw) для отправки и получения сообщений, мониторинга состояния подключения.

Автоматизированное тестирование: Написание тестов с использованием фреймворков.

Юнит-тесты: Проверка отдельных компонентов, работающих с WebSocket (например, парсинг сообщений, логика обработки событий).

Интеграционные тесты: Проверка взаимодействия клиента с WebSocket-сервером.

Инструменты для автоматизации:

XCTest: Стандартный фреймворк для тестирования в Xcode. Можно писать тесты, имитирующие отправку и получение сообщений.

Mocks / Stubs: Использование мок-объектов или заглушек для имитации WebSocket-сервера в юнит-тестах.

Специализированные библиотеки: Библиотеки, позволяющие создавать тестовые клиенты WebSocket или имитировать сервер (например, Starscream для клиента, Starscream / Vapor для сервера, если тестируется полный цикл).

Особенности тестирования WebSocket на iOS:

Фоновый режим: Проверка поведения WebSocket при переходе приложения в фоновый режим и обратно.

Сетевые условия: Тестирование работы при нестабильном соединении, потере пакетов.

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

В целом, тестирование фокусируется на надежности обмена данными и устойчивости соединения.

Да, является. Swift и Objective-C (основные языки разработки под iOS/macOS) являются сильно типизированными языками.

В сильно типизированных языках типы переменных известны либо на этапе компиляции (статическая типизация, как в Swift), либо на этапе выполнения (динамическая типизация, есть элементы в Objective-C). Это помогает выявлять многие ошибки, связанные с несоответствием типов, до запуска программы.

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

Безопасность: Исключение ошибок, связанных с несовместимыми типами.

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

Читаемость кода: Явное указание типов улучшает понимание кода.

Поддержка инструментов: IDE предоставляют лучшую автодополнение и рефакторинг.

Swift - статически типизированный язык, что означает проверку типов на этапе компиляции. Objective-C имеет элементы как статической, так и динамической типизации (например, позднее связывание).

Пример статической типизации в Swift:

Пример динамической типизации (полиморфизма) в Objective-C:

Сборщик мусора (GC) автоматически определяет и освобождает память, занимаемую объектами, на которые больше нет активных ссылок. Работает асинхронно или в определенные моменты выполнения.

ARC (Automatic Reference Counting) - это механизм компилятора, который во время компиляции добавляет код для увеличения и уменьшения счетчика ссылок на объекты. Когда счетчик ссылок достигает нуля, память освобождается немедленно.

Основные отличия:

Время освобождения памяти: GC - непредсказуемо, ARC - детерминированно (при обнулении счетчика ссылок).

Производительность: GC может вызывать паузы (stop-the-world events), ARC имеет предсказуемую производительность, накладные расходы распределены по ходу выполнения программы.

Циклические ссылки: GC может справляться с циклическими ссылками автоматически (если поддерживает их), ARC требует явного разрешения (weak/unowned ссылки).

Реализация: GC - runtime механизм, ARC - компиляционная техника.

Среда выполнения: GC популярен в языках вроде Java, C#, Python. ARC — стандартный механизм управления памятью в Swift и Objective-C.

Пример использования weak ссылки в Swift для предотвращения цикла ссылок:

Массивы в Swift представляют собой упорядоченные коллекции элементов одного типа. Они являются value type (структурами) в отличии от NSArray в Objective-C.

Ключевые особенности:

Типобезопасность: Массивы содержат элементы строго одного типа. При попытке добавить элемент другого типа возникает ошибка компиляции.

Упорядоченность: Элементы хранятся в определенной последовательности и доступны по индексу, начиная с 0.

Изменяемость: Массивы могут быть изменяемыми (если объявлены с var) или неизменяемыми (если объявлены с let).

Value Type: При присваивании массива новой переменной или передаче его функции происходит копирование (copy-on-write). Это означает, что модификация копии не влияет на оригинал до момента первой фактической модификации, что оптимизирует производительность.

Основные операции:

Создание:

// Пустой массив

var emptyArray: [Int] = []

// Массив с начальными значениями

var numbers = [1, 2, 3]

Создание:

Доступ к элементам:

let firstElement = numbers[0]

Доступ к элементам:

Добавление элементов:

numbers.append(4)

numbers += [5, 6]

Удаление элементов:

numbers.remove(at: 0)

numbers.removeLast()

Удаление элементов:

Итерация:

for number in numbers {

print(number)

}

Итерация:

Получение количества элементов:

let count = numbers.count

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

CALayer: Базовый строительный блок для визуального контента на экране. Каждый UIView имеет свой собственный слой (layer), который управляет внешним видом, анимированием и иерархией подобъектов.

CAShapeLayer: Используется для рисования векторных фигур. Определяет контуры, заполнение и обводку на основе CGPath.

CATextLayer: Отображает форматированный текст. Более эффективен для отображения текста, чем UILabel, особенно при рендеринге большого количества текста или в анимированных представлениях.

CAGradientLayer: Рисует градиент заливки. Позволяет плавно переходить между несколькими цветами.

CAReplicatorLayer: Создает множество копий своего сублоя, применяя к ним трансформации.

CAScrollLayer: Обеспечивает прокручиваемую область просмотра для своих сублоев.

CATiledLayer: Разделяет большое изображение на плитки для более эффективного рендеринга больших объемов контента.

CAEmitterLayer: Создает и анимирует систему частиц, например, для имитации дыма, огня или снега.

В анимации: Слои используются для создания явных (CABasicAnimation, CAKeyframeAnimation) и неявных анимаций, а также для определения свойств, которые могут анимироваться.

В Core Image: Фильтры Core Image работают с данными изображений, часто представляемыми как слои.

В SceneKit: 3D-сцены состоят из узлов, которые содержат геометрию и материалы, связанные с рендерингом, который осуществляется с использованием слоев.

В SpriteKit: 2D-графика в SpriteKit организуется в узлы, которые по своей сути являются легковесными слоями.

В AVFoundation: Видеоконтент может быть отображен с использованием AVPlayerLayer, который является подклассом CALayer.

UIView - это высокоуровневая абстракция, предоставляющая:

Обработку событий касаний и жестов.

Управление деревом субпредставлений (subviews).

Поддержку авторазметки (Auto Layout и старые Autoresizing Masks).

Специализированное поведение через подклассы (например, UIButton, UILabel, UIImageView).

Интеграцию с UIKit и жизненный цикл UIViewController.

Удобный механизм инвалидации и перерисовки содержимого (setNeedsDisplay()).

CALayer - это низкоуровневый компонент Core Animation, отвечающий за визуальное представление:

Содержит растровое изображение (bitmap) или графические примитивы для отображения.

Отвечает за позиционирование, трансформации, тени, границы и т.д.

Является основой для анимации.

Не обрабатывает события касаний напрямую.

UIView содержит один или несколько CALayer для отображения своего содержимого и обработки взаимодействия с пользователем и другими представлениями в иерархии. CALayer, будучи более примитивным, не предоставляет такого же уровня функциональности для построения пользовательских интерфейсов в UIKit.

GCD (Grand Central Dispatch) — это низкоуровневый API для управления параллелизмом и асинхронностью в приложениях на основе C, C++, Objective-C и Swift. Он предоставляет набор инструментов для управления очередями задач (dispatch queues) и выполнения блоков кода (closures/blocks).

Основные понятия GCD:

Очереди (Dispatch Queues):

Сериальные (Serial): выполняют задачи строго по одной в порядке добавления.

Параллельные (Concurrent): могут выполнять несколько задач одновременно.

Задачи (Dispatch Work Items):

Представляют собой блоки кода, которые ставятся в очередь для асинхронного или синхронного выполнения.

Синхронное и Асинхронное выполнение:

Синхронное: текущий поток ждет завершения задачи.

Асинхронное: задача ставится в очередь, текущий поток продолжает работу.

Приемущества использования GCD:

Улучшенная производительность: Эффективное использование ресурсов многоядерных процессоров.

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

Снижение сложности: Асинхронное выполнение задач на главном потоке предотвращает блокировку UI.

Пример создания и использования очередей:

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

В Swift все классы неявно наследуются от базового класса NSObject, даже если это не указано явно. Это отличает Swift от некоторых других языков, где может быть несколько корневых классов.

Цепочка наследования представляет собой иерархию, где каждый класс наследует свойства и методы от своего родительского класса (суперкласса). Класс может иметь только одного непосредственного суперкласса.

Пример:

Ключевые моменты:

Наследование: Класс B: A означает, что класс B наследует от класса A.

Суперкласс: Класс, от которого наследуют. В примере Animal - суперкласс для Dog, а Dog - суперкласс для Poodle.

Подкласс: Класс, который наследует от другого класса. В примере Dog - подкласс Animal, а Poodle - подкласс Dog.

super: Используется для доступа к реализации методов или инициализаторов суперкласса.

override: Ключевое слово, используемое для переопределения методов или свойств суперкласса в подклассе.

final: Модификатор, который можно применить к классу, методу или свойству, чтобы запретить его переопределение или наследование.

Таблица, иллюстрирующая цепочку наследования в примере:

Цепочка наследования позволяет повторно использовать код, создавать специализированные версии классов и организовывать структуру данных в иерархическом виде.

Актор (Actor) в Swift — это тип, который изолирует свое состояние, предотвращая одновременный доступ из разных потоков и тем самым устраняя data races.

Основные характеристики:

Изоляция состояния: Все свойства актора доступны только изнутри его методов или из других акторов при соответствующих модификаторах доступа.

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

Отсутствие наследования: Акторы не поддерживают наследование.

Отсутствие nonisolated let: Свойства могут быть помечены как nonisolated, если они являются константами (let) и имеют потокобезопасный тип. Это позволяет получать доступ к ним без await.

Пример:

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

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

Плюсы:

Улучшенное разделение ответственности: Модель, Представление и ViewModel четко отделены, что упрощает поддержку и тестирование.

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

Повышенная переиспользуемость кода: Logic во ViewModel часто можно переиспользовать между различными представлениями.

Поддержка реактивного программирования: MVVM хорошо сочетается с паттернами реактивного программирования (например, RxSwift, Combine) для связывания данных между ViewModel и View.

Меньше бойлерплейта во View: View становится "тупой" и просто отображает данные из ViewModel и отправляет события.

Минусы:

Более высокая сложность для простых приложений: Для очень простых UI может показаться избыточным.

Потребность в инструментах привязки данных: Требует механизмов для привязки данных между ViewModel и View (например, KVO, реактивные фреймворки), что добавляет некоторую сложность.

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

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

Разрешения экранов iPhone сильно варьируются в зависимости от модели. Вот несколько примеров для последних моделей:

iPhone 14 / 13 / 12 / 11 / XR: 2532 x 1170 пикселей (460 ppi)

iPhone 14 Pro / 13 Pro / 12 Pro: 2556 x 1179 пикселей (460 ppi)

iPhone 14 Plus / 13 Pro Max / 12 Pro Max: 2778 x 1284 пикселей (458 ppi)

iPhone SE (3-го поколения): 1334 x 750 пикселей (326 ppi)

Важно учитывать точки (points), а не пиксели, при разработке интерфейсов в iOS, так как система масштабирует содержимое в зависимости от плотности пикселей (ppi). Например, на iPhone 14 Pro Max размер в точках составляет 428 x 926 pt.

Произойдет ошибка компиляции. Тип аргумента (функция) несовместим с ожидаемым типом параметра (Double). Компилятор не позволит собрать проект.

В Swift в словаре (Dictionary) в качестве ключа может использоваться любой тип, соответствующий протоколу Hashable.

Протокол Hashable наследуется от протокола Equatable.

Equatable: Требует реализации оператора сравнения ==, позволяющего проверить, являются ли два экземпляра типа равными.

Hashable: Требует реализации свойства hashValue (в старых версиях Swift) или вызова метода hasher.combine() в методе hash(into:), который предоставляет уникальное целое число для каждого экземпляра типа. Это хеш-значение используется для быстрого поиска элементов в словаре.

Большинство стандартных типов Swift, таких как String, Int, Double, Bool, Array и Set (если их элементы также Hashable), уже соответствуют протоколу Hashable по умолчанию.

Для пользовательских типов (структур, классов, перечислений), чтобы использовать их в качестве ключей словаря, необходимо явно обеспечить их соответствие протоколу Hashable. Для структур и перечислений с ассоциативными значениями, поля которых также Hashable, соответствие Hashable может быть синтезировано компилятором автоматически при добавлении декларации Hashable. Для классов может потребоваться ручная реализация hash(into:).

Пример структуры, соответствующей Hashable:

Main queue - это последовательная очередь (serial queue), работающая в главном потоке (main thread) приложения. Она используется для обновления пользовательского интерфейса и обработки UI-событий. Все задачи, отправленные в main queue, выполняются строго последовательно.

Global queues - это параллельные очереди (concurrent queues), предоставляемые системой. Они используются для выполнения фоновых задач, не связанных с UI, что позволяет избежать блокировки main thread. Существует несколько глобальных очередей с разными приоритетами качества обслуживания (Quality of Service, QoS).

Вот таблица с основными типами глобальных очередей по приоритету QoS:

Пример отправки задачи в main и глобальную очередь:

Value types (например, struct, enum, основные числовые типы) хранят свое значение напрямую. При присваивании или передаче в функцию создается копия значения.

Reference types (например, class, function, closure) хранят ссылку на место в памяти, где находится само значение. При присваивании или передаче создается копия ссылки, указывающая на то же самое значение в памяти.

Основные отличия:

Пример на Swift:

Принцип подстановки Барбары Лисков (LSP).

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

Пример нарушения:

Если использовать Square там, где ожидается Rectangle, поведение Square по изменению только одной стороны (ширины или высоты) приведет к непредсказуемым результатам, так как базовый класс Rectangle не имеет такого поведения.

Для предотвращения нарушений LSP часто приходится переосмысливать иерархии классов, использовать композицию вместо наследования или вводить более абстрактные интерфейсы/протоколы. Это может добавить сложности в проектирование на начальных этапах.

Таблица сравнения:

Реляционные БД хранят данные в таблицах со строками и столбцами, связанных ключами. Имеют фиксированную схему. Подходят для структурных данных с сильными связями.

Нереляционные (NoSQL) БД используют различные модели хранения данных (документные, ключе-значение, графовые). Имеют гибкую/динамическую схему. Подходят для неструктурных данных, масштабирования и высокой доступности.

Ключевые отличия в таблице:

Существуют следующие способы размещения задач в очереди GCD:

Асинхронное выполнение (async): Задача добавляется в очередь, и выполнение сразу же продолжается на текущем потоке, не дожидаясь завершения задачи в очереди.

DispatchQueue.global().async {

// Код задачи

}

Синхронное выполнение (sync): Задача добавляется в очередь, и текущий поток блокируется до тех пор, пока задача в очереди не завершится. Избегайте использования sync на главном потоке для задач, выполняющихся длительное время, чтобы не блокировать UI.

DispatchQueue.global().sync {

// Код задачи, текущий поток будет ждать

}

Отложенное выполнение (asyncAfter): Задача добавляется в очередь с указанной задержкой. Выполнение начнется не раньше указанного времени.

DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {

// Код задачи выполнится через 2 секунды

}

Выполнение в группе (DispatchGroup): Задачи могут быть добавлены в DispatchGroup для синхронизации их завершения. Можно дождаться выполнения всех или быть уведомленным (notify) после их завершения.

let group = DispatchGroup()

group.enter()

// Задача 1

group.leave()

}

group.enter()

// Задача 2

group.leave()

}

group.notify(queue: .main) {

// Все задачи в группе завершены

}

Применение итераций (concurrentPerform): Позволяет выполнить заданный блок кода iterations раз параллельно на конкурентной очереди.

DispatchQueue.concurrentPerform(iterations: 100) { index in

// Код, выполняющийся для каждого индекса параллельно

}

DispatchWorkItem: Оборачивает блок кода и позволяет отменить выполнение задачи до ее старта или после добавления в очередь, если она еще не начала выполняться.

var workItem: DispatchWorkItem?

workItem = DispatchWorkItem {

// Код задачи

}

DispatchQueue.global().async(execute: workItem!)

// Можно отменить задачу, если она еще не началась

// workItem?.cancel()

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

В контексте абстрактных классов (которые в Swift отсутствуют в явном виде):

Имитация абстрактного класса с помощью класса: Создается класс, который может иметь наследников. Некоторые методы могут быть помечены как fatalError или иметь пустую реализацию, ожидая переопределения в подклассах. Состояние (свойства) может быть общим для всех наследников.

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

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

Чтобы схлопнуть хедер, можно изменить константу его constraint высоты на 0.

Пример на Auto Layout:

Пример с использованием NSLayoutConstraint.activate/deactivate:

Выбор метода зависит от того, как вы настроили constraints. Использование constant проще для уже существующих constraints. Активация/деактивация полезна, когда нужно быстро переключаться между различными состояниями constraints или когда несколько constraints определяют высоту.

Важно вызвать layoutIfNeeded() (желательно внутри анимационного блока), чтобы изменения константы или активности constraints применились и UI обновился плавно.

Паттерны проектирования в ООП делятся на три основные категории: порождающие, структурные и поведенческие.

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

Фабричный метод (Factory Method)

Абстрактная фабрика (Abstract Factory)

Строитель (Builder)

Прототип (Prototype)

Одиночка (Singleton)

Строитель (Builder)

Структурные паттерны — определяют отношения между классами и объектами. Помогают строить более крупные структуры из более мелких. Примеры:

Адаптер (Adapter)

Мост (Bridge)

Компоновщик (Composite)

Декоратор (Decorator)

Фасад (Facade)

Легковес (Flyweight)

Заместитель (Proxy)

Адаптер (Adapter)

Мост (Bridge)

Фасад (Facade)

Заместитель (Proxy)

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

Цепочка обязанностей (Chain of Responsibility)

Команда (Command)

Итератор (Iterator)

Посредник (Mediator)

Хранитель (Memento)

Наблюдатель (Observer)

Состояние (State)

Стратегия (Strategy)

Шаблонный метод (Template Method)

Посетитель (Visitor)

Команда (Command)

Итератор (Iterator)

Хранитель (Memento)

Состояние (State)

Выбор конкретного паттерна зависит от решаемой задачи и контекста. Многие паттерны можно комбинировать.

iOS использует автоматический подсчет ссылок (ARC) для управления памятью. ARC автоматически освобождает объекты, как только на них перестают ссылаться.

Основные концепции:

Strong Reference: Увеличивает счетчик ссылок объекта. Объект не будет освобожден, пока на него есть strong ссылки.

Weak Reference: Не увеличивает счетчик ссылок. Используется для предотвращения циклов сильных ссылок. Ссылается на объект, который может быть освобожден. Если объект освобождается, weak ссылка становится nil.

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

Циклы сильных ссылок (Retain Cycles): Возникают, когда два или более объекта имеют сильные ссылки друг на друга, не позволяя ни одному из них быть освобожденным. Решаются с помощью weak или unowned ссылок.

Пример цикла сильных ссылок и его решение:

Решение с использованием weak:

Unowned vs Weak:

Используй weak, когда ссылка может стать nil (объект может быть освобожден первым).

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

Замыкания (Closures) и циклы ссылок:

Замыкания могут захватывать ссылки на объекты, создавая циклы сильных ссылок. Это решается с помощью списка захвата (capture list).

Пример:

Отладка проблем с памятью:

Memory Graph Debugger: В Xcode позволяет визуализировать связи между объектами и выявлять циклы сильных ссылок.

Instruments (Allocations, Leaks): Инструменты для мониторинга выделения и освобождения памяти, поиска утечек.

State — обертка свойства во SwiftUI, позволяющая управлять локальным состоянием представления. При изменении значения свойства, обернутого в @State, представление автоматически обновляется.

StateObject — обертка свойства во SwiftUI для создания и управления объектами, соответствующими протоколу ObservableObject, которые являются частью жизненного цикла представления. Эффективно для объектов, созданных внутри представления и живущих с ним.

ObservedObject — обертка свойства во SwiftUI для управления ссылками на существующие объекты, соответствующие протоколу ObservableObject, которые были переданы извне. SwiftUI следит за изменениями в этих объектах и обновляет представление.

Сравнительная таблица:

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

Пример использования @StateObject и @ObservedObject:

В Swift существует три основных типа ссылок:

Strong Reference: Стандартный тип. Увеличивает счетчик ссылок (reference count) объекта. Объект не будет освобожден из памяти, пока на него существует хотя бы одна сильная ссылка.

Weak Reference: Неувеличивающий счетчик ссылок. Помечается ключевым словом weak. Используется для предотвращения циклов сильных ссылок (retain cycles), когда два объекта сильно ссылаются друг на друга. Слабая ссылка является опционалом, так как объект может быть деаллоцирован в любой момент.

// Пример Weak Reference

class Person {

let name: String

weak var apartment: Apartment? // Слабая ссылка

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

deinit { print("\(name) is being deinitialized") }

}

class Apartment {

let unit: String

var tenant: Person? // Сильная ссылка

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

deinit { print("Apartment \(unit) is being deinitialized") }

}

var john: Person? = Person(name: "John Appleseed")

var unit4A: Apartment? = Apartment(unit: "4A")

john!.apartment = unit4A

unit4A!.tenant = john

john = nil // John деаллоцируется, так как Apartment.tenant не держит сильной ссылки

Unowned Reference: Неувеличивающий счетчик ссылок, помечается ключевым словом unowned. Используется, когда уверен, что ссылка всегда будет указывать на объект с большим или таким же временем жизни. В отличие от weak, неуправляемая ссылка не является опционалом. Попытка доступа к объекту через неуправляемую ссылку после его деаллокации приведет к ошибке выполнения (runtime error).

// Пример Unowned Reference

class Customer {

let name: String

var card: CreditCard?

}

class CreditCard {

let number: UInt64

unowned let customer: Customer // Неуправляемая ссылка

init(number: UInt64, customer: Customer) {

self.number = number

self.customer = customer

}

deinit { print("Credit Card #\(number) is being deinitialized") }

}

var john: Customer? = Customer(name: "John Appleseed")

john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

john = nil // Оба объекта деаллоцируются

Краткое сравнение:

Основные отличия:

Тип значения vs. Тип ссылки: Структуры - это типы значения, классы - типы ссылки. При присвоении струкcтуры или передаче ее в функцию, копируется значение. При присвоении класса или передаче его в функцию, копируется ссылка на блок памяти.

Наследование: Структуры не поддерживают наследование. Классы поддерживают наследование, позволяя создавать иерархии объектов.

Deinitializers: Структуры не имеют деинициализаторов. Классы могут иметь деинициализатор (deinit) для освобождения ресурсов.

Identity Equality: Для структур сравнивается значение всех их свойств (при использовании == по умолчанию). Для классов == по умолчанию сравнивает ссылки (идентичность).

Производительность: Структуры обычно более производительны при работе с небольшим объемом данных, так как не используют динамическое выделение памяти на куче и не имеют накладных расходов на подсчет ссылок (ARC).

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

Существует несколько способов обеспечения безопасности при работе с историей изображений в iOS:

Контроль доступа к библиотеке фотографий:

Используйте PHPhotoLibrary.requestAuthorization(_ closure: (PHAuthorizationStatus) -> Void) для запроса разрешения пользователя на доступ к библиотеке.

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

Обрабатывайте различные статусы авторизации (authorized, denied, restricted, notDetermined).

// Запрос разрешения на доступ к библиотеке фотографий

PHPhotoLibrary.requestAuthorization { status in

switch status {

case .authorized:

// Доступ предоставлен, можно работать с фото

print("Доступ разрешен")

case .denied, .restricted:

// Доступ запрещен или ограничен

print("Доступ запрещен")

case .notDetermined:

// Пользователь еще не принял решение

print("Доступ не определен")

case .limited:

// Доступ к ограниченному набору фото (iOS 14+)

print("Ограниченный доступ")

@unknown default:

// Неизвестный статус

print("Неизвестный статус")

}

}

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

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

// Создание конфигурации для PHPickerViewController

var config = PHPickerConfiguration()

config.filter = .images // Фильтруем только изображения

config.selectionLimit = 0 // Неограниченный выбор (0) или ограничение по количеству

let picker = PHPickerViewController(configuration: config)

picker.delegate = self // Назначение делегата для обработки выбора

present(picker, animated: true) // Презентация контроллера выбора

Хранение изображений:

В песочнице приложения: Если изображения требуют конфиденциальности, храните их внутри директорий приложения (Documents, Library/Caches и т.д.). Эти директории приватны для приложения.

Использование Keychain: Для хранения чувствительных метаданных об изображениях (например, ссылки на зашифрованные файлы, если таковые используются), можно использовать связку ключей (Keychain). Изображения туда хранить не следует из-за ограничений по размеру.

Внешнее хранилище: При загрузке в облако или на сервер обеспечьте шифрование при передаче (HTTPS) и, при необходимости, шифрование на стороне сервера.

Шифрование изображений:

Для особо конфиденциальных изображений можно реализовать собственное шифрование файлов перед их сохранением. Используйте стандартные криптографические API из CryptoKit или CommonCrypto.

// Пример использования CryptoKit для шифрования данных (для иллюстрации принципа)

import CryptoKit

func encryptData(_ data: Data, using key: SymmetricKey) throws -> Data {

// Шифрование с использованием ChaChaPoly

let sealedBox = try ChaChaPoly.seal(data, using: key)

return sealedBox.combined // Получаем зашифрованный блок данных

}

func decryptData(_ encryptedData: Data, using key: SymmetricKey) throws -> Data {

// Расшифровка

let sealedBox = try ChaChaPoly.SealedBox(combined: encryptedData)

return try ChaChaPoly.open(sealedBox, using: key) // Получаем исходные данные

}

Очистка данных:

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

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

Очистка данных:

Минимальные права доступа:

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

Обработка метаданных:

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

Использование песочницы (Sandbox):

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

Отложенные права на запись (Scoped Access):

При работе с файлами, выбранными пользователем из других источников (например, через UIDocumentPickerViewController), используйте scoped access с помощью Security-Scoped Bookmarks, чтобы иметь ограниченный, но постоянный доступ к выбранным файлам без необходимости полного разрешения на доступ ко всему пространству файлов. (актуально больше для файлов, но принцип применим к изображениям как к файлам).

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

Жизненный цикл объекта в iOS управляется механизмом подсчета ссылок (Reference Counting). Наиболее распространенный способ — Automatic Reference Counting (ARC).

Этапы жизненного цикла:

Создание (Creation): Объект создается путем инициализации класса. Счетчик ссылок устанавливается в 1.

// Инициализация объекта класса MyClass

let myObject = MyClass()

Использование (Usage): Объект используется в приложении. Другие объекты могут получать ссылки на него, увеличивая счетчик ссылок.

// Другой объект получает ссылку

anotherObject.referenceToMyObject = myObject

// Счетчик ссылок myObject увеличивается

Уничтожение (Deallocation): Когда счетчик ссылок объекта достигает нуля, runtime автоматически вызывает метод deinit() (для классов Objective-C - dealloc). Объект освобождает занимаемую память.

deinit {

// Код для освобождения ресурсов

print("Объект уничтожен")

}

Проблемы и решения:

Retain Cycles (Циклы сильных ссылок): Возникают, когда два или более объекта имеют сильные ссылки друг на друга. Счетчик ссылок каждого объекта никогда не достигает нуля, и объекты не будут деинициализировать.

Решения:

Weak References: Обозначаются ключевым словом weak. Не увеличивают счетчик ссылок. Используются для ссылок к родительским объектам или делегатам, когда связь является временной или опциональной.

Unowned References: Обозначаются ключевым словом unowned. Не увеличивают счетчик ссылок. Используются, когда связанные объекты всегда имеют одинаковый жизненный цикл, и ссылка гарантированно не будет nil в момент использования. Если объект, на который ссылается unowned ссылка, был деинициализирован, попытка доступа к такой ссылке приведет к runtime-ошибке.

Пример weak и unowned в замыканиях для избежания циклов сильных ссылок:

Правильное управление ссылками крайне важно для предотвращения утечек памяти (memory leaks) и поддержания стабильности приложения.

Использовать NotificationCenter для отслеживания появления (UIKeyboardWillShowNotification) и скрытия (UIKeyboardWillHideNotification) клавиатуры. В обработчиках этих событий можно изменить отступы или сдвинуть содержимое ScrollView или TableView.

Альтернативные подходы:

Использовать библиотеку IQKeyboardManagerSwift. Она автоматически управляет сдвигом контента.

В SwiftUI использовать модификатор .ignoresSafeArea(.keyboard, edges: .bottom).

Пример кода для ручной обработки через NotificationCenter:

Ключевые свойства из словаря userInfo уведомления:

Предотвращение циклических сильных ссылок (retain cycles).

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

Компилятор гарантирует, что ссылка 'unowned' всегда указывает на действительный объект. Если попытаться получить доступ к 'unowned' ссылке после освобождения объекта, приложение аварийно завершит работу. Это делает ошибки с 'unowned' более явными и помогает быстрее обнаружить проблемы.

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

Цепочка ответчиков (responder chain) — это последовательность объектов, унаследованных от UIResponder, которые имеют возможность обработать событие.

Типичная последовательность включает:

UIResponder, получивший событие изначально: Это может быть UIView (например, кнопка) или UIViewController.

Супервью: Если ответчик не обрабатывает событие, оно передается его супервью.

Контроллер представления (UIViewController): Если супервью не обрабатывает событие, оно может быть передано его контроллеру.

Окно (UIWindow): Если контроллер не обрабатывает событие, оно передается объекту UIWindow.

Приложение (UIApplication): Если окно не обрабатывает событие, оно передается объекту UIApplication.

Представитель приложения (AppDelegate): В случае, если UIApplication не обрабатывает событие, оно может быть передано его представителю.

Передача события происходит путем вызова соответствующего метода обработки события (touchesBegan(_:with:), motionBegan(_:with:) и т.д.) у следующего ответчика в цепочке, если текущий ответчик не обработал событие (т.е., если реализация метода не вызывает super).

UIView — высокоуровневая абстракция, предоставляющая возможность интерактивности (обработка событий касаний, жестов), систему координат для отрисовки содержимого и поддержку Auto Layout. Он является наследником UIResponder.

CALayer — низкоуровневый графический примитив, который отвечает за отрисовку визуального содержимого. CALayer не является наследником UIResponder и не участвует в цепочках событий. Каждый UIView имеет свой собственный корневой CALayer (доступен через свойство layer), который управляет его визуальным представлением. CALayer используется для выполнения сложных анимаций, трансформаций и для управления тенями, границами и содержимым подслоев.

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

Разновидности полиморфизма:

Ad-hoc (специальный) полиморфизм:

Перегрузка (Overloading): Методы с одинаковым именем, но разной сигнатурой (количество или типы параметров) в одном классе.

Приведение типов (Coercion): Неявное или явное преобразование типов данных.

Parametric (параметрический) полиморфизм: Использование родовых (generics) типов, позволяющее писать код, работающий с разными типами данных без потери типобезопасности.

Subtype (полиморфизм подтипов): Возможность использовать объект подкласса там, где ожидается объект суперкласса.

Пример полиморфизма подтипов в Swift:

Преимущества полиморфизма:

Повышает гибкость и расширяемость кода.

Упрощает внесение изменений и добавление новой функциональности.

Способствует повторному использованию кода.

Делает код более читаемым и поддерживаемым.

Разница в механизме управления памятью (ARC - Automatic Reference Counting).

Сильная ссылка (Strong reference): Повышает счетчик ссылок объекта на 1. Объект не может быть освобожден из памяти, пока на него существует хотя бы одна сильная ссылка. Это поведение по умолчанию для свойств и переменных в Swift.

Слабая ссылка (Weak reference): Не повышает счетчик ссылок объекта. Используются для предотвращения циклических ссылок, когда два или более объекта имеют сильные ссылки друг на друга, создавая утечку памяти. Слабые ссылки всегда опциональны (Optional), потому что объект, на который они ссылаются, может быть деаллоцирован в любое время. Если объект освобожден, слабая ссылка автоматически становится nil.

Пример циклической ссылки и ее предотвращения с помощью слабой ссылки:

Без weak для tenant, деинициализаторы не были бы вызваны, что указывает на утечку памяти.

Используя инициализаторы.

Инициализаторы могут быть:

Designated Initializers: Основные инициализаторы, полностью инициализирующие все свойства.

Convenience Initializers: Вспомогательные инициализаторы, вызывающие designated initializers, чтобы упростить создание экземпляра.

Failable Initializers: Инициализаторы, которые могут вернуть nil в случае ошибки инициализации.

ViewController, View, Core Graphics, Core Animation, UIKit, SwiftUI.

Замыкание (closure) в Swift — это самодостаточный блок функциональности ({}), который может быть передан и использован в коде. Замыкания могут захватывать и хранить ссылки на любые константы и переменные из контекста, в котором они определены.

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

В качестве аргументов функций (например, Completion Handlers).

Для определения поведения при перечислении коллекций (например, используя методы map, filter, reduce, sorted).

Для отложенного выполнения кода.

Синтаксис:

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

Автоматическое захват переменных (capturing values).

Возможность определения замыканий без явного указания типов параметров и возвращаемого значения (синтаксический сахар).

Использование сокращенных имен параметров ($0, $1 и т.д.).

Синтаксис замыкающей скобки (trailing closure syntax).

Захват переменных:

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

Списки захвата (Capture Lists):

Используются для явного указания, как переменные должны быть захвачены - по значению или по ссылке, а также для избежания циклов сильных ссылок (retain cycles).

Для предотвращения циклов сильных ссылок при захвате объектов классов используется синтаксис [weak self] или [unowned self].

Перед тем, как представление станет видимым на экране.

Произойдут следующие шаги:

Будет выделена память для значения аргумента внутри области видимости функции.

Значение переданного целого числа будет скопировано в эту область памяти.

Внутри функции можно будет обращаться к этому значению по имени параметра.

Изменения значения параметра внутри функции не повлияют на исходное значение, переданное при вызове, поскольку передача integer происходит по значению (copy-by-value).

Пример на языке Swift:

Модификатор strong удерживает сильную ссылку на объект, предотвращая его деаллокацию до тех пор, пока существует хотя бы одна сильная ссылка. Используется по умолчанию для переменных, объявленных без специальных модификаторов.

Пример:

Переменные, объявленные в протоколе в iOS (Swift), определяют требования к свойствам, которые должен реализовать класс, структура или перечисление, подписывающиеся на этот протокол. Такие переменные должны иметь определённый тип и могут быть как только для чтения (get), так и для чтения и записи (get set).

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

Пример:

Сериализованные очереди обрабатывают задачи строго последовательно, одна за другой. Пока не завершится выполнение текущей задачи, следующая не начнет выполняться.

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

Пример создания:

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

Кесс-протоколы (Marker Protocols).

Ассоциативные протоколы (Associated Type Protocols).

Протоколы с требованиями к методам и свойствам.

Эти три типа описывают основные категории протоколов в Swift по их назначению и функциональности.

Протокол Hashable позволяет использовать объекты в хешируемых коллекциях, таких как Set и Dictionary. Соответствующий тип должен предоставлять hashValue, позволяющий генерировать хеш объекта.

Основные моменты:

Типы, соответствующие Hashable, генерируют целое число, представляющее их значение.

Если два объекта равны (==), их хеш-значения должны быть одинаковыми.

Если два объекта имеют одинаковые хеш-значения, они не обязательно равны.

Swift автоматически синтезирует реализацию Hashable для структур и перечислений, если все их члены соответствуют Hashable.

Для классов или пользовательских структур с не-Hashable членами требуется реализовать hash(into:) вручную.

Пример ручной реализации:

Семантика значения (Value Semantics)

Объекты с семантикой значения копируются при присваивании или передаче в функцию. Каждая копия независима от оригинала. Структуры (struct) и перечисления (enum) в Swift имеют семантику значения.

Семантика ссылки (Reference Semantics)

Объекты с семантикой ссылки делятся ссылкой на одну и ту же область памяти. При присваивании или передаче в функцию создается новая ссылка на существующий объект. Изменения через одну ссылку видны через другую. Классы (class) в Swift имеют семантику ссылки.

Главным образом, на том потоке, откуда было вызвано создание экземпляра. Явного ограничения на выполнение init методов на определенном потоке нет.

Однако, следует учитывать особенности:

Main Actor: Если класс или структура помечены как @MainActor, их инициализация по умолчанию будет выполнена в главном потоке, даже если вызов создания экземпляра произошел из другого потока. Это связано с Actor Isolation.

@MainActor

class MyClass {

init() {

// Этот код выполняется в main thread, даже если вызывается из фонового потока.

}

}

Использование асинхронных примитивов: Async/await и другие инструменты могут влиять на контекст выполнения, но сам метод init остается синхронным. Если внутри init вызывается асинхронная функция, то асинхронная часть будет выполняться в соответствующем контексте (например, на пуле потоков по умолчанию для await), но код до await и после него в init будет выполнен на потоке, где начался init.

** UIKit/AppKit:** Некоторые компоненты UI должны использоваться только в главном потоке. Инициализация таких компонентов (например, UIView) должна происходить в главном потоке, иначе могут возникнуть ошибки или некорректное поведение.

В большинстве случаев, если нет явных ограничений (как @MainActor) или взаимодействия с UI, init просто выполняется на потоке вызова. При необходимости выполнять асинхронную работу или тяжелые вычисления во время инициализации, лучше вынести эту логику за пределы init и выполнить ее асинхронно после создания объекта.

Существует несколько популярных способов:

CocoaPods:

Децентрализованный менеджер зависимостей, написанный на Ruby.

Использует .podspec файлы для описания зависимости.

Устанавливается командой sudo gem install cocoapods.

Добавляет Podfile в корень проекта.

Запускается pod install для установки зависимостей и создания рабочего пространства .xcworkspace.

CocoaPods:

Carthage:

Децентрализованный менеджер зависимостей, который собирает фреймворки.

Написан на Swift.

Предпочитает бинарные фреймворки.

Устанавливается через Homebrew brew install carthage.

Использует Cartfile для описания зависимостей.

Запускается carthage update --platform iOS.

Скомпилированные фреймворки добавляются вручную в Target > General > Frameworks, Libraries, and Embedded Content.

Carthage:

Написан на Swift.

Swift Package Manager (SPM):

Встроенный в Xcode менеджер зависимостей для Swift.

Централизованный подход с использованием репозиториев пакетов.

Использует Package.swift для описания зависимостей.

Интегрирован непосредственно в Xcode.

Добавление зависимостей через File > Add Packages... или Project > Package Dependencies.

Автоматическое разрешение и скачивание зависимостей.

Сравнение:

Выбор зависит от предпочтений команды, типа зависимостей и необходимости тесной интеграции с Xcode. SPM становится стандартом де-факто, особенно для новых проектов на Swift.

А если вопрос прозвучит не так, как вы готовились?

Так бывает чаще всего. ИзиСобес слышит вопрос интервьюера и подсказывает ответ прямо во время разговора — его не видно ни в Zoom, ни при демонстрации экрана.

Посмотреть ИзиСобес