جولة سريعة

مقتبس من جولة A Swift الأصلية على Swift.org مع بعض التعديلات. تم تأليف المحتوى الأصلي بواسطة شركة Apple Inc. ومرخص بموجب ترخيص Creative Commons Attribution 4.0 International (CC BY 4.0) .
عرض على TensorFlow.org تشغيل في جوجل كولاب عرض المصدر على جيثب

يقترح التقليد أن أول برنامج بلغة جديدة يجب أن يطبع الكلمات "Hello، World!" على الشاشة. في Swift، يمكن القيام بذلك في سطر واحد:

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

إذا كنت قد كتبت تعليمات برمجية بلغة C أو Objective-C، فإن بناء الجملة هذا يبدو مألوفًا لك - في Swift، يعد هذا السطر من التعليمات البرمجية برنامجًا كاملاً. لا تحتاج إلى استيراد مكتبة منفصلة للحصول على وظائف مثل الإدخال/الإخراج أو التعامل مع السلسلة. يتم استخدام التعليمات البرمجية المكتوبة على النطاق العالمي كنقطة دخول للبرنامج، لذلك لا تحتاج إلى وظيفة main() . لا تحتاج أيضًا إلى كتابة الفواصل المنقوطة في نهاية كل عبارة.

تمنحك هذه الجولة معلومات كافية لبدء كتابة التعليمات البرمجية في Swift من خلال توضيح كيفية إنجاز مجموعة متنوعة من مهام البرمجة. لا تقلق إذا لم تفهم شيئًا ما، فكل ما تم تقديمه في هذه الجولة مشروح بالتفصيل في بقية هذا الكتاب.

قيم بسيطة

استخدم let لإنشاء ثابت و var لإنشاء متغير. لا يلزم معرفة قيمة الثابت في وقت الترجمة، ولكن يجب عليك تعيين قيمة له مرة واحدة بالضبط. هذا يعني أنه يمكنك استخدام الثوابت لتسمية قيمة تحددها مرة واحدة ولكن تستخدمها في العديد من الأماكن.

var myVariable = 42
myVariable = 50
let myConstant = 42

يجب أن يكون للثابت أو المتغير نفس نوع القيمة التي تريد تخصيصها له. ومع ذلك، ليس عليك دائمًا كتابة النوع بشكل صريح. إن توفير قيمة عند إنشاء ثابت أو متغير يتيح للمترجم استنتاج نوعه. في المثال أعلاه، يستنتج المترجم أن myVariable هو عدد صحيح لأن قيمته الأولية هي عدد صحيح.

إذا كانت القيمة الأولية لا توفر معلومات كافية (أو إذا لم تكن هناك قيمة أولية)، فحدد النوع عن طريق كتابته بعد المتغير، مفصولاً بنقطتين. ملاحظة: استخدام Double بدلاً من Float لأرقام الفاصلة العائمة يمنحك المزيد من الدقة، وهو نوع النقطة العائمة الافتراضي في Swift.

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

لا يتم أبدًا تحويل القيم ضمنيًا إلى نوع آخر. إذا كنت بحاجة إلى تحويل قيمة إلى نوع مختلف، فقم بشكل صريح بإنشاء مثيل للنوع المطلوب.

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

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

هناك طريقة أبسط لتضمين القيم في السلاسل: اكتب القيمة بين قوسين، ثم اكتب شرطة مائلة عكسية (``) قبل الأقواس. على سبيل المثال:

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

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

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

استخدم ثلاث علامات اقتباس مزدوجة ( """ ) للسلاسل التي تشغل عدة أسطر. تتم إزالة المسافة البادئة في بداية كل سطر مقتبس، طالما أنها تطابق المسافة البادئة لعلامات الاقتباس الختامية. على سبيل المثال:

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

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

I still have 8 pieces of fruit.

قم بإنشاء المصفوفات والقواميس باستخدام الأقواس ( [] )، والوصول إلى عناصرها عن طريق كتابة الفهرس أو المفتاح بين الأقواس. يُسمح بالفاصلة بعد العنصر الأخير.

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

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

تنمو المصفوفات تلقائيًا عند إضافة العناصر.

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

لإنشاء مصفوفة أو قاموس فارغ، استخدم بناء جملة المُهيئ.

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

إذا كان من الممكن استنتاج معلومات النوع، فيمكنك كتابة مصفوفة فارغة كـ [] وقاموس فارغ كـ [:] - على سبيل المثال، عند تعيين قيمة جديدة لمتغير أو تمرير وسيطة إلى دالة.

shoppingList = []
occupations = [:]

التحكم في التدفق

استخدم if و switch لإنشاء شروط شرطية، واستخدم for - in for while و repeat - while لإنشاء حلقات. الأقواس حول الشرط أو متغير الحلقة اختيارية. الأقواس حول الجسم مطلوبة.

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

في عبارة if ، يجب أن يكون الشرط عبارة عن تعبير منطقي - وهذا يعني أن التعليمات البرمجية مثل if score { ... } هي خطأ، وليست مقارنة ضمنية بالصفر.

يمكنك استخدام if و let معًا للعمل مع القيم التي قد تكون مفقودة. يتم تمثيل هذه القيم كاختيارات. تحتوي القيمة الاختيارية إما على قيمة أو تحتوي على nil للإشارة إلى أن القيمة مفقودة. اكتب علامة استفهام ( ? ) بعد نوع القيمة لوضع علامة على القيمة كاختيارية.

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

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

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

إذا كانت القيمة الاختيارية nil ، فإن الشرط يكون false ويتم تخطي الكود الموجود بين الأقواس. بخلاف ذلك، يتم إلغاء تغليف القيمة الاختيارية وتعيينها إلى الثابت بعد let ، مما يجعل القيمة غير المغلفة متاحة داخل كتلة التعليمات البرمجية.

هناك طريقة أخرى للتعامل مع القيم الاختيارية وهي توفير قيمة افتراضية باستخدام ?? المشغل. إذا كانت القيمة الاختيارية مفقودة، فسيتم استخدام القيمة الافتراضية بدلاً من ذلك.

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

تدعم المحولات أي نوع من البيانات ومجموعة واسعة من عمليات المقارنة، فهي لا تقتصر على الأعداد الصحيحة واختبارات المساواة.

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

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

لاحظ كيف يمكن استخدام let في أحد الأنماط لتعيين القيمة التي تطابق ذلك الجزء من النمط إلى ثابت.

بعد تنفيذ الكود الموجود داخل علبة التبديل المطابقة، يخرج البرنامج من بيان التبديل. لا يستمر التنفيذ حتى الحالة التالية، لذا ليست هناك حاجة لفصل المفتاح بشكل صريح في نهاية رمز كل حالة.

يمكنك استخدام for - in للتكرار على العناصر الموجودة في القاموس من خلال توفير زوج من الأسماء لاستخدامه لكل زوج من قيمة المفتاح. القواميس عبارة عن مجموعة غير مرتبة، لذلك يتم تكرار مفاتيحها وقيمها بترتيب عشوائي.

let interestingNumbers = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)
25

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

استخدم while لتكرار مجموعة من التعليمات البرمجية حتى يتغير الشرط. يمكن أن تكون حالة الحلقة في النهاية بدلاً من ذلك، مما يضمن تشغيل الحلقة مرة واحدة على الأقل.

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

print(n)
128

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

print(m)
128

يمكنك الاحتفاظ بفهرس في حلقة — إما باستخدام ..< لإنشاء نطاق من الفهارس أو عن طريق كتابة تهيئة وشرط وزيادة صريحة. هاتان الحلقتان تفعلان نفس الشيء:

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

print(total)
6

استخدم ..< لإنشاء نطاق يحذف قيمته العليا، واستخدم ... لإنشاء نطاق يتضمن كلا القيمتين.

الوظائف والإغلاقات

استخدم func للإعلان عن دالة. قم باستدعاء دالة باتباع اسمها بقائمة من الوسائط بين قوسين. استخدم -> لفصل أسماء المعلمات وأنواعها عن نوع الإرجاع الخاص بالوظيفة.

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

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

بشكل افتراضي، تستخدم الوظائف أسماء المعلمات الخاصة بها كتسميات لوسائطها. اكتب تسمية وسيطة مخصصة قبل اسم المعلمة، أو اكتب _ لعدم استخدام تسمية وسيطة.

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

استخدم صفًا لإنشاء قيمة مركبة، على سبيل المثال، لإرجاع قيم متعددة من دالة. يمكن الإشارة إلى عناصر الصف إما بالاسم أو بالرقم.

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

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

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

يمكن أن تكون متداخلة الوظائف. تتمتع الوظائف المتداخلة بإمكانية الوصول إلى المتغيرات التي تم الإعلان عنها في الوظيفة الخارجية. يمكنك استخدام الوظائف المتداخلة لتنظيم التعليمات البرمجية في وظيفة طويلة أو معقدة.

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

الوظائف هي نوع من الدرجة الأولى. هذا يعني أن الدالة يمكنها إرجاع دالة أخرى كقيمتها.

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

يمكن أن تأخذ الدالة دالة أخرى كأحد الوسائط الخاصة بها.

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

الوظائف هي في الواقع حالة خاصة من عمليات الإغلاق: كتل من التعليمات البرمجية التي يمكن استدعاؤها لاحقًا. يتمتع الكود الموجود في الإغلاق بإمكانية الوصول إلى أشياء مثل المتغيرات والوظائف التي كانت متوفرة في النطاق الذي تم إنشاء الإغلاق فيه، حتى لو كان الإغلاق في نطاق مختلف عند تنفيذه - لقد رأيت مثالاً على ذلك بالفعل مع الوظائف المتداخلة. يمكنك كتابة خاتمة بدون اسم من خلال الكود المحيط بالأقواس ( {} ). استخدم in لفصل الوسائط ونوع الإرجاع عن النص.

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

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

لديك عدة خيارات لكتابة الإغلاقات بشكل أكثر إيجازًا. عندما يكون نوع الإغلاق معروفًا بالفعل، مثل رد الاتصال للمفوض، يمكنك حذف نوع معلماته أو نوع الإرجاع الخاص به أو كليهما. تُرجع عمليات إغلاق العبارة المفردة ضمنيًا قيمة بيانها الوحيد.

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

يمكنك الرجوع إلى المعلمات بالرقم بدلاً من الاسم، وهذا الأسلوب مفيد بشكل خاص في عمليات الإغلاق القصيرة جدًا. يمكن أن يظهر الإغلاق الذي تم تمريره كوسيطة أخيرة للدالة مباشرة بعد الأقواس. عندما يكون الإغلاق هو الوسيط الوحيد للدالة، يمكنك حذف الأقواس بالكامل.

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

الكائنات والفئات

استخدم class متبوعًا باسم الفصل لإنشاء فصل دراسي. تتم كتابة إعلان الخاصية في الفصل بنفس طريقة كتابة الإعلان الثابت أو المتغير، باستثناء أنه يكون في سياق الفصل. وبالمثل، تتم كتابة إعلانات الطريقة والوظيفة بنفس الطريقة.

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

قم بإنشاء مثيل للفئة عن طريق وضع الأقواس بعد اسم الفئة. استخدم بناء الجملة النقطي للوصول إلى خصائص وأساليب المثيل.

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

يفتقد هذا الإصدار من فئة Shape شيئًا مهمًا: مُهيئ لإعداد الفئة عند إنشاء مثيل. استخدم init لإنشاء واحد.

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

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

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

لاحظ كيف يتم استخدام self لتمييز خاصية name من وسيطة name إلى المُهيئ. يتم تمرير الوسائط إلى المُهيئ مثل استدعاء دالة عند إنشاء مثيل للفئة. تحتاج كل خاصية إلى قيمة معينة — إما في إعلانها (كما هو الحال مع numberOfSides ) أو في المُهيئ (كما هو الحال مع name ).

استخدم deinit لإنشاء أداة إلغاء التهيئة إذا كنت بحاجة إلى إجراء بعض عمليات التنظيف قبل إلغاء تخصيص الكائن.

تتضمن الفئات الفرعية اسم فئتها الفائقة بعد اسم فئتها، مفصولة بنقطتين. ليست هناك حاجة إلى أن تقوم الفئات بتصنيف فرعي لأي فئة جذر قياسية، لذا يمكنك تضمين فئة فائقة أو حذفها حسب الحاجة.

يتم تمييز الطرق الموجودة في فئة فرعية والتي تتجاوز تطبيق الفئة الفائقة override - يتم اكتشاف تجاوز طريقة ما عن طريق الصدفة، دون override ، بواسطة المترجم كخطأ. يكتشف المترجم أيضًا الأساليب ذات override التي لا تتجاوز فعليًا أي طريقة في الفئة الفائقة.

class Square: NamedShape {
    var sideLength: Double

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

    func area() -> Double {
        return sideLength * sideLength
    }

    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)."
    }
}
let test = Square(sideLength: 5.2, name: "my test square")
print(test.area())
print(test.simpleDescription())
27.040000000000003
A square with sides of length 5.2.

// Experiment:
// - Make another subclass of `NamedShape` called `Circle` that takes a radius and a name as
//   arguments to its initializer.
// - Implement an `area()` and a `simpleDescription()` method on the `Circle` class.

بالإضافة إلى الخصائص البسيطة التي تم تخزينها، يمكن أن تحتوي الخصائص على أداة getter وأداة ضبط.

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 بتعيين القيم الأولية بدءًا من الصفر وزيادة بمقدار واحد في كل مرة، ولكن يمكنك تغيير هذا السلوك عن طريق تحديد القيم بشكل صريح. في المثال أعلاه، يُعطى الآس بشكل صريح قيمة أولية قدرها 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 ، يمكنك وضع علامة على الكود الذي يمكن أن يؤدي إلى خطأ عن طريق كتابة محاولة أمامه. داخل كتلة 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 .