Spec-Zone.ru › Swift Language

Обзор Swift

Изучите возможности и синтаксис Swift.

Традиция гласит, что первая программа на новом языке должна выводить на экран «Hello, world!». В Swift это можно сделать в одной строке:

print("Hello, world!")
// Prints "Hello, world!"

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

Этот обзор предоставляет достаточно информации для начала программирования на Swift, показывая, как выполнять различные задачи программирования. Не волнуйтесь, если вы что-то не понимаете — все, что представлено в этом обзоре, подробно объясняется в остальной части этой книги.

Простые значения

Используйте let для создания константы и var для создания переменной. Значение константы необязательно должно быть известно во время компиляции, но вы должны присвоить ей значение ровно один раз. Это означает, что вы можете использовать константы для именования значения, которое вы определяете один раз, но используете во многих местах.

var myVariable = 42
myVariable = 50
let myConstant = 42

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

Если начальное значение не предоставляет достаточно информации (или если нет начального значения), укажите тип, написав его после переменной, разделяя их двоеточием.

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70

Эксперимент: Создайте константу с явным типом Float и значением 4.

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

let label = "The width is "
let width = 94
let widthLabel = label + String(width)

Эксперимент: Попробуйте удалить преобразование в String из последней строки. Какую ошибку вы получите?

Есть еще более простой способ включить значения в строки: запишите значение в скобки и поставьте обратный слэш (\) перед скобками. Например:

let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."

Эксперимент: Используйте \() для включения вычисления с плавающей точкой в строку и включения имени кого-то в приветствие.

Используйте три двойных кавычки (""") для строк, занимающих несколько строк. Отступы в начале каждой строки в кавычках удаляются, если они совпадают с отступами закрывающих кавычек. Например:

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

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

var fruits = ["strawberries", "limes", "tangerines"]
fruits[1] = "grapes"

var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
 ]
occupations["Jayne"] = "Public Relations"

Массивы автоматически увеличиваются по мере добавления элементов.

fruits.append("blueberries")
print(fruits)
// Prints "["strawberries", "grapes", "tangerines", "blueberries"]"

Вы также используете квадратные скобки для записи пустого массива или словаря. Для массива запишите [], а для словаря — [:].

fruits = []
occupations = [:]

Если вы присваиваете пустой массив или словарь новой переменной или в другом месте, где нет информации о типе, вам нужно указать тип.

let emptyArray: [String] = []
let emptyDictionary: [String: Float] = [:]

Управление потоком

Используйте if и switch для создания условных выражений и используйте циклы for-in, 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)
// Prints "11"

В операторе if условное выражение должно быть булевым выражением — это означает, что код, такой как if score { ... }, является ошибкой, а не неявным сравнением с нулем.

Вы можете написать if или switch после знака равенства (=) присваивания или после return, чтобы выбрать значение на основе условия.

let scoreDecoration = if teamScore > 10 {
    "🎉"
} else {
    ""
}
print("Score:", teamScore, scoreDecoration)
// Prints "Score: 11 🎉"

Вы можете использовать if и let вместе, чтобы работать со значениями, которые могут отсутствовать. Эти значения представлены как необязательные значения. Необязательное значение либо содержит значение, либо содержит nil, чтобы указать, что значение отсутствует. Напишите знак вопроса (?) после типа значения, чтобы пометить его как необязательное.

var optionalString: String? = "Hello"
print(optionalString == nil)
// Prints "false"

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}

Эксперимент: Измените optionalName на nil. Какое приветствие вы получите? Добавьте условный оператор else, который устанавливает другое приветствие, если optionalName равно nil.

Если необязательное значение nil, условное выражение false и код в фигурных скобках пропускается. В противном случае необязательное значение распаковывается и присваивается константе после let, что делает распакованное значение доступным внутри блока кода.

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

let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"

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

if let nickname {
    print("Hey, \(nickname)")
}
// Doesn't print anything, because nickname is nil.

Переключатели поддерживают любые типы данных и широкий спектр операций сравнения — они не ограничены целыми числами и проверкой на равенство.

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.")
}
// Prints "Is it a spicy red pepper?"

Эксперимент: Попробуйте удалить случай по умолчанию. Какую ошибку вы получите?

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

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

Используйте циклы 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 (_, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)
// Prints "25"

Эксперимент: Замените _ именем переменной и отслеживайте, какой тип числа был наибольшим.

Используйте цикл while для повторения блока кода, пока условие не изменится. Условие цикла может быть в конце, гарантируя, что цикл выполняется как минимум один раз.

var n = 2
while n < 100 {
    n *= 2
}
print(n)
// Prints "128"

var m = 2
repeat {
    m *= 2
} while m < 100
print(m)
// Prints "128"

Эксперимент: Измените условие с m < 100 на m < 0, чтобы увидеть, как while и repeat-while ведут себя по-разному, когда условие цикла изначально ложно.

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

var total = 0
for i in 0..<4 {
    total += i
}
print(total)
// Prints "6"

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

Функции и замыкания

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

func greet(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")

Эксперимент: Удалите параметр day. Добавьте параметр для включения сегодняшнего обеденного спецпредложения в приветствие.

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

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "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)
// Prints "120"
print(statistics.2)
// Prints "120"

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

func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()

Функции являются типами первого класса. Это означает, что функция может возвращать другую функцию в качестве своего значения.

func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

Функция может принимать другую функцию в качестве одного из своих аргументов.

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]
hasAnyMatches(list: numbers, condition: lessThanTen)

Функции фактически являются особым случаем замыканий: блоков кода, которые могут быть вызваны позже. Код в замыкании имеет доступ к таким вещам, как переменные и функции, которые были доступны в области видимости, где было создано замыкание, даже если замыкание находится в другой области видимости при его выполнении — вы уже видели пример этого с вложенными функциями. Вы можете написать замыкание без имени, заключив код в фигурные скобки ({}). Используйте in для разделения аргументов и типа возвращаемого значения от тела.

numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})

Эксперимент: Перепишите замыкание, чтобы оно возвращало ноль для всех нечетных чисел.

У вас есть несколько вариантов, чтобы написать замыкания более лаконично. Когда тип замыкания уже известен, например, обратный вызов для делегата, вы можете опустить тип параметров, его тип возвращаемого значения или оба. Замыкания с одним оператором неявно возвращают значение своего единственного оператора.

let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
// Prints "[60, 57, 21, 36]"

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

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
// Prints "[20, 19, 12, 7]"

Объекты и классы

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

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

Эксперимент: Добавьте свойство константы с let и добавьте другой метод, который принимает аргумент.

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

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")
test.area()
test.simpleDescription()

Эксперимент: Создайте другой подкласс NamedShape, названный Circle, который принимает радиус и имя в качестве аргументов для своего инициализатора. Реализуйте метод area() и метод simpleDescription() в классе Circle.

Помимо простых свойств, которые хранятся, свойства могут иметь getter и setter.

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)
// Prints "9.3"
triangle.perimeter = 9.9
print(triangle.sideLength)
// Prints "3.3000000000000003"

В setter для perimeter новое значение имеет неявное имя newValue. Вы можете указать явное имя в скобках после set.

Обратите внимание, что инициализатор класса EquilateralTriangle имеет три разных этапа:

  1. Установка значения свойств, объявленных подклассом.
  2. Вызов инициализатора суперкласса.
  3. Изменение значения свойств, определенных суперклассом. Любая дополнительная настройка, использующая методы, геттеры или сеттеры, также может быть выполнена на этом этапе.

Если вам не нужно вычислять свойство, но вам все еще нужно предоставить код, выполняемый до и после установки нового значения, используйте 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)
// Prints "10.0"
print(triangleAndSquare.triangle.sideLength)
// Prints "10.0"
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
// Prints "50.0"

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

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength

Перечисления и структуры

Используйте 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
let aceRawValue = ace.rawValue

Эксперимент: Напишите функцию, которая сравнивает два значения Rank, сравнивая их исходные значения.

По умолчанию 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()

Эксперимент: Добавьте метод color() к Suit, который возвращает «черный» для пик и треф, и возвращает «красный» для бубен и червей.

Обратите внимание на два способа обращения к случаю hearts перечисления выше: при присвоении значения константе hearts используется полное имя случая перечисления Suit.hearts, поскольку константа не имеет явного типа. Внутри оператора switch используется сокращенная форма .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)")
}
// Prints "Sunrise is at 6:00 am and sunset is at 8:09 pm."

Эксперимент: Добавьте третий случай к ServerResponse и в оператор switch.

Обратите внимание, как времена восхода и заката извлекаются из значения ServerResponse в рамках сопоставления значения с случаями оператора switch.

Используйте 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()

Эксперимент: Напишите функцию, которая возвращает массив, содержащий полную колоду карт, с одной картой каждой комбинации ранга и масти.

Асинхронность

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

func fetchUserID(from server: String) async -> Int {
    if server == "primary" {
        return 97
    }
    return 501
}

Вы помечаете вызов асинхронной функции, написав await перед ней.

func fetchUsername(from server: String) async -> String {
    let userID = await fetchUserID(from: server)
    if userID == 501 {
        return "John Appleseed"
    }
    return "Guest"
}

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

func connectUser(to server: String) async {
    async let userID = fetchUserID(from: server)
    async let username = fetchUsername(from: server)
    let greeting = await "Hello \(username), user ID \(userID)"
    print(greeting)
}

Используйте Task для вызова асинхронных функций из синхронного кода, не ожидая их возврата.

Task {
    await connectUser(to: "primary")
}
// Prints "Hello Guest, user ID 97"

Используйте группы задач для структурирования асинхронного кода.

let userIDs = await withTaskGroup(of: Int.self) { group in
    for server in ["primary", "secondary", "development"] {
        group.addTask {
            return await fetchUserID(from: server)
        }
    }

    var results: [Int] = []
    for await result in group {
        results.append(result)
    }
    return results
}

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

actor ServerConnection {
    var server: String = "primary"
    private var activeUsers: [Int] = []
    func connect() async -> Int {
        let userID = await fetchUserID(from: server)
        // ... communicate with server ...
        activeUsers.append(userID)
        return userID
    }
}

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

let server = ServerConnection()
let userID = await server.connect()

Протоколы и расширения

Используйте 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()
b.adjust()
let bDescription = b.simpleDescription

Эксперимент: Добавьте еще одно требование к ExampleProtocol. Какие изменения вам нужно внести в SimpleClass и SimpleStructure, чтобы они все еще соответствовали протоколу?

Обратите внимание на использование ключевого слова mutating в объявлении SimpleStructure, чтобы отметить метод, изменяющий структуру. Объявление SimpleClass не требует пометки каких-либо своих методов как изменяющих, поскольку методы класса всегда могут изменять класс.

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

extension Int: ExampleProtocol {
    var simpleDescription: String {
        return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
 }
print(7.simpleDescription)
// Prints "The number 7"

Эксперимент: Напишите расширение для типа Double, которое добавляет свойство absoluteValue.

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

let protocolValue: any ExampleProtocol = a
print(protocolValue.simpleDescription)
// Prints "A very simple class.  Now 100% adjusted."
// print(protocolValue.anotherProperty)  // Uncomment to see the error

Несмотря на то, что переменная 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)
}
// Prints "Job sent"

Эксперимент: Измените имя принтера на "Never Has Toner", чтобы функция send(job:toPrinter:) генерировала ошибку.

Вы можете предоставить несколько блоков `catch`, обрабатывающих определенные ошибки. Вы пишете шаблон после `catch` так же, как вы пишете после `case` в операторе `switch`.

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)
}
// Prints "Job sent"

Эксперимент: Добавьте код для выброса ошибки внутри блока `catch`. Какой тип ошибки вам нужно сгенерировать, чтобы ошибка обрабатывалась первым блоком `catch`? А что насчёт второго и третьего блоков?

Другой способ обработки ошибок — использовать `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
}
if fridgeContains("banana") {
    print("Found a banana")
}
print(fridgeIsOpen)
// Prints "false"

Обобщения

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

func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
    var result: [Item] = []
    for _ in 0..<numberOfTimes {
         result.append(item)
    }
    return result
}
makeArray(repeating: "knock", numberOfTimes: 4)

Вы можете создавать обобщённые формы функций и методов, а также классов, перечислений и структур.

// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
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
}
anyCommonElements([1, 2, 3], [3])

Эксперимент: Измените функцию anyCommonElements(_:_:), чтобы создать функцию, возвращающую массив элементов, общих для любых двух последовательностей.

Написание <T: Equatable> эквивалентно написанию <T> ... where T: Equatable.

This source file is part of the Swift.org open source project
Copyright © 2014 - 2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API