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

0

Перейдем к более открытому варианту тестирования – собеседование 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 вам по плечу.-

МЕРОПРИЯТИЯ

Комментарии 0

ВАКАНСИИ

Добавить вакансию
Junior QA Engineer
Москва, по итогам собеседования
Junior Product Analyst
Новосибирск, от 50000 RUB до 100000 RUB
QA Manual Specialist
Москва, от 155000 RUB до 275000 RUB

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ