סיור סוויפט

מותאם מהסיור המקורי A Swift ב- Swift.org עם שינויים. התוכן המקורי נכתב על ידי Apple Inc. ברישיון תחת רישיון Creative Commons Attribution 4.0 International (CC BY 4.0) .
הצג באתר TensorFlow.org הפעל ב-Google Colab צפה במקור ב-GitHub

המסורת מציעה שהתוכנית הראשונה בשפה חדשה צריכה להדפיס את המילים "שלום, עולם!" על המסך. בסוויפט ניתן לעשות זאת בשורה אחת:

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

אם כתבת קוד ב-C או ב-Objective-C, התחביר הזה נראה לך מוכר - ב-Swift, שורת הקוד הזו היא תוכנית שלמה. אינך צריך לייבא ספריה נפרדת עבור פונקציונליות כמו קלט/פלט או טיפול במחרוזות. קוד שנכתב בהיקף גלובלי משמש כנקודת הכניסה לתוכנית, כך שאינך זקוק לפונקציה main() . אתה גם לא צריך לכתוב נקודה-פסיק בסוף כל הצהרה.

סיור זה נותן לך מספיק מידע כדי להתחיל לכתוב קוד בסוויפט על ידי כך שהוא מראה לך כיצד לבצע מגוון משימות תכנות. אל תדאג אם אתה לא מבין משהו - כל מה שהוצג בסיור זה מוסבר בפירוט בהמשך הספר הזה.

ערכים פשוטים

השתמש let כדי ליצור קבוע וב- var כדי ליצור משתנה. הערך של קבוע לא צריך להיות ידוע בזמן הקומפילציה, אבל עליך להקצות לו ערך פעם אחת בדיוק. זה אומר שאתה יכול להשתמש בקבועים כדי לתת שם לערך שאתה קובע פעם אחת אבל משתמש במקומות רבים.

var myVariable = 42
myVariable = 50
let myConstant = 42

קבוע או משתנה חייב להיות זהה לערך שברצונך להקצות לו. עם זאת, לא תמיד צריך לכתוב את הסוג במפורש. מתן ערך בעת יצירת קבוע או משתנה מאפשר למהדר להסיק מהסוג שלו. בדוגמה למעלה, המהדר מסיק ש- myVariable הוא מספר שלם מכיוון שהערך ההתחלתי שלו הוא מספר שלם.

אם הערך ההתחלתי אינו מספק מספיק מידע (או אם אין ערך התחלתי), ציין את הסוג על ידי כתיבתו אחרי המשתנה, מופרד בנקודתיים. הערה: שימוש Double במקום Float למספרי נקודה צפה נותן לך דיוק רב יותר, והוא סוג הנקודה הצפה המוגדרת כברירת מחדל ב-Swift.

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
// Experiment:
// Create a constant with an explicit type of `Float` and a value of 4.

ערכים לעולם אינם מומרים באופן מרומז לסוג אחר. אם אתה צריך להמיר ערך לסוג אחר, צור במפורש מופע מהסוג הרצוי.

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

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

ישנה דרך פשוטה אפילו יותר לכלול ערכים במחרוזות: כתוב את הערך בסוגריים, וכתוב קו נטוי אחורי (``) לפני הסוגריים. לדוגמה:

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

let oranges = 5
print("I have \(apples + oranges) pieces of fruit.")
I have 8 pieces of fruit.

// Experiment:
// Use `\()` to include a floating-point calculation in a string and to include someone's name in a
// greeting.

השתמש בשלושה מרכאות כפולות ( """ ) עבור מחרוזות שתופסות שורות מרובות. הזחה בתחילת כל שורה במירכאות מוסרת, כל עוד היא תואמת את ההזחה של המרכאות הסוגרות. לדוגמה:

let quotation = """
    Even though there's whitespace to the left,
    the actual lines aren't indented.
        Except for this line.
    Double quotes (") can appear without being escaped.

    I still have \(apples + oranges) pieces of fruit.
    """
print(quotation)
Even though there's whitespace to the left,
the actual lines aren't indented.
    Except for this line.
Double quotes (") can appear without being escaped.

I still have 8 pieces of fruit.

צור מערכים ומילון באמצעות סוגריים בסוגריים ( [] ), וקבל גישה לאלמנטים שלהם על ידי כתיבת האינדקס או המפתח בסוגריים. פסיק מותר אחרי האלמנט האחרון.

var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"

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

מערכים גדלים אוטומטית תוך כדי הוספת אלמנטים.

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

כדי ליצור מערך ריק או מילון, השתמש בתחביר האתחול.

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

אם ניתן להסיק מידע על סוג, אתה יכול לכתוב מערך ריק בתור [] ומילון ריק בתור [:] - לדוגמה, כאשר אתה מגדיר ערך חדש למשתנה או מעביר ארגומנט לפונקציה.

shoppingList = []
occupations = [:]

בקרת זרימה

השתמש if switch כדי ליצור תנאים, והשתמש for - in , for , while repeat - while ליצור לולאות. סוגריים סביב התנאי או משתנה הלולאה הם אופציונליים. יש צורך בפלטה מסביב לגוף.

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

במשפט if , התנאי חייב להיות ביטוי בוליאני - פירוש הדבר שקוד כגון if score { ... } הוא שגיאה, לא השוואה מרומזת לאפס.

אתה יכול להשתמש if let ביחד כדי לעבוד עם ערכים שאולי חסרים. ערכים אלה מיוצגים כאופציונליים. ערך אופציונלי מכיל ערך או מכיל nil כדי לציין שחסר ערך. כתוב סימן שאלה ( ? ) אחרי סוג הערך כדי לסמן את הערך כאופציונלי.

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

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

// Experiment:
// Change `optionalName` to `nil`. What greeting do you get?
// Add an `else` clause that sets a different greeting if `optionalName` is `nil`.

אם הערך האופציונלי הוא nil , התנאי הוא false והקוד בסוגריים נדלג. אחרת, הערך האופציונלי מפורק ומוקצה לקבוע שאחרי let , מה שהופך את הערך הלא-עטוף לזמין בתוך גוש הקוד.

דרך נוספת לטפל בערכים אופציונליים היא לספק ערך ברירת מחדל באמצעות ?? מַפעִיל. אם הערך האופציונלי חסר, ערך ברירת המחדל משמש במקום זאת.

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

מתגים תומכים בכל סוג של נתונים ובמגוון רחב של פעולות השוואה - הם אינם מוגבלים למספרים שלמים ולבדיקות לשוויון.

let vegetable = "red pepper"
switch vegetable {
case "celery":
    print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
    print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
    print("Is it a spicy \(x)?")
default:
    print("Everything tastes good in soup.")
}
Is it a spicy red pepper?

// Experiment:
// Try removing the default case. What error do you get?

שימו לב כיצד ניתן להשתמש ב- let בתבנית כדי להקצות את הערך שתאם את החלק הזה של התבנית לקבוע.

לאחר ביצוע הקוד בתוך מקרה ה-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 (kind, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)
25

// Experiment:
// Add another variable to keep track of which kind of number was the largest, as well as what that
// largest number was.

השתמש while כדי לחזור על גוש קוד עד לשינוי תנאי. התנאי של לולאה יכול להיות בסוף במקום זאת, כדי להבטיח שהלולאה מופעלת לפחות פעם אחת.

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

print(n)
128

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

print(m)
128

אתה יכול לשמור אינדקס בלולאה - או באמצעות ..< כדי ליצור טווח של אינדקסים או על ידי כתיבת אתחול, תנאי והגדלה מפורשת. שתי הלולאות האלה עושות את אותו הדבר:

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

print(total)
6

השתמש ב ..< כדי ליצור טווח שמשמיט את הערך העליון שלו, והשתמש ב ... כדי ליצור טווח הכולל את שני הערכים.

פונקציות וסגירות

השתמש func כדי להכריז על פונקציה. קרא לפונקציה על ידי ביצוע שמה עם רשימת ארגומנטים בסוגריים. השתמש -> כדי להפריד בין שמות וסוגי הפרמטרים לסוג ההחזרה של הפונקציה.

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

// Experiment:
// Remove the `day` parameter. Add a parameter to include today’s lunch special in the greeting.

כברירת מחדל, פונקציות משתמשות בשמות הפרמטרים שלהן בתור תוויות עבור הארגומנטים שלהן. כתוב תווית ארגומנט מותאמת אישית לפני שם הפרמטר, או כתוב _ כדי לא להשתמש בתווית ארגומנט.

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

השתמש ב-tuple כדי ליצור ערך מורכב - לדוגמה, כדי להחזיר ערכים מרובים מפונקציה. ניתן להתייחס לרכיבים של tuple או לפי שם או לפי מספר.

func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0

    for score in scores {
        if score > max {
            max = score
        } else if score < min {
            min = score
        }
        sum += score
    }

    return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
120
120

ניתן לקנן פונקציות. לפונקציות מקוננות יש גישה למשתנים שהוכרזו בפונקציה החיצונית. ניתן להשתמש בפונקציות מקוננות כדי לארגן את הקוד בפונקציה ארוכה או מורכבת.

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

פונקציות הן סוג ממדרגה ראשונה. המשמעות היא שפונקציה יכולה להחזיר פונקציה אחרת כערך שלה.

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

פונקציה יכולה לקחת פונקציה אחרת כאחד מהארגומנטים שלה.

func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
print(hasAnyMatches(list: numbers, condition: lessThanTen))
true

פונקציות הן למעשה מקרה מיוחד של סגירות: בלוקים של קוד שניתן לקרוא להם מאוחר יותר. לקוד בסגירה יש גישה לדברים כמו משתנים ופונקציות שהיו זמינות ב-scope שבו הסגירה נוצרה, גם אם הסגירה נמצאת ב-scope אחר בעת ביצועה - ראית דוגמה לכך כבר עם פונקציות מקוננות. אתה יכול לכתוב סגירה ללא שם על ידי קוד מסביב עם סוגרים ( {} ). השתמש in כדי להפריד בין הארגומנטים לבין סוג החזרה מהגוף.

let mappedNumbers = numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})
print(mappedNumbers)
[60, 57, 21, 36]

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

יש לך כמה אפשרויות לכתוב סגירות בצורה תמציתית יותר. כאשר סוג הסגירה כבר ידוע, כגון התקשרות חוזרת עבור נציג, אתה יכול להשמיט את סוג הפרמטרים שלו, סוג ההחזר שלו או שניהם. סגירות של הצהרות בודדות מחזירות באופן מרומז את הערך של ההצהרה היחידה שלהן.

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

אתה יכול להתייחס לפרמטרים לפי מספר במקום לפי שם - גישה זו שימושית במיוחד בסגירות קצרות מאוד. סגירה שהועברה כארגומנט האחרון לפונקציה יכולה להופיע מיד אחרי הסוגריים. כאשר סגירה היא הארגומנט היחיד לפונקציה, אתה יכול להשמיט לחלוטין את הסוגריים.

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

חפצים וכיתות

השתמש class ואחריה בשם הכיתה כדי ליצור כיתה. הצהרת מאפיין במחלקה נכתבת באותו אופן כמו הצהרת קבועה או משתנה, אלא שהיא בהקשר של מחלקה. כמו כן, הצהרות שיטה ופונקציות נכתבות באותו אופן.

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}
// Experiment:
// Add a constant property with `let`, and add another method that takes an argument.

צור מופע של מחלקה על ידי הצבת סוגריים אחרי שם המחלקה. השתמש בתחביר נקודות כדי לגשת למאפיינים ולשיטות של המופע.

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

בגרסה הזו של המחלקה Shape חסר משהו חשוב: מאתחול להגדרת המחלקה בעת יצירת מופע. השתמש init כדי ליצור אחד.

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

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

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

שימו לב כיצד משתמשים self כדי להבחין בין מאפיין name לבין ארגומנט name לאתחול. הארגומנטים לאתחול מועברים כמו קריאה לפונקציה כאשר יוצרים מופע של המחלקה. לכל מאפיין יש להקצות ערך - או בהצהרה שלו (כמו עם numberOfSides ) או באתחול (כמו עם name ).

השתמש deinit כדי ליצור דה-initializer אם עליך לבצע ניקוי כלשהו לפני ביטול הקצאת האובייקט.

תת-מחלקות כוללות את שם מחלקת העל שלהן אחרי שם המחלקה שלהן, מופרדות באמצעות נקודתיים. אין דרישה למחלקות לתת-סיווג כל מחלקת שורש סטנדרטית, אז אתה יכול לכלול או להשמיט מחלקת-על לפי הצורך.

שיטות בתת-מחלקה שעוקפות את היישום של מחלקת העל מסומנות ב- override - עקיפה של מתודה בטעות, ללא override , מזוהה על ידי המהדר כשגיאה. המהדר גם מזהה שיטות עם override שלא ממש עוקפות אף מתודה ב- superclass.

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.

בנוסף למאפיינים פשוטים המאוחסנים, לנכסים יכולים להיות גטר ו-seter.

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }

    var perimeter: Double {
        get {
            return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
        }
    }

    override func simpleDescription() -> String {
        return "An equilateral triangle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)
9.3
3.3000000000000003

במגדיר עבור perimeter , לערך החדש יש את השם המרומז newValue . אתה יכול לספק שם מפורש בסוגריים לאחר set .

שימו לב שלמאתחל למחלקה EquilateralTriangle יש שלושה שלבים שונים:

  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)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
10.0
10.0
50.0

כשאתה עובד עם ערכים אופציונליים, אתה יכול לכתוב ? לפני פעולות כמו שיטות, מאפיינים והרשמה. אם הערך לפני ? הוא nil , הכל אחרי ? מתעלמים וערך הביטוי כולו הוא nil . אחרת, הערך האופציונלי יבוטל, והכל אחרי ה- ? פועל על הערך הלא עטוף. בשני המקרים, הערך של הביטוי כולו הוא ערך אופציונלי.

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

ספירות ומבנים

השתמש enum כדי ליצור ספירה. כמו מחלקות וכל שאר הסוגים בעלי השם, לספירות יכולות להיות שיטות המשויכות אליהן.

enum Rank: Int {
    case ace = 1
    case two, three, four, five, six, seven, eight, nine, ten
    case jack, queen, king

    func simpleDescription() -> String {
        switch self {
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}
let ace = Rank.ace
print(ace)
let aceRawValue = ace.rawValue
print(aceRawValue)
ace
1

// Experiment:
// Write a function that compares two `Rank` values by comparing their raw values.

כברירת מחדל, Swift מקצה את הערכים הגולמיים שמתחילים מאפס ומתגברים באחד בכל פעם, אבל אתה יכול לשנות התנהגות זו על ידי ציון ערכים מפורש. בדוגמה שלמעלה, Ace מקבל במפורש ערך גולמי של 1 , ושאר הערכים הגולמיים מוקצים לפי הסדר. אתה יכול גם להשתמש במחרוזות או במספרי נקודה צפה כסוג הגולמי של ספירה. השתמש במאפיין rawValue כדי לגשת לערך הגולמי של מקרה ספירה.

השתמש באתחול init?(rawValue:) כדי ליצור מופע של ספירה מערך גולמי. הוא מחזיר את מקרה הספירה התואם את הערך הגולמי או nil אם אין Rank תואם.

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

ערכי המקרים של ספירה הם ערכים ממשיים, לא רק עוד דרך לכתוב את הערכים הגולמיים שלהם. למעשה, במקרים שבהם אין ערך גולמי משמעותי, אינך חייב לספק אותו.

enum Suit {
    case spades, hearts, diamonds, clubs

    func simpleDescription() -> String {
        switch self {
        case .spades:
            return "spades"
        case .hearts:
            return "hearts"
        case .diamonds:
            return "diamonds"
        case .clubs:
            return "clubs"
        }
    }
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
// Experiment:
// Add a `color()` method to `Suit` that returns "black" for spades and clubs, and returns "red" for
// hearts and diamonds.

שימו לב לשתי הדרכים שבהן מתייחסים לעיל למקרה Hearts של הספירה: כאשר מקצים ערך לקבוע hearts , מתייחסים למקרה הספירה Suit.Hearts בשמו המלא מכיוון שלקבוע אין סוג מפורש שצוין. בתוך המתג, מקרה הספירה מכונה על ידי הצורה המקוצרת .Hearts כי הערך של self כבר ידוע כחליפה. אתה יכול להשתמש בטופס המקוצר בכל פעם שסוג הערך כבר ידוע.

אם לספירה יש ערכים גולמיים, ערכים אלו נקבעים כחלק מההצהרה, כלומר לכל מופע של מקרה ספירה מסוים יש תמיד אותו ערך גולמי. אפשרות נוספת למקרי ספירה היא שיהיו ערכים המשויכים למקרה - ערכים אלו נקבעים בעת ביצוע המופע, והם יכולים להיות שונים עבור כל מופע של מקרה ספירה. אתה יכול לחשוב על הערכים המשויכים כמתנהגים כמו מאפיינים מאוחסנים של מופע מקרה הספירה.

לדוגמה, שקול את המקרה של בקשת זמני הזריחה והשקיעה משרת. השרת מגיב עם המידע המבוקש, או שהוא מגיב עם תיאור של מה השתבש.

enum ServerResponse {
    case result(String, String)
    case failure(String)
}

let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")

switch success {
case let .result(sunrise, sunset):
    print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
    print("Failure...  \(message)")
}
Sunrise is at 6:00 am and sunset is at 8:09 pm.

// Experiment:
// Add a third case to `ServerResponse` and to the switch.

שימו לב כיצד זמני הזריחה והשקיעה נשלפים מערך ServerResponse כחלק מהתאמת הערך למקרי המתג.

השתמש struct כדי ליצור מבנה. מבנים תומכים ברבים מאותן התנהגויות כמו מחלקות, כולל שיטות ומתחלים. אחד ההבדלים החשובים ביותר בין מבנים למחלקות הוא שמבנים מועתקים תמיד כשהם מועברים בקוד שלך, אבל מחלקות מועברות באמצעות הפניה.

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
// Experiment:
// Write a function that returns an array containing a full deck of cards, with one card of each
// combination of rank and suit.

פרוטוקולים והרחבות

השתמש protocol כדי להכריז על פרוטוקול.

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

כיתות, ספירות ומבנים יכולים כולם לאמץ פרוטוקולים.

class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += "  Now 100% adjusted."
    }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription

struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}
var b = SimpleStructure()
print(b.adjust())
print(b.simpleDescription)
()
A simple structure (adjusted)

// Experiment:
// Add another requirement to `ExampleProtocol`.
// What changes do you need to make to `SimpleClass` and `SimpleStructure` so that they still
// conform to the protocol?

שימו לב לשימוש במילת המפתח mutating בהצהרה של SimpleStructure כדי לסמן שיטה שמשנה את המבנה. ההצהרה של SimpleClass לא צריכה אף אחת מהשיטות שלה מסומנות כמוטציה מכיוון שמתודות במחלקה יכולות תמיד לשנות את המחלקה.

השתמש extension כדי להוסיף פונקציונליות לסוג קיים, כגון שיטות חדשות ומאפיינים מחושבים. אתה יכול להשתמש בתוסף כדי להוסיף התאמה לפרוטוקול לסוג שהוכרז במקום אחר, או אפילו לסוג שייבאת מספרייה או מסגרת.

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

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

אתה יכול להשתמש בשם פרוטוקול בדיוק כמו כל סוג אחר בשם - לדוגמה, כדי ליצור אוסף של אובייקטים בעלי סוגים שונים אך כולם תואמים לפרוטוקול בודד. כאשר אתה עובד עם ערכים שהסוג שלהם הוא סוג פרוטוקול, שיטות מחוץ להגדרת הפרוטוקול אינן זמינות.

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

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

למרות שלמשתנה protocolValue יש סוג זמן ריצה של SimpleClass , המהדר מתייחס אליו כאל הסוג הנתון של ExampleProtocol . משמעות הדבר היא שאינך יכול לגשת בטעות לשיטות או למאפיינים שהמחלקה מיישמת בנוסף להתאמה לפרוטוקול שלה.

טיפול בשגיאות

אתה מייצג שגיאות באמצעות כל סוג שמאמץ את פרוטוקול Error .

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

השתמש throw כדי לזרוק שגיאה throws כדי לסמן פונקציה שיכולה לזרוק שגיאה. אם אתה זורק שגיאה בפונקציה, הפונקציה חוזרת מיד והקוד שקרא לפונקציה מטפל בשגיאה.

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

ישנן מספר דרכים לטפל בשגיאות. דרך אחת היא להשתמש do-catch . בתוך הבלוק do , אתה מסמן קוד שיכול לזרוק שגיאה על ידי כתיבת try לפניו. בתוך בלוק ה- catch , השגיאה ניתנת אוטומטית error השם אלא אם כן תיתן לה שם אחר.

do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}
Job sent

// Experiment:
// Change the printer name to `"Never Has Toner"`, so that the `send(job:toPrinter:)` function
// throws an error.

אתה יכול לספק מספר בלוקים catch המטפלים בשגיאות ספציפיות. אתה כותב דפוס אחרי catch בדיוק כמו שאתה כותב אחרי case במתג.

do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}
Job sent

// Experiment:
// Add code to throw an error inside the `do` block.
// What kind of error do you need to throw so that the error is handled by the first `catch` block?
// What about the second and third blocks?

דרך נוספת לטפל בשגיאות היא להשתמש try? כדי להמיר את התוצאה לאופציונלי. אם הפונקציה זורקת שגיאה, השגיאה הספציפית נמחקת והתוצאה היא nil . אחרת, התוצאה היא אופציונלי המכיל את הערך שהפונקציה החזירה.

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

השתמש defer כדי לכתוב גוש קוד שמתבצע אחרי כל קוד אחר בפונקציה, רגע לפני שהפונקציה חוזרת. הקוד מבוצע ללא קשר לשאלה אם הפונקציה זורקת שגיאה. אתה יכול להשתמש defer כדי לכתוב קוד הגדרה וניקוי זה ליד זה, למרות שהם צריכים להתבצע בזמנים שונים.

var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]

func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }

    let result = fridgeContent.contains(food)
    return result
}
print(fridgeContains("banana"))
print(fridgeIsOpen)
false
false

גנריות

כתוב שם בתוך סוגריים זווית כדי ליצור פונקציה או סוג גנרי.

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

אתה יכול ליצור צורות כלליות של פונקציות ושיטות, כמו גם מחלקות, ספירות ומבנים.

// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
print(possibleInteger)
some(100)

השתמש where אחרי שם הסוג כדי לציין רשימה של דרישות - לדוגמה, לדרוש מהטיפוס ליישם פרוטוקול, לדרוש משני סוגים להיות זהים, או לדרוש מחלקה שתהיה בעלת מחלקה מסוימת.

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
    where T.Element: Equatable, T.Element == U.Element
{
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
    return false
}
print(anyCommonElements([1, 2, 3], [3]))
true

כתיבת <T: Equatable> זהה לכתיבת <T> ... where T: Equatable .