50 вопросов и ответов для собеседования по Swift в 2022 году

Viktor Grushevskii
iOS Dev — Mobile Development
14 min readApr 6, 2022

Эта статья является переводом статьи от Artturi Jalli

50 вопросов для собеседования

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

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

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

Примечание переводчика (не настаиваю, но!):

Ещё больше интересных статей (и интересных фактов) можно прочесть в канале об iOS-разработке.

1. Что вызывает ошибку в этом фрагменте кода? Как вы могли бы ее исправить?

let n1: Int = 1  let n2: Float = 2.0  let n3: Double = 3.34  var result = n1 + n2 + n3

Ответ

В Swift неявное приведение типов между двумя типами данных невозможно.

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

Чтобы исправить это, необходимо преобразовать каждое значение к одному типу данных. Например:

var result = Double(n1) + Double(n2) + n3

2. Каким будет значение переменной len? Почему?

var arr1 = [1, 2, 3]
var arr2 = arr1
arr2.append(4)var len = arr1.count

Ответ

Значение len равно 3, т.е. количество элементов в arr1 равно 3. Это происходит потому, что присвоение arr1 к arr2 фактически означает, что копия arr1 присваивается к arr2, поэтому arr1 не затрагивается.

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

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

3. В чем здесь проблема и как вы можете ее решить?

Рассмотрим этот фрагмент кода, который пытается получить цвет темы из локального хранилища устройства iOS:

var color = UserDefaults.standard.string(forKey: "themeColor")!  
print(color)

Смогли ли вы заметить ошибку и исправить ее?

Ответ

Первая строка извлекает цвет темы из user defaults. Этот метод, однако, возвращает optional (поскольку themeColor может быть не определен). Если ключ не найден, возвращается nil, что приводит к крашу:

fatal error: unexpectedly found nil while unwrapping an Optional value

Это происходит потому, что в первой строке используется ! для force unwrap optional, которое теперь nil. Force unwrapping должно использоваться только тогда, когда вы на 100% уверены, что значение не nil.

Чтобы исправить это, вы можете использовать optional binding для проверки, найдено ли значение для ключа:

if let color = defaults.stringForKey("themeColor") {
print(color)
}

4. Какие потенциальные улучшения вы здесь видите?

Вы просматриваете пулл-реквест и столкнулись с этим методом:

func turnTo(direction: String){
if direction == "North" {
northAction()
} else if direction == "East" {
eastAction()
} else if direction == "South" {
southAction()
} else if direction == "West" {
westAction()
} else {
print("No valid direction specified")
}
}

Какие улучшения вы можете предложить автору кода?

Ответ

Даже если этот код может работать, есть два момента, которые следует учитывать.

  • Использование жестко закодированных строк типа (например, "West") - плохая идея. Что если кто-то неправильно напишет это слово? Чтобы решить эту проблему, следует отказаться от жестко закодированных строк и вместо них использовать перечисление.
  • Кроме того, как насчет использования оператора switch вместо длинного оператора if-else?

Благодаря этим улучшениям код станет более безопасным и читабельным:

enum Direction {
case North
case East
case South
case West
}
func turnTo(direction: Direction){
switch direction {
case .North: northAction()
case .East: eastAction()
case .South: southAction()
case .West: westAction()
default:
print("No valid direction specified")
}
}

5. Что такое перечисления (enumerations) в Swift?

Перечисление (enumeration)- это группа связанных значений.

Перечисления позволяют писать безопасный для типов код.

enum Direction {
case North
case East
case South
case West
}

Теперь в своем коде вы можете вызвать, например, Direction.North, вместо того чтобы использовать мистическую строку "North" (которая легко может быть неправильно написана и вызвать раздражающие ошибки).

Больше информации о перечислениях можно прочесть в этой статье.

6. Что такое Optional в Swift? Как его создать?

Optional - это тип, который может хранить либо значение, либо nil. Вы можете создать optional, добавив вопросительный знак ? после любого типа:

var number: Int? = 10

7. Что такое typealias в Swift? Как его можно создать?

Typealias, как следует из названия, является псевдонимом для существующего типа данных.

Вы можете создать его следующим образом:

typealias Weight = Float

Теперь вы можете использовать Weight вместо Float:

let mass1: Weight = 150.0
let mass2: Weight = 220.0
let total: Weight = mass1 + mass2

8. Назовите некоторые преимущества использования Swift.

Вот лишь некоторые из них:

  • Swift — язык с типобезопасностью
  • В нем есть поддержка замыканий
  • Поддерживаются опциональные типы
  • Встроенная обработка ошибок
  • Поддерживается сопоставление шаблонов

9. Назовите 5 утверждений передачи управления (Control Transfer Statements) и опишите, как их использовать.

Вот они сверху-вниз:

  • Break
  • Continue
  • Fallthrough
  • Throw
  • Return

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

Например, вы можете использовать оператор передачи управления break для завершения выполнения цикла for, когда продолжение цикла считается ненужным:

for choice in choices:
if isCorrect(choice):
print("Correct choice found!")
break

10. Предложите небольшую доработку для следующего кода.

if age < 18 {
driveCar()
} else {
doNotDrive()
}

Этот код хорошо работает — но можете ли вы предложить небольшое улучшение рефакторинга, чтобы сделать его еще лучше?

Ответ

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

age < 18 ? driveCar() : doNotDrive()

11. Как можно улучшить читаемость кода?

В нашей компании 20 разработчиков и 20 уникальных стилей кодирования. Как мы можем внедрить некоторые общие стили кодирования/лучшие практики?

Ответ

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

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

12. Зачем нужен completion handler в Swift?

Completion handlers (обработчики завершения) — это замыкания в действии. Предположим, вы выполняете трудоемкую задачу, например сетевой запрос, и хотите что-то сделать сразу после завершения запроса.

Но вы определенно не хотите тратить ресурсы впустую, проверяя несколько раз, продолжается ли процесс или нет. Здесь используются обработчики завершения. Обработчик завершения — это замыкание, которое «вернется» сразу после завершения трудоемкого процесса. Узнайте больше о замыканиях и о том, как передать функцию в качестве параметра.

13. Как тестировать приложение без физического устройства?

Если у вас нет устройства iOS, вы можете использовать симуляторы устройств iOS от Apple для тестирования своих приложений на Mac.

14. Что делает init() в Swift?

Метод init() используется для инициализации экземпляра.

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

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

15. Let и Var в Swift?

В Swift вы можете использовать let для создания константы (значения, которое нельзя изменить) и var для создания переменной (значения, которое может быть изменено позже).

Некоторые дополнительные подробности вы можете найти в этой статье.

16. Что такое plist?

Plist, или список свойств, — это словарь пар ключ-значение, которые используются для хранения данных в файловой системе вашего проекта. Например, info.plist.

17. Для чего нужны Protocols в Swift? Приведите пример.

Протокол действует как чертеж для свойств, методов и т.д. Он описывает, как должен вести себя тип, соответствующий ему.

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

Вот пример протокола, описывающего животное:

protocol Animal {
var name: String { get set }
var color: String { get set }
func makeSound()
}

Давайте создадим классы Cat и Dog, которые оба соответствуют протоколу Animal. Таким образом, требуется, чтобы они оба реализовывали поведение, описанное в протоколе Animal - то есть переменные name, color и метод makeSound():

class Cat: Animal {
var name = "Luna"
var color = "gray"
func makeSound() {
print("Meow!")
}
}
class Dog: Animal {
var name = "Charlie"
var color = "black"
func makeSound() {
print("Woof!")
}
}

18. Для чего нужен оператор вида «??» ?

Оператор двойного вопросительного знака ?? известен как оператор объединения (слияния) nil. Он возвращает значение в левой части, если оно не равно nil. Если левая часть равна nil, то возвращается значение в правой части.

Его можно использовать как сокращение для проверки того, является ли опциональное значение nil. Например, вы можете заменить это:

var name: String?if name != nil {
print(name)
} else {
print("N/A")
}

На это:

print(name ?? "N/A")

19. Для чего используется Guard?

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

Например, оператор guard используется для выхода из функции:

func myFun() {
guard false else {
print("This block is run")
return
}
print("This is never run")
}
myFun()

Вывод:

This block is run

Узнайте больше о ключевом слове guard, прочитав эту статью.

20. Каковы три основных типа коллекций в Swift?

  • Массивы: Массив — это упорядоченная коллекция значений.
  • Наборы: Набор — это неупорядоченная коллекция значений.
  • Словари: Словарь — это неупорядоченная коллекция пар ключ-значение.

21. Для чего используется Defer в Swift?

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

func printStuff() {
defer {
print("I some printed numbers and now I exit the scope")
}
print("4")
}
printStuff()
// Output:
// 4
// I printed numbers and now I exit the scope

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

22. Можно ли поменять местами две переменные без третьей переменной-помощника?

Это классический вопрос на собеседовании Swift.

Да, это возможно.

С помощью tuple destructuring вы можете решить проблему следующим образом:

var a = 1
var b = 2
(a, b) = (b, a)

23. В чем разница между структурами и классами?

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

24. Что такое необязательная цепочка (Optional Chaining) ?

Необязательная цепочка означает, что вы можете безопасно вызвать свойство чего-то, что может быть nil.

Optional chaining работает, как следует из названия, путем объединения одного или нескольких необязательных значений с помощью оператора со знаком вопроса ?, например, так:

something?.someValue?.someMethod()

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

25. Что такое опциональное связывание (optional binding) ?

Опциональное связывание проверяет, содержит ли опция значение или нет. Если опция имеет значение, опциональное связывание делает это значение временно доступным:

Например, следующий код проверяет, является ли имя nil или нет. Если нет, то создается временная константа realName и ей присваивается значение name.

var name: String? = "Charles"if let realName = name {
print (realName)
}

Вывод:

Charles

26. Объясните архитектуру MVC

MVC (Model-View-Controller) — это программная архитектура для разработки приложений для iOS. Это одна из фундаментальных концепций разработки приложений для iOS.

Множество iOS-фреймворков используют MVC.

Идея MVC заключается в передаче данных из одного места в другое. Это означает, что любой объект попадает в одну из этих трех категорий:

  • Модель: Модель представляет данные приложения. Она хранит информацию, например, товары в магазине. Модель управляет состоянием приложения.
  • Вид: Вид отвечает за отображение и взаимодействие с пользовательским интерфейсом. Например, представление отображает таблицу товаров для пользователя вашего приложения.
  • Контроллер: Контроллер — это то, что склеивает модель и представление. Он отвечает за управление логикой, которая происходит между ними.

27. Что такое параметр In-Out в Swift?

Параметр inout позволяет изменять значение параметра внутри функции.

Чтобы сделать параметр in-out, используйте ключевое слово inout перед типом параметра.

Чтобы передать переменную в качестве in-out, используйте & перед ее именем.

Например:

func change(_ number: inout Int){
number = 2
}
var number = 1
change(&number)
print(number)
// Output:
// 2

Здесь можно прочитать подробнее о параметрах inout.

28. Что такое tuple? Продемонстрируйте, как работать с ними

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

Значения tuple не обязательно должны быть одного типа.

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

Например:

var coordinates3D = (1.0, 2.0, 5.0)

Чтобы получить доступ к значению внутри tuple, используйте точечную нотацию и индекс:

let xPos = coordinates3D.0

Кортежи также могут быть созданы таким образом, чтобы каждое значение имело имя:

var coordinates3D = (x: 1.0, y: 2.0, z: 5.0)

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

let xPos = coordinates3D.x

29. Что такое Swift Messages?

Swift Messages — это библиотека, используемая для отображения сообщений в виде строки состояния в верхней или нижней части экрана устройства iOS.

30. Можно ли задать параметру функции значение по умолчанию?

Можно задать значение по умолчанию для параметра:

func eat(food: String = "spaghetti") {
print("Yum! I ate some good \(food).")
}

31. Что такое дженерики? Приведите пример использования дженериков

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

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

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

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

struct Vec3D<T> {
let x, y, z: T
init(x: T, y: T, z: T) {
self.x = x
self.y = y
self.z = z
}
}
let intVector = Vec3D(x: 1, y: 2, z: 5)
let floatVector = Vec3D(x: 1.0, y: 2.0, z: 5.0)

32. Чем будет свойство pounds в следующем примере?

class Weight {
var kilograms: Float = 0.0
var pounds: Float {
get {
return (kilograms * 2.205)
}
set(newWeight) {
kilograms = newWeight / 2.205
}
}
}
let weight = Weight()
weight.kilograms = 100
print(weight.pounds) // prints '220.5'
weight.pounds = 315
print(weight.kilograms) // prints '142.85715'

Ответ

Свойство pounds также известно как вычисляемое свойство.

В Swift вычисляемые свойства не хранятся в объекте. Вычисляемое свойство означает, что его значение вычисляется “по требованию” только при попытке доступа к нему. Вы можете создавать вычисляемые свойства с помощью методов get и (необязательно) set.

  • Метод get выполняет вычисление "по требованию", когда вызывается weight.pounds.
  • Метод set обновляет килограммы, когда обновляются фунты. (Обратите внимание, что метод set является необязательным, и вам не нужен такой метод для создания вычисляемого свойства).

33. В чем разница между операторами == и ===?

  • == - оператор равенства.
  • === - оператор тождества.

Оператор равенства == используется для проверки равенства двух типов Equatable:

"Hello" == "Hello"
10.0 == 5.0 + 5.0

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

class Fruit {
var name = "Banana"
}
let fruit1 = Fruit()
let fruit2 = fruit1 // fruit2 now points to same address as fruit
fruit1 === fruit2 // true

Узнайте больше о разнице между == и === здесь.

34. Что такое расширения?

В Swift вы можете использовать расширения для добавления функциональности к существующему типу.

В Swift вы можете создать расширение с помощью ключевого слова extension:

extension SomeExistingType {
// add new functionality here
}

35. Что такое вложенная функция?

Вложенная функция — это комбинация функции внутри функции:

func outer() {
func inner() {
// Do something here
}
}

36. Как создать базовый класс в Swift?

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

37. Что такое Force Unwrapping (принудительное разворачивание) ? Когда его следует использовать?

Force Unwrapping (принудительное разворачивание) пытается преобразовать опциональное значение в значение независимо от того, содержит оно значение или нет.

Принудительное разворачивание небезопасно, потому что если опция nil и вы попытаетесь ее развернуть, это вызовет ошибку, которая приведет к краху приложения. Таким образом, ее следует избегать, если вы не уверены на 100%, что опция не является nil.

38. Перечислите преимущества функций высшего порядка.

  • Они обеспечивают гибкость.
  • Они полезны в асинхронных вызовах, где обычные функции не могут быть использованы.
  • Иногда они улучшают качество кода и делают его короче и лаконичнее.

39. Fileprivate vs Private?

  • Свойство fileprivate может быть прочитано в любом месте того же файла Swift, но не за его пределами.
  • Свойство private можно прочитать только внутри типа, в котором оно было объявлено (а также в расширениях этого типа в том же файле).

Подробнее о private и fileprivate здесь.

40. Какие функции есть в Swift?

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

Обычно функции принимают некоторые значения, с которыми они могут работать.

41. Nil vs None в Swift?

Между ними нет разницы:

nil == .none // returns true

Пожалуй, единственное “отличие” заключается в том, что использование nil встречается чаще, чем использование none.

42. Что такое dictionary (словарь) в Swift?

Словарь — это основной тип коллекции в Swift. Он может использоваться для хранения пар ключ-значение.

Вы можете легко получить доступ к значению, зная ключ:

let dict = ["a": 1, "b": 2]
let valueOfA = dict["a"]

43. Что делает ключевое слово Mutating?

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

Например:

struct Fruit {
var type: String
mutating func convertToBanana() {
self.type = "Banana"
}
}
var fruit = Fruit(type: "Apple")
fruit.convertToBanana()
print(fruit.type) // prints "Banana"

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

44. Можете ли вы устранить проблему в этом коде?

Приведенный ниже код выдает ошибку компилятора. Что не так? Как вы можете это исправить?

struct Apple {}
func pick(apple: Apple?) {
guard let apple = apple else {
print("No apple found!")
}
print(apple)
}

Ответ

Блок else оператора guard требует выходной путь.

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

struct Apple {}
func pick(apple: Apple?) {
guard let apple = apple else {
print("No apple found!")
return
}
print(apple)
}

45. Что такое Deinitializer (деинициализатор) ? Как его создать?

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

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

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

Вот пример деинициализатора, который устанавливает number обратно в 0 при деаллокации экземпляра Example.

var number = 15class Example {
init() {
number *= 10
}

deinit {
number = 0
}
}

46. В чем разница между функциями и методами?

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

47. Как запретить наследование класса?

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

final class Animal {
let name = "I'm a furry animal"
}

Подробнее о преимуществах final можно прочитать здесь.

48. Что такое Lazy Variables (ленивые переменные)? Когда их следует использовать?

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

Например:

lazy var tallest: Person? = {
return people.max(by: { $0.height < $1.height })
}()

Чтобы узнать больше о lazy, ознакомьтесь с этой статьей.

49. Что такое (autoclosure) автозамыкание в Swift? Как и когда его следует использовать?

Автозамыкание оборачивает аргумент функции в замыкание.

Когда вызывается autoclosure, оно возвращает значение выражения, завернутого внутрь.

Автозамыкание — это не что иное, как синтаксическое удобство для написания более чистого кода.

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

Это происходит потому, что autoclosure позволяет не использовать фигурные скобки {}.

Это может сделать код более читабельным.

Однако помните, что Apple говорит об использовании автозамыканий :

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

Вот пример того, как autoclosure упрощает код. В первом фрагменте используется обычное замыкание, а во втором - autoclosure. Посмотрите, как вызов функции I_will стал более читабельным во втором фрагменте:

func I_will(_ perform_action: () -> Void) {
perform_action()
}
I_will({
print("Hello, world!")
})
func I_will(_ perform_action: @autoclosure () -> Void) {
perform_action()
}
I_will(print("Hello, world"))

Как видите, вызов функции I_will больше не требует использования фигурных скобок.

50. Чего не хватает в этом фрагменте кода?

enum Example {
case something(Int, Example)
}

Ответ

В Swift можно создавать рекурсивные перечисления, подобные приведенным выше. Ниже приведен абстрактный пример — однако использование рекурсивных перечислений по умолчанию отключено, вам необходимо включить его с помощью ключевого слова indirect:

enum Example {
indirect case something(Int, Example)
}

Заключение

Это большое количество вопросов для собеседования по Swift.

Спасибо, что прочитали. Надеюсь, вы найдете их полезными, и они помогут вам найти работу вашей мечты!

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

#iOS #Dev #MobileDevelopment #вопросы #собеседование

--

--

Viktor Grushevskii
iOS Dev — Mobile Development

iOS Developer. I create apps, design and sometimes travel. You could find some of my thoughts here: https://t.me/iOS_Career