Адаптировано из оригинального A Swift Tour на Swift.org с изменениями. Автором оригинального контента является компания Apple Inc. Лицензия Creative Commons Attribution 4.0 International (CC BY 4.0) .
Посмотреть на TensorFlow.org | Запустить в Google Colab | Посмотреть исходный код на GitHub |
Традиция предполагает, что первая программа на новом языке должна напечатать слова «Hello, world!» на экране. В Swift это можно сделать в одну строку:
print("Hello, world!")
Hello, world!
Если вы написали код на C или Objective-C, этот синтаксис покажется вам знакомым — в Swift эта строка кода представляет собой законченную программу. Вам не нужно импортировать отдельную библиотеку для таких функций, как ввод/вывод или обработка строк. Код, написанный в глобальной области видимости, используется в качестве точки входа в программу, поэтому функция main()
вам не нужна. Вам также не нужно писать точки с запятой в конце каждого оператора.
Этот тур дает вам достаточно информации, чтобы начать писать код на Swift, показывая, как выполнять различные задачи программирования. Не волнуйтесь, если вы чего-то не понимаете — все, что представлено в этом туре, подробно объясняется в остальной части книги.
Простые значения
Используйте let
для создания константы и var
для создания переменной. Значение константы не обязательно должно быть известно во время компиляции, но вы должны присвоить ей значение ровно один раз. Это означает, что вы можете использовать константы для присвоения имени значению, которое вы определяете один раз, но используете во многих местах.
var myVariable = 42
myVariable = 50
let myConstant = 42
Константа или переменная должна иметь тот же тип, что и значение, которое вы хотите ей присвоить. Однако не всегда требуется явно указывать тип. Предоставление значения при создании константы или переменной позволяет компилятору определить ее тип. В приведенном выше примере компилятор определяет, что myVariable
является целым числом, поскольку его начальное значение является целым числом.
Если начальное значение не предоставляет достаточно информации (или если начальное значение отсутствует), укажите тип, написав его после переменной, разделенной двоеточием. Примечание. Использование Double
вместо Float
для чисел с плавающей запятой дает большую точность и является типом с плавающей запятой по умолчанию в Swift.
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
// Experiment:
// Create a constant with an explicit type of `Float` and a value of 4.
Значения никогда не преобразуются неявно в другой тип. Если вам нужно преобразовать значение в другой тип, явно создайте экземпляр нужного типа.
let label = "The width is "
let width = 94
print(label + String(width))
The width is 94
// Experiment:
// Try removing the conversion to `String` from the last line. What error do you get?
Есть еще более простой способ включения значений в строки: запишите значение в круглые скобки и перед скобками напишите обратную косую черту (``). Например:
let apples = 3
print("I have \(apples) apples.")
I have 3 apples.
let oranges = 5
print("I have \(apples + oranges) pieces of fruit.")
I have 8 pieces of fruit.
// Experiment:
// Use `\()` to include a floating-point calculation in a string and to include someone's name in a
// greeting.
Используйте три двойные кавычки ( """
) для строк, занимающих несколько строк. Отступ в начале каждой строки в кавычках удаляется, если он соответствует отступу закрывающих кавычек. Например:
let quotation = """
Even though there's whitespace to the left,
the actual lines aren't indented.
Except for this line.
Double quotes (") can appear without being escaped.
I still have \(apples + oranges) pieces of fruit.
"""
print(quotation)
Even though there's whitespace to the left, the actual lines aren't indented. Except for this line. Double quotes (") can appear without being escaped. I still have 8 pieces of fruit.
Создавайте массивы и словари, используя скобки ( []
), и получайте доступ к их элементам, записывая индекс или ключ в скобках. Запятая допускается после последнего элемента.
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
print(occupations)
["Jayne": "Public Relations", "Kaylee": "Mechanic", "Malcolm": "Captain"]
Массивы автоматически увеличиваются по мере добавления элементов.
shoppingList.append("blue paint")
print(shoppingList)
["catfish", "bottle of water", "tulips", "blue paint", "blue paint"]
Чтобы создать пустой массив или словарь, используйте синтаксис инициализатора.
let emptyArray = [String]()
let emptyDictionary = [String: Float]()
Если информация о типе может быть выведена, вы можете записать пустой массив как []
и пустой словарь как [:]
— например, когда вы устанавливаете новое значение для переменной или передаете аргумент функции.
shoppingList = []
occupations = [:]
Поток управления
Используйте if
и switch
для создания условных операторов, а также for
- in
, for
, while
и repeat
- while
для создания циклов. Круглые скобки вокруг условия или переменной цикла необязательны. Требуются подтяжки вокруг тела.
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
print(teamScore)
11
В операторе if
условие должно быть логическим выражением — это означает, что такой код, как if score { ... }
является ошибкой, а не неявным сравнением с нулем.
Вы можете использовать if
и let
вместе для работы со значениями, которые могут отсутствовать. Эти значения представлены как необязательные. Необязательное значение либо содержит значение, либо содержит nil
что указывает на отсутствие значения. Поставьте знак вопроса ( ?
) после типа значения, чтобы отметить значение как необязательное.
var optionalString: String? = "Hello"
print(optionalString == nil)
false
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
print(greeting)
Hello, John Appleseed
// Experiment:
// Change `optionalName` to `nil`. What greeting do you get?
// Add an `else` clause that sets a different greeting if `optionalName` is `nil`.
Если необязательное значение равно nil
, условие имеет значение false
и код в фигурных скобках пропускается. В противном случае необязательное значение разворачивается и присваивается константе после let
, что делает развернутое значение доступным внутри блока кода.
Другой способ обработки необязательных значений — указать значение по умолчанию с помощью оператора ??
оператор. Если необязательное значение отсутствует, вместо него используется значение по умолчанию.
let nickName: String? = nil
let fullName: String = "John Appleseed"
print("Hi \(nickName ?? fullName)")
Hi John Appleseed
Коммутаторы поддерживают любые типы данных и широкий спектр операций сравнения — они не ограничиваются целыми числами и проверками на равенство.
let vegetable = "red pepper"
switch vegetable {
case "celery":
print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
default:
print("Everything tastes good in soup.")
}
Is it a spicy red pepper?
// Experiment:
// Try removing the default case. What error do you get?
Обратите внимание, как let
можно использовать в шаблоне для присвоения константе значения, соответствующего этой части шаблона.
После выполнения кода внутри соответствующего случая переключателя программа выходит из оператора переключателя. Выполнение не продолжается до следующего случая, поэтому нет необходимости явно прерывать переключатель в конце кода каждого случая.
Вы используете for
in
для перебора элементов словаря, предоставляя пару имен для каждой пары ключ-значение. Словари представляют собой неупорядоченную коллекцию, поэтому их ключи и значения перебираются в произвольном порядке.
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
25
// Experiment:
// Add another variable to keep track of which kind of number was the largest, as well as what that
// largest number was.
Используйте while
, чтобы повторять блок кода до тех пор, пока не изменится условие. Вместо этого условие цикла может быть в конце, гарантируя, что цикл будет выполнен хотя бы один раз.
var n = 2
while n < 100 {
n = n * 2
}
print(n)
128
var m = 2
repeat {
m = m * 2
} while m < 100
print(m)
128
Вы можете хранить индекс в цикле — либо используя ..<
для создания диапазона индексов, либо записывая явную инициализацию, условие и приращение. Эти два цикла делают одно и то же:
var total = 0
for i in 0..<4 {
total += i
}
print(total)
6
Используйте ..<
, чтобы создать диапазон, в котором отсутствует верхнее значение, и используйте ...
, чтобы создать диапазон, включающий оба значения.
Функции и замыкания
Используйте func
для объявления функции. Вызовите функцию, указав после ее имени список аргументов в круглых скобках. Используйте ->
, чтобы отделить имена и типы параметров от типа возвращаемого значения функции.
func greet(name: String, day: String) -> String {
return "Hello \(name), today is \(day)."
}
print(greet(name: "Bob", day: "Tuesday"))
Hello Bob, today is Tuesday.
// Experiment:
// Remove the `day` parameter. Add a parameter to include today’s lunch special in the greeting.
По умолчанию функции используют имена своих параметров в качестве меток для своих аргументов. Напишите пользовательскую метку аргумента перед именем параметра или напишите _
, чтобы не использовать метку аргумента.
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
print(greet("John", on: "Wednesday"))
Hello John, today is Wednesday.
Используйте кортеж для создания составного значения, например для возврата нескольких значений из функции. К элементам кортежа можно обращаться либо по имени, либо по номеру.
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
120 120
Функции могут быть вложенными. Вложенные функции имеют доступ к переменным, объявленным во внешней функции. Вы можете использовать вложенные функции для организации кода в длинной или сложной функции.
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
print(returnFifteen())
15
Функции являются первоклассным типом. Это означает, что функция может возвращать в качестве значения другую функцию.
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
print(increment(7))
8
Функция может принимать другую функцию в качестве одного из своих аргументов.
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
print(hasAnyMatches(list: numbers, condition: lessThanTen))
true
На самом деле функции представляют собой особый случай замыканий: блоки кода, которые можно вызвать позже. Код замыкания имеет доступ к таким вещам, как переменные и функции, которые были доступны в области, в которой было создано замыкание, даже если при выполнении замыкание находится в другой области действия — вы уже видели пример этого с вложенными функциями. Вы можете написать замыкание без имени, заключив код в фигурные скобки ( {}
). Используйте in
для отделения аргументов и типа возвращаемого значения от тела.
let mappedNumbers = numbers.map({ (number: Int) -> Int in
let result = 3 * number
return result
})
print(mappedNumbers)
[60, 57, 21, 36]
// Experiment:
// Rewrite the closure to return zero for all odd numbers.
У вас есть несколько вариантов более краткого написания замыканий. Если тип замыкания уже известен, например обратный вызов для делегата, вы можете опустить тип его параметров, тип возвращаемого значения или и то, и другое. Замыкания одного оператора неявно возвращают значение своего единственного оператора.
let mappedNumbers2 = numbers.map({ number in 3 * number })
print(mappedNumbers2)
[60, 57, 21, 36]
Вы можете ссылаться на параметры по номеру, а не по имени — этот подход особенно полезен при очень коротких замыканиях. Замыкание, передаваемое в качестве последнего аргумента функции, может появляться сразу после круглых скобок. Если замыкание является единственным аргументом функции, вы можете полностью опустить круглые скобки.
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
[20, 19, 12, 7]
Объекты и классы
Используйте class
, за которым следует имя класса, чтобы создать класс. Объявление свойства в классе записывается так же, как объявление константы или переменной, за исключением того, что оно находится в контексте класса. Аналогично, объявления методов и функций пишутся одинаково.
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
// Experiment:
// Add a constant property with `let`, and add another method that takes an argument.
Создайте экземпляр класса, поставив круглые скобки после имени класса. Используйте точечный синтаксис для доступа к свойствам и методам экземпляра.
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
В этой версии класса Shape
отсутствует кое-что важное: инициализатор для настройки класса при создании экземпляра. Используйте init
, чтобы создать его.
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
Обратите внимание, как self
используется для того, чтобы отличить свойство name
от аргумента name
инициализатора. Аргументы инициализатору передаются как вызов функции при создании экземпляра класса. Каждому свойству необходимо присвоенное значение — либо в его объявлении (как в случае с numberOfSides
), либо в инициализаторе (как в случае с name
).
Используйте deinit
для создания деинициализатора, если вам нужно выполнить некоторую очистку перед освобождением объекта.
Подклассы включают имя суперкласса после имени класса, разделенное двоеточием. Не требуется, чтобы классы создавали подклассы для любого стандартного корневого класса, поэтому при необходимости вы можете включать или опускать суперкласс.
Методы подкласса, которые переопределяют реализацию суперкласса, помечаются значком override
— случайное переопределение метода без override
обнаруживается компилятором как ошибка. Компилятор также обнаруживает методы с override
, которые фактически не переопределяют какой-либо метод в суперклассе.
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let test = Square(sideLength: 5.2, name: "my test square")
print(test.area())
print(test.simpleDescription())
27.040000000000003 A square with sides of length 5.2.
// Experiment:
// - Make another subclass of `NamedShape` called `Circle` that takes a radius and a name as
// arguments to its initializer.
// - Implement an `area()` and a `simpleDescription()` method on the `Circle` class.
Помимо простых сохраняемых свойств, свойства могут иметь метод получения и установки.
class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}
var perimeter: Double {
get {
return 3.0 * sideLength
}
set {
sideLength = newValue / 3.0
}
}
override func simpleDescription() -> String {
return "An equilateral triangle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)
9.3 3.3000000000000003
В установщике perimeter
новое значение имеет неявное имя newValue
. Вы можете указать явное имя в круглых скобках после set
.
Обратите внимание, что инициализатор класса EquilateralTriangle
состоит из трех разных шагов:
Установка значения свойств, объявленных подклассом.
Вызов инициализатора суперкласса.
Изменение значения свойств, определенных суперклассом. На этом этапе также можно выполнить любую дополнительную работу по настройке, в которой используются методы, геттеры или сеттеры.
Если вам не нужно вычислять свойство, но все же необходимо предоставить код, который запускается до и после установки нового значения, используйте willSet
и didSet
. Предоставленный вами код запускается каждый раз, когда значение изменяется за пределами инициализатора. Например, приведенный ниже класс гарантирует, что длина стороны его треугольника всегда равна длине стороны его квадрата.
class TriangleAndSquare {
var triangle: EquilateralTriangle {
willSet {
square.sideLength = newValue.sideLength
}
}
var square: Square {
willSet {
triangle.sideLength = newValue.sideLength
}
}
init(size: Double, name: String) {
square = Square(sideLength: size, name: name)
triangle = EquilateralTriangle(sideLength: size, name: name)
}
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
10.0 10.0 50.0
При работе с необязательными значениями вы можете написать ?
перед такими операциями, как методы, свойства и индексация. Если значение перед ?
равен nil
, все после ?
игнорируется, а значение всего выражения равно nil
. В противном случае необязательное значение разворачивается, и все, что после ?
действует на развернутое значение. В обоих случаях значение всего выражения является необязательным.
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
print(optionalSquare?.sideLength)
Optional(2.5)
Перечисления и структуры
Используйте enum
для создания перечисления. Подобно классам и всем другим именованным типам, перечисления могут иметь связанные с ними методы.
enum Rank: Int {
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
func simpleDescription() -> String {
switch self {
case .ace:
return "ace"
case .jack:
return "jack"
case .queen:
return "queen"
case .king:
return "king"
default:
return String(self.rawValue)
}
}
}
let ace = Rank.ace
print(ace)
let aceRawValue = ace.rawValue
print(aceRawValue)
ace 1
// Experiment:
// Write a function that compares two `Rank` values by comparing their raw values.
По умолчанию Swift присваивает необработанные значения, начиная с нуля и каждый раз увеличивая их на единицу, но вы можете изменить это поведение, явно указав значения. В приведенном выше примере Ace явно присваивается необработанное значение 1
, а остальные необработанные значения присваиваются по порядку. Вы также можете использовать строки или числа с плавающей запятой в качестве необработанного типа перечисления. Используйте свойство rawValue
для доступа к необработанному значению случая перечисления.
Используйте инициализатор init?(rawValue:)
для создания экземпляра перечисления из необработанного значения. Он возвращает либо регистр перечисления, соответствующий необработанному значению, либо nil
если соответствующего Rank
нет.
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
Значения регистра перечисления — это фактические значения, а не просто еще один способ записи их необработанных значений. Фактически, в тех случаях, когда значимого необработанного значения нет, вам не нужно его предоставлять.
enum Suit {
case spades, hearts, diamonds, clubs
func simpleDescription() -> String {
switch self {
case .spades:
return "spades"
case .hearts:
return "hearts"
case .diamonds:
return "diamonds"
case .clubs:
return "clubs"
}
}
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
// Experiment:
// Add a `color()` method to `Suit` that returns "black" for spades and clubs, and returns "red" for
// hearts and diamonds.
Обратите внимание на два способа, с помощью которых случай перечисления Hearts
упоминается выше: При присвоении значения константе hearts
случай перечисления Suit.Hearts
упоминается по его полному имени, поскольку для константы не указан явный тип. Внутри переключателя случай перечисления обозначается сокращенной формой .Hearts
, поскольку значение self
уже известно как масть. Вы можете использовать сокращенную форму в любое время, когда тип значения уже известен.
Если перечисление имеет необработанные значения, эти значения определяются как часть объявления, что означает, что каждый экземпляр конкретного случая перечисления всегда имеет одно и то же необработанное значение. Другой вариант для случаев перечисления — иметь значения, связанные с делом — эти значения определяются при создании экземпляра и могут быть разными для каждого экземпляра случая перечисления. Вы можете думать о том, что связанные значения ведут себя как сохраненные свойства экземпляра случая перечисления.
Например, рассмотрим случай запроса времени восхода и захода солнца с сервера. Сервер либо отвечает запрошенной информацией, либо описанием того, что пошло не так.
enum ServerResponse {
case result(String, String)
case failure(String)
}
let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")
switch success {
case let .result(sunrise, sunset):
print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
print("Failure... \(message)")
}
Sunrise is at 6:00 am and sunset is at 8:09 pm.
// Experiment:
// Add a third case to `ServerResponse` and to the switch.
Обратите внимание, как время восхода и захода солнца извлекается из значения ServerResponse
как часть сопоставления значения со случаями переключения.
Используйте struct
для создания структуры. Структуры поддерживают многие из тех же вариантов поведения, что и классы, включая методы и инициализаторы. Одно из наиболее важных различий между структурами и классами заключается в том, что структуры всегда копируются при передаче в коде, а классы передаются по ссылке.
struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
// Experiment:
// Write a function that returns an array containing a full deck of cards, with one card of each
// combination of rank and suit.
Протоколы и расширения
Используйте protocol
, чтобы объявить протокол.
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
Классы, перечисления и структуры могут использовать протоколы.
class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class."
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
print(b.adjust())
print(b.simpleDescription)
() A simple structure (adjusted)
// Experiment:
// Add another requirement to `ExampleProtocol`.
// What changes do you need to make to `SimpleClass` and `SimpleStructure` so that they still
// conform to the protocol?
Обратите внимание на использование ключевого слова mutating
в объявлении SimpleStructure
для обозначения метода, изменяющего структуру. Для объявления SimpleClass
не требуется, чтобы какой-либо из его методов был помечен как изменяющийся, поскольку методы класса всегда могут его модифицировать.
Используйте extension
, чтобы добавить функциональность к существующему типу, например новые методы и вычисляемые свойства. Вы можете использовать расширение, чтобы добавить соответствие протоколу к типу, объявленному где-то еще, или даже к типу, который вы импортировали из библиотеки или платформы.
extension Int: ExampleProtocol {
public var simpleDescription: String {
return "The number \(self)"
}
public mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
The number 7
// Experiment:
// Write an extension for the `Double` type that adds an `absoluteValue` property.
Имя протокола можно использовать так же, как и любой другой именованный тип — например, для создания коллекции объектов разных типов, но соответствующих одному протоколу. При работе со значениями, тип которых является типом протокола, методы вне определения протокола недоступны.
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
A very simple class. Now 100% adjusted.
// Uncomment to see the error.
// protocolValue.anotherProperty
Несмотря на то, что protocolValue
имеет тип времени выполнения SimpleClass
, компилятор рассматривает ее как заданный тип ExampleProtocol
. Это означает, что вы не можете случайно получить доступ к методам или свойствам, которые реализует класс в дополнение к его соответствию протоколу.
Обработка ошибок
Вы представляете ошибки, используя любой тип, который принимает протокол Error
.
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
Используйте throw
, чтобы выдать ошибку, и throws
, чтобы отметить функцию, которая может выдать ошибку. Если вы выдаете ошибку в функции, функция немедленно возвращается, и код, вызвавший функцию, обрабатывает ошибку.
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
Существует несколько способов обработки ошибок. Один из способов — использовать do-catch
. Внутри блока do
вы отмечаете код, который может вызвать ошибку, записывая перед ним try. Внутри блока catch
ошибке автоматически присваивается имя error
, если вы не дадите ей другое имя.
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
Job sent
// Experiment:
// Change the printer name to `"Never Has Toner"`, so that the `send(job:toPrinter:)` function
// throws an error.
Вы можете предоставить несколько блоков catch
, которые обрабатывают определенные ошибки. Вы пишете шаблон после catch
так же, как и после case
в переключателе.
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
Job sent
// Experiment:
// Add code to throw an error inside the `do` block.
// What kind of error do you need to throw so that the error is handled by the first `catch` block?
// What about the second and third blocks?
Другой способ обработки ошибок — использовать try?
чтобы преобразовать результат в необязательный. Если функция выдает ошибку, конкретная ошибка отбрасывается и результат равен nil
. В противном случае результатом является необязательный параметр, содержащий значение, возвращенное функцией.
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
Используйте defer
для записи блока кода, который выполняется после всего остального кода в функции, непосредственно перед возвратом функции. Код выполняется независимо от того, выдает ли функция ошибку. Вы можете использовать defer
для написания кода настройки и очистки рядом друг с другом, даже если они должны выполняться в разное время.
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
let result = fridgeContent.contains(food)
return result
}
print(fridgeContains("banana"))
print(fridgeIsOpen)
false false
Дженерики
Напишите имя в угловых скобках, чтобы создать общую функцию или тип.
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result = [Item]()
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
print(makeArray(repeating: "knock", numberOfTimes: 4))
["knock", "knock", "knock", "knock"]
Вы можете создавать общие формы функций и методов, а также классов, перечислений и структур.
// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
print(possibleInteger)
some(100)
where
после имени типа, чтобы указать список требований — например, чтобы потребовать, чтобы тип реализовал протокол, чтобы два типа были одинаковыми или чтобы класс имел определенный суперкласс.
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
where T.Element: Equatable, T.Element == U.Element
{
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
print(anyCommonElements([1, 2, 3], [3]))
true
Написание <T: Equatable>
аналогично написанию <T> ... where T: Equatable
.