Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Szybka wycieczka

Zaczerpnięte z oryginalnej trasy A Swift Tour na Swift.org z modyfikacjami. Oryginalna zawartość została opracowana przez firmę Apple Inc. Na licencji Creative Commons Attribution 4.0 International (CC BY 4.0) License .
Zobacz na TensorFlow.org Wyświetl źródło na GitHub

Tradycja sugeruje, że pierwszy program w nowym języku powinien wypisywać słowa „Hello, world!” na ekranie. W Swift można to zrobić w jednej linii:

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

Jeśli napisałeś kod w C lub Objective-C, ta składnia wygląda znajomo - w Swift ta linia kodu jest kompletnym programem. Nie musisz importować oddzielnej biblioteki dla funkcji takich jak wejście / wyjście lub obsługa ciągów. Kod napisany w zakresie globalnym jest używany jako punkt wejścia dla programu, więc nie potrzebujesz funkcji main() . Nie musisz też pisać średników na końcu każdej instrukcji.

Ta prezentacja zawiera wystarczająco dużo informacji, aby rozpocząć pisanie kodu w języku Swift, pokazując, jak wykonywać różne zadania programistyczne. Nie martw się, jeśli czegoś nie rozumiesz - wszystko przedstawione w tej wycieczce zostało szczegółowo wyjaśnione w dalszej części tej książki.

Proste wartości

Użyj let aby utworzyć stałą i var aby utworzyć zmienną. Wartość stałej nie musi być znana w czasie kompilacji, ale należy przypisać jej wartość dokładnie raz. Oznacza to, że możesz użyć stałych do nazwania wartości, którą określasz raz, ale której używasz w wielu miejscach.

var myVariable = 42
myVariable = 50
let myConstant = 42

Stała lub zmienna musi mieć ten sam typ, co wartość, którą chcesz do niej przypisać. Jednak nie zawsze musisz jawnie pisać typ. Podanie wartości podczas tworzenia stałej lub zmiennej pozwala kompilatorowi wywnioskować jej typ. W powyższym przykładzie kompilator wnioskuje, że myVariable jest liczbą całkowitą, ponieważ jej wartość początkowa jest liczbą całkowitą.

Jeśli wartość początkowa nie zawiera wystarczających informacji (lub jeśli nie ma wartości początkowej), określ typ, zapisując go po zmiennej, oddzielając go dwukropkiem. Uwaga: użycie Double zamiast Float dla liczb zmiennoprzecinkowych zapewnia większą precyzję i jest domyślnym typem zmiennoprzecinkowym w 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.

Wartości nigdy nie są niejawnie konwertowane na inny typ. Jeśli chcesz przekonwertować wartość na inny typ, jawnie utwórz wystąpienie żądanego typu.

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

// Experiment:
// Try removing the conversion to `String` from the last line. What error do you get?

Istnieje jeszcze prostszy sposób na umieszczenie wartości w ciągach znaków: wpisz wartość w nawiasach, a przed nawiasami napisz ukośnik odwrotny (``). Na przykład:

let apples = 3
"I have \(apples) apples."
"I have 3 apples."

let oranges = 5
"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.

Użyj trzech podwójnych cudzysłowów ( """ ) dla ciągów zajmujących wiele wierszy. Wcięcie na początku każdego cytowanego wiersza jest usuwane, o ile pasuje do wcięcia zamykającego cudzysłowu. Na przykład:

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.

Twórz tablice i słowniki za pomocą nawiasów kwadratowych ( [] ) i uzyskuj dostęp do ich elementów, pisząc indeks lub klucz w nawiasach. Po ostatnim elemencie można umieścić przecinek.

var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
 
var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
occupations
▿ 3 elements
  ▿ 0 : 2 elements

    - key : "Malcolm"
    - value : "Captain"
  ▿ 1 : 2 elements
    - key : "Kaylee"
    - value : "Mechanic"
  ▿ 2 : 2 elements
    - key : "Jayne"
    - value : "Public Relations"

Tablice automatycznie rosną w miarę dodawania elementów.

shoppingList.append("blue paint")
shoppingList
▿ 5 elements

  - 0 : "catfish"
  - 1 : "bottle of water"
  - 2 : "tulips"
  - 3 : "blue paint"
  - 4 : "blue paint"

Aby utworzyć pustą tablicę lub słownik, użyj składni inicjatora.

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

Jeśli można wywnioskować informacje o typie, można zapisać pustą tablicę jako [] i pusty słownik jako [:] - na przykład, gdy ustawiasz nową wartość dla zmiennej lub przekażesz argument do funkcji.

shoppingList = []
occupations = [:]

Kontrola przepływu

Użyj if i switch aby tworzyć warunki warunkowe, a for - in , for , while i repeat - while aby tworzyć pętle. Nawiasy wokół warunku lub zmiennej pętli są opcjonalne. Wymagane są szelki wokół ciała.

let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
teamScore
11

W instrukcji if warunek musi być wyrażeniem boolowskim - oznacza to, że kod taki jak if score { ... } jest błędem, a nie niejawnym porównaniem do zera.

Możesz użyć if i let razem, aby pracować z wartościami, których może brakować. Te wartości są reprezentowane jako opcje. Wartość opcjonalna zawiera wartość lub zawiera nil aby wskazać, że brakuje wartości. Wpisz znak zapytania ( ? ) Po typie wartości, aby oznaczyć wartość jako opcjonalną.

var optionalString: String? = "Hello"
optionalString == nil
false

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}
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`.

Jeśli wartością opcjonalną jest nil , warunek jest false a kod w nawiasach klamrowych jest pomijany. W przeciwnym razie wartość opcjonalna jest rozpakowywana i przypisywana do stałej po let , co sprawia, że ​​nieopakowana wartość jest dostępna wewnątrz bloku kodu.

Innym sposobem obsługi wartości opcjonalnych jest podanie wartości domyślnej przy użyciu ?? operator. Jeśli brakuje wartości opcjonalnej, zamiast niej używana jest wartość domyślna.

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

Przełączniki obsługują dowolny rodzaj danych i szeroką gamę operacji porównania - nie są ograniczone do liczb całkowitych i testów równości.

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?

Zwróć uwagę, jak można użyć let we wzorcu, aby przypisać wartość, która pasowała do tej części wzorca, do stałej.

Po wykonaniu kodu w dopasowanej obudowie przełącznika, program wychodzi z instrukcji switch. Wykonywanie nie jest kontynuowane do następnego przypadku, więc nie ma potrzeby jawnego przerywania przełączania na końcu kodu każdej sprawy.

Używasz for - in do iteracji po elementach w słowniku przez podanie pary nazw do użycia dla każdej pary klucz-wartość. Słowniki są nieuporządkowaną kolekcją, więc ich klucze i wartości są iterowane w dowolnej kolejności.

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

Użyj while aby powtórzyć blok kodu, dopóki warunek się nie zmieni. Zamiast tego stan pętli może znajdować się na końcu, dzięki czemu pętla zostanie uruchomiona przynajmniej raz.

var n = 2
while n < 100 {
    n = n * 2
}

n
128

var m = 2
repeat {
    m = m * 2
} while m < 100

m
128

Indeks można utrzymywać w pętli - albo używając ..< aby utworzyć zakres indeksów, albo pisząc jawną inicjalizację, warunek i inkrementację. Te dwie pętle robią to samo:

var total = 0
for i in 0..<4 {
    total += i
}

total
6

Użyj ..< aby utworzyć zakres pomijający jego górną wartość, i użyj ... aby utworzyć zakres obejmujący obie wartości.

Funkcje i zamknięcia

Użyj func aby zadeklarować funkcję. Wywołaj funkcję, podążając za jej nazwą z listą argumentów w nawiasach. Użyj -> aby oddzielić nazwy i typy parametrów od zwracanego typu funkcji.

func greet(name: String, day: String) -> String {
    return "Hello \(name), today is \(day)."
}
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.

Domyślnie funkcje używają nazw parametrów jako etykiet swoich argumentów. Wpisz niestandardową etykietę argumentu przed nazwą parametru lub wpisz _ aby nie używać etykiety argumentu.

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
"Hello John, today is Wednesday."

Użyj krotki, aby utworzyć wartość złożoną - na przykład, aby zwrócić wiele wartości z funkcji. Do elementów krotki można się odwoływać za pomocą nazwy lub numeru.

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

Funkcje mogą być zagnieżdżane. Funkcje zagnieżdżone mają dostęp do zmiennych zadeklarowanych w funkcji zewnętrznej. Za pomocą funkcji zagnieżdżonych można organizować kod w funkcji, która jest długa lub złożona.

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

Funkcje są pierwszorzędne. Oznacza to, że funkcja może zwrócić inną funkcję jako swoją wartość.

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

Funkcja może przyjąć inną funkcję jako jeden ze swoich argumentów.

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

Funkcje są w rzeczywistości specjalnym przypadkiem domknięć: blokami kodu, które można wywołać później. Kod w zamknięciu ma dostęp do rzeczy, takich jak zmienne i funkcje, które były dostępne w zakresie, w którym zostało utworzone zamknięcie, nawet jeśli zamknięcie znajduje się w innym zakresie, gdy jest wykonywane - przykład tego już widziałeś z zagnieżdżonymi funkcjami. Możesz napisać zamknięcie bez nazwy, otaczając kod nawiasami klamrowymi ( {} ). Użyj in aby oddzielić argumenty i zwrócić typ od treści.

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

  - 0 : 60
  - 1 : 57
  - 2 : 21
  - 3 : 36

// Experiment:
// Rewrite the closure to return zero for all odd numbers.

Masz kilka opcji bardziej zwięzłego pisania zamknięć. Gdy typ zamknięcia jest już znany, na przykład wywołanie zwrotne dla delegata, można pominąć typ jego parametrów, typ zwracany lub oba. Zamknięcia pojedynczych instrukcji niejawnie zwracają wartość ich jedynej instrukcji.

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

Możesz odwoływać się do parametrów po numerze zamiast po nazwie - takie podejście jest szczególnie przydatne w przypadku bardzo krótkich domknięć. Zamknięcie przekazane jako ostatni argument funkcji może pojawić się bezpośrednio po nawiasach. Gdy zamknięcie jest jedynym argumentem funkcji, możesz całkowicie pominąć nawiasy.

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

Obiekty i klasy

Użyj class a po niej nazwy klasy, aby utworzyć klasę. Deklaracja właściwości w klasie jest zapisywana w taki sam sposób, jak deklaracja stałej lub zmiennej, z tą różnicą, że znajduje się w kontekście klasy. Podobnie deklaracje metod i funkcji są zapisywane w ten sam sposób.

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.

Utwórz instancję klasy, umieszczając nawiasy po nazwie klasy. Użyj składni kropkowej, aby uzyskać dostęp do właściwości i metod instancji.

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

W tej wersji klasy Shape brakuje czegoś ważnego: inicjatora do skonfigurowania klasy podczas tworzenia instancji. Użyj init aby go utworzyć.

class NamedShape {
    var numberOfSides: Int = 0
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

Zwróć uwagę, w jaki sposób self jest używane do odróżnienia właściwości name od argumentu name w inicjatorze. Argumenty do inicjatora są przekazywane jak wywołanie funkcji podczas tworzenia wystąpienia klasy. Każda właściwość wymaga przypisania wartości - albo w swojej deklaracji (jak w przypadku numberOfSides ), albo w inicjatorze (jak w przypadku name ).

Użyj deinit aby utworzyć deinicjalizator, jeśli musisz wykonać pewne czyszczenie, zanim obiekt zostanie cofnięty.

Podklasy zawierają nazwę nadklasy po nazwie klasy, oddzielając ją dwukropkiem. Nie ma wymogu, aby klasy stanowiły podklasy dowolnej standardowej klasy głównej, więc w razie potrzeby można dołączyć lub pominąć nadklasę.

Metody w podklasie, które przesłaniają implementację nadklasy, są oznaczone jako override - override metody przez przypadek, bez override , jest wykrywane przez kompilator jako błąd. Kompilator wykrywa również metody z override , które w rzeczywistości nie przesłaniają żadnej metody w nadklasie.

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

Oprócz przechowywanych prostych właściwości właściwości mogą mieć metodę pobierającą i ustawiającą.

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

W programie ustawiającym dla perimeter nowa wartość ma niejawną nazwę newValue . Można podać jednoznaczną nazwę w nawiasach po set .

Zauważ, że inicjator klasy EquilateralTriangle ma trzy różne kroki:

  1. Ustawianie wartości właściwości deklarowanych przez podklasę.

  2. Wywołanie inicjalizatora nadklasy.

  3. Zmiana wartości właściwości zdefiniowanych przez nadklasę. W tym momencie można również wykonać wszelkie dodatkowe prace konfiguracyjne, które wykorzystują metody, metody pobierające lub ustawiające.

Jeśli nie musisz obliczać właściwości, ale nadal musisz dostarczyć kod, który jest uruchamiany przed i po ustawieniu nowej wartości, użyj willSet i didSet . Podany kod jest uruchamiany za każdym razem, gdy wartość zmienia się poza inicjatorem. Na przykład poniższa klasa zapewnia, że ​​długość boku trójkąta jest zawsze taka sama, jak długość boku kwadratu.

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

Podczas pracy z wartościami opcjonalnymi możesz pisać ? przed operacjami, takimi jak metody, właściwości i indeksy dolne. Jeśli wartość przed ? jest nil , wszystko po ? jest ignorowane, a wartość całego wyrażenia wynosi nil . W przeciwnym razie wartość opcjonalna jest rozpakowywana, a wszystko po znaku ? działa na nieopakowanej wartości. W obu przypadkach wartość całego wyrażenia jest wartością opcjonalną.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
optionalSquare?.sideLength
▿ Optional<Double>

  - some : 2.5

Wyliczenia i struktury

Użyj enum aby utworzyć wyliczenie. Podobnie jak klasy i wszystkie inne nazwane typy, wyliczenia mogą mieć skojarzone z nimi metody.

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.

Domyślnie Swift przypisuje nieprzetworzone wartości, zaczynając od zera i zwiększając za każdym razem o jeden, ale możesz zmienić to zachowanie, jawnie określając wartości. W powyższym przykładzie Asowi jawnie nadano nieprzetworzoną wartość 1 , a pozostałe surowe wartości są przypisane w kolejności. Możesz również użyć ciągów lub liczb zmiennoprzecinkowych jako surowego typu wyliczenia. Użyj właściwości rawValue aby uzyskać dostęp do surowej wartości przypadku wyliczenia.

Użyj init?(rawValue:) aby utworzyć wystąpienie wyliczenia z wartości surowej. Zwraca albo przypadek wyliczenia pasujący do surowej wartości, albo nil jeśli nie ma pasującej Rank .

if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}

Wartości przypadków w wyliczeniu są wartościami rzeczywistymi, a nie tylko innym sposobem zapisywania ich surowych wartości. W rzeczywistości w przypadkach, gdy nie ma znaczącej wartości surowej, nie musisz jej podawać.

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.

Zwróć uwagę na dwa sposoby, w jakie powyżej odniesiono się do przypadku Hearts w wyliczeniu: Podczas przypisywania wartości do stałej hearts , do przypadku wyliczenia Suit.Hearts odwołuje się pełna nazwa, ponieważ stała nie ma określonego jawnego typu. Wewnątrz przełącznika przypadek wyliczenia jest określany przez formę skróconą. .Hearts ponieważ wartość self jest już znana jako garnitur. Możesz użyć skróconej formy zawsze, gdy typ wartości jest już znany.

Jeśli wyliczenie ma surowe wartości, te wartości są określane w ramach deklaracji, co oznacza, że ​​każde wystąpienie określonego przypadku wyliczenia ma zawsze tę samą wartość surową. Innym wyborem dla przypadków wyliczenia jest posiadanie wartości skojarzonych ze sprawą - te wartości są określane podczas tworzenia wystąpienia i mogą być różne dla każdego wystąpienia przypadku wyliczenia. Można myśleć o skojarzonych wartościach jako o zachowaniu się jak przechowywane właściwości instancji przypadku wyliczenia.

Na przykład rozważmy przypadek żądania czasu wschodu i zachodu słońca z serwera. Serwer odpowiada, podając żądane informacje lub odpowiada, opisując, co poszło nie tak.

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.

Zwróć uwagę, jak godziny wschodu i zachodu słońca są wyodrębniane z wartości ServerResponse jako część dopasowania wartości do przypadków przełącznika.

Użyj struct aby utworzyć strukturę. Struktury obsługują wiele takich samych zachowań jak klasy, w tym metody i inicjatory. Jedną z najważniejszych różnic między strukturami i klasami jest to, że struktury są zawsze kopiowane, gdy są przekazywane w kodzie, ale klasy są przekazywane przez odwołanie.

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.

Protokoły i rozszerzenia

Użyj protocol aby zadeklarować protokół.

protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}

Wszystkie klasy, wyliczenia i struktury mogą przyjmować protokoły.

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()
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?

Zwróć uwagę na użycie słowa kluczowego mutating w deklaracji SimpleStructure do oznaczenia metody, która modyfikuje strukturę. Deklaracja SimpleClass nie wymaga żadnej z jej metod oznaczonych jako mutujące, ponieważ metody klasy mogą zawsze modyfikować klasę.

Użyj extension aby dodać funkcje do istniejącego typu, takie jak nowe metody i obliczone właściwości. Możesz użyć rozszerzenia, aby dodać zgodność protokołu do typu zadeklarowanego w innym miejscu, a nawet do typu zaimportowanego z biblioteki lub struktury.

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

// Experiment:
// Write an extension for the `Double` type that adds an `absoluteValue` property.

Możesz użyć nazwy protokołu tak samo jak każdego innego nazwanego typu - na przykład, aby utworzyć kolekcję obiektów, które mają różne typy, ale wszystkie są zgodne z jednym protokołem. Podczas pracy z wartościami, których typ jest typem protokołu, metody spoza definicji protokołu nie są dostępne.

let protocolValue: ExampleProtocol = a
protocolValue.simpleDescription
"A very simple class.  Now 100% adjusted."

// Uncomment to see the error.
// protocolValue.anotherProperty

Mimo że zmienna protocolValue ma typ środowiska wykonawczego SimpleClass , kompilator traktuje ją jako dany typ ExampleProtocol . Oznacza to, że nie można przypadkowo uzyskać dostępu do metod lub właściwości, które klasa implementuje oprócz zgodności z protokołem.

Obsługa błędów

Błędy reprezentujesz za pomocą dowolnego typu, który przyjmuje protokół Error .

enum PrinterError: Error {
    case outOfPaper
    case noToner
    case onFire
}

Użyj funkcji throw aby zgłosić błąd, a throws aby oznaczyć funkcję, która może zgłosić błąd. Jeśli zgłosisz błąd w funkcji, funkcja zwraca natychmiast, a kod, który wywołał funkcję, obsługuje błąd.

func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}

Istnieje kilka sposobów radzenia sobie z błędami. Jednym ze sposobów jest użycie do-catch . Wewnątrz do bloku, to oznaczenie kodowe, które mogą rzucić błąd pisząc spróbować przed nim. Wewnątrz bloku catch błąd jest automatycznie nadawany error nazwy, chyba że nadasz mu inną nazwę.

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.

Możesz podać wiele bloków catch które obsługują określone błędy. Piszesz wzór po catch tak jak robisz to po case w przełączniku.

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?

Innym sposobem radzenia sobie z błędami jest użycie try? aby przekonwertować wynik na opcjonalny. Jeśli funkcja zgłasza błąd, określony błąd jest odrzucany, a wynik jest nil . W przeciwnym razie wynik jest opcjonalny i zawiera wartość zwróconą przez funkcję.

let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")

Użyj defer aby napisać blok kodu, który jest wykonywany po całym innym kodzie w funkcji, tuż przed powrotem funkcji. Kod jest wykonywany niezależnie od tego, czy funkcja zgłasza błąd. Możesz użyć defer aby napisać obok siebie kod konfiguracji i czyszczenia, nawet jeśli muszą one być wykonywane w różnym czasie.

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
}
fridgeContains("banana")
fridgeIsOpen
false

Generics

Wpisz nazwę w nawiasach ostrych, aby utworzyć ogólną funkcję lub typ.

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)
▿ 4 elements

  - 0 : "knock"
  - 1 : "knock"
  - 2 : "knock"
  - 3 : "knock"

Możesz tworzyć ogólne formy funkcji i metod, a także klas, wyliczeń i struktur.

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

Użyj parametru where po nazwie typu, aby określić listę wymagań - na przykład, aby wymagać, aby typ implementował protokół, aby dwa typy były takie same lub aby klasa miała określoną nadklasę.

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])
true

Pisanie <T: Equatable> jest tym samym, co pisanie <T> ... where T: Equatable .