Swift | Вопросы собесов
2.19K subscribers
30 photos
961 links
Download Telegram
🤔 В чем разница между points (точками) и pixels (пикселями)?

- Points (pt) – абстрактные единицы измерения в iOS, независимые от физического разрешения экрана.
- Pixels (px) – физические точки дисплея, отображающие контент.
На Retina-дисплеях 1 точка (pt) может состоять из 2×2 или 3×3 пикселей (px) (@2x, @3x). Это позволяет поддерживать четкость интерфейса на разных устройствах.


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

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

🚩Основы наследования

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

🟠Создание подкласса
Подкласс наследует (или "расширяет") базовый класс. Он может переопределять унаследованные методы и свойства, добавлять новые методы и свойства, а также добавлять инициализаторы или изменять существующие.

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

🟠Предотвращение переопределения
Можно предотвратить переопределение методов, свойств или индексаторов с помощью ключевого слова final. Если метод, свойство или индексатор объявлен как final, то он не может быть переопределён в подклассе.
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// Этот метод будет переопределен в подклассах, если необходимо
}
}

class Bicycle: Vehicle {
var hasBasket = false
}

class Car: Vehicle {
var gear = 1
final func drive() {
print("Car is moving")
}
override func makeNoise() {
print("Vroom!")
}
}


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

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

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

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

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

Барьер (Barrier) – это механизм синхронизации потоков, который позволяет контролировать порядок выполнения операций** в многопоточной среде.

🚩Барьеры памяти (Memory Barriers)

Используются в многопоточном программировании для управления порядком операций с памятью.
В многопоточной среде процессоры и компиляторы могут оптимизировать порядок команд, что может привести к непредсказуемому поведению. Барьеры памяти предотвращают это.
#include <stdatomic.h>

atomic_int sharedValue = 0;

void updateValue() {
atomic_store_explicit(&sharedValue, 10, memory_order_release);
}

void readValue() {
int value = atomic_load_explicit(&sharedValue, memory_order_acquire);
}


🚩Барьеры в GCD (`dispatch_barrier`)

Используются в Grand Central Dispatch (GCD)** для контроля порядка выполнения задач в DispatchQueue.
- Позволяет блокировать очередь на время выполнения задачи.
- Все задачи до барьера выполняются параллельно.
- Барьер ждёт завершения всех предыдущих задач, выполняется эксклюзивно, затем разрешает следующие задачи.
let queue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)

queue.async {
print("Задача 1")
}
queue.async {
print("Задача 2")
}

// БАРЬЕРНАЯ ОПЕРАЦИЯ
queue.async(flags: .barrier) {
print("🔴 Барьер: все предыдущие задачи завершены, выполняюсь один!")
}

queue.async {
print("Задача 3")
}


🚩Барьеры в OpenMP (Параллельные вычисления)

В OpenMP (#pragma omp barrier) ждёт завершения всех потоков перед выполнением следующего кода.
#pragma omp parallel
{
printf("До барьера\n");
#pragma omp barrier
printf("После барьера\n");
}


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

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

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

В Domain-Driven Design (DDD) управление зависимостями важно для создания четко разделенных и легко управляемых компонентов. Это достигается следующими методами:

🚩Методы управления зависимостями в DDD

🟠Инъекция зависимостей
Инъекция зависимостей позволяет передавать зависимости через конструкторы или сеттеры, снижая связанность и упрощая тестирование.
interface UserRepository {
findById(id: string): Promise<User | null>;
}

class UserService {
private userRepository: UserRepository;

constructor(userRepository: UserRepository) {
this.userRepository = userRepository;
}

public async getUserById(id: string): Promise<User | null> {
return await this.userRepository.findById(id);
}
}


🟠Сервисы домена
Сервисы домена содержат бизнес-логику, не принадлежащую конкретным сущностям или значимым объектам, и могут взаимодействовать с несколькими объектами или внешними сервисами.
class PaymentService {
private paymentGateway: PaymentGateway;

constructor(paymentGateway: PaymentGateway) {
this.paymentGateway = paymentGateway;
}

public async processPayment(order: Order): Promise<boolean> {
return await this.paymentGateway.charge(order.totalAmount);
}
}


🟠Анти-коррупционные слои
Анти-коррупционные слои защищают доменную модель от внешних влияний и преобразуют данные в понятные форматы.
class ExternalUserService {
public getUserData(id: string): ExternalUser {
// Получение данных от внешнего сервиса
}
}

class UserService {
private externalUserService: ExternalUserService;

constructor(externalUserService: ExternalUserService) {
this.externalUserService = externalUserService;
}

public getUser(id: string): User {
const externalUser = this.externalUserService.getUserData(id);
return new User(externalUser.id, externalUser.name, externalUser.email);
}
}


🟠Контейнеры зависимостей
Контейнеры управляют созданием и жизненным циклом зависимостей, упрощая конфигурацию и разрешение зависимостей.
import "reflect-metadata";
import { Container, injectable, inject } from "inversify";

@injectable()
class UserRepositoryImpl implements UserRepository {
public async findById(id: string): Promise<User | null> {
// Реализация метода
}
}

@injectable()
class UserService {
private userRepository: UserRepository;

constructor(@inject("UserRepository") userRepository: UserRepository) {
this.userRepository = userRepository;
}

public async getUserById(id: string): Promise<User | null> {
return await this.userRepository.findById(id);
}
}

// Конфигурация контейнера
const container = new Container();
container.bind<UserRepository>("UserRepository").to(UserRepositoryImpl);
container.bind<UserService>(UserService).toSelf();

// Разрешение зависимостей
const userService = container.get<UserService>(UserService);


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

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


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

Это абстрактная структура данных, работающая по принципу LIFO (Last In, First Out), что означает "последний пришёл — первый вышел". Это значит, что последний добавленный элемент будет первым при извлечении из стека. Под капотом реализации стека могут быть разные, и они зависят от конкретного языка программирования и задач, которые необходимо решить.

🟠Массивы
Один из самых распространённых способов реализации стека — это использование массива. В такой реализации элементы стека хранятся в массиве, и индекс последнего элемента (вершина стека) отслеживается отдельной переменной.
struct Stack<Element> {
private var storage: [Element] = []

mutating func push(_ element: Element) {
storage.append(element)
}

mutating func pop() -> Element? {
return storage.popLast()
}

func peek() -> Element? {
return storage.last
}

var isEmpty: Bool {
return storage.isEmpty
}
}


🟠Связные списки
Стек можно реализовать с использованием связных списков, где каждый элемент списка содержит данные и ссылку на следующий элемент в стеке. Вершина стека в такой реализации — это начало связного списка.
class Node<Element> {
var value: Element
var next: Node?

init(value: Element) {
self.value = value
}
}

struct Stack<Element> {
private var head: Node<Element>?

mutating func push(_ element: Element) {
let node = Node(value: element)
node.next = head
head = node
}

mutating func pop() -> Element? {
let node = head
head = head?.next
return node?.value
}

func peek() -> Element? {
return head?.value
}

var isEmpty: Bool {
return head == nil
}
}


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

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

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

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

Да, если использовать Notification Service Extension в iOS, можно изменить содержимое уведомления до показа. Однако это работает только для remote notification с mutable-content: 1.


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

Каждый способ создания макетов (layout'а) в iOS-разработке имеет свои преимущества и недостатки.

🚩Interface Builder (IB)

Визуальное редактирование
Удобный графический интерфейс для быстрой настройки и визуального просмотра изменений.
Интеграция с Auto Layout
Простая настройка ограничений (constraints) для адаптивного интерфейса.
Быстрое прототипирование
Возможность быстрого создания и изменения макетов без написания кода.
Меньшая гибкость
Ограниченные возможности для создания сложных и динамических макетов.
Конфликты при совместной работе
Трудности при слиянии изменений в storyboard или xib файлах в больших командах.
Производительность
Более медленное время загрузки по сравнению с программными подходами.

🚩Auto Layout

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

🚩Programmatic Layout

Полный контроль
Возможность точной настройки макета с помощью кода.
Гибкость
Легкость создания динамических и условных макетов.
Управление версиями
Легче управлять изменениями в коде по сравнению с визуальными файлами.
Большая трудоемкость
Требует больше времени и усилий для настройки, особенно для сложных макетов.
Меньшая наглядность
Отсутствие визуального редактора может затруднить представление конечного результата.

🚩Stack Views

Упрощение макетов
Легкость создания и управления сложными макетами с минимальными усилиями.
Интеграция с Auto Layout
Автоматическое управление ограничениями для вложенных элементов.
Адаптивность
Поддержка различных ориентаций и размеров экранов.
Ограниченная гибкость
Менее гибкие по сравнению с чистым Auto Layout или программными подходами.
Поддержка
Не всегда подходят для всех типов макетов, особенно для более сложных компоновок.

🚩SwiftUI

Декларативный синтаксис
Простота и понятность кода благодаря декларативному подходу.
Превью в реальном времени
Мгновенное обновление интерфейса при изменении кода.
Интеграция с Swift
Современные возможности языка и тесная интеграция с экосистемой Apple.
Кроссплатформенность
Поддержка различных платформ (iOS, macOS, watchOS, tvOS).
Требования к версии iOS
Поддержка только iOS 13 и выше, что может ограничить использование на старых устройствах.
Зрелость фреймворка
Некоторые функции еще находятся в стадии разработки, и могут быть ограничены возможности по сравнению с UIKit.

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

Fault — это "заглушка" объекта в Core Data, созданная для оптимизации.
Когда объект запрашивается из базы, он сначала создаётся как fault — пустая обёртка, содержащая только идентификатор. Данные подгружаются только при обращении к свойствам.
Это снижает расход памяти и повышает производительность при работе с большими выборками.


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

В Swift словарь (`Dictionary<Key, Value>`) – это неупорядоченная коллекция пар `ключ → значение`, где ключи уникальны.

🟠Сравнение двух словарей (`==`)
Swift позволяет сравнивать два словаря по их ключам и значениям, если и ключи, и значения соответствуют протоколу Equatable.
let dict1 = ["name": "Alice", "age": "25"]
let dict2 = ["name": "Alice", "age": "25"]
let dict3 = ["name": "Bob", "age": "30"]

print(dict1 == dict2) // true (значения одинаковые)
print(dict1 == dict3) // false (разные значения)


Ошибка при сравнении Dictionary без Equatable
Если значения не соответствуют `Equatable`, сравнение не сработает:
struct User {
let name: String
}

let dict1 = ["user": User(name: "Alice")]
let dict2 = ["user": User(name: "Alice")]

// Ошибка: Type 'User' does not conform to protocol 'Equatable'
// print(dict1 == dict2)


Решение: Сделать User Equatable:
struct User: Equatable {
let name: String
}

print(dict1 == dict2) // Теперь работает!


🟠Поиск и сравнение отдельных значений
Можно сравнивать отдельные элементы по ключу.
let dict = ["name": "Alice", "age": "25"]

if dict["name"] == "Alice" {
print("Имя совпадает") //
}


🟠Сравнение по ключам (`keys`) и значениям (`values`)
Можно сравнить только ключи или только значения.
let dict1 = ["name": "Alice", "age": "25"]
let dict2 = ["age": "30", "name": "Alice"]

print(dict1.keys == dict2.keys) // true (ключи одинаковые)


Сравнение значений
print(Set(dict1.values) == Set(dict2.values)) //  true (если порядок неважен)


🟠Сравнение словарей с разными типами (`Any`)
Если словарь хранит Any, то прямое сравнение не сработает, и нужно сравнивать элементы вручную.
let dict1: [String: Any] = ["name": "Alice", "age": 25]
let dict2: [String: Any] = ["name": "Alice", "age": 25]

// Функция сравнения словарей с `Any`
func areDictionariesEqual(_ dict1: [String: Any], _ dict2: [String: Any]) -> Bool {
return NSDictionary(dictionary: dict1).isEqual(to: dict2)
}

print(areDictionariesEqual(dict1, dict2)) // true


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

Через инициализацию (constructor injection), сеттеры, property injection, либо через DI-контейнер (например, Swinject, Resolver или Hilt в Android). В iOS чаще используют первый и третий способ.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊1
🤔 Может ли у 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
👍1
🤔 Как можно итерироваться по коллекции?

По коллекции можно итерироваться:
- С помощью цикла for.
- Через итераторы.
- Используя методы коллекций (например, map, filter, forEach).
- Через генераторы или ленивые последовательности (в функциональных языках).


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

Когда пользователь нажимает на иконку приложения на домашнем экране, iOS проходит несколько этапов перед тем, как приложение становится активным.


🚩Разберём путь события подробнее

🟠Пользователь нажимает на иконку (SpringBoard)
iOS-устройства управляются системой SpringBoard – это оболочка, отвечающая за домашний экран, иконки, фоновые процессы.
Когда пользователь тапает на иконку приложения, SpringBoard отправляет событие UIApplicationLaunchOptionsKey в систему.

🟠iOS загружает процесс приложения
Если приложение не запущено:
- iOS создаёт новый процесс и выделяет память.
- Загружаются зависимости (библиотеки, фреймворки).
- Создаётся объект UIApplication.

🟠Вызывается `application(_:didFinishLaunchingWithOptions:)`
Здесь приложение инициализируется и загружается основной UI.
Метод в AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
print("Приложение запущено")
return true
}


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

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


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

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

🚩Основные аспекты

🟠Отправка уведомлений
Любой объект может отправить уведомление с помощью Notification Center. Уведомления идентифицируются по имени (Notification.Name). Можно передать дополнительную информацию в виде словаря (userInfo).
NotificationCenter.default.post(name: .myNotification, object: nil)   


🟠Получение уведомлений
Объекты могут регистрироваться для получения уведомлений с определенным именем. Для обработки уведомлений используется метод, который будет вызван при получении уведомления.
NotificationCenter.default.addObserver(self, selector: #selector(handleNotification), name: .myNotification, object: nil)

@objc func handleNotification(notification: Notification) {
print("Received notification")
}


🟠Удаление наблюдателей
Важно удалять наблюдателей, когда они больше не нужны, чтобы избежать утечек памяти. Обычно это делается в методе deinit или перед уничтожением объекта.
NotificationCenter.default.removeObserver(self, name: .myNotification, object: nil)   


🚩Плюсы

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

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

1⃣Обновление интерфейса
Когда данные изменяются, можно отправить уведомление для обновления пользовательского интерфейса.
NotificationCenter.default.post(name: .dataDidUpdate, object: nil)   


2⃣Реакция на системные события
Например, можно подписаться на уведомление о смене состояния сети.
NotificationCenter.default.addObserver(self, selector: #selector(handleNetworkChange), name: .reachabilityChanged, object: nil)   


3⃣Межмодульное взаимодействие
Различные модули приложения могут общаться друг с другом через Notification Center, не создавая сильных зависимостей.
NotificationCenter.default.post(name: .userDidLogin, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(updateUI), name: .userDidLogin, object: nil)


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Escaping и non-escaping — где требуется self?

В escaping closures требуется явно использовать self, так как они захватываются и могут использоваться позже. В non-escaping — можно обойтись без self.

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

UserDefaults — это хранилище для сохранения простых данных (строки, числа, массивы). Но если нужно сохранить сложные объекты, их сначала кодируют в `Data` (Codable), а затем сохраняют.

🟠Сохранение и загрузка простых данных
Для примитивных типов (строки, числа, массивы, словари) UserDefaults работает без кодирования:
let defaults = UserDefaults.standard

// Сохранение
defaults.set("Иван", forKey: "username")
defaults.set(25, forKey: "age")

// Чтение
let name = defaults.string(forKey: "username") ?? "Нет имени"
let age = defaults.integer(forKey: "age")

print(name, age) // Иван 25


🟠Сохранение сложных объектов (Codable)
Если нужно сохранить свой объект, сначала его нужно закодировать в Data.
struct User: Codable {
let name: String
let age: Int
}


Сохранение в UserDefaults
let user = User(name: "Иван", age: 25)
let defaults = UserDefaults.standard

if let encoded = try? JSONEncoder().encode(user) {
defaults.set(encoded, forKey: "user")
}


Загрузка из UserDefaults
if let savedData = defaults.data(forKey: "user"),
let savedUser = try? JSONDecoder().decode(User.self, from: savedData) {
print(savedUser.name, savedUser.age) // Иван 25
}


🟠Удаление данных из `UserDefaults`
Чтобы удалить ключ:
defaults.removeObject(forKey: "user")


🟠Сохранение массива объектов
Можно сохранить массив объектов, просто закодировав его:
let users = [
User(name: "Иван", age: 25),
User(name: "Анна", age: 30)
]

if let encoded = try? JSONEncoder().encode(users) {
defaults.set(encoded, forKey: "users")
}

// Читаем массив обратно
if let savedData = defaults.data(forKey: "users"),
let savedUsers = try? JSONDecoder().decode([User].self, from: savedData) {
print(savedUsers) // [{name: Иван, age: 25}, {name: Анна, age: 30}]
}


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