Swift | Вопросы собесов
2.15K subscribers
29 photos
960 links
Download Telegram
🤔 Расскажи про букву D в solid

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

🚩Плохой пример (Нарушение DIP)

class MySQLDatabase {
func fetchData() -> String {
return "Данные из MySQL"
}
}

class DataManager {
let database = MySQLDatabase() // Прямая зависимость от MySQL

func getData() -> String {
return database.fetchData()
}
}


🚩Хороший пример (Используем DIP)

Вводим абстракцию (Протокол)
protocol Database {
func fetchData() -> String
}


Реализуем конкретные базы данных
class MySQLDatabase: Database {
func fetchData() -> String {
return "Данные из MySQL"
}
}

class PostgreSQLDatabase: Database {
func fetchData() -> String {
return "Данные из PostgreSQL"
}
}


Используем абстракцию в DataManager
class DataManager {
private let database: Database // Зависимость от абстракции

init(database: Database) {
self.database = database
}

func getData() -> String {
return database.fetchData()
}
}


Использование
let mySQLDataManager = DataManager(database: MySQLDatabase())
print(mySQLDataManager.getData()) // "Данные из MySQL"

let postgreSQLDataManager = DataManager(database: PostgreSQLDatabase())
print(postgreSQLDataManager.getData()) // "Данные из PostgreSQL"


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

Heap object (объект в куче) — это объект, созданный в динамической области памяти (куче). Он остаётся в памяти до тех пор, пока на него существуют ссылки, и освобождается автоматически (например, сборщиком мусора или ARC). Такие объекты используются для хранения данных с неопределённым временем жизни.

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

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

🚩Проблемы

🟠Утечки памяти (Retain Cycles)
Одной из самых распространенных проблем является утечка памяти из-за циклов удержания (retain cycles). Это происходит, когда два или более объекта удерживают ссылки друг на друга, препятствуя освобождению памяти. В этом примере closure захватывает self, что создает цикл удержания: MyClass держит сильную ссылку на closure, а closure держит сильную ссылку на self.
class MyClass {
var value: Int = 0
var closure: (() -> Void)?

func setupClosure() {
closure = {
self.value += 1
}
}
}

let instance = MyClass()
instance.setupClosure()


🟠Непредсказуемое поведение и условия гонки (Race Conditions)
Когда замыкания захватывают изменяемый контекст, это может привести к условиям гонки и непредсказуемому поведению, особенно при работе в многопоточном окружении. Если метод increment вызывается из разных потоков, это может привести к условиям гонки и некорректному изменению значения count.
class Counter {
var count = 0

func increment() {
DispatchQueue.global().async {
self.count += 1
}
}
}

let counter = Counter()
counter.increment()


🟠Задержки в освобождении ресурсов
Если замыкания захватывают тяжелые ресурсы (например, файлы, сети), это может привести к задержкам в их освобождении, что может негативно сказаться на производительности приложения. Если FileHandler освобождается, но замыкание все еще захватывает file, это может привести к задержке в освобождении файлового дескриптора.
class FileHandler {
var file: File?

func processFile() {
DispatchQueue.global().async {
self.file?.read()
}
}
}


🟠Потеря захваченных данных
Когда используется слабая ссылка (weak), замыкание может обнаружить, что захваченный объект освобожден, что приводит к тому, что слабая ссылка становится nil. Это требует дополнительных проверок и обработки.
class MyClass {
var value: Int = 0
var closure: (() -> Void)?

func setupClosure() {
closure = { [weak self] in
guard let strongSelf = self else { return }
strongSelf.value += 1
}
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🤔 В какой момент проставляется retain/release при использовании ARC?

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


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

В Swift (и во многих других языках программирования) по умолчанию числовой литерал с плавающей запятой интерпретируется как `Double`, а не Float.

🚩Основные причины:

🟠Бóльшая точность
Double имеет 64 бита, а Float32 бита. Это значит, что Double может хранить более точные значения, что особенно важно при математических вычислениях.

🟠Совместимость со стандартными API
Большинство API и стандартных библиотек Swift (например, sin(), cos(), pow()) работают именно с Double.
Например:

   let x = 3.14 // По умолчанию это Double
let y = sin(x) // sin() принимает Double


🟠Производительность на современных процессорах
На современных 64-битных процессорах операции с Double выполняются так же быстро или даже быстрее, чем с Float, из-за оптимизаций в аппаратном обеспечении.

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

   let a: Float = 0.1 + 0.2
print(a == 0.3) // false 😱


🚩Как сделать `Float` вручную?
Если всё же нужен Float, надо указать это явно:
let number: Float = 3.14


или
let number = 3.14 as Float


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

Это паттерны, связанные с созданием объектов. Они управляют созданием экземпляров, чтобы сделать код более гибким, масштабируемым и независимым от конкретных классов. Примеры: Factory Method, Abstract Factory, Builder, Singleton, Prototype.


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

В Swift переменные (свойства), объявленные в протоколе, должны указывать:

🟠Только для чтения (`get`)
Если свойство объявлено как { get }, класс или структура, реализующая протокол, должна предоставить как минимум геттер
protocol Animal {
var name: String { get } // Только чтение
}

struct Dog: Animal {
let name = "Барсик" // Реализуем только get
}

let dog = Dog()
print(dog.name) // "Барсик"


Можно также использовать вычисляемое свойство:
struct Cat: Animal {
var name: String {
return "Мурзик"
}
}


🟠Для чтения и записи (`get set`)
Если свойство { get set }, класс или структура обязательно должны предоставить и get, и set.
protocol Vehicle {
var speed: Int { get set } // Чтение и запись
}

class Car: Vehicle {
var speed: Int = 100 // Реализуем и get, и set
}

let car = Car()
car.speed = 120 // Можно изменить значение
print(car.speed) // 120


Вычисляемое свойство тоже подойдёт, если оно имеет get и set:
class Bike: Vehicle {
private var internalSpeed = 50

var speed: Int {
get { return internalSpeed }
set { internalSpeed = newValue }
}
}


🟠Статические свойства (`static`)
Если свойство должно быть общим для всех экземпляров (не индивидуальным), то оно объявляется static.
protocol Config {
static var appVersion: String { get }
}

struct AppSettings: Config {
static let appVersion = "1.0.0"
}

print(AppSettings.appVersion) // "1.0.0"


Класс может использовать class var, если свойство можно переопределять в подклассах:
class AppInfo: Config {
class var appVersion: String {
return "2.0.0"
}
}


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

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


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

В 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
🤔 В чём суть добавления в concurrent-очередь задачи через .sync?

Добавление задачи в concurrent queue через .sync означает:
- Задача выполняется синхронно — вызывающий поток ждёт завершения.
- Даже если очередь параллельная, .sync блокирует текущий поток до завершения блока.
Это может привести к deadlock'у, если .sync вызван изнутри той же очереди.


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

В Swift все типы данных делятся на два вида по семантике хранения и передачи:

🚩Значимая семантика (Value Semantics)

Структуры (struct), перечисления (enum) и кортежи (tuple) передаются по значению, то есть каждое присваивание создаёт копию объекта.
struct User {
var name: String
}

var user1 = User(name: "Alice")
var user2 = user1 // Копия!

user2.name = "Bob"

print(user1.name) // "Alice" (НЕ изменилось)
print(user2.name) // "Bob"


🚩Ссылочная семантика (Reference Semantics)

Классы (class), акторы (actor) и замыкания (closure) передаются по ссылке, то есть несколько переменных могут ссылаться на один и тот же объект.
class User {
var name: String
init(name: String) {
self.name = name
}
}

var user1 = User(name: "Alice")
var user2 = user1 // Передача ссылки!

user2.name = "Bob"

print(user1.name) // "Bob" (ИЗМЕНИЛОСЬ!)
print(user2.name) // "Bob"


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN 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