Перейти к содержимому

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

  • автор:

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

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

По сути функции являются частным случаем замыканий. Замыкания могут иметь одну из трех форм:

  • глобальные функции, которые имеют имя и которые не сохраняют значения внешних переменных и констант
  • вложенные функции, которые имеют имя и которые сохраняют значения внешних переменных и констант
  • замыкающие выражения (closure expressions), которые не имеют имени и которые могут сохранять значения внешних переменных и констант

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

Замыкающие выражения в общем случае имеют следующий синтаксис:

 < (параметры) ->тип_возвращаемого_значения in инструкции >

Если замыкания не имеют параметров или не возвращают никакого значения, то соответствующие элементы при определении замыкания могут опускаться.

Подобно тому, как переменная или константа могут представлять ссылку на функцию, они также могут представлять ссылку на замыкание:

let hello = < print("Hello world")>hello() hello()

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

Фактически константа hello в данном случае имеет тип ()->() или ()-gt;Void :

let hello: ()->Void =

Дополнительно можно определить список параметров с помощью ключевого слова in :

let hello = < (message: String) in print(message) >hello("Hello") hello("Salut") hello("Ni hao")

В данном случае замыкание принимает один параметр — message, который представляет тип String. Список параметров указывается до ключевого слова in, а после идут инструкции функции.

Также можно определить возвращаемое значение:

let sum = < (x: Int, y: Int) ->Int in return x + y > print(sum(2, 5)) // 7 print(sum(12, 15)) // 27 print(sum(5, 3)) // 8

Замыкания как аргументы функций

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

func operation(_ a: Int, _ b: Int, _ action: (Int, Int) -> Int) -> Int < return action(a, b) >let x = 10 let y = 12 let result1 = operation(x, y, <(a: Int, b: Int) ->Int in return a + b >) print(result1) // 22 var result2 = operation(x, y, <(a: Int, b: Int) ->Int in return a - b>) print(result2) // -2

Здесь функция operation() в качестве третьего параметра принимает другую функцию, которой передаются значения первого и второго параметров. Но нам необязательно определять дополнительные функции, поскольку в operation мы можем передать замыкающее выражение.

В первом случае это выражение производит сложение параметров, а во втором случае — их вычитание.

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

let x = 10 let y = 12 let result1 = operation(x, y, <(a, b) in a + b >) print(result1) // 22 let result2 = operation(x, y, <(a, b) in a - b >) print(result2) // -2

Компилятор видит, замыкающее выражение передается в качестве значения для параметра типа (Int, Int) -> Int , то есть в качестве функции, которая принимает параметры типа Int. Поэтому можно не указывать тип параметров a и b. Также компилятор определяет, что функция возвращает значение типа Int, поэтому выражение после ключевого слова in воспринимается как возвращаемое значение, и явным образом можно не использовать оператор return.

Но мы можем еще больше сократить замыкание, используя сокращения для параметров:

let x = 10 let y = 12 let result1 = operation(x, y, ) print(result1) // 22 let result2 = operation(x, y, ) print(result2) // -2

$0 представляет первый переданный в функцию параметр, а $1 — второй параметр. Система автоматически распознает их и поймет, что они представляют числа.

Однако поскольку здесь выполняются примитивные операции — сложение и вычитание двух чисел, то мы можем сократить замыкания еще больше:

let x = 10 let y = 12 let result1 = operation(x, y, +) print(result1) // 22 let result2 = operation(x, y, -) print(result2) // -2

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

Доступ к контексту

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

func action() -> (()->Int) < var val = 0 return < val = val+1 return val >> let inc = action() print(inc()) // 1 print(inc()) // 2

Здесь определена функция action, которая, в свою очередь, сама возвращает функцию. По факту она возвращает замыкающее выражение, которое увеличивает внешнюю переменную val на единицу и затем возвращает ее значение. Но при вызове мы видим, что переменная val сохраняет свое значение после увеличения, оно не сбрасывается обратно к нулю при каждом вызове функции. То есть переменная val представляет состояние, где замыкание может хранить данные.

Захват значений

Замыкающие выражения обладают способностью сохранять начальные значения переданных в них переменных. Например, рассмотрим следующую ситуацию:

var a = 14 var b = 2 let myClosure: () -> Int = print(myClosure()) // 16 a = 5 b = 6 print(myClosure()) // 11

Замыкающее выражение, на которое указывает константа myClosure, складывает значения переменных a и b. С изменением значений переменных также меняется результат замыкания myClosure. Однако мы можем зафиксировать начальные значения переменных:

var a = 14 var b = 2 let myClosure: () -> Int = <[a, b] in return a + b>print(myClosure()) // 16 a = 5 b = 6 print(myClosure()) // 16

Передав переменные в квадратные скобки: [a, b] , мы тем самым фиксируем их начальные значения. И даже если значения этих переменных в какой-то момент изменятся, замыкание будет оперировать прежними значениями.

Автозамыкания (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 не будет опубликован. Обязательные поля помечены *