17525

Собеседование iOS-разработчика: устные вопросы по языку Swift

Перейдем к более открытому варианту тестирования – собеседование iOS-разработчика с устными вопросами по Swift. Вместе с ними также приведены ответы.

Собеседование iOS

Предыдущая статья: Язык Swift: вопросы и ответы на собеседовании

Собеседование iOS: устные вопросы

Начальный уровень

Вопрос №1 – Swift 1.0 и далее

Что такое опционал и какие проблемы он призван решать?

[spoiler title='Решение внутри' style='default' collapse_link='true']

Опционал считается мощным инструментом языка Свифт. Если после объявления типа поставить вопросительный знак, это будет указывать на то, что значение либо есть, либо его нет, а String и String? – это два разных типа. Например, в Objective-C отсутствие значения доступно только в ссылочных типах, и используется специальное значение nil.

Swift с его опционалами расширяет концепцию отсутствия значения как для ссылочных, так и для других типов. Данная переменная может содержать или какое-то значение, или nil. Чтобы узнать, что «внутри», достаточно развернуть опционал.

[/spoiler]

Вопрос №2 – Swift 1.0 и далее

Когда вы должны использовать структуру, а когда – класс?

[spoiler title='Решение внутри' style='default' collapse_link='true']

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

В Swift классы и структуры отличаются рядом особенностей. Выведем обобщенную разницу следующим образом:

  1. Классы поддерживают наследование, структуры – нет.
  2. Классы являются ссылочными типами, структуры – это типы значений.

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

Подробную информацию можно найти здесь.

[/spoiler]

Вопрос №3 – Swift 1.0 и далее

Что такое дженерики и какие проблемы они решают?

[spoiler title='Решение внутри' style='default' collapse_link='true']

Дженерики используются, чтобы алгоритмы безопасно работали с типами. В Swift дженерики могут юзаться как в функциях, так и в типах данных: например, в классах, структурах, etc.

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

Например, в следующем коде вторая функция является «клоном» первой: она просто принимает строки вместо целых чисел:

func areIntEqual(x: Int, _ y: Int) -> Bool {
  return x == y
}

func areStringsEqual(x: String, _ y: String) -> Bool {
  return x == y
}

areStringsEqual("ray", "ray") // true
areIntEqual(1, 1) // true

Знающий Objective-C может подумать о решении с помощью NSObject:

import Foundation

func areTheyEqual(x: NSObject, _ y: NSObject) -> Bool {
  return x == y
}

areTheyEqual("ray", "ray") // true
areTheyEqual(1, 1) // true

Этот код работает, но есть одно «но»: он позволяет сравнивать строку с целым числом. Например:

areTheyEqual(1, "ray")

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

func areTheyEqual<T: Equatable>(x: T, _ y: T) -> Bool {
  return x == y
}

areTheyEqual("ray", "ray")
areTheyEqual(1, 1)

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

Собеседование iOS

[/spoiler]

Вопрос №4 – Swift 1.0 и далее

Объясните суть неявно развернутых (извлеченных) опционалов.

[spoiler title='Решение внутри' style='default' collapse_link='true']

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

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

[/spoiler]

Вопрос №5 – Swift 1.0 и далее

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

Подсказка: Есть по крайней мере семь способов.

[spoiler title='Решение внутри' style='default' collapse_link='true']

  • оператор принудительного извлечения ! – небезопасный;
  • неявное извлечение – во многих случаях небезопасно;
  • опциональное связывание – безопасное;
  • опциональная цепочка  – безопасна;
  • оператор объединения со значением nil – безопасный;
  • Swift 2.0 оператор guard – безопасный;
  • Swift 2.0 паттерн опционала – безопасный.

[/spoiler]

Swift

Средний уровень

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

Вопрос №1 – Swift 1.0 и далее

Swift – это язык ООП или функциональный язык программирования?

[spoiler title='Решение внутри' style='default' collapse_link='true']

Swift – гибридный язык, который поддерживает оба типа. Он реализует три основных принципа ООП:

  • Инкапсуляция
  • Наследование
  • Полиморфизм

Что же касается Swift как функционального языка, то существуют разные, но эквивалентные способы его определения.

Один из наиболее распространенных в Википедии: «... парадигма программирования, которая рассматривает вычисления как оценку математических функций, избегает изменяющихся состояний и изменяемых данных».

Нельзя утверждать, что Swift – полноценный функциональный язык, но основы присутствуют.

[/spoiler]

Вопрос №2 – Swift 1.0 и далее

Что из этого включено в Swift?

  • Generic-классы
  • Generic-структуры
  • Generic-протоколы

[spoiler title='Решение внутри' style='default' collapse_link='true']

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

3 частично реализуется через типы. Это не общий тип как таковой. Его часто называют ассоциированным типом, и он определяется, когда протокол принимается типом.

[/spoiler]

Вопрос №3 – Swift 1.0 и далее

Иногда собеседование iOS включает каверзные вопросы вроде этого.

В Objective-C константу можно объявить следующим образом:

const int number = 0;

Это вариант для Swift:

let number = 0

Есть ли разница между ними? Если да, можете ли вы объяснить, как они отличаются?

[spoiler title='Решение внутри' style='default' collapse_link='true']

Константа – это переменная, инициализированная значением времени компиляции или выражением, разрешенным во время компиляции.

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

[/spoiler]

Вопрос №4 – Swift 1.0 и далее

Чтобы объявить статическое свойство или функцию, используйте модификатор static. На примере структуры:

struct Sun {
  static func illuminate() {}
}

Для классов можно использовать либо static, либо модификатор class. Они достигают одной и той же цели, но на самом деле разные. Можете ли вы объяснить, чем они отличаются?

[spoiler title='Решение внутри' style='default' collapse_link='true']

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

При применении к классам static становится псевдонимом для class final. Например, в этом коде компилятор будет жаловаться, когда вы попытаетесь переопределить illuminate():

class Star {
  class func spin() {}
  static func illuminate() {}
}

class Sun : Star {
  override class func spin() {
    super.spin()
  }
  override static func illuminate() { // error
    super.illuminate()
  }
}

[/spoiler]

Вопрос №5 – Swift 1.0 и далее

Можете ли вы добавить сохраненное свойство с помощью расширения? Объясните.

[spoiler title='Решение внутри' style='default' collapse_link='true']

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

[/spoiler]

Продвинутый уровень

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

Вопрос №1 – Swift 1.2

Объясните проблему в Swift 1.2, связанную с объявлением перечисления с дженерик-типами. Возьмем, например, перечисление Either с двумя дженерик-типами T и V. При этом T используется как связанный тип значения для Left, а V – для Right:

enum Either<T, V> {
  case Left(T)
  case Right(V)
}

[spoiler title='Решение внутри' style='default' collapse_link='true']

Ошибка компиляции со следующим (загадочным) сообщением об ошибке:

unimplemented IR generation feature non-fixed multi-payload enum layout

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

class Box<T> {
  let value: T
  init(_ value: T) {
    self.value = value
  }
}

enum Either<T, V> {
  case Left(Box<T>)
  case Right(Box<V>)
}

Эта проблема была решена в версии Swift 2.0.

[/spoiler]

Вопрос №2 – Swift 1.0 и далее

Замыкающие выражения – это элементы (значения) или ссылочные типы?

[spoiler title='Решение внутри' style='default' collapse_link='true']

Замыкающие выражения – это ссылочные типы.

[/spoiler]

Вопрос №3 – Swift 1.0 и далее

Тип UInt используется для хранения целых чисел без знака. Он реализует инициализатор для преобразования:

init(_ value: Int)

Однако следующий код генерирует исключение ошибки, если вы указываете отрицательное значение:

let myNegative = UInt(-1)

Зная, что отрицательное число внутренне представлено, и используя два дополнения в качестве положительного числа, как можно преобразовать отрицательное число Int в UInt, сохраняя при этом представление своей памяти?

[spoiler title='Решение внутри' style='default' collapse_link='true']

Для этого есть инициализатор:

UInt(bitPattern: Int)

[/spoiler]

Вопрос №4 – Swift 1.0 и далее

Опишите ситуацию, когда можно получить циклическую ссылку в Swift?

[spoiler title='Решение внутри' style='default' collapse_link='true']

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

Вы решите проблему, заменив одну из сильных ссылок на weak или unowned.

[/spoiler]

Вопрос №5 – Swift 2.0 и далее

В Swift 2.0 есть ключевое слово для создания рекурсивных перечислений. Пример:

enum List<T> {
    case Node(T, List<T>)
}

Что это за ключевое слово?

[spoiler title='Решение внутри' style='default' collapse_link='true']

Это ключевое слово indirect, которое допускает рекурсивные случаи перечисления следующим образом:

enum List<T> {
    indirect case Cons(T, List<T>)
}

[/spoiler]

Поздравляем с успешно пройденным материалом! Теперь даже самое сложное собеседование iOS вам по плечу.-

Комментарии

BUG
LIVE