Swift | Вопросы собесов
2.18K subscribers
30 photos
961 links
Download Telegram
🤔 Где могут располагаться стеки помимо структуры

В программировании стек (stack) — это структура данных и область памяти, используемая для хранения временных данных. В зависимости от контекста, стек может существовать в нескольких местах.

🟠Стек вызовов (Call Stack)
Это главная область памяти в оперативной памяти (RAM), где хранятся:
- Локальные переменные функций.
- Адреса возврата после завершения функции.
- Контекст выполнения программы.
func functionA() {
functionB()
}

func functionB() {
print("Вызов B")
}

functionA()

func infiniteRecursion() {
infiniteRecursion() // Бесконечный вызов
}


🟠Стек в структуре данных (например, `Array`)
В Swift можно реализовать стек вручную, используя массив (Array).
struct Stack<T> {
private var elements: [T] = []

mutating func push(_ item: T) {
elements.append(item)
}

mutating func pop() -> T? {
return elements.popLast()
}
}

var stack = Stack<Int>()
stack.push(10)
stack.push(20)
print(stack.pop()!) // 20


🟠Потоковый стек (Thread Stack)
Каждый поток (Thread) в многопоточном программировании получает свой стек.
DispatchQueue.global().async {
print("Фоновый поток")
}
print("Главный поток")


🟠Стек в автоматическом управлении памятью (ARC)
Swift использует Automatic Reference Counting (ARC) для управления памятью.
Когда объект больше не нужен, он удаляется из памяти (в том числе из стека).
func createPerson() {
let person = "Иван" // Создаётся в стеке
print(person)
}

createPerson() // Переменная person удаляется при выходе из функции


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Нужно ли отслеживать статус задачи в DispatchWorkItem?

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


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Как бы реализовал словарь в Swift при помощи массива?

В Swift словарь (Dictionary) представляет собой структуру данных, которая хранит пары ключ-значение и обеспечивает быстрый доступ к значениям по ключу (O(1) в среднем). Но если бы мы хотели реализовать словарь на основе массива, то пришлось бы использовать линейный поиск, что делает операции менее эффективными (O(n)).

🚩Реализация словаря через массив кортежей

Один из простых способов создать словарь на базе массива — использовать массив кортежей (ключ, значение).
struct ArrayDictionary<Key: Equatable, Value> {
private var items: [(Key, Value)] = [] // Храним пары ключ-значение

// Получение значения по ключу
func value(for key: Key) -> Value? {
for (k, v) in items {
if k == key {
return v
}
}
return nil
}

// Добавление или обновление значения
mutating func insert(value: Value, for key: Key) {
for i in 0..<items.count {
if items[i].0 == key {
items[i].1 = value // Обновляем значение, если ключ уже есть
return
}
}
items.append((key, value)) // Добавляем новую пару
}

// Удаление элемента по ключу
mutating func remove(for key: Key) {
items.removeAll { $0.0 == key }
}
}

// Пример использования
var myDict = ArrayDictionary<String, Int>()
myDict.insert(value: 42, for: "age")
myDict.insert(value: 30, for: "height")

print(myDict.value(for: "age")!) // 42
myDict.remove(for: "age")
print(myDict.value(for: "age")) // nil


🚩Оптимизация: Использование бинарного поиска

Если мы отсортируем массив по ключам, можно использовать бинарный поиск (O(log n)) для ускорения поиска ключа.
struct SortedArrayDictionary<Key: Comparable, Value> {
private var items: [(Key, Value)] = []

// Бинарный поиск индекса ключа
private func index(of key: Key) -> Int? {
var left = 0
var right = items.count - 1
while left <= right {
let mid = (left + right) / 2
if items[mid].0 == key {
return mid
} else if items[mid].0 < key {
left = mid + 1
} else {
right = mid - 1
}
}
return nil
}

// Получение значения по ключу
func value(for key: Key) -> Value? {
if let index = index(of: key) {
return items[index].1
}
return nil
}

// Вставка с сохранением сортировки
mutating func insert(value: Value, for key: Key) {
if let index = index(of: key) {
items[index].1 = value
} else {
items.append((key, value))
items.sort { $0.0 < $1.0 } // Сортируем после вставки
}
}
}

// Использование
var sortedDict = SortedArrayDictionary<String, Int>()
sortedDict.insert(value: 50, for: "b")
sortedDict.insert(value: 20, for: "a")
sortedDict.insert(value: 70, for: "c")

print(sortedDict.value(for: "b")!) // 50


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 Что такое race condition?

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


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from easyoffer
🎉 easyoffer 2.0 — релиз уже в этом месяце!

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

В честь запуска мы готовим ограниченную акцию:

Первые 500 покупателей получат:
🚀 PRO тариф на 1 год с 50% скидкой

Что нужно сделать:

🔔 Подпишитесь на этот Telegram-канал, чтобы первыми узнать о старте релиза. Сообщение появится в нем раньше, чем где-либо еще — вы успеете попасть в число первых 500 и получить максимальную выгоду. 🎁 А еще только для подписчиков канала ценный бонус в подарок к PRO тарифу.

📅 Официальный запуск — уже совсем скоро.
Следите за новостями и не пропустите старт!
🤔 Чем отличается garbage collector и подсчет ссылок?

Garbage Collector (GC) и Automatic Reference Counting (ARC) – это два разных подхода к управлению памятью в программировании. Они решают одну задачу: автоматическое освобождение неиспользуемой памяти, но делают это по-разному.

🚩Garbage Collector (GC)

Java, Kotlin, C#, Python, JavaScript
- GC периодически просматривает всю память приложения и ищет объекты, на которые больше нет ссылок.
- Когда такие объекты находятся, они удаляются, а память освобождается.
- Это автоматический процесс, который запускается по мере необходимости.

🚩Подсчет ссылок (ARC - Automatic Reference Counting)

Где используется: Swift, Objective-C
- Каждый объект имеет счетчик ссылок (reference count).
- Когда переменная создает ссылку на объект, счетчик увеличивается.
- Когда переменная перестает ссылаться на объект, счетчик уменьшается.
- Когда счетчик достигает нуля, объект удаляется из памяти сразу же.
class Person {
var pet: Pet?
}

class Pet {
var owner: Person?
}

let person = Person()
let pet = Pet()

person.pet = pet
pet.owner = person // Теперь оба объекта держат друг друга, и ARC их не удалит


Решение – использовать weak:
class Pet {
weak var owner: Person? // Теперь утечки памяти не будет
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 На какие две группы делятся типы в Swift?

– Value types — struct, enum, tuple.
– Reference types — class, function, NSObject-производные.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
🤔 В чем проблема, если все переменные будут делаться в вик?

Если все переменные в коде сделать weak, объекты могут мгновенно удаляться из памяти, потому что никто не будет владеть ими (strong reference).

🚩Как работает `weak`?

- weak создает слабую ссылку – объект не увеличивает счетчик ссылок (retain count).
- Если нет других (strong) ссылок на объект, он удаляется (ARC освобождает память).
- weak переменные всегда являются Optional, потому что объект может стать nil в любой момент.
class Person {
weak var name: String? // Ошибка! Строки – это value type, weak нельзя
}


Проблема, если weak используется везде
class Car {
weak var model: String? // Ошибка (value type)
weak var owner: Person? // ⚠️ Будет nil, если нет других strong ссылок
}

class Person {
var car: Car?
}

var person: Person? = Person()
person?.car = Car()
person?.car?.owner = person // `owner` - weak, объект сразу удалится

print(person?.car?.owner) // nil, объект Person уничтожен


🚩Когда `weak` полезен?

🟠Избегание циклов ссылок (retain cycle)
Например, между delegate и ViewController
   protocol SomeDelegate: AnyObject {
func doSomething()
}

class ViewController {
weak var delegate: SomeDelegate? // Prevent retain cycle
}


🟠Когда объект не должен владеть другим объектом
Например, ячейки в UITableView не должны владеть ViewController.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 В чём разница между fileprivate, private и public private(set) уровнями доступа?

- private — ограничивает доступ к свойству или методу в пределах текущей области, например, внутри одного класса или расширения.
- fileprivate — расширяет доступ до всего файла, в котором объявлен элемент. Позволяет разным типам в одном файле работать с приватными членами друг друга.
- public private(set) — свойство можно читать откуда угодно, но изменять только внутри текущего модуля. Это позволяет внешнему коду обращаться к значению, но не менять его напрямую.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие изменения надо сделать в constrait перед вызовом layout?

Перед вызовом layoutIfNeeded() или layoutSubviews() в iOS-приложении, нужно изменить значение констрейнта и вызвать анимацию, если необходимо. Вот основные шаги:

🟠Обновление значения констрейнта
Перед вызовом layoutIfNeeded(), измените свойство констрейнта (например, constant у NSLayoutConstraint).
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
@IBOutlet weak var heightConstraint: NSLayoutConstraint!

override func viewDidLoad() {
super.viewDidLoad()

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.changeButtonHeight()
}
}

func changeButtonHeight() {
heightConstraint.constant = 100 // Меняем значение констрейнта
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded() // Перестраиваем макет
}
}
}


🟠Если меняется набор констрейнтов
Если нужно удалить/добавить констрейнты, используйте `activate()` / `deactivate()`.
var expanded = false

@IBOutlet weak var smallHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var largeHeightConstraint: NSLayoutConstraint!

func toggleHeight() {
expanded.toggle()

if expanded {
NSLayoutConstraint.deactivate([smallHeightConstraint])
NSLayoutConstraint.activate([largeHeightConstraint])
} else {
NSLayoutConstraint.deactivate([largeHeightConstraint])
NSLayoutConstraint.activate([smallHeightConstraint])
}

UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}


🟠Если работа идёт в `viewDidLoad()`
Во viewDidLoad() элементы ещё не отрисованы, поэтому layoutIfNeeded() не сработает. Используйте viewDidAppear() или вызовите layoutIfNeeded() после view.layoutIfNeeded().
override func viewDidLoad() {
super.viewDidLoad()
heightConstraint.constant = 100
view.layoutIfNeeded() // НЕ обновит макет, потому что он ещё не загружен
}


Решение
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

heightConstraint.constant = 100
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}


Разница между layoutIfNeeded() и setNeedsLayout()
heightConstraint.constant = 100
view.setNeedsLayout() // Обновление произойдет на следующем цикле рендера


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое inout и как его использовать?

Ключевое слово inout указывает, что переменная будет передана в функцию по ссылке, а не по значению. Это значит, что функция сможет изменить значение переменной за пределами своей области видимости. Чтобы передать значение в inout-параметр, перед именем переменной ставится &.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🤔 Какие есть инструменты для работы с потоками?

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

🟠GCD (Grand Central Dispatch) – главный инструмент для потоков
GCD – это низкоуровневая технология, позволяющая управлять задачами (тасками) в очередях (DispatchQueue).
DispatchQueue.global(qos: .background).async {
print("Фоновый поток")

DispatchQueue.main.async {
print("Вернулись в главный поток")
}
}


🟠OperationQueue – более удобный API для задач
OperationQueue – это более гибкая и объектно-ориентированная альтернатива GCD.
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2 // Ограничение на 2 задачи одновременно

queue.addOperation {
print("Операция 1")
}

queue.addOperation {
print("Операция 2")
}


🟠Actors – безопасная работа с потоками в Swift 5.5+
С actor можно работать с потоками без гонок данных, потому что все его свойства защищены от одновременного доступа.
actor Counter {
private var value = 0

func increment() {
value += 1
}

func getValue() -> Int {
return value
}
}

let counter = Counter()

Task {
await counter.increment()
print(await counter.getValue()) // Потокобезопасный доступ
}


🟠Task & Async/Await (Swift 5.5+) – современный подход к асинхронности
С async/await код становится читаемым и удобным.
func fetchData() async -> String {
try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 секунда задержки
return "Данные загружены"
}

Task {
let result = await fetchData()
print(result)
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Как можно расширять ячейку?

Расширение ячейки (UITableViewCell, UICollectionViewCell) делается через добавление вью, анимаций, динамических constraints или swipe-действий. Также можно использовать кастомные layout'ы или auto-sizing.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что значит половина массива?

Половина массива — это подмножество элементов массива, которое составляет примерно 50% его длины. В зависимости от контекста, это может быть:
Первая половина – элементы от начала массива до середины.
Вторая половина – элементы от середины до конца массива.
Любая подгруппа, близкая к 50% – например, при нечетном количестве элементов можно взять либо на один элемент больше, либо меньше.

let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

let middleIndex = array.count / 2 // Делим пополам

let firstHalf = Array(array[..<middleIndex]) // Первая половина
let secondHalf = Array(array[middleIndex...]) // Вторая половина

print(firstHalf) // [1, 2, 3, 4, 5]
print(secondHalf) // [6, 7, 8, 9, 10]


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

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

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊6
🤔 Почему порядок ключей будет сохраняться между мутациями в словаре?

Начиная с Swift 4, стандартный Dictionary сохраняет порядок вставки ключей. Даже при изменении значений или добавлении новых элементов порядок сохраняется, пока явным образом не удаляются ключи или не происходит массовая перераспределённая перестройка.
Это стало возможным благодаря обновлённой реализации хеш-таблицы внутри Dictionary.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
🤔 Может ли у UI View не быть layer'а и наоборот?

UIView всегда имеет CALayer, так как UIView — это обёртка над CALayer в UIKit.
CALayer может существовать без UIView, потому что это низкоуровневый элемент Core Animation, который не зависит от UIKit.

🚩`UIView` всегда содержит `CALayer`

Каждый UIView внутри себя содержит CALayer, который отвечает за отрисовку.
let view = UIView()
print(view.layer) // Всегда существует!


🚩`CALayer` может существовать без `UIView`

CALayer можно создать и добавить в иерархию без UIView.
let layer = CALayer()
layer.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
layer.backgroundColor = UIColor.red.cgColor

if let window = UIApplication.shared.windows.first {
window.layer.addSublayer(layer) // Добавляем без UIView!
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
🤔 Как может возникнуть проблема версии приоритета?

Проблема возникает, когда задача с более низким приоритетом получает доступ к ресурсу раньше задачи с высоким приоритетом. Это может происходить из-за отсутствия синхронизации или блокировок — пример: priority inversion.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое мьютекс (mutex)?

Мьютекс (от англ. "mutex" - mutual exclusion, взаимное исключение) — это механизм синхронизации, используемый в многопоточном программировании для предотвращения одновременного доступа нескольких потоков к общим ресурсам, таким как переменные, структуры данных или файлы. Он помогает избежать состояния гонки (race condition), когда результат выполнения программы зависит от неопределённого порядка доступа потоков к ресурсу.

🚩Основные концепции мьютекса

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

🟠Захват и освобождение
Поток захватывает мьютекс перед доступом к общему ресурсу и освобождает его после завершения работы с этим ресурсом. Если мьютекс уже захвачен другим потоком, текущий поток будет блокирован до тех пор, пока мьютекс не будет освобождён.

🚩Пример использования мьютекса в Swift

import Foundation

class SafeCounter {
private var value = 0
private let lock = NSLock()

func increment() {
lock.lock() // Захват мьютекса
value += 1
lock.unlock() // Освобождение мьютекса
}

func getValue() -> Int {
lock.lock() // Захват мьютекса
let currentValue = value
lock.unlock() // Освобождение мьютекса
return currentValue
}
}

let counter = SafeCounter()
DispatchQueue.global().async {
for _ in 0..<1000 {
counter.increment()
}
}

DispatchQueue.global().async {
for _ in 0..<1000 {
counter.increment()
}
}

// Подождём немного, чтобы дать потокам закончить работу
Thread.sleep(forTimeInterval: 1)
print("Final counter value: \(counter.getValue())")


🚩Плюсы и минусы

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

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

Мёртвые блокировки (Deadlocks)
Если мьютексы захватываются в неправильном порядке, это может привести к ситуации, когда два или более потока блокируют друг друга, ожидая освобождения мьютексов.

Производительность
Чрезмерное использование мьютексов может привести к снижению производительности из-за увеличения времени ожидания потоков.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какой есть механизм для показа экрана?

Обычно используются:
- pushViewController (в UINavigationController);
- present(_:animated:) — для модального показа;
- В SwiftUI — NavigationStack, .sheet, .fullScreenCover.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 Расскажи про механизм отмены в operatsion

В Operation (ранее NSOperation) в Foundation есть встроенный механизм отмены, который позволяет корректно завершить операцию, если она больше не нужна. Однако отмена не прерывает выполнение автоматически – код внутри операции должен сам проверять флаг отмены и корректно завершаться.

🚩Как работает отмена `Operation`?

Вызывается cancel() – операция помечается как отмененная.
Флаг isCancelled становится true, но операция продолжает выполняться, если не проверяет этот флаг.
Операция должна самостоятельно проверять isCancelled и прерываться.
class MyOperation: Operation {
override func main() {
for i in 1...10 {
if isCancelled { return } // Проверяем, отменена ли операция
print("Выполняется шаг \(i)")
sleep(1) // Симуляция работы
}
}
}

let queue = OperationQueue()
let operation = MyOperation()

queue.addOperation(operation)

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
operation.cancel() // Отменяем через 3 секунды
}


🚩Отмена асинхронных операций

Если операция асинхронная (isAsynchronous = true), просто проверять isCancelled недостаточно. Надо корректно управлять состояниями (isExecuting, isFinished).
class AsyncOperation: Operation {
private var _executing = false
private var _finished = false

override var isAsynchronous: Bool { true }

override var isExecuting: Bool {
get { return _executing }
set {
willChangeValue(for: \.isExecuting)
_executing = newValue
didChangeValue(for: \.isExecuting)
}
}

override var isFinished: Bool {
get { return _finished }
set {
willChangeValue(for: \.isFinished)
_finished = newValue
didChangeValue(for: \.isFinished)
}
}

override func start() {
if isCancelled {
isFinished = true
return
}

isExecuting = true
executeTask()
}

private func executeTask() {
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
if self.isCancelled {
self.complete()
return
}
print("Асинхронная операция завершена")
self.complete()
}
}

private func complete() {
isExecuting = false
isFinished = true
}
}

let queue = OperationQueue()
let asyncOp = AsyncOperation()
queue.addOperation(asyncOp)

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
asyncOp.cancel() // Отменяем через 1 секунду
}


🚩Отмена зависимых операций

Если у вас есть зависимости между операциями, отмена одной может автоматически отменить все последующие:
let op1 = MyOperation()
let op2 = MyOperation()
op2.addDependency(op1)

let queue = OperationQueue()
queue.addOperations([op1, op2], waitUntilFinished: false)

// Отменяем первую операцию, вторая тоже не выполнится
op1.cancel()


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM