RSVP für Ihr lokales TensorFlow Everywhere-Event noch heute!
Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Eine schnelle Tour

Adaptiert von der ursprünglichen A Swift Tour auf Swift.org mit Modifikationen. Der ursprüngliche Inhalt wurde von Apple Inc. verfasst und unter der Creative Commons Attribution 4.0 International (CC BY 4.0) -Lizenz lizenziert .
Ansicht auf TensorFlow.org Quelle auf GitHub anzeigen

Die Tradition schlägt vor, dass das erste Programm in einer neuen Sprache die Wörter "Hallo Welt!" auf dem Bildschirm. In Swift kann dies in einer einzigen Zeile erfolgen:

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

Wenn Sie Code in C oder Objective-C geschrieben haben, kommt Ihnen diese Syntax bekannt vor - in Swift ist diese Codezeile ein vollständiges Programm. Sie müssen keine separate Bibliothek für Funktionen wie Eingabe / Ausgabe oder Zeichenfolgenbehandlung importieren. Im globalen Bereich geschriebener Code wird als Einstiegspunkt für das Programm verwendet, sodass Sie keine main() Funktion benötigen. Sie müssen auch keine Semikolons am Ende jeder Anweisung schreiben.

Diese Tour bietet Ihnen genügend Informationen, um Code in Swift zu schreiben, indem sie Ihnen zeigt, wie Sie eine Vielzahl von Programmieraufgaben ausführen können. Machen Sie sich keine Sorgen, wenn Sie etwas nicht verstehen - alles, was in dieser Tour vorgestellt wird, wird im Rest dieses Buches ausführlich erklärt.

Einfache Werte

Verwenden Sie let , um eine Konstante zu var , und var , um eine Variable zu erstellen. Der Wert einer Konstante muss zur Kompilierungszeit nicht bekannt sein, aber Sie müssen ihr genau einmal einen Wert zuweisen. Dies bedeutet, dass Sie Konstanten verwenden können, um einen Wert zu benennen, den Sie einmal bestimmen, aber an vielen Stellen verwenden.

var myVariable = 42
myVariable = 50
let myConstant = 42

Eine Konstante oder Variable muss denselben Typ haben wie der Wert, den Sie ihr zuweisen möchten. Sie müssen den Typ jedoch nicht immer explizit schreiben. Wenn Sie beim Erstellen einer Konstanten oder Variablen einen Wert angeben, kann der Compiler auf seinen Typ schließen. Im obigen Beispiel schließt der Compiler, dass myVariable eine Ganzzahl ist, da sein Anfangswert eine Ganzzahl ist.

Wenn der Anfangswert nicht genügend Informationen enthält (oder wenn kein Anfangswert vorhanden ist), geben Sie den Typ an, indem Sie ihn nach der durch einen Doppelpunkt getrennten Variablen schreiben. Hinweis: Die Verwendung von Double anstelle von Float für Gleitkommazahlen bietet mehr Genauigkeit und ist der Standard-Gleitkommatyp in 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.

Werte werden niemals implizit in einen anderen Typ konvertiert. Wenn Sie einen Wert in einen anderen Typ konvertieren müssen, erstellen Sie explizit eine Instanz des gewünschten Typs.

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?

Es gibt eine noch einfachere Möglichkeit, Werte in Zeichenfolgen einzufügen: Schreiben Sie den Wert in Klammern und schreiben Sie einen Backslash (``) vor die Klammern. Beispielsweise:

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.

Verwenden Sie drei doppelte Anführungszeichen ( """ ) für Zeichenfolgen, die mehrere Zeilen einnehmen. Der Einzug am Anfang jeder in Anführungszeichen gesetzten Zeile wird entfernt, sofern er mit dem Einzug der schließenden Anführungszeichen übereinstimmt. Beispiel:

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.

Erstellen Sie Arrays und Wörterbücher mit Klammern ( [] ) und greifen Sie auf ihre Elemente zu, indem Sie den Index oder den Schlüssel in Klammern schreiben. Nach dem letzten Element ist ein Komma zulässig.

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

Arrays wachsen automatisch, wenn Sie Elemente hinzufügen.

shoppingList.append("blue paint")
print(shoppingList)
["catfish", "bottle of water", "tulips", "blue paint", "blue paint"]

Verwenden Sie die Initialisierersyntax, um ein leeres Array oder Wörterbuch zu erstellen.

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

Wenn Typinformationen abgeleitet werden können, können Sie ein leeres Array als [] und ein leeres Wörterbuch als [:] schreiben - beispielsweise, wenn Sie einen neuen Wert für eine Variable festlegen oder ein Argument an eine Funktion übergeben.

shoppingList = []
occupations = [:]

Kontrollfluss

Verwenden Sie if und switch , um Bedingungen zu erstellen, und for - in , for , while und repeat - while , um Schleifen zu erstellen. Klammern um die Bedingungs- oder Schleifenvariable sind optional. Zahnspangen um den Körper sind erforderlich.

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

In einer if Anweisung muss die Bedingung ein boolescher Ausdruck sein. Dies bedeutet, dass Code, z. B. if score { ... } ein Fehler ist, kein impliziter Vergleich mit Null.

Sie können if und let zusammen verwenden, um mit möglicherweise fehlenden Werten zu arbeiten. Diese Werte werden als Option dargestellt. Ein optionaler Wert enthält entweder einen Wert oder nil um anzuzeigen, dass ein Wert fehlt. Schreiben Sie ein Fragezeichen ( ? ) Nach dem Typ eines Werts, um den Wert als optional zu markieren.

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

Wenn der optionale Wert nil , ist die Bedingung false und der Code in geschweiften Klammern wird übersprungen. Andernfalls wird der optionale Wert entpackt und der Konstante after let zugewiesen, wodurch der entpackte Wert im Codeblock verfügbar wird.

Eine andere Möglichkeit, mit optionalen Werten umzugehen, besteht darin, einen Standardwert mit dem ?? Operator. Wenn der optionale Wert fehlt, wird stattdessen der Standardwert verwendet.

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

Switches unterstützen jede Art von Daten und eine Vielzahl von Vergleichsoperationen - sie sind nicht auf Ganzzahlen und Tests auf Gleichheit beschränkt.

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?

Beachten Sie, wie let in einem Muster verwendet werden kann, um den Wert, der diesem Teil eines Musters entspricht, einer Konstanten zuzuweisen.

Nachdem der Code in dem übereinstimmenden Switch-Fall ausgeführt wurde, verlässt das Programm die switch-Anweisung. Die Ausführung wird nicht mit dem nächsten Fall fortgesetzt, sodass es nicht erforderlich ist, den Schalter am Ende des Codes jedes Falls explizit auszubrechen.

Sie verwenden for - in , um Elemente in einem Wörterbuch zu durchlaufen, indem Sie ein Namenspaar angeben, das für jedes Schlüssel-Wert-Paar verwendet werden soll. Wörterbücher sind eine ungeordnete Sammlung, daher werden ihre Schlüssel und Werte in einer beliebigen Reihenfolge wiederholt.

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.

Verwenden Sie while , um einen Codeblock zu wiederholen, bis sich eine Bedingung ändert. Der Zustand einer Schleife kann stattdessen am Ende sein, wodurch sichergestellt wird, dass die Schleife mindestens einmal ausgeführt wird.

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

Sie können einen Index in einer Schleife halten - entweder mit ..< , um einen Indexbereich zu erstellen, oder indem Sie eine explizite Initialisierung, Bedingung und ein Inkrement schreiben. Diese beiden Schleifen machen dasselbe:

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

print(total)
6

Verwenden Sie ..< , um einen Bereich zu erstellen, in dem der obere Wert weggelassen wird, und verwenden Sie ... , um einen Bereich zu erstellen, der beide Werte enthält.

Funktionen und Verschlüsse

Verwenden Sie func , um eine Funktion zu deklarieren. Rufen Sie eine Funktion auf, indem Sie ihrem Namen eine Liste von Argumenten in Klammern folgen. Verwenden Sie -> , um die Parameternamen und -typen vom Rückgabetyp der Funktion zu trennen.

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.

Standardmäßig verwenden Funktionen ihre Parameternamen als Beschriftungen für ihre Argumente. Schreiben Sie eine benutzerdefinierte Argumentbezeichnung vor den Parameternamen oder schreiben Sie _ , um keine Argumentbezeichnung zu verwenden.

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

Verwenden Sie ein Tupel, um einen zusammengesetzten Wert zu erstellen, z. B. um mehrere Werte von einer Funktion zurückzugeben. Die Elemente eines Tupels können entweder mit Namen oder mit Nummer bezeichnet werden.

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

Funktionen können verschachtelt werden. Verschachtelte Funktionen haben Zugriff auf Variablen, die in der äußeren Funktion deklariert wurden. Sie können verschachtelte Funktionen verwenden, um den Code in einer langen oder komplexen Funktion zu organisieren.

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

Funktionen sind ein erstklassiger Typ. Dies bedeutet, dass eine Funktion eine andere Funktion als Wert zurückgeben kann.

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

Eine Funktion kann eine andere Funktion als eines ihrer Argumente verwenden.

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

Funktionen sind eigentlich ein Sonderfall von Schließungen: Codeblöcke, die später aufgerufen werden können. Der Code in einem Abschluss hat Zugriff auf Variablen und Funktionen, die in dem Bereich verfügbar waren, in dem der Abschluss erstellt wurde, auch wenn sich der Abschluss bei seiner Ausführung in einem anderen Bereich befindet. Sie haben ein Beispiel dafür bereits mit verschachtelten Funktionen gesehen. Sie können einen Abschluss ohne Namen schreiben, indem Sie den Code mit geschweiften Klammern ( {} ) umgeben. Verwenden Sie in , um die Argumente zu trennen und den Typ vom Text zurückzugeben.

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.

Sie haben mehrere Möglichkeiten, um Verschlüsse präziser zu schreiben. Wenn der Typ eines Abschlusses bereits bekannt ist, z. B. der Rückruf für einen Delegaten, können Sie den Typ seiner Parameter, seinen Rückgabetyp oder beides weglassen. Einzelanweisungsabschlüsse geben implizit den Wert ihrer einzigen Anweisung zurück.

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

Sie können Parameter nach Nummer anstatt nach Name referenzieren - dieser Ansatz ist besonders bei sehr kurzen Abschlüssen nützlich. Ein Abschluss, der als letztes Argument an eine Funktion übergeben wurde, kann unmittelbar nach den Klammern erscheinen. Wenn ein Abschluss das einzige Argument für eine Funktion ist, können Sie die Klammern vollständig weglassen.

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

Objekte und Klassen

Verwenden Sie class gefolgt vom Klassennamen, um eine Klasse zu erstellen. Eine Eigenschaftsdeklaration in einer Klasse wird genauso geschrieben wie eine Konstanten- oder Variablendeklaration, außer dass sie sich im Kontext einer Klasse befindet. Ebenso werden Methoden- und Funktionsdeklarationen auf die gleiche Weise geschrieben.

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.

Erstellen Sie eine Instanz einer Klasse, indem Sie nach dem Klassennamen Klammern setzen. Verwenden Sie die Punktsyntax, um auf die Eigenschaften und Methoden der Instanz zuzugreifen.

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

In dieser Version der Shape Klasse fehlt etwas Wichtiges: ein Initialisierer zum Einrichten der Klasse beim Erstellen einer Instanz. Verwenden Sie init , um eine zu erstellen.

class NamedShape {
    var numberOfSides: Int = 0
    var name: String

    init(name: String) {
        self.name = name
    }

    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

Beachten Sie, wie self verwendet wird, um die name Eigenschaft vom name Argument zum Initialisierer zu unterscheiden. Die Argumente an den Initialisierer werden wie ein Funktionsaufruf übergeben, wenn Sie eine Instanz der Klasse erstellen. Jeder Eigenschaft muss ein Wert zugewiesen werden - entweder in ihrer Deklaration (wie bei numberOfSides ) oder im Initialisierer (wie bei name ).

Verwenden Sie deinit , um einen Deinitializer zu erstellen, wenn Sie eine Bereinigung durchführen müssen, bevor die deinit des Objekts aufgehoben wird.

Unterklassen enthalten ihren Oberklassennamen nach ihrem Klassennamen, getrennt durch einen Doppelpunkt. Es ist nicht erforderlich, dass Klassen eine Standardstammklasse unterklassifizieren, sodass Sie bei Bedarf eine Oberklasse einschließen oder weglassen können.

Methoden in einer Unterklasse, die die Implementierung der Oberklasse überschreiben, sind mit override gekennzeichnet. Das versehentliche override einer Methode ohne override wird vom Compiler als Fehler erkannt. Der Compiler erkennt auch Methoden mit override , die keine Methode in der Oberklasse überschreiben.

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.

Zusätzlich zu einfachen Eigenschaften, die gespeichert werden, können Eigenschaften einen Getter und einen Setter haben.

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

Im Setter für perimeter hat der neue Wert den impliziten Namen newValue . Sie können nach dem Festlegen einen expliziten Namen in Klammern set .

Beachten Sie, dass der Initialisierer für die EquilateralTriangle Klasse drei verschiedene Schritte umfasst:

  1. Festlegen des Werts von Eigenschaften, die von der Unterklasse deklariert werden.

  2. Aufruf des Initialisierers der Oberklasse.

  3. Ändern des Werts der von der Oberklasse definierten Eigenschaften. An dieser Stelle können auch zusätzliche Einrichtungsarbeiten durchgeführt werden, bei denen Methoden, Getter oder Setter verwendet werden.

Wenn Sie die Eigenschaft nicht berechnen müssen, aber dennoch Code bereitstellen müssen, der vor und nach dem Festlegen eines neuen Werts ausgeführt wird, verwenden Sie willSet und didSet . Der von Ihnen angegebene Code wird jedes Mal ausgeführt, wenn sich der Wert außerhalb eines Initialisierers ändert. Die folgende Klasse stellt beispielsweise sicher, dass die Seitenlänge des Dreiecks immer der Seitenlänge des Quadrats entspricht.

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

Wenn Sie mit optionalen Werten arbeiten, können Sie schreiben ? vor Operationen wie Methoden, Eigenschaften und Subskription. Ist der Wert vor dem ? ist nil , alles nach dem ? wird ignoriert und der Wert des gesamten Ausdrucks ist nil . Andernfalls wird der optionale Wert ausgepackt und alles nach dem ? wirkt auf den unverpackten Wert. In beiden Fällen ist der Wert des gesamten Ausdrucks ein optionaler Wert.

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

Aufzählungen und Strukturen

Verwenden Sie enum , um eine Aufzählung zu erstellen. Wie Klassen und alle anderen benannten Typen können auch Aufzählungen Methoden zugeordnet werden.

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.

Standardmäßig weist Swift die Rohwerte zu, die bei Null beginnen und jedes Mal um eins erhöht werden. Sie können dieses Verhalten jedoch ändern, indem Sie explizit Werte angeben. Im obigen Beispiel erhält Ace explizit den Rohwert 1 , und die restlichen Rohwerte werden der Reihe nach zugewiesen. Sie können auch Zeichenfolgen oder Gleitkommazahlen als Rohtyp einer Aufzählung verwenden. Verwenden Sie die Eigenschaft rawValue um auf den Rohwert eines Aufzählungsfalls zuzugreifen.

Verwenden Sie den Initialisierer init?(rawValue:) , um eine Instanz einer Aufzählung aus einem Rohwert zu init?(rawValue:) . Es wird entweder der Aufzählungsfall zurückgegeben, der mit dem Rohwert übereinstimmt, oder nil wenn kein übereinstimmender Rank .

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

Die Fallwerte einer Aufzählung sind tatsächliche Werte und nicht nur eine andere Art, ihre Rohwerte zu schreiben. In Fällen, in denen es keinen aussagekräftigen Rohwert gibt, müssen Sie keinen angeben.

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.

Beachten Sie die zwei Möglichkeiten , dass die Hearts - Fall der Aufzählung oben genannten: Wenn Wert auf die Zuweisung von hearts Konstante, die Aufzählung Fall Suit.Hearts bezeichnet mit vollem Namen , weil die Konstante angegeben keine explizite Typ. Innerhalb des Schalters wird der Aufzählungsfall durch die abgekürzte Form bezeichnet. .Hearts da der Wert des self bereits als Anzug bekannt ist. Sie können das abgekürzte Formular immer dann verwenden, wenn der Werttyp bereits bekannt ist.

Wenn eine Aufzählung Rohwerte hat, werden diese Werte als Teil der Deklaration bestimmt. Dies bedeutet, dass jede Instanz eines bestimmten Aufzählungsfalls immer denselben Rohwert hat. Eine andere Möglichkeit für Aufzählungsfälle besteht darin, dem Fall Werte zuzuordnen. Diese Werte werden beim Erstellen der Instanz festgelegt und können für jede Instanz eines Aufzählungsfalls unterschiedlich sein. Sie können sich vorstellen, dass sich die zugehörigen Werte wie gespeicherte Eigenschaften der Aufzählungsfallinstanz verhalten.

Betrachten Sie beispielsweise den Fall, dass Sie die Sonnenauf- und -untergangszeiten von einem Server anfordern. Der Server antwortet entweder mit den angeforderten Informationen oder mit einer Beschreibung dessen, was schief gelaufen ist.

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.

Beachten Sie, wie die Sonnenauf- und -untergangszeiten aus dem ServerResponse Wert extrahiert werden, um den Wert mit den Switch-Fällen abzugleichen.

Verwenden Sie struct , um eine Struktur zu erstellen. Strukturen unterstützen viele der gleichen Verhaltensweisen wie Klassen, einschließlich Methoden und Initialisierer. Einer der wichtigsten Unterschiede zwischen Strukturen und Klassen besteht darin, dass Strukturen immer kopiert werden, wenn sie in Ihrem Code herumgereicht werden, Klassen jedoch als Referenz übergeben werden.

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.

Protokolle und Erweiterungen

Verwenden Sie das protocol , um ein Protokoll zu deklarieren.

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

Klassen, Aufzählungen und Strukturen können Protokolle übernehmen.

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?

Beachten Sie die Verwendung des mutating Schlüsselworts in der Deklaration von SimpleStructure , um eine Methode zu markieren, die die Struktur ändert. Die Deklaration von SimpleClass benötigt keine ihrer Methoden, die als mutierend markiert sind, da Methoden für eine Klasse die Klasse immer ändern können.

Verwenden Sie die extension , um einem vorhandenen Typ Funktionen hinzuzufügen, z. B. neue Methoden und berechnete Eigenschaften. Sie können eine Erweiterung verwenden, um Protokollkonformität zu einem Typ hinzuzufügen, der an anderer Stelle deklariert ist, oder sogar zu einem Typ, den Sie aus einer Bibliothek oder einem Framework importiert haben.

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.

Sie können einen Protokollnamen wie jeden anderen benannten Typ verwenden, um beispielsweise eine Sammlung von Objekten zu erstellen, die unterschiedliche Typen haben, aber alle einem einzigen Protokoll entsprechen. Wenn Sie mit Werten arbeiten, deren Typ ein Protokolltyp ist, sind Methoden außerhalb der Protokolldefinition nicht verfügbar.

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

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

Obwohl die Variable protocolValue Laufzeittyp SimpleClass , behandelt der Compiler ihn als den angegebenen Typ von ExampleProtocol . Dies bedeutet, dass Sie nicht versehentlich auf Methoden oder Eigenschaften zugreifen können, die die Klasse zusätzlich zu ihrer Protokollkonformität implementiert.

Fehlerbehandlung

Sie stellen Fehler mit jedem Typ dar, der das Error übernimmt.

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

Verwenden Sie throw um einen Fehler throws , und throw, um eine Funktion zu markieren, die einen Fehler auslösen kann. Wenn Sie einen Fehler in eine Funktion auslösen, wird die Funktion sofort zurückgegeben und der Code, der die Funktion aufgerufen hat, behandelt den Fehler.

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

Es gibt verschiedene Möglichkeiten, mit Fehlern umzugehen. Eine Möglichkeit ist die Verwendung von do-catch . Innerhalb des do Blocks markieren Sie Code, der einen Fehler auslösen kann, indem Sie try davor schreiben. Innerhalb des catch Blocks erhält der Fehler automatisch den error sofern Sie ihm keinen anderen Namen geben.

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.

Sie können mehrere catch Blöcke bereitstellen, die bestimmte Fehler behandeln. Sie schreiben ein Muster nach dem catch genauso wie nach dem case in einem Schalter.

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?

Eine andere Möglichkeit, mit Fehlern umzugehen, ist die Verwendung von try? um das Ergebnis in ein optionales zu konvertieren. Wenn die Funktion einen Fehler auslöst, wird der spezifische Fehler verworfen und das Ergebnis ist nil . Andernfalls ist das Ergebnis optional und enthält den Wert, den die Funktion zurückgegeben hat.

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

Verwenden Sie defer , um einen Codeblock zu schreiben, der nach allen anderen Codes in der Funktion ausgeführt wird, kurz bevor die Funktion zurückkehrt. Der Code wird unabhängig davon ausgeführt, ob die Funktion einen Fehler auslöst. Sie können defer , um Setup- und Bereinigungscode nebeneinander zu schreiben, obwohl sie zu unterschiedlichen Zeiten ausgeführt werden müssen.

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

Generika

Schreiben Sie einen Namen in spitze Klammern, um eine generische Funktion oder einen generischen Typ zu erstellen.

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

Sie können generische Formen von Funktionen und Methoden sowie Klassen, Aufzählungen und Strukturen erstellen.

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

Verwenden Sie where nach dem Typnamen, um eine Liste von Anforderungen anzugeben, z. B. um zu verlangen, dass der Typ ein Protokoll implementiert, dass zwei Typen identisch sind oder dass eine Klasse eine bestimmte Oberklasse hat.

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

Das Schreiben von <T: Equatable> ist dasselbe wie das Schreiben von <T> ... where T: Equatable .