Для чего нужны автозамыкания swift
Перейти к содержимому

Для чего нужны автозамыкания swift

  • автор:

Используем замыкания в Swift по полной

Несмотря на то, что в Objective-C 2.0 присутствуют замыкания (известные как блоки), ранее эппловский API использовал их неохотно. Возможно, отчасти поэтому многие программисты под iOS с удовольствием эксплуатировали сторонние библиотеки, вроде AFNetworking, где блоки применяются повсеместно. С выходом Swift, а также добавлением новой функциональности в API, работать с замыканиями стало чрезвычайно удобно. Давайте рассмотрим, какими особенностями обладает их синтаксис в Swift, и какие трюки можно с ними «вытворять».

Продвигаться будем от простого к сложному, от скучного к веселому. Заранее приношу извинения за обильное использование мантр «функция», «параметр» и «Double», но из песни слов не выкинешь.

Часть 1. Вводная

1.1. Объекты первого класса

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

func add(op1: Double, op2: Double) -> Double < return op1 + op2 >func subtract(op1: Double, op2: Double) -> Double

Их тип будет описываться следующим образом:

(Double, Double) -> Double

Прочесть это можно так: «Перед нами тип функции с двумя входными параметрами типа Double и возвращаемым значением типа Double
Мы можем создать переменную такого типа:

// Описываем переменную var operation: (Double, Double) -> Double // Смело присваиваем этой переменной значение // нужной нам функции, в зависимости от каких-либо условий: for i in 0.. else < operation = subtract >let result = operation(1.0, 2.0) // "Вызываем" переменную println(result) > 

Код, описанный выше, выведет в консоли:
3.0
-1.0

1.2. Замыкания

Используем еще одну привилегию объекта первого класса. Возвращаясь к предыдущему примеру, мы могли бы создать такую новую функцию, которая бы принимала одну из наших старых функций типа (Double, Double) -> Double в качестве последнего параметра. Вот так она будет выглядеть:

// (1) func performOperation(op1: Double, op2: Double, operation: (Double, Double) -> Double) -> Double < // (2) return operation(op1, op2) // (3) >
  • op1 типа Double (op — сокращенное от «операнд»)
  • op2 типа Double
  • operation типа (Double, Double) -> Double

Давайте теперь передадим в качестве третьего аргумента не переменную, а анонимную функцию, заключив ее в фигурные <> скобки. Переданный таким образом параметр и будет называться замыканием:

let result = performOperation(1.0, 2.0, <(op1: Double, op2: Double) ->Double in return op1 + op2 // (5) >) // (4) println(result) // Выводит 3.0 в консоли 
  • псевдонимов op1, op2 типа Double для использования внутри замыкания
  • возвращаемого значения замыкания -> Double
  • ключевого слова in

Часть 2. Веселая.
Синтаксический сахар и неожиданные «плюшки»

Авторы Swift приложили немало усилий, чтобы пользователи языка могли писать как можно меньше кода и как можно больше тратить свое драгоценное время на чтение Хабра размышления об архитектуре проекта. Взяв за основу наш пример с арифметическими операциями, посмотрим, до какого состояния мы сможем его «раскрутить».

2.1. Избавляемся от типов при вызове.

Во-первых, можно не указывать типы входных параметров в замыкании явно, так как компилятор уже знает о них. Вызов функции теперь выглядит так:

performOperation(1.0, 2.0, <(op1, op2) ->Double in return op1 + op2 >) 
2.2. Используем синтаксис «хвостового замыкания».

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

performOperation(1.0, 2.0) <(op1, op2) ->Double in return op1 + op2 > 
2.3. Не используем ключевое слово «return».

Приятная (в некоторых случаях) особенность языка заключается в том, что если код замыкания умещается в одну строку, то результат выполнения этой строки автоматичеси будет возвращен. Таким образом ключевое слово «return» можно не писать:

performOperation(1.0, 2.0) <(op1, op2) ->Double in op1 + op2 > 
2.4. Используем стенографические имена для параметров.

Идем дальше. Интересно, что Swift позволяет использовать так называемые стенографические (англ. shorthand) имена для входных параметров в замыкании. Т.е. каждому параметру по умолчанию присваивается псевдоним в формате $n, где n — порядковый номер параметра, начиная с нуля. Таким образом, нам, оказывается, даже не нужно придумывать имена для аргументов. В таком случае весь «заголовок» замыкания уже не несет в себе никакой смысловой нагрузки, и его можно опустить:

performOperation(1.0, 2.0)

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

2.5. Ход конем: операторные функции.

Все это были еще цветочки. Сейчас будет ягодка.
Давайте посмотрим на предыдущую запись и зададимся вопросом, что уже знает компилятор о замыкании? Он знает количество параметров (2) и их типы (Double и Double). Знает тип возвращаемого значения (Double). Так как в коде замыкания выполняется всего одна строка, он знает, что ему нужно возвращать в качестве результата его выполнения. Можно ли упростить эту запись как-то еще?
Оказывается, можно. Если замыкание работает только с двумя входными аргументами, в качестве замыкания разрешается передать операторную функцию, которая будет выполняться над этими аргументами (операндами). Теперь наш вызов будет выглядеть следующим образом:

performOperation(1.0, 2.0, +) 

Красота!
Теперь можно производить элементарные операции над нашими операндами в зависимости от некоторых условий, написав при этом минимум кода.

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

func performComparisonOperation(op1: Double, op2: Double, operation: (Double, Double) -> Bool) -> Bool < return operation(op1, op2) >println(performComparisonOperation(1.0, 1.0, >=)) // Выведет "true" println(performComparisonOperation(1.0, 1.0, <)) // Выведет "false" 

Или битовые операции:

func performBitwiseOperation(op1: Bool, op2: Bool, operation: (Bool, Bool) -> Bool) -> Bool < return operation(op1, op2) >println(performBitwiseOperation(true, true, ^)) // Выведет "false" println(performBitwiseOperation(true, false, |)) // Выведет "true" 

Swift — в некотором роде забавный язык программирования. Надеюсь, статья будет полезной для тех, кто начинает знакомиться с этим языком, а также для тех, кому просто интересно, что там происходит у разработчиков под iOS и Mac OS X.
___________________________________________________________________

UPD.: Реальное применение

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

Если вам нужно создать очередь с приоритетом, можно использовать двоичную кучу (binary heap). Как известно, это может быть как MinHeap, так и MaxHeap, т.е. кучи, где в корне дерева находится минимальный или максимальный элемент соотвественно. Базовая реализация MinHeap от MaxHeap будет отличаться по сути только проверочными сравнениями при восстановлении инварианта двоичной кучи после добавления/удаления элемента.

Таким образом, мы могли создать базовый класс BinaryHeap, который будет содержать свойство comparison типа (T, T) -> Bool. А конструктор этого класса будет принимать способ сравнения и затем использовать его в методах heapify. Прототип базового класса выглядел бы так:

class BinaryHeap: DebugPrintable < private var array: Arrayprivate var comparison: (T, T) -> Bool private var used: Int = 0 // Бла-бла-бла // Internal Methods internal func removeTop() -> T? < //. >internal func getTop() -> T? < //. >// Public Methods: func addValue(value: T) < if used == self.array.count < self.grow() >self.array[used] = value heapifyToTop(used, comparison) // Одно из мест, где используется функция сравнения self.used++ > init(size newSize: Int, comparison newComparison: (T, T) -> Bool) < array = [T?](count: newSize, repeatedValue: nil) comparison = newComparison >> 

Теперь для того, чтобы создать классы MinHeap и MaxHeap нам достаточно унаследоваться от BinaryHeap, а в их конструкторах просто явно указать, какое сравнение применять. Вот так будет выглядеть наши классы:

class MaxHeap: BinaryHeap  < func getMax() ->T? < return self.getTop() >func removeMax() -> T? < return self.removeTop() >init(size newSize: Int) < super.init(size: newSize, $1>) > > 
class MinHeap: BinaryHeap  < func getMin() ->T? < return self.getTop() >func removeMin() -> T? < return self.removeTop() >init(size newSize: Int) < super.init(size: newSize, ) > > 
  • swift
  • замыкания
  • ios development
  • программирование

Автозамыкания (autoclosures)

Автозамыкания — замыкания, которые автоматически создаются для заключения выражения, которое было передано в качестве аргумента функции, и оно не принимает никаких аргументов при вызове, оно возвращает значение выражения, которое заключено внутри нее. Автозамыкания позволяют вам откладывать вычисления, потому как код внутри них не исполняется, пока вы сами его не запустите. Это полезно для кода, который может иметь сторонние эффекты или просто является дорогим в вычислительном отношении, потому что вы можете контролировать время исполнения этого когда. Пример ниже отображает как замыкания откладывают вычисления:

var customersInLine = ["Крис", "Алеша", "Евгения", "Борька", "Данилка"] let nextCustomer = < customersInLine.removeAtIndex(0) >print(customersInLine.count) // выводит "5" print("Следующий клиент - \(nextCustomer())!") // выводит “Следующий клиент - Крис!" print(customersInLine.count) // выводит “4”

Даже если первый элемент массива customersInLine удален в качестве части замыкания, то дальнейшее исполнение не поддерживается до тех пор, пока само замыкание фактически не будет вызвано. Если замыкание так и не вызывается, то выражение внутри него никогда и не вычисляется. Обратите внимание, что nextCustomer является не String, а () -> String, то есть функция не принимает аргументов, но возвращает строку. Вы получите то же самое поведение, когда сделаете это внутри функции:

// customersInLine is ["Алеша", "Евгения", "Борька", "Данилка"] func serveNextCustomer(customer: () -> String) < print("Now serving \(customer())!") >serveNextCustomer( < customersInLine.removeAtIndex(0) >) // выведет "Следующий клиент - Алеша!”

Функция serveNextCustomer(_:) описанная выше принимает явное замыкание, которое возвращает имя следующего клиента. Следующая версия этой же функции ниже выполняет ту же самую операцию, но вместо использования явного замыкания, она использует автозамыкание, поставив маркировку при помощи атрибута @autoclosure. Теперь вы можете вызывать функцию, как будто бы она принимает аргумент String вместо замыкания.

// customersInLine is ["Евгения", "Борька", "Данилка"] func serveNextCustomer(@autoclosure customer: () -> String) < print("Now serving \(customer())!") >serveNextCustomer(customersInLine.removeAtIndex(0)) // выведет "Следующий клиент - Евгения!"

Заметка

Слишком частое использование автозамыканий может сделать ваш код сложным для чтения. Контекст и имя функции должны обеспечивать ясность отложенности исполнения кода.

Атрибут @autoclosure предполагает наличие атрибута @noescape, который указывает на то, что замыкание используется только внутри функции. Что значит, что замыканию не позволяется храниться так, чтобы она могла “сбежать” из области видимости функции и исполниться после того, как функция уже возвратила значение. Если вы хотите, чтобы замыкание могло “сбежать”, то используйте другую форму атрибута — @autoclosure(escaping):

// customersInLine is ["Борька", "Данилка"] var customerClosures: [() -> String] = [] func collectCustomerClosures(@autoclosure(escaping) customer: () -> String ) < customerClosures.append(customer) >collectCustomerClosures(customersInLine.removeAtIndex(0)) collectCustomerClosures(customersInLine.removeAtIndex(0)) print("Всего \(customerClosures.count) замыкани(я/й).") // выведет "Collected 2 closures." for customerClosure in customerClosures < print("Следующий клиент \(customerClosure())!") >// выведет "Следующий клиент - Борька!" // выведет "Следующий клиент - Данилка!"

В коде выше, вместо того, чтобы вызывать переданное замыкание в качестве аргумента customer, функция collectCustomerClosures(_:) добавляет замыкание к массиву customerClosures. Массив объявлен за пределами функции, что означает, что замыкание в массиве может быть исполнено после того, как функция вернет значение. В результате значение аргумента customer должен иметь “разрешение” на “побег” из зоны видимости функции.

Originally published at swiftbook.ru on October 9, 2015.

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

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

Перед вами список из 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")

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

Теперь в своем коде вы можете вызвать, например, 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 для завершения выполнения цикла for , когда продолжение цикла считается ненужным:

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

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

if age < 18 < driveCar() >else

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

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

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
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")

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

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

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

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

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

struct Vec3D  < 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 = 15 class Example < init() < number *= 10 >deinit < number = 0 >>

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

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

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

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

final class 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: () -> Void) < perform_action() >I_will(< print("Hello, world!") >)

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

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

enum Example

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

enum Example

Заключение

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

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

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

Swift — closures (замыкания в Swift)

Рассмотрим одну из тем в языке программирования Swift — замыкания.

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

Думаю, вы уже знакомы с таким понятием, как функция. Так вот, функция — это частный случай замыкания.

Замыкания могут быть:

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

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

Синтаксис замыкания следующий:

Пример использования замыкания в переменной представлен ниже:

Как думаете, какой тип будет иметь переменная closure?
Нет, не Bool.
Данная переменная возвратит функциональный типа () -> Bool

Давайте попробуем применить замыкания на практике. Создадим массив от 1 до 10. А дальше сформируем функцию, которая должна будет в себе содержать замыкания, задающее правило для фильтрации входящих значений массива.

Разберемся что здесь происходит:

  • создан массив numbers
  • объявлена функция, которая принимает массив из чисел. Также функция принимает замыкания функционального типа [ (Int) -> Bool ]. Функция возвращает отфильтрованный массив
  • Внутри функции мы создаем пустов массив, куда будут помещаться результаты фильтрации. Далее мы проходимся по каждому элементу через цикл. Вызываем аргумент функции closure куда помещаем эл-т. Замыкание же возвращает тип bool, с которым дальше справляется условие (if) и добавляет его в результирующий список.
  • Формируем переменную newList в которую помещаем массив из цифр, а также создаем замыкание, которое создает правило добавления элементов, которые меньше 5.

Поработаем с возможностями замыканий.

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

Как мы видим, Swift автоматически подсказывает что используется функциональный тип в параметре by. Отсортируем этот список по возрастанию:

Применим наши знания и сделаем код еще более читабельным:

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

Захват переменных

Поговорим о захвате переменных. Что это значит? Бывают случаи, когда замыкания обращаются к внешним (не находящимся внутри их) переменным, которые могут изменяться с течением времени, однако необходимо запомнить эти внешние переменные и не использовать дальнейшие изменяющиеся переменные. Приведу пример.

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

C помощью конструкции [varibale1, variable2] in у нас появляется возможность “запомнить” внешние переменные и не подвергать их изменению.

Автозамыкания

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

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

Зададим функциональный тип параметру () -> Int и при вызове функции передадим замыкание.

Таким образом мы сэкономим ресурсы телефона. Параметр будет вызываться только при обращении к нему.

Чтобы облегчить наш код, существуют автозамыкания @autoclosure

Всем спасибо за прочитанную статью. Хотелось бы получить от вас фидбек в виде лайков или комментарием.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *