Swift_Note Basics

🧩 Summary: Swift Concurrency Essentials

1. async

Use async to mark a function that runs asynchronously.

1
2
3
4
5
6
func fetchUserID(from server: String) async -> Int {
    if server == "primary" {
        return 97
    }
    return 501
}

2. await

You mark a call to an asynchronous function by writing await in front of it.

1
2
3
4
5
6
7
func fetchUsername(from server: String) async -> String {
    let userID = await fetchUserID(from: server)
    if userID == 501 {
        return "John Appleseed"
    }
    return "Guest"
}

Use async let to call an asynchronous function, letting it run in parallel with other asynchronous code. When you use the value it returns, write await.

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

3. Tast

Use Task to call asynchronous functions from synchronous code, without waiting for them to return.

Task { await connectUser(to: “primary”) }

4. withTaskGroup()

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

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

}

5. Actor

Actors are similar to classes, except they ensure that different asynchronous functions can safely interact with an instance of the same actor at the same time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
actor ServerConnection {
    var server: String = "primary"
    private var activeUsers: [Int] = []
    func connect() async -> Int {
        let userID = await fetchUserID(from: server)
        // ... communicate with server ...
        activeUsers.append(userID)
        return userID
    }
}

When you call a method on an actor or access one of its properties, you mark that code with await to indicate that it might have to wait for other code that’s already running on the actor to finish.

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

Protocols and Extensions

mutating

Notice the use of the mutating keyword in the declaration of SimpleStructure to mark a method that modifies the structure. The declaration of SimpleClass doesn’t need any of its methods marked as mutating because methods on a class can always modify the class.

1
2
3
4
protocol ExampleProtocol {
     var simpleDescription: String { get }
     mutating func adjust()
}

Classes, enumerations, and structures can all adopt protocols.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class SimpleClass: ExampleProtocol {
     var simpleDescription: String = "A very simple class."
     var anotherProperty: Int = 69105
     func adjust() {
          simpleDescription += "  Now 100% adjusted."
     }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription


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

Use extension to add functionality to an existing type, such as new methods and computed properties. You can use an extension to add protocol conformance to a type that’s declared elsewhere, or even to a type that you imported from a library or framework.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
extension Int: ExampleProtocol {
    var simpleDescription: String {
        return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
 }
print(7.simpleDescription)
// Prints "The number 7"

You can use a protocol name just like any other named type — for example, to create a collection of objects that have different types but that all conform to a single protocol. When you work with values whose type is a boxed protocol type, methods outside the protocol definition aren’t available.

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

Even though the variable protocolValue has a runtime type of SimpleClass, the compiler treats it as the given type of ExampleProtocol. This means that you can’t accidentally access methods or properties that the class implements in addition to its protocol conformance.

Error Handling

Error

You represent errors using any type that adopts the Error protocol.

1
2
3
4
5
enum PrinterError: Error {
    case outOfPaper
    case noToner
    case onFire
}

Use throw to throw an error and throws to mark a function that can throw an error. If you throw an error in a function, the function returns immediately and the code that called the function handles the error.

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

There are several ways to handle errors. One way is to use do-catch. Inside the do block, you mark code that can throw an error by writing try in front of it. Inside the catch block, the error is automatically given the name error unless you give it a different name.

1
2
3
4
5
6
7
do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}
// Prints "Job sent"

You can provide multiple catch blocks that handle specific errors. You write a pattern after catch just as you do after case in a switch.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}
// Prints "Job sent"

defer

Use defer to write a block of code that’s executed after all other code in the function, just before the function returns. The code is executed regardless of whether the function throws an error. You can use defer to write setup and cleanup code next to each other, even though they need to be executed at different times.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]


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


    let result = fridgeContent.contains(food)
    return result
}
if fridgeContains("banana") {
    print("Found a banana")
}
print(fridgeIsOpen)
// Prints "false"

Generics (泛型)

Write a name inside angle brackets to make a generic function or type.

1
2
3
4
5
6
7
8
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)

You can make generic forms of functions and methods, as well as classes, enumerations, and structures.

1
2
3
4
5
6
7
// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)

Use where right before the body to specify a list of requirements — for example, to require the type to implement a protocol, to require two types to be the same, or to require a class to have a particular superclass.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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])

Optional Binding

1
2
3
4
5
6
7
var name: String? = "Scott"

if let unwrappedName = name {
    print("Hello, \(unwrappedName)")
} else {
    print("No name found.")
}

Providing a Fallback Value

1
2
3
4
let name: String? = nil
let greeting = "Hello, " + (name ?? "friend") + "!"
print(greeting)
// Prints "Hello, friend!"

Force Unwrapping

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)


let number = convertedNumber!


guard let number = convertedNumber else {
    fatalError("The number was invalid")
}

Debugging with Assertions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// This assertion fails because -3 isn't >= 0.

if age > 10 {
    print("You can ride the roller-coaster or the ferris wheel.")
} else if age >= 0 {
    print("You can ride the ferris wheel.")
} else {
    assertionFailure("A person's age can't be less than zero.")
}

Enforcing Preconditions

1
2
// In the implementation of a subscript...
precondition(index > 0, "Index must be greater than zero.")
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy