Skip to content

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

Notifications You must be signed in to change notification settings

vyachesIavskiy/iOS-Interview-Questions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 

Repository files navigation

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

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

Легенда

🟢 - Junior уровень
🟡 - Middle уровень
🔴 - Senior уровень

Пример кода:
let example = "Hello, Example!"

Мои комментарии

Общие вопросы

1. 🟢 Назовите основные принципы OOP.

Три основных принципа OOP - Инкапсуляция, Наследование, Полиморфизм.

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

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


2. 🟢 Что такое POP (Protocol Oriented Programming)? Чем отличается POP от OOP?

POP - Protocol Oriented Programming, подход, при котором структура программы реализуется через протоколы (интерфейсы) и их реализацию. POP отличается от OOP тем, что при использовании POP, вы создаете протоколы, которые затем реализуют ваши типы, а не создаете типы, которые потом необходимо наследовать.


3. 🟢 Чем отличается фукнция от метода?

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


4. 🟢 Что такое рекурсия? Как ее правильно использовать и какие у нее есть нюансы?

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


5. 🟢 Что такое Big-O notation? Для чего используется?

Big-O notation - показатель сложности алгоритма. Данный показатель может использоваться для оценки сложности алгоритма по скорости или по памяти.

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


6. 🟡 Что такое KVO/KVM? Для чего их исользуют?

KVO - Key-Value Observing - механизм, при котором мы можем отслеживать изменения свойства экземпляра, получая доступ к данному свойству по ключу.
KVM - Key-Value Modification - механизм, который позволяет нам модифицировать свойство экземпляра, получая доступ к данному свойству по ключу.

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


7. 🟡 Какие алгоритмы поиска и сортировки вы знаете? Какие исользуете в работе?

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

Лично я крайне негативно отношусь к данному вопросу, так как все мы прекрасно понимаем, что в реальности в 99% случаях будет использованы sort() и filter() из стандартной библиотеки.


8. 🟡 Что такое реактивное программирование? Есть ли опыт написания программ реактивно?

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

Если у вас есть опыт реакта, рекомендую четко указывать, каким фреймворком для этого пользовались. К примеру, RxSwift и Combine имеют очень много различий и умение пользоваться одним не так легко проэцируется на другой.


9. 🟡 Что такое Future/Promise?

Future или Promise - два названия одного и того же подхода, одного из способов реализации рективного программирования. Это прокси (контейнер), в котором должно быть значение в каком-то времени в будущем. Изначально данный контейнер может быть пуст и при попытки получить его значение, выполнение программы блокируется до тех пор, пока значение в контейнере не появится.


10. 🟡 Что такое Publisher/Subscriber?

Publisher и Subscriber - это еще один подход, с помощью которого возможна реализация реактивного программирования.
Publisher - объект, который выдает значения в течении некоторого или неограниченного времени.
Subscriber - объект, который подписывается на выдачу значений publisher'a и обрабатывает получаемые значения от него.


11. 🔴 Что такое слабосвязанный код? В чем его преимущество перед сильносвязанным?

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

protocol ColorProvider {
    var color: UIColor { get }
}

struct LCDColorProvider: ColorProvider {
    var color: UIColor {
        UIColor(red: 0.3, green: 0.24, blue: 0.71, alpha: 1)
    }
}

struct P3ColorProvider: ColorProvider {
    var color: UIColor {
        UIColor(displayP3Red: 0.32, green: 0.264, blue: 0.7142, alpha: 1)
    }
}

struct ColorSettings {
    var colorProvider: ColorProvider
}

let lcdSettings = ColorSettings(colorProvider: LCDColorProvider())
let p3Settings = ColorSettings(colorProvider: P3ColorProvider())

12. 🔴 Как вы оптимизируете время компиляции программы?

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


13. 🔴 Как вы оптимизируете время запуска программы?

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


Swift

1. 🟢 Какие структуры данных есть в Swift?

В Swift существуют следующие структуры данных:

  • enum
  • struct
  • class
  • actor
  • protocol

Описание каждой структуры с легкостью находятся в книге The Swift Programming Language.


2. 🟢 Каике структуры данных относятся к value type? Какие к reference type?

К value type относятся enum и struct.
К reference type относятся class, actor. Так же к reference type относятся closure и func, хоть они и не являются структурами данных.


3. 🟢 В чем отличие value type и reference type с точки зрения памяти и хранения?

Экземпляры value type хранятся в стеке (stack), в то время, как reference type хранятся в куче (heap).
Так же, при присваивании одного экземпляра value type другому экземпляру того же типа, происходит копирование из одного экземпляра в другой, в то время, как при присваивании одного экземпляра reference type другому экземпляру того же типа, происходит копирование ссылки на объект, а не самого объекта.


4. 🟢 Что такое closure и зачем они нужны? Чем отличаются от func?

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


5. 🟢 Что такое escaping и nonescaping? Для чего они используются?

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


6. 🟢 Что такое capture list? Для чего он используется?

Capture list или список захвата - это механизм, использующийся в замыканиях. Благодаря ему замыкание имеет доступ к переменным/константам, объявленными за пределами тела замыкания, а так же к полям типа, внутри которого объявлено замыкание. По умолчанию, все переменные/константы, объявленные внутри функции, в которой объявлено замыкание, попадают в список захвата замыкания. Если данные переменные/константы являются экземплярами reference type типов, то они захватываются сильной ссылкой.
self по умолчанию не захватывается, но любое упоминание self внутри замыкания автоматически добавляет его в список захвата сильной ссылкой, если self - экземпляр reference type.
Список захвата необходим замыканию для того, чтобы была возможность работать с данными, которые объявлены за пределами тела замыкания.


7. 🟢 closure - это value type или reference type? Почему? А func?

closure - это reference type. Если бы closure была бы value type, то при передаче ее в функцию, она бы копировалась, что привело бы к копированию всех объектов, которые попадают в ее список захвата и потерю контекста, где closure была определена.
func - это так же reference type. По своей сути closure и func - это одно и тоже, так что и "под капотом" они реализованы одинаково.


8. 🟢 Что такое CoW (Copy on Write)? Как и где он реализован в Swift?

CoW - это механизм, при котором при присваивании одного экземпляра другому, копирование объекта не происходит до тех пор, пока один из экземпляров не будет модифицирован. Данный механизм используется во всех value type типах в Swift.


9. 🟢 Что такое mutating?

mutating - это ключевое слово в Swift, которое позволяет методу, указанному как mutating, менять self в value type типах. По умолчанию все методы value type типов nonmutating.


10. 🟢 В чем отличие между var и let? Есть ли разница между value type и reference type с точки зрения var и let?

var - ключевое слово для объявления переменной.
let - ключевое слово для объявления константы.

Переменные value type и reference type ведут себя одинаково.
Константе value type не может быть присвоен другой экземпляр данного типа, а так же все ее внутренние поля не могут быть изменены.
Константе reference type не может быть присвоен другой экземпляр данного типа, но ее внутренние неконстантные поля могут быть изменены.


11. 🟢 Назовите все уровни доступа и что они означают в Swift?

private - самый закрытый уровень доступа. Поля и методы типа с данным уровнем доступа видны только внутри объявления типа и расширениях, объявленных в том же файле, что и сам тип.
fileprivate - поля и методы типа с данным уровнем доступа видны снаружи объявления типа, но только в пределах файла, в котором объявлен тип.
internal - уровень доступа по умолчанию. Поля и методы типа видны снаружи объявления типа в пределах модуля, в котором объявлен тип.
public - поля и методы типа видны снаружи объявления типа внутри и за пределами модуля, в котором объявлен тип. Однако, за пределами данного модуля тип с данным уровнем доступа не может быть наследован, а поля и методы перегружены.
open - самый открытый уровень доступа. Тоже самое, что и public, но наследование и перегрузка не запрещены.


12. 🟢 Чем отличается public и open?

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


13. 🟢 Что такое final?

final - ключевое слово, которое запрещает дальнейшее наследование типа или перегрузку поля или метода типа.


14. 🟢 Назовите особенности enum в Swift

enum в Swift может иметь rawValue отличный от Int, в отличии от многих языков программирования. Так же, каждое значение перечисления может иметь вложенные значения любого типа в любом количестве, включая данное перечисление. Если вложенное значение является данным перечислением, перед объявлением данного значения ставится ключевое слово indirect.


15. 🟢 Что такое Optional?

Optional - специальное перечисление в Swift, которое позволяет показать, что значение отсутствует, что обозначается ключевым словом nil.


16. 🟢 Что такое if let?

if let - это конструкция безопасного разворачивания Optional. Код внутри блока if let будет выполнен только в том случае, если проверяемый Optional не равен nil.

var myNumber: Int? = 12
if let number = myNumber {
    print("My number is \(number)") // prints Mu number is 12
}

17. 🟢 Что такое guard?

guard - это ключевое слово контроля выполнения кода, которое проверяет условие на истинность. У guard есть только блок else, который выполнится, если условие не действительно. else блок guard обязан прервать выполнение блока кода, в котором объявлен guard.

func increment(number: Int) -> Int {
    guard number >= 10 else {
        return number
    }
    
    return number + 1
}

let incrementedNumber = increment(number: 4) // result is 5
let notIncrementedNumber = increment(number: 13) // result is 13

18. 🟢 В чем разница между if let и guard let?

Обе данных конструкции безопасно разворачивают Optional и выполняют блок кода только в том случае, если значение Optional не равно nil. Единственное различие в том, что else блок guard обязан выйти из блока кода, где был объявлен guard.


19. 🟢 Что делают операторы ?, ! и ???

Оператор ? - это оператор опционального связывания при доступе к значению Optional.

struct Person {
    var name: String
    var lastName: String
}

var me: Person? = Person(name: "Karl", lastName: "Brown")
me?.name = "Mark"

Оператор ! - это оператор небезопасного разворачивания Optional. Должен использоваться только тогда, когда вы на 100% уверенны, что значение Optional не равно nil.

var number: Int? = 10
var noNumber: Int?

print(number!) // ok, prints 10
print(noNumber!) // crash, unexpectedly found nil while unwrapping an optional value

Оператор ?? - это оператор безопасного разворачивания Optional и использования его значения или предоставления значения по умолчанию, если значение Optional равно nil.

var number: Int? = 10
var noNumber: Int?

print(number ?? 12) // prints 10
print(noNumber ?? 12) // prints 12

20. 🟢 Что такое extension? Что может быть в extension, а чего нет?

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


21. 🟢 Что такое protocol? Для чего они используются?

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


22. 🟢 Что такое Any? Что такое AnyObject? Для чего они используются?

Any - специальный тип, который является всем сразу и ничем одновременно. Каждый тип в Swift может быть привиден к типу Any. Any используется тогда, когда мы не можем использовать generics, но при этом хотим иметь гибкость передачи аргументов разного типа. Недостаток у такого подхода всего один: так как в итоге тип данных будет Any, мы должны наверняка знать, какой тип был до этого, чтобы получить возможность восстановить данные после получения.
AnyObject - специальный тип, такой же, как и Any, но с ограничением только на reference type. Каждый AnyObject может быть Any, но не каждый Any может быть AnyObject. Ключевое слово AnyObject так же используется для ограничения протокола на реализацию только reference type типами.


23. 🟢 Назовите типы для работы с коллекциями, которые есть в Swift.

В Swift есть три типа для работы с коллекциями - Array, Set и Dictionary.
Array - массив данных. Элементы имеют порядок, доступ к элементам происходит по индексу.
Set - неупорядоченное множество. В множестве не может быть двух одинаковых элементов. Равенство элементов достигается проверкой hash значения каждого элемента. В связи с этим элементы множества обязаны реализовать протокол Hashable. Доступ по индексу невозможен.
Dictionary - словарь, элементы которого представляют из себя пару (ключ, значение). Ключи словаря должны быть уникальны. Это достигается ровно так же, как и в случае с множеством, через протокол Hashable. Значения словаря могут быть любым типом, на них не накладываются ограничения. Так же значения не должны быть уникальными. Доступ осуществляется по ключу и возвращает Optional значение, так как элемента по заданному ключу может не быть. Реализован через hash table.

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


24. 🟢 Что будет, если добавить в Set два объекта с одинаковым значением hash?

Если в Set попытаться добавить два объекта с одинаковым значением hash, то при добавлении первого элемента, он будет успешно добавлен в множество. При попытке добавить второй элемент в множество, элемент добавлен не будет.


25. 🟢 Что такое forEach/map/flatMap/compactMap/filter/reduce? Какие еще примеры функционального программирования вы знаете в Swift?

forEach, map, flatMap, compactMap, filter и reduce - функции протокола Sequence в Swift, которые дают возможность работать с коллекциями, используя функциональный подход.
В данном случае:

forEach - выполняет блок кода для каждого элемента коллекции.

let arr = [1, 2, 3, 4]
arr.forEach { print($0) }

// prints
// 1
// 2
// 3
// 4

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

let numbers = [1, 2, 3, 4]
let mapped = numbers.map { 
    Array(repeating: $0, count: $0)
} // mapped = [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]] 

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

let numbers = [1, 2, 3, 4]
let flatMapped = numbers.flatMap { 
    Array(repeating: $0, count: $0)
}
// flatMapped = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]

compactMap - выполняет блок кода для каждого элемента коллекции, в результате чего образовывается новая коллекция. Отличается от map тем, что размер результирующей коллекции не обязательно равен размеру изначальной. Элементы изначальной коллекции можут быть Optional и хранить в себе nil. Такой элемент не попадает в результирующую коллекцию. Тип элементов результирующей коллекции так же может измениться, как и при выполнении map.

let numbers: [Int?] = [1, nil, 3, 4, nil]
let compactMapped = numbers.compactMap {
    if let element = $0 {
        Array(repeating: element, count: element)
    } else {
        return nil
    }
}
// compactMapped = [[1], [3, 3, 3], [4, 4, 4, 4]]

filter - выполняет блок кода для каждого элемента коллекции, результатом которого есть значение Bool. При возврате из блока false, элемент исключается из результирующей коллекции.

let map = [
    "weather": "Good",
    "temperature": 27,
    "humidity": 34.3
]

let result = map.filter { (key, _) in
    key == "temperature"
} // result = ["temperature": 27]

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

let arr = [1, 2, 3, 4]
let resut = arr.reduce(0) { partialResult, value in
    if partialResult == 0 {
        return value
    }
    
    return partialResult * 10 + value
} // result = 1234

Это далеко не все подобный функции коллекций, но про эти спрашивают чаще всего.


26. 🟢 Чем отличается compactMap от flatMap?

compactMap - функция, которая формирует новую коллекцию из текущей, исключая все элементы, значением которых является nil.
flatMap - функция, формирует новую коллекцию, "схлопывая" накопление вложенные коллекциии или Optional до одной коллекции или Optional.


27. 🟢 Что такое weak и unowned? Чем они отличаются?

weak и unowned - это два ключевых слова для создания слабой ссылки. Применимо только к экземплярам reference type типов. Используются для предотвращения reference cycle. weak и unowned отличаются между собой так же, как и операторы ? и ! соответственно. weak не гарантирует наличия значения, в результате чего может использоваться только с optional. unowned предполагает, что значение всегда будет и, по сути, является force unwrapped optional. Если в процессе выполнения значение unowned будет nil, при попытке обратиться к данному значению будет краш.


28. 🟡 Как реализован Optional?

Здесь можно много говорить, но лучше один раз показать, чем сто раз рассказать, так что просто воспроизводим enum из стандартной библиотеки. Благо он небольшой.

enum Optional<Wrapped> {
    case some(Wrapped)
    case none
}

Да, все так просто


29. 🟡 Что такое lazy var? Для чего она используется?

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


30. 🟡 Что такое generics? Для чего они используются?

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


31. 🟡 Что такое associated type? Для чего он используется?

associated type - механизм параметризации протокола. Используется для обеспечения большей гибкости работы с протоколом в силу ухода от конкретного типа к параметру. По своей сути generics для протокола.


32. 🟡 Какие ограничения есть для протоколов, у которых есть associated type?

Так как протоколы с assotiated type не являются достаточно конкретными, объявить переменную данного типа или передать напрямик подобный тип как параметр функции невозможно.
Здесь на помощь нам приходят ключевые слова any и some. Данные два ключевых слова решают одну и ту же проблему, но немного по-разному.
any - создает коробку вокруг конкретного типа, который удовлетворяет данному протоколу. some - указывает, что пусть это не известно заранее, но на момент компиляции программы конкретный тип переменной будет известен точно.


33. 🟡 Чем отличается цикл for от forEach?

Цикл for имеет все преимущества контроля выполнения и может быть прерван раньше, чем весь цикл будет пройден.
forEach не может быть прерван и гарантированно пройдет по всем элементам коллекции.


34. 🔴 Можно ли объеденить код на ObjC и Swift в одном проекте? Если да, то как?

Да, такая возможность присутствует. В первую очередь необходимо создать bridging header файл, который будет инклудить в себя все header файлы ObjC кода, которые вы планируете использовать в Swift коде. Все Swfit типы, которые планируется использовать внутри ObjC кода, необходимо маркировать @objc аттрибутом, или наследовать от NSObject класса.


35. 🔴 Можно ли объеденить код на C/C++ и Swift в одном проекте? Если да, то как?

Да, такая возможность так же присутствует. Для начала необходимо выполнить все шаги, которые позволяют использовать ObjC код в Swift проекте. После чего использовать C/C++ код из ObjC кода, или переходить на так называемый ObjC++, смесь ObjC и C++, так как Swift не может напрямую работать с C/C++ кодом.


36. 🔴 Чем отличается вызов функции в Swift и отправка сообщения в ObjC?

При вызове функции или метода в Swift функция, которую мы собираемся вызвать заранее определена и точно существует. Это может быть глобальная фукнция, метод экземпляра, или типа, функция может быть перегружена, но она точно есть и определена. Swift так работает в силу того, что является жестко типизированным языком.
ObjC не имеет фукнций или методов, зато имеет сообщения, или селекторы, при вызове которых ObjC runtime пытается найти вызываемый селектор у объекта, у которого вы пытаетесь его вызвать. Если селектор будет найден, он будет вызван. Если нет - ничего не произойдет.
Главное различие данных подходов в том, что вызов функции в Swift определяется на этапе компиляции, в то время как вызов селектора в ObjC - на этапе выполнения. Это позволяет в ObjC определять селекторы, которые вызываются после того, как программа уже написана, просто динамически подгружая их, к примеру, через библиоотеку.


37. 🔴 Что такое conditional conformance? Как его реализовать и для чего используют?

conditional conformance - это механизм языка Swift, который позволяет реализовать протокол generic типом при соблюдении тех или иных условий.

protocol Loggable {
    func log()
}

extension Loggable {
    func log() {
        print(self)
    }
}

extension Array: Loggable where Element: Loggable {}
extension Int: Loggable {}

[1, 2, 3, 4].log() // prints [1, 2, 3, 4]

38. 🔴 Что такое async/await? Что такое Task? Что такое actor?

async и await - это два ключевых слова, которые работают в паре и позволяют выполнять код асинхронно.
async - указывается в объявлении функции или метода и указывает, что данная функция или метод могут быть приостановлены для ожидания выполнения асинхронной операции.
await - пишется перед вызовом любой функции или метода, которые имеют ключевое слово async в объявлении и являются возможной точкой приостановления для ожидания выполнения асинхронной операции.
Task - тип, который представлят собой асинхронную задачу. Используется для перехода из синхронной среды в асинхронную. async функции и методы можно вызвать только внутри Task или из async функций или методов.
actor - недавно появившаяся структура данных, которая очень похожа на class, но имеет ряд отличий. Во-первых, actor не может быть унаследован или наследовать другой тип. Во-вторых, actor гарантирует безопасность доступа к своим полям и вызова своих методов. Это называется изолированное состояние. Чтение/запись/вызов методов actor возможно только в асинхронной среде, за исключением тех полей и методов, которые имеют аттрибут nonisolated.

func doSomeVeryLongStuff() async {
    ...
}

Task {
    await doSomeVeryLongStuff() 
}

39. 🔴 Что такое swizzling? В чем его преимущества/недостатки?

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

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


40. 🔴 Что такое условная компиляция? Для чего она используется?

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


Паттерны

1. 🟢 Как расшифровывается SOLID?

S - single responsibility. Каждый элемент программы должен выполнять только одну роль.
O - open-closed. Элементы программы должны быть открыты к расширению, но закрыты к модификации.
L - Liskov substitution. Элементы программы, работающие с базовыми классами или протоколами, должны так же работать и с наследниками или реализаторами протокола.
I - Interface segregation. Множество специализированных интерфейсов лучше, чем один большой на все сразу. D - Dependency inversion. Строить зависимости необходимо на интерфейсах, нежеле на конкретных типах.

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


2. 🟢 Какие паттерны используются в iOS?

В iOS три самых распространенных паттерна - это singleton, delegate и observer. С недавних пор к ним присоединились publisher и actor.


3. 🟢 Что такое Singleton? Что с ним не так?

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


4. 🟢 Как реализовать Singleton в Swift?

Еще один вопрос, когда лучше просто написать сразу код.

final class Singleton {
    static let shared = Singleton()
    
    private init() {}
} 

Я видел синглтоны, которые реализовывали через struct. В целом это допустимо, но накладывает целый ряд дополнительных трудностей в использовании такого синглтона.


5. 🟢 Что такое Delegate? Как его использовать?

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

Примеры: UITableViewDataSource и UITableViewDelegate


6. 🟢 Что такое Observer? Как его использовать?

Observer - поведенческий паттерн, который позволяет наладить механизм подписки на события нескольким объектам и реагировать на эти события.

Пример: NotificationCenter.


7. 🟢 Назовите все парттерны, которые вы знаете. Перечислите те, которые вы использовали на практике.

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

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


UI

1. 🟢 Назовите все состояния, в которых может находиться iOS приложение (Application life cycle). Назовите примеры, когда может наступить каждое из состояний.

iOS приложение может находится в одном из нескольких состояний:

  • Not running - приложение не запущено.
  • Foreground inactive - приложение находится на экране, но не является first responder. Такое может произойти, например, когда ваше приложение перекрывает UI телефонного звонка.
  • Foreground active - ваше приложение находится на экране и является first responder.
  • Background - ваше приложение свернуто и не находится на экране, однако оно все еще работает и получает обновления от системы.
  • Suspended - ваше приложение свернуто, не находится на экране и больше не получает обновления от системы.

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


2. 🟢 Какие из этих состояний вы используете чаще всего в работе?

Вопрос довольно глупый, но он все равно бывает. В 99.9999% случаев вы работаете с Foreground Active состоянием.


3. 🟢 Что такое Storyboard? Что такое Xib? Чем отличаются Xib и Nib? Для чего их используют?

Storyboard, Xib и Nib - это, по своей сути, одно и тоде - XML файл, который описывает UI приложения. Формат данного файла не имеет документации и данные файлы не предполагают редактирование за пределами Interface Builder.
Xib и Nib не отличаются вообще ничем, это два названия для одного и тоже термина, на смену Nib пришел Xib. Xib описывает UI отдельно взятой UIView или нескольких UIView.
Storyboard отличается от Xib тем, что имеет ряд дополнительных возможностей, так как описывает UI не UIView, а одного или нескольких UIViewController. В связи с этим Storyboard может так же определять взаимодействия и переходы между разными UIViewController.


4. 🟢 Какие UI фреймворки доступны в iOS? Чем они отличаются?

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


5. 🟢 Что такое UIKit?

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


6. 🟢 Что такое AutoLayout? Для чего его используют?

AutoLayout - это система внутри UIKit, которая отвечает за размещение и размеры элементов на экране приложения. AutoLayout работает через constraints - элементы, которые являются частью уравнения, которое AutoLayout решает, когда размещает и задает размер ваших UI элементов на экране.


7. 🟢 Какое минимальное количество constraints надо задать, чтобы определить положение UIView на экране?

Есть несколько вариантов ответа на этот вопрос:

  • 2 constraints, одна по вертикали, а вторая по горизонтали, если intrinsicContentSize вьюхи определен.
  • 3 constraints, если intrinsicContentSize вьюхи определен только по одной оси. Тогда необходим дополнительный constraint по оси, по которой intrinsicContentSize не определен.
  • 4 constraints, по две по вертикали и горизонтали, если intrinsicContentSize вьюхи не определен.

8. 🟢 Назовите основные аттрибуты constraint и что каждый из них означает

constraint имеет следующие аттрибуты:

  • firstItem - первый якорь элемента, к которому относится данный constraint.
  • secondItem - второй якорь элемента, к которому относится данный constraint. Элемент может быть другим или тем же. Якорь может быть nil.
  • constant - константа constraint.
  • multiplier - множитель константы или значения якоря элемента.
  • relation - отношение между якорями. Может быть equalTo, greaterThanOrEqualTo, lessThanOrEqualTo`.
  • priority - приоритет constraint. Находится в диапазоне от 0 до 1000.

9. 🟢 Что такое contentHuggingPriority и contentCompressionResistancePriority? Как их использовать и для чего?

contentHuggingPriority - это приоритет, который говорит насколько вероятно, что UIView станет больше своего необходимого размера.
contentCompressionResistancePriority - это приоритет, который говорит насколько вероятно, что UIView станет меньше своего необходимого размера.
Данный приоритеты используются вместе с приоритетами constraint для решения неопределенности, при которой две UIView не могут определить свой размер.


10. 🟢 Что такое UIStackView? В чем его преимущества/недостатки?

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


11. 🟢 Жизненный цикл UIViewController. Назовите все методы-обработчики жизненного цикла и когда каждый из них вызывается.

У UIViewController есть следующие методы-обработчики жизненного цикла:

  • viewDidLoad - вызывается всего один раз для каждого UIViewController в течени жизненного цикла. Сигнализирует о том, что вью была загружена и теперь может быть использована.
  • viewWillAppear - вызывается перед тем, как UIViewController будет показан на экране. Может быть вызван более одного раза за жизненный цикл.
  • viewDidAppear - вызывается после того, как UIViewController был показан на экране и анимация его представления завершилась. Может быть вызван более одного раза за жизненный цикл.
  • viewWillDisappear - вызывается перед тем, как UIViewController будет убран с экрана. Может быть вызван более одного раза за жизненный цикл.
  • viewDidDisappear - вызывается после того, как UIViewController был убран с экрана и анимация его исчезновения завершилась. Может быть вызван более одного раза за жизненный цикл.

12. 🟢 Чем отличаются frame и bounds?

frame - это прямоугольник, который представляет положение и размер UIView в системе координат ее superView.
bounds - это прямоугольник, который представляет положение и размер UIView в системе координат этой же UIView. origin bounds всегда находится в координате (0, 0).


13. 🟢 Что такое UISegue? Как они обрабатываются? Какие есть альтернативы и в чем преимущества/недостатки каждого из них?

UISegue - это встроенный тип, который отвечает за переход между несколькими UIViewController, объявленный в Storyboard.
Чтобы обработать UISegue, необходимо перегрузить метод prepare.
Альтернативой использования UISegue будет создание и презентация UIViewController вручную в том месте, где необходимо выполнить переход с одного UIViewController на другой. Как правило UISegue не используют в силу того, что они всецело опираются на identifier, который является просто строкой из-за чего работать с ним не очень удобно.


14. 🟢 UINavigationController - что это, для чего используется и как реализован?

UINavigationController - это UIViewController, который является контейнером для других UIViewController и осуществляет навигацию между ними. UINavigationController имеет UINavigationBar, который показывается в верхней части экрана, а так же имеет стандрартные анимации перехода между UIViewController-ами, переход между которыми происходит в данный момент. Является одним из самый важных и основных компонентов UI.


15. 🟢 UITabController - что это, для чего используется и как реализован?

UITabController - это UIViewController, который является контейнером для других UIViewController и осуществляет переход между ними посредстом табов. UITabController показывает таб бар в нижней части экрана, где у каждой табы может быть своя иконка и название. Является одним из основных компонентов UI, однако используется не всегда.


16. 🟢 Что такое UITableView? Для чего его использовать и что необходимо сделать, чтобы его использовать?

UITableView - UIview, которая представляет собой вертикальный список, каждый элемент которого - это ячейка списка. UITableView - это один из наиболее часто используюемых компонентов при построении UI приложения и его можно встретить практически в каждом приложении.

Чтобы использовать UITableView, необходимо реализовать протокол UITableViewDataSource. В этом случае вы можете отобразить данные, но не сможете с ними взаимодействовать. Если же необходимо обрабатывать пользовательское взаимодействие с таблицей, необходимо так же реализовать протокол UITableViewDelegate.
У UITableViewDataSource есть альтернатива - UITableViewDiffableDataSource. В отличии от UITableViewDataSource это не протокол, а конкретный тип, который реализовывает более современный подход к работе с таблицами.

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


17. 🟢 Что такое UICollectionView? Для чего его использовать и что необходимо сделать, чтобы его использовать?

UICollectionView - UIview, которая представляет собой коллекцию, состоящую из ячеек. Визуальное представление UICollectionView зависит от его collectionViewLayout и может быть абсолютно любым. Самый частый случай - это вертикальная или горизонтальная сетка элементов. Так же как и UITableView является одним из наиболее часто используюемых компонентов при построении UI приложения.

Чтобы использовать UICollectionView, необходимо реализовать протокол UICollectionViewDataSource. В этом случае вы можете отобразить данные, но не сможете с ними взаимодействовать. Если же необходимо обрабатывать пользовательское взаимодействие с коллекцией, необходимо так же реализовать протокол UICollectionViewDelegate.
У UICollectionViewDataSource есть альтернатива - UICollectionViewDiffableDataSource. В отличии от UICollectionViewDataSource это не протокол, а конкретный тип, который реализовывает более современный подход к работе с коллекциями.
Так же, если вы хотите использовать collectionViewLayout отличный от UICollectionViewFlowLayout, его так же необходимо отдельно создать, настроить и указать коллекции.

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


18. 🟢 В чем отличие UICollectionView и UITableView?

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


19. 🟢 Что такое GestureRecognizer? Какие они бывают?

GestureRecognizer - это встроенный тип, позволяющий обрабатывать различные жесты пользователя. Они бывают следуюших типов:

  • UITapGestureRecognizer - обрабатывает короткое нажатие.
  • UIPinchGestureRecognizer - обрабатывает двухпальцевый жест увеличения и уменьшения.
  • UIRotationGestureRecognizer - обрабатывает двухпальцевый жест поворота.
  • UISwipeGestureRecognizer - обрабатывает быстрый смах по экрану.
  • UIPanGestureRecognizer - обрабатывает жест перемещения пальца по экрану.
  • UIScreenEdgePanGestureRecognizer - обрабатывает жест перемещения пальца от края экрана.
  • UILongPressGestureRecognizer - обрабатывает жест длинного нажатия на экран.
  • UIHoverGestureRecognizer - обрабатывает жест наведения курсора на элемент UI. Доступен только на iPadOS.

20. 🟢 Что такое LaunchScreen.storyboard? Можно ли динамически менять его элементы или поставить ему класс-обработчик? Почему?

LaunchScreen.storyboard - это специальный storyboard, который показывается в момент запуска приложения. Содержимое данного storyboard нельзя динамически настраивать так же, как и UIViewController не может иметь класс-обработчик, так как LaunchScreen.storyboard показывается до того, как метод application(_:didFinishLaunchingWithOptions:) будет вызван.


21. 🟡 Что такое size class? Какие они бывают? Для чего их используют?

Size class - это параметр, указывающий на то, в каком окружении в данный момент выполняется наше приложение. Есть два значения size class - по-вертикали и по-горизонтали. Комбинация этих значений может указать нам на диапазон устройст и состояний устройств, в которых мы сейчас исполняемся. Используется для построения различного UI в зависимости от доступного пространства на экране.
Size class может быть одним из следующих значений:

  • compact
  • regular
  • unspecified

Как определить девайс и положение девайса по size class:

  • [Width: Compact, Height: Regular]:
    • все айфоны в портретном режиме;
    • все айпэды в режиме overlay или splitView в соотношении 1/3 в ландшафтном или портретном режиме;
    • все айпэды, кроме 12.9", в режиме splitView в соотношении 1/2 в ландшафтном режиме.
  • [Width: Compact, Height: Compact]:
    • все айфоны в ландшафтном режиме, кроме Plus/Pro Max айфонов.
  • [Width: Regular, Height: Compact]: Plus/Pro Max айфоны в ландшафтном режиме.
  • [Width: Regular, Height: Regular]:
    • все айпэды в полноэкранном режиме;
    • 12.9" айпэды в режиме splitView в соотношении 1/2 в ландшафтном режиме.

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


22. 🟡 Как построить UI в коде? Какие преимущества/недостатки у данного подхода?

Для того, чтобы построить UI в коде, необходимо всю работу Interface Builder-а взять на себя. А именно, создать экземпляры необходимых UIView, настроить их нужным образом, добавить во view hierarchy и задать между ними constraints.
Преимущества данного подхода в том, что, во-первых, вы больше не связываетесь с Interface Builder. Больше не может случится ситуация, при которой в файле IB случилась непоправимая ошибка, в результате чего Xcode больше не может отрендерить UI в принципе и вам надо вручную искать ошибку в сгенерированном недокументированном файле. Во-вторых, мерж конфликты решать в коде значительно проще, чем в IB файлах.
Недостатки вытекают из достоинств: слишком много кода, который просто настраивает UI, наглядно не видно, что именно и как настроено, работать с size class становится немного сложнее.

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


23. 🟡 В чем разница между CALayer и UIView? Для чего нужен CALayer?

CALayer является компонентом CoreGraphics - низкоуровнего API, который непосредственно занимается рендером и анимациями. CALyaer является наследником CGLayer. UIView является контейнером для CALayer и у каждой UIView есть как минимум один CALayer, как у каждого UIViewController есть как минимум один UIView.


24. 🟡 Как можно реализовать кастомную анимацию перехода между двумя экранами?

Для этого необходимо реализовать протокол UIViewControllerAnimatedTransitioning, затем задать значение custom в поле modalPresentationStyle. Значение полю transitioningDelegate присвоить объект, который реализовал UIViewControllerAnimatedTransitioning. Все данные поля необходимо присваивать объекту UIViewController с которого необходимо реализовать кастомный переход.


25. 🟡 UISplitViewController - что это, для чего используется и как реализован? Какие недавние изменения в нем появились?

UISplitViewController - это UIViewController, который является контейнером для других UIViewController. UISplitViewController располагает свои viewControllers друг рядом с другом по горизонтали. Первая колонка по умолчанию будет значительно уже второй. Данное расположение доступно только для девайсов, которые имеют regular size class по горизонтали. При значении compact size class по горизонтали UISplitViewController превращается в обычный UINavigationController.
С недавних пор UISplitViewController был обновлен. Ему полностью переписали API, добавили возможность сделать три колонки, а так же сделать полноценно отдельный UIViewController для девайсов со значением compact size class по горизонтали.


26. 🟡 В чем разница между layoutIfNeeded(), setNeedsLayout() и layoutSubviews()?

layoutIfNeeded() - метод UIView, который инициирует полный перерасчет всего UI данной UIView и всех ее subViews, если текущий UI не актуален.
setNeedsLayout() - метод UIView, указывающий UIView, что текущее отображение UI данной UIview более не актуально и должно быть пересчитано.
layoutSubviews() - метод UIView, который инициирует полный перерасчет всего UI данной UIView и всех ее subViews. В отличии от layoutIfNeeded() не проверяет текущий UI на актуальность.


27. 🟡 Что такое UIResponder? Для чего он используется? Как происходит обработка событий UIResponder?

UIResponder - тип UIKit, который отвечает за обработку всех событий пользователя. Обработка событий UIResponder происходит по цепочке ответственности. Если данный UIResponder не может обработать то или иное событие, он пробрасывает событие далее своим дочерним UIResponder. Так событие опускается до тех пор, пока не будет обработано, или, пока не закончатся UIResponder-ы.


28. 🟡 Какие UI операции можно делать не на главном потоке?

Вопрос с подвохом. Если мы говорим об операциях, которые непосредственно взаимодействуют с UI нашего приложения, который находится на экране или во вью стеке, но все подобные операции обязаны выполняться только на главном потоке. Без исключний.
Если же мы говорим о UI операциях в целом, то все операции, которые не взаимодействуют с UI во вью стеке, можно выполнять где-угодно. Другими словами, получить данные картинки и создать UIImage можно на любом потоке, но присвоить UIImageView поле image можно только на главном потоке, если данная UIImageView находится во вью стеке.


29. 🟡 Что такое UIScene? Для чего их используют?

UIScene - тип UIKit, который появился в iOS 13. UIScene отвечает за хранение UIWindow приложения. UIScene имеет смысл только на iPadOS. Данный тип позволяет осуществлять запуск более одного экземпляра приложения одновременно. Так же, благодаря UIScene на iPadOS работает мультиоконный режим, при котором несколько приложений могут находится на экране одновременно.


30. 🟡 Сколько LaunchScreen сторибордов может быть в приложении? Если более одного, то как система определяет, какой использовать сейчас?

По умолчанию, при создании любого iOS приложения, Xcode сгенерирует один LaunchScreen.storyboard, который будет использовать при любом запуске приложения. Однако, вы можете создать и другие подобные storyboard-ы. При реализации URL схем и Push notification, вы можете указать какой конкретно LaunchScreen.storyboard вы хотели бы использовать, если при обработке схемы или уведомления приложение будет запускаться с нуля.


31. 🔴 Что такое CADisplayLink? Для чего его используют?

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


32. 🔴 Что такое SwiftUI?

SwiftUI - новый фреймворк для UI, который не столько строит и рендерит сам UI, сколько описывает его и опирается на реальный функционал других API, в зависимости от того, на какой платформе будет исполняться код, написанный с помощью SwiftUI.
Данный фреймворк все еще довольно молод и не позволяет воссоздать абсолютно все, что можно сделать на UIKit/AppKit, однако SwiftUI позволяет внедрять UIKit/AppKit код в SwiftUI код и наоборот.


33. 🔴 Что такое View? Для чего он используется?

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


34. 🔴 Что такое ViewModifier? Для чего он используется?

ViewModifier - второй по значимости протокол в SwiftUI. С его помощью мы можем модифицировать уже созданную View, меняя текущие параметры или добавляя новые.


Хранение данных

1. 🟢 Какие виды persistency вы знаете в iOS?

Из коробки в iOS нам доступны следующие хранилища: UserDefaults, CoreData, KeyChain и запись в файл.


2. 🟢 Что такое plist файл? Для чего он использутеся? Какие типы данных можно хранить в plist файле?

plist файл в iOS - это файл, который содержит информацию в виде (ключ, значение), как и Dictionary. Используется для различных целей, как правило, содержит набор настроек или информации, специфической для вашего приложения.

Пример: Info.plist


3. 🟢 Что такое UserDefaults? Для чего они используются?

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


4. 🟡 Что такое CoreData? Когда и для чего ее используют?


5. 🟡 Какие альтернативы CoreData вы знаете и в чем их преимущества/недостатки?


6. 🟡 Назовите основные элементы CoreData.


7. 🟡 Где могут храниться данные из CoreData?


8. 🟡 Что такое NSStoreCoordinator? Для чего его используют?


9. 🟡 Что такое NSManagedObjectContext? Для чего его используют?


10. 🟡 Что такое NSFetchRequest? Для чего его используют?


11. 🟡 Что такое NSFetchResultsController? Для чего его используют?


12. 🟡 Как работать с CoreData в многопоточной среде?


13. 🟡 Как реализовать чтение/запись файла в iOS?


14. 🟡 Что такое KeyChain? Для чего он используется?


15. 🟡 Какой persistance вы выберете для разных типов данных?


Работа с сетью

1. 🟢 Что такое REST? Что такое RESTful API?

REST - архитектурный подход, который чаще всего используется при построении клиент-севрверных приложений. Сервер и клиент общаются друг с другом посредством HTTP запросов. Клиент отправляет серверу запросы и сообщения с данными, получая данные в ответ, с которыми дальше и может работать. Связь между клиентом и сервером всегда инициирует клиент.
RESTful API - это набор запросов, которые сервер может обработать и предоставить ответ на них от клиентов. Общение с сервером за пределами данного API возможно, но находится за пределами понимания REST.


2. 🟢 Какие виды HHTP запросов вы знаете? Какими пользуетесь чаще всего?

HTTP запросы могут быть следующих типов:

  • GET - метод, использующийся для получения ресурсов с сервера.
  • POST - метод, использующийся для отправки новых ресурсов на сервер.
  • PUT - метод, использующийся для отправки новых или уже имеющихся ресурсов на сервер. Если данный ресурс на сервере уже имеется, он будет заменен новым полностью.
  • PATCH - метод, использующийся для частичного обновления уже имеющегося ресурса на сервере. В отличии от POST, ресурс не перезаписывается полностью, а обновляется только теми данными, которые указаны в запросе.
  • DELETE - метод, использующийся для удаления ресурса с сервера.

Скорее всего, чаще всего вы выпользуетесь GET и POST


3. 🟢 Что такое JSON? Для чего он используется?

JSON - формат данных, который представляет собой файл, содержащий информацию в виде { "ключ": значение }. Данный формат является самым распространенным форматом сообщений между клиентом и сервером при построении клиент-серверных приложений.


4. 🟢 Как реализовать парсинг JSON в iOS? Что такое Encodable/Decodable?

На сегодняшний день парсинг JSON в iOS довольно прост и делается чуть ли не в две строки. Чтобы распарсить JSON, необходимо объявить тип, который будет хранить информацию из JSON, реализовать протокол Decodable данным типом, чтобы он мог парсить контейнер с данными, затем получить данные с сервера, и воспользоваться методом decode типа JSONDecoder.
Encodable - протокол, реализация которого позволяет любому типу закодировать себя в формат выбранного кодировщика.
Decodable - протокол, реализация которого позволяет любому типу раскодировать себя из формата выбранного декодировщика.
Codable - объеденение Encodable и Decodable.

struct Model {
    var name: String
    var lastName: String
}

// Get data from somehwere
// JOSN format is
// {
//     "name": "Mark",
//     "lastName": "White"
// }
let data: Data = ...

do {
    let model = try JSONDecoder().decode(Model.self, from: data)
    print(model) // prints Model(name: "Mark", lastName: "White") 
} catch {
    // Handle parsing error here
}

5. 🟢 Что такое URLSession? Для чего его используют?

URLSession - встроенный в iOS тип, с помощью которого приложение может совершать запросы к серверу и получать сообщения в ответ. Вы можете создать свою сессию, а можете пользоваться сессией shared, которая создается системой для каждого приложения.


6. 🟢 Что такое URL и URLRequest? В чем их отличие? Когда нужно использовать какой?

URL - встроенный тип, представляющий собой путь к ресурсу, как локальному, так и размещенному удаленно.
URLRequest - встроенный тип, представляющий собой HTTP запрос, который вы можете отправить в сеть.

URL и URLRequest отличаются тем, что URLRequest - это не просто путь к ресурсу, но сам запрос, который содержит путь к ресурсу, а так же информацию, как именно мы будем запрашивать данный ресурс и какое сообщение мы будем отправлять в сеть. URL является частью URLRequest.
Как правило вы не используете URL напрямик с URLSession, вы всегда создаете полноценный URLRequest. Однако, использование URL с URLSession возможно, в таком случае система создат URLRequest за вас. Это будет HTTP GET Request с путем к ресурсу без дополнительной информации.


7. 🟡 Какие альтернативы есть URLSession? Назовите их преимущества/недостатки


8. 🟡 Что такое WebSocket? Для чего он используется? В чем отличие от обычнх HTTP запросов?


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

1. 🟢 Что такое счетчик ссылок? Для чего он нужен?

Счетчик ссылок - это механизм всех reference type типов в Swift, благодаря которому reference type типы работают так, как они работают. Так как при присваивании одному экземпляру reference type типа другого экземпляра этого же типа, происходит не копирование реального объекта, а лишь ссылки на него, нам необходимо знать, когда на данный объект никто не будет ссылаться, чтобы мы могли его удалить. Счетчик ссылок как раз и считает ссылки на объект.
Важно уточнить, что ключевые слова weak и unowned не влияют на счетчик ссылок. Именно поэтому они всегда являются Optional и именно поэтому удается решать проблему reference cycle.


2. 🟢 Что такое reference cycle? Когда он может произойти? Что необходимо сделать, чтобы избежать таких ситуаций?

reference cycle - цикл ссылок или циклическая ссылка, ситуация, при которой один или более объектов указывают друг на друга. Данная ситуация может произойти по разным причинам, однако, самая частая из них - это неявный захват self внутри замыкание, которое удерживает self.
Чтобы избежать reference cycle, необходимо одну из ссылок сделать weak или unowned. Какую именно зависит от реализации программы и бизнес логики.

class ViewController: UIViewController {
    @IBOutlet var titleLabel: UILabel!
    
    let network = Networ()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        network.fetch { newTitle in
            self.titleLabel.text = newTitle
        }
    }
}

В примере выше образуется reference cycle. ViewController удерживает переменную network. При вызове функции fetch(completionHandler:) ее completionHandler удерживает self (ViewController).
Для решения данной проблемы в список захвата completionHandler self необходимо передать как weak

class ViewController: UIViewController {
    @IBOutlet var titleLabel: UILabel!
    
    let network = Networ()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        network.fetch { [weak self] newTitle in
            self?.titleLabel.text = newTitle
        }
    }
}

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


3. 🟡 Какая модель работы с памятью в iOS?

iOS использует ARC модель. ARC - Automated Reference Counting - система, которая построена на факте того, что вы не будете работать с объектами напрямик, а будете работать со ссылками на эти объекты. При создании объекта так же создается и первая ссылка на данный объект, которая кладется в переменную/константу, которая инициировала создание объекта. При попытки скопировать такой объект, система будет создавать новую ссылку на уже существующий объект. Таким образом, когда количество ссылок на объект станет равным нулю, объект удаляется.
Через этот механизм реализованы reference type типы Swift и весь ObjC Runtime iOS.


4. 🟡 Что такое stack и heap? Для чего используюется каждая из них?


5. 🟡 Что такое memory leak? Как вычеслить memory leak? Как его избежать?


6. 🔴 Что такое ObjC Runtime? Что такое диспетчиризация методов, dispatch table? Назовите преимущества и недостатки.


Асинхронность и многопоточность

1. 🟡 Что такое многопоточность? Что такое concurrency?


2. 🟡 Что такое асинхронное и синхронное исполнение?


3. 🟡 Что такое последовательное и параллельное исполнение? Как можно реализовать каждый из типов?


4. 🟡 Какие задачи могут потребовать асинхронного выполнения?


5. 🟡 Какие способы выполнения асинхронной работы в iOS вы знаете?


6. 🟡 Что такое GCD? Для чего он используется?


7. 🟡 Что такое Operation и OperationQueue? Для чего они используется?


8. 🟡 Что такое DispatchGroup? Для чего он используется?


9. 🟡 Что такое Qos (Quality of Service)? Где использутеся? Для чего? Какие есть уровни?


10. 🟡 Что такое race codition? Когда может произойти такая ситуация? Как предотвратить?


11. 🟡 Что такое deadlock? Когда может произойти такая ситуация? Как предотвратить?


12. 🟡 Можно ли реализовать многопоточность на одноядерном процессоре?


13. 🟡 Что такое globalActor и @MainActor? Для чего используются?


14. 🔴 Что такое NSTread? Для чего он используется?


15. 🔴 Чем отличаются GCD, OperationQueue, DispatchGroup и NSThread?


16. 🔴 Что такое mutex? Для чего он используется?


17. 🔴 Что такое semaphore? Для чего он использутеся? Чем отличается от mutex?


18. 🔴 Как обеспечить безопасный доступ к переменной в многопоточной среде?


Архитектурные подходы

1. 🟢 Что такое клиент-серверная архитектура? В роли кого как правило выступает iOS приложение?

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


2. 🟢 Что такое MVC? Из чего он состоит? Что не так с Apple MVC?

MVC - Model-View-Controller. Самая базовая архитектура приложения, которую в свое время пытались реализовать Apple, но получилось лишь отчасти.
MVC состоит из:

  • Model - данные приложения, с которыми мы работаем.
  • View - UI приложения и все, что взаимодействует с пользователем.
  • Controller - медиатор между Model и View. Реагирует на изменения View и действия пользователя, обновляя Model; обновляет View, при изменении Model.

Apple MVC грустно известен как Massive ViewController. Изначального разделения между View и Controller не существует, из-за чего ViewController делает слишком много работы и имеет тенденцию становится очень большим и трудно изменяемым. Использования Apple MVC необходимо избегать при любой возможности.


3. 🟢 Что такое MVP? Из чего он состоит?

MVP - Model-View-Presenter. По сути, это тот же MVC, но с менее путающими терминами. Так как в iOS есть ViewController, но нет ViewPresenter, намного легче представить ViewController как View, при этом полностью не запутавшись в том, кто и чем должен заниматься.
MVP состоит из:

  • Model - данные приложения, с которыми мы работаем.
  • View - UI приложения и все, что взаимодействует с пользователем.
  • Presenter - медиатор между Model и View. Реагирует на изменения View и действия пользователя, обновляя Model; обновлет View, при изменении Model.

4. 🟡 Что такое MVVM? Из чего он состоит?

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

  • Model - данные приложения, с которыми мы работаем.
  • View - UI приложения и все, что взаимодействует с пользователем.
  • ViewModel - медиатор между Model и View. ViewModel подписывается на обновления Model, обновляя View, а так же подписывается на события View, обновляя Model.

MVVM можно реализовать и без реактивного программирования, но тогда это будет больше похоже на MVP


5. 🟡 Что такое VIPER? Из чего он состоит?

VIPER - View-Interactor-Presenter-Entity-Router. VIPER - это попытка создать кольцо всевластия. Единая архитектура, которая хороша во всем и решает все проблемы человечества. К сожалению, мы живем в неидеальном мире и ничего подобного быть не может, так что и у VIPER есть свои недостатки.
VIPER состоит из:

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

Как правило один VIPER модуль - это один экран приложения, но данное правило не является строгим.

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


6. 🔴 Что не так с VIPER? Какие минусы разных архитектурных подходов вы находили и как их решали?

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


7. 🔴 В чем разница между MVC, MVP, MVVM и VIPER? Какие еще архитектурные подходы вы ззнаете?

Из описания ответов на вопросы про MVC, MVP и MVVM можно сделать вывод, что это практически одно и тоже, с разницей на имена и стиль программирования. VIPER же отличается от них тем, что пытается решить те вопросы, которые MVC/MVP/MVVM просто не покрывает.

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


8. 🔴 Какие критерии выбора архитектуры при разработки приложения/фичи?

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

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


Debug

1. 🟢 Какие инструменты отладки в Xcode вы знаете? Какими пользовались?

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


2. 🟢 Для чего используется логгирование? Какие уровни логгирования бывают?

Логгирование - механизм фиксации и отображения текущего состояния выполнения приложения.
Чаще всего встречаюся следующие уровни логгирования:

  • debug - с таким уровнем записываются сообщения, имеющие смысл только при отладке программы.
  • info - с таким уровнем записываются сообщения, несущие вспомогательную информацию.
  • default/verbose - с таким уровнем записываются сообщения, несущие основную или важную информацию.
  • error/warning - с таким уровнем записываются сообщения, несущие информацию об ошибке или предупреждении, не завершающие работу программы.
  • fault/error - с таким уровнем записываются сообщения, несущие информацию об ошибке, в результате которой дальнейшее выполнение программы невозможно.

3. 🟢 Что такое llvm?

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

llvm знать весь, конечно же, не обязательно, но хотя бы основыне команды знать желательно, ибо все мы знаем как хорошо работает Xcode.


4. 🟢 Что такое View Hierarchy Debugger?

View Hierarchy Debugger - это инструмент, встроенный в Xcode, который позволяет захватить текущий UI с экрана и разложить его на слои, предоставляя возможность визуально оценить верность отображения UI относительно дизайна, а так же отследить ошибки или не соответствия.


5. 🟡 Что такое thread sanitiser?


6. 🟡 Что такое memory graph?


7. 🟡 Какие инструменты вы используете для дебага UI приложения?

Вы можете использовать все, что угодно, но про View Hierarchy Debugger вы точно должны упомянуть. Все остальное - плюс в копилочку скиллов и карму.


Git

1. 🟢 Что такое система контроля версий? Какие вам известны и какие вы использовали?

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


2. 🟢 Что такое push/pull/fetch/merge?

Рассмотрим данные команды на примере git:

  • push - команда, которая отправляет ваши текущие закоммиченные изменения в репозиторию на удаленный репозиторий (remote)
  • pull - команда, которая проверяет наличие новых коммитов на удаленном репозитории и скачивает их, если они присутствуют
  • fetch - команда, которая проверяет наличие новых коммитов на удаленном репозитории, но не скачивает их
  • merge - команда, которая сливает две вертки репозитория в одну. При выполнении данной команды могут возникнуть конфликты.

3. 🟢 Что такое Git Flow? Опишите основные принципы данного подхода

Git Flow - это порядок взаимодействия разработчиков с репозиторием, призванный минимизировать риски при выкатывании новых версий программы.
Как правило Git Flow описывает систему релиза вашей программы через релиз ветки, по следующей схеме:
При подготовке нового релиза программы создается релиз ветка.
Для каждой новой фичи данного релиза создается ветка фичи от ветки релиза.
При выполнении фичи, ветка фичи мержится в ветку релиза, тем самым выполняя требования данного релиза.
При достижении всех требований релиза, тестирования и прочих проверок, ветка релиза мержится в master/main ветку репозитория.


4. 🟢 Что такое Pull/Merge Request? Зачем их используют? Что такое Code Review?

Pull/Meger Request - это механизм web интерфейска удаленного репозитория, при котором мы не мержем изменения из одной ветки в другую, а создаем запрос на такой мерж. Web интерфейс позволяет другим участникам репозитория увидеть изменения, который собираются быть смержены перед тем, как они будут смержены.
Процесс оценивания данных изменений называется Code Review. В ходе Code Review могут быть предложены правки, предложения, выставлены замечания к автору запроса.


5. 🟡 Для чего нужен .gitignore файл?

.gitignore файл - это специальный файл, который использует git, в котором описываются все файлы и папки, которые не должны попадать в репозиторий. Шаблон такого файла для любого языка/платформы с легкостью находятся в интернете, а Xcode, при создании репозитория через него, с недавних пор и сам научился создавать этот файл за вас со всем, что там должно быть для любого iOS приложения.
Однако, это не значит, что вы не можете/вам не понадобится добавлять туда что-то свое.


6. 🟡 Чем отличается merge от rebase? Как решаются конфликты при использовании git?

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

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


7. 🟡 Что такое Git hook? Для чего они используются? Использовали ли вы на практике?


8. 🔴 Как перенести репозиторий с одного сервиса на другой? Как при этом сохранить историю коммитов?


Тестирование

1. 🟢 Что такое unit тесты? Для чего они нужны?

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

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


2. 🟡 Какие преимущества и недостатки предоставляют unit тесты?


3. 🟡 Назовите три основных блока unit теста


4. 🟡 Чем отличается Stub от Mock?

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


5. 🟡 Что такое TDD? Какие у него преимущества/недостатки перед другими подходами?


6. 🔴 Что такое интеграционные тесты? Для чего они используются?


7. 🔴 Что такое UI тесты? Для чего они нужны?


8. 🔴 Что такое Snapshot тесты? Для чего они нужны?


9. 🔴 Если в приложении нет ни одного теста, как вы будете расставлять приоритеты для написания unit тестов? Будете ли вы писать тесты в принципе?


Зависимости и сторонние библиотеки

1. 🟢 Что такое dependency manager? Для чего они используются? Какие приходилось использовать?

Dependency manager - это программа или утилита, которая упралвляет зависимостями, которые использует ваш проект.
На сегодняшний день существуют три самых популярных менеджера зависимостей:

  • CocoaPods
  • Carthage
  • Swift Package Manager (SPM)

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


2. 🟢 Что такое CocoaPods? Как их использовать?

CocoaPods - это dependency manager, утилита, которая помогает работать с внешними зависимостями и добавлять их в ваш проект.
Чтобы использовать CocoaPods, необходимо сначала скачать их любым удобным для вас способом и затем использовать их интерфейс через терминал.
Сами зависимости и настройки проекта описываются в файле Podfile.

Если вы используете мак с Apple Silicon, то любой удобный для вас способ только один - homebrew. Установив CocoaPods другим способом у вас могут быть проблемы с их запуском без rosetta.


3. 🟢 Что такое Carthage? Как его использовать?

Carthage - это dependency manager, утилита, которая помогает работать с внешними зависимостями и добавлять их в ваш проект.
Чтобы использовать Carthage, необходимо сначала скачать их любым удобным для вас способом и затем использовать их интерфейс через терминал.
Сами зависимости и настройки проекта описываются в файле Cartfile.


4. 🟢 Что такое Swift Package Manager (SPM)? Как его использовать?

Swift Package Manager - это dependency manager, утилита, которая помогает работать с внешними зависимостями и добавлять их в ваш проект.
На сегодняшний день, чтобы использовать Swift Package Manager, вас не нужно ничего устанавливать или настраивать дополнительно. Все зависимости SPM описываются прямо в файле проекта, настраиваются через интерфейс Xcode, там же скачиваются и добавляются в зависимости проекта.


5. 🟡 В чем отличие CocoaPods, Carthage и SPM? Какие у них преимущества/недостатки?


CI/CD и дистрибуция

1. 🟡 Что такое CI (continuous integration)?


2. 🟡 Что такое CD (continuous delivery)?


3. 🟡 Что такое AppStore Connect? Для чего его используют?


4. 🟡 Что такое Testflight? Для чего его используют?


5. 🟡 Как распространить билд в AppStore Connect и Testflight?


6. 🟡 Что такое bundle identifier?


7. 🟡 Что такое provisioning profile?


8. 🟡 Что такое development certificate?


9. 🟡 Что такое entitlements файл? Что в нем находится?


About

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

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published