50 вопросов и ответов для собеседования по Swift в 2022 году
Эта статья является переводом статьи от Artturi Jalli
Перед вами список из 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 = arr1arr2.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.0let 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 fruitfruit1 === 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 #вопросы #собеседование