Перейдем к более открытому варианту тестирования – собеседование iOS-разработчика с устными вопросами по Swift. Вместе с ними также приведены ответы.
Предыдущая статья: Язык 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 классы и структуры отличаются рядом особенностей. Выведем обобщенную разницу следующим образом:
- Классы поддерживают наследование, структуры – нет.
- Классы являются ссылочными типами, структуры – это типы значений.
Нет универсального правила, которое определяет, что лучше. Общая рекомендация – использовать оптимальный для достижения конкретной цели инструмент. Но лучше отдать предпочтение структурам, если не требуется наследование или ссылочная семантика.
Подробную информацию можно найти здесь.
[/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)
Этот код достигает намеченного результата и предотвращает передачу параметров другого типа.
[/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]
Средний уровень
Собеседование 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 вам по плечу.-
Комментарии