The introduction
Today, I started to systematically learn Swift. I used to have a scattered look at the difference between LET and var and generics. I only know that it is a protocol-oriented and type-safe language, with much better performance than OC. It took so long to join Swift and felt so behind (T_T)… All right, forget it. Just pick it up today. For me, the most effective learning method is to study the official documents while translating them, which will impress me more. (Ok, I admit that this is the most inefficient way T_T, but my memory is not very good, so I can only use this method.) In the process of learning, I finally understand the use of some special functions in Swift, such as tuples, optional values and unpacking:? And! , generics, If let and If var, etc., explain to learn a language, but also do from the official documentation (personal opinion, do not like to be gusher)! Let’s explore the wonders of Swift! Fighting~
If you already know the basics of Swift, refer to the next section: Basics
Welcome to SWIFT
1 about Swift
Swift is a great way to write software, whether it’s for a phone, desktop, server, or anything else that runs code. It is a safe, fast, and interactive programming language that combines the best of modern linguistic thinking with the wisdom of Apple’s engineering culture and the contributions of the Apple open source community. The compiler was optimized for performance, the language for development, and neither was compromised.
Swift is friendly to new programmers. It is an industry-quality programming language that is as expressive and fun as a scripting language. Writing Swift code on playground lets you play with the code and see the results immediately without the overhead of building and running the application.
Swift defines a number of common programming errors by adopting modern programming patterns:
- Variables are always initialized before they can be used.
- Check the array index for out-of-bounds errors.
- Check whether the integers overflow.
- Optional variables ensure that nil values are explicitly handled.
- Automatic memory management
- Error handling allows you to control recovery from unexpected errors.
Swift code is compiled and optimized to maximize modern hardware. The syntax and standard library are designed with the guiding principle that the obvious way to write code should also have the best performance. Its combination of security and speed makes Swift an excellent choice for everything from “Hello, World! To the entire operating system.
Swift combines powerful type inference and pattern matching with a modern lightweight syntax that allows complex ideas to be expressed in a clear and concise manner. As a result, code is not only easier to write, but also easier to read and maintain.
Swift has been years in the making and is constantly developing new features and functions. Our goals for Swift are ambitious. We can’t wait to see what you’ve created with it.
2 Version Compatibility
This book describes Swift 5.2, the default version of Swift included in Xcode 11.4. You can use Xcode 11.4 to build with Swift 5.2, Swift 4.2 or Swift 4 to write targets.
Most Swift 5.2 features are also available when you build Swift 4 and Swift 4.2 code with Xcode11.4. That is, the following changes only apply to code using Swift 5.2 or higher:
- Functions that return opaque types require a Swift 5.1 runtime.
- Give it a try? Expressions do not introduce additional optional levels to expressions that already return optional levels.
- The initialization expression for a large integer literal is inferred to be of the correct integer type. For example, UInt64(0xFFFF_FFFF_FFFF_FFFF_FFFF) calculates the correct value instead of overflows.
A target written in Swift 5.2 can depend on a target written in Swift 4.2 or Swift 4, and vice versa. This means that if you have a large project divided into multiple frameworks, you can migrate code from Swift 4 to Swift 5.2 in one go.
3 Swift tour
According to tradition, the first program written in a new language should print “Hello, world! “On the screen. In Swift, this can be done in one line:
print("Hello, world!")
// Prints "Hello, world!"
Copy the code
This syntax will be familiar to you if you’re writing code in C or Objective-C — in Swift, this line of code is a complete program. There is no need to import a separate library for functions such as input/output or string processing. Code written in the global scope is used as an entry point for the program, so the main() function is not required. You also don’t need to put a semicolon at the end of every statement.
This guide gives you enough information to start coding with Swift by showing you how to complete various programming tasks. Don’t worry if you don’t understand — everything covered on this journey is explained in detail later in the book.
Please note that this chapter is opened as playground in Xcode for the best possible experience. Playgrounds allows you to edit code and see the results immediately. Download Playground
3.1 Simple values
Let is used to create constants and var is used to create variables. The value of a constant does not need to be known at compile time, but it must be assigned exactly once. This means that you can use constants to name values that are certain once but used in many places.
var myVariable = 42
myVariable = 50
let myConstant = 42
Copy the code
The type of a constant or variable must be the same as the type to be assigned. However, you don’t always have to write types explicitly. Provide a value when you create a constant or variable so that the compiler can infer its type. In the example above, the compiler deduces that myVariable is an integer because its initial value is an integer.
If the initial value does not provide enough information (or there is no initial value), specify the type by writing it to the end of the variable, separated by a colon.
let implicitInteger = 70
letImplicitDouble = 70.0let explicitDouble: Double = 70
Copy the code
Experiment to create a constant with an explicit Float type and a value of 4.
A value is never implicitly converted to another type. If you need to convert a value to another type, explicitly create an instance of the desired type.
let label = "The width is "
let width = 50
let labelWidth = label + String(width)
Copy the code
The experiment attempts to remove the conversion string from the last line. What’s the error?
There is an even easier way to include values in a string: write the value in parentheses, followed by a backslash (). Such as:
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples. "
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
Copy the code
Experiment with using \ () to include floating point calculations in a string and to include someone’s name in a greeting.
For strings that take up more than one line, use three double quotes (“””). As long as it matches the indentation of the closing quotation mark, you can remove the indentation at the beginning of each quoted line. Such as:
let quotation = """
I said "I have \(apples) apples."
And then I said "I have \(apples + oranges) pieces of fruit.""""
Copy the code
Arrays and dictionaries are created using square brackets ([]) and their elements are accessed by writing indexes or keys inside the brackets. A comma is allowed after the last element.
var shoppingList = ["catfish"."water"."tulips"]
shoppingList[1] = "bottle of water"
var occupations = [
"Malcolm": "Captain"."Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
Copy the code
The array grows automatically as you add elements.
shoppingList.append("blue paint")
print(shoppingList)
Copy the code
To create an empty array or dictionary, use the initialization syntax.
let emptyArray = [String]()
let emptyDictionary = [String: Float]()
Copy the code
If type information can be inferred, empty arrays can be written as [] and empty dictionaries as [:] — for example, when setting new values for variables or passing arguments to functions.
shoppingList = []
occupations = [:]
Copy the code
3.2 the control flow
Use if and switch to generate conditional statements, and for-in, while, and repeat-while to generate loops. Parentheses around conditions or loop variables are optional. Braces are needed around the body.
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
print(teamScore)
// Prints "11"
Copy the code
In an if statement, the conditional statement must be a Boolean expression — which means code such as if score{… } is an error, not an implied comparison of zero.
You can use if and let together to handle possible missing values. These values are expressed as optional. Optional values either contain a value or nil to indicate a missing value. Write a question mark (?) after the value type. To mark the value as optional.
var optionalString: String? = "Hello"
print(optionalString == nil)
// Prints "false"
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
Copy the code
Experiment changes optionalName to nil. What kind of greetings did you receive? If optionalName is nil, add an else clause to set a different greeting.
If the optional value is nil, the condition is false and the code in braces is skipped. Otherwise, the optional value is unpacked and assigned to the constant after the let, making the unpacked value available in the code block.
Another way to handle optional values is to use?? Operators. If an optional value is missing, the default value is used.
let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)"
Copy the code
Switches support any type of data and a variety of comparison operations – they are not limited to integer and equality tests.
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.")
}
// Prints "Is it a spicy red pepper?"
Copy the code
Experiment with removing default to see what happens
In enumeration, there is no default and no error is reported
Notice how let is used in a pattern to assign values that match the pattern to a constant.
After executing the code in the matching Switch case, the program exits from the Switch statement. Execution does not continue to the next case, so there is no need to explicitly break the switch at the end of the code in each case.
You can iterate over items in a dictionary using for-in by providing a pair of names for each key-value pair. Dictionaries are an unordered collection, so their keys and values will be iterated over in any order.
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)
// Prints "25"
Copy the code
The experiment adds another variable to keep track of which number is the largest and what the largest number is.
Use while to repeat the code block until the condition changes. The conditions for the loop can be placed at the end to ensure that the loop runs at least once.
var n = 2
while n < 100 {
n *= 2
}
print(n)
// Prints "128"
var m = 2
repeat {
m *= 2
} while m < 100
print(m)
// Prints "128"
Copy the code
You can use.. Save indexes in a loop to generate index ranges.
var total = 0
for i in0.. <4 { total += i }print(total)
// Prints "6"
Copy the code
< creates a range that ignores its values and uses… Generate a range containing these two values.
var total = 0
for i in0... 4 { total += i }print(total)
// Prints "10"
Copy the code
3.3 Functions and closures
Use func to declare a function. A function is called by enclosing a list of arguments in parentheses after its name. Use -> to separate the parameter name and type from the return type of the function.
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")
Copy the code
Example Delete the day parameter. Add a parameter to include today’s special lunch in the greeting.
By default, functions label their arguments with their parameter names. Write a custom parameter label before the parameter name, or write _ to omit the parameter label.
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
Copy the code
Use tuples to generate compound values — for example, returning multiple values from a function. Elements of a tuple can be referenced by name or number.
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)
// Prints "120"
print(statistics.2)
// Prints "120"
Copy the code
Functions can be nested. Nested functions can access variables declared in external functions. You can use nested functions to organize your code into a long or complex function.
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
Copy the code
Functions are of a type. This means that a function can return another function as its value.
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
Copy the code
A function can take another function as one of its arguments.
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true}}return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
Copy the code
Functions are really a special case of closures: blocks of code that can be called later. Code in a closure can access variables and functions that are available in the scope in which the closure was created, even if the closure is executed in a different scope — you’ve already seen examples of this in nested functions. You can write nameless closures by using curly braces ({}) around your code. Used to separate parameters and return types from the body.
numbers.map({ (number: Int) -> Int in
let result = 3 * number
return result
})
Copy the code
Experiment with rewriting the closure to return zero for all odd numbers.
numbers.map({ (number: Int) -> Int in
ifnumber % 2 ! = 0 {return0}return number
})
Copy the code
There are several ways to write closures more succinctly. If you already know the type of a closure (such as a delegate’s callback), you can omit the type of its arguments, the return type, or both. Single-statement closures implicitly return the value of their unique statement.
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
// Prints "[60, 57, 21, 36]"
Copy the code
Parameters can be referenced by numbers rather than names — an approach that is particularly useful in very short closures. Closures passed as the last argument to a function can appear immediately after the parentheses. When the closure is the only argument to a function, you can omit the parentheses entirely.
let sortedNumbers = numbers.sorted { $0 > The $1 }
print(sortedNumbers)
// Prints "[20, 19, 12, 7]"
Copy the code
3.4 Objects and Classes
Create a class using the class followed by the class name. An attribute declaration in a class is written in the same way as a constant or variable declaration, except that it is in the context of the class. Similarly, method and function declarations are written in the same way.
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."}}Copy the code
The experiment uses let to add a constant property and another method that accepts parameters.
An instance of a class is created by enclosing parentheses after the class name. Use point syntax to access instance properties and methods.
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
Copy the code
This version of Shape is missing something important: the initializer used to set the class when creating instances. Create one using 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."}}Copy the code
Notice how self is used to distinguish the name attribute from the initializer’s name parameter. When an instance of a class is created, the arguments to the initializer are passed like function calls. Each attribute needs to be assigned a value — either in its declaration (such as numberOfSides) or in the initializer (such as Name).
If you need to perform some cleanup before releasing an object, use deinit to create a DeInitializer.
Subclasses include their superclass names after their class names, separated by colons. Classes do not need to subclass any standard root classes, so they can include or omit parent classes as needed.
Methods on subclasses that override parent class implementations are marked as overridden – accidentally overridden methods that the compiler detects as errors if not overridden. The compiler also detects methods with Override that don’t actually override any of the methods in the parent class.
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."}}let test= Square (sideLength: 5.2, name:"my test square")
test.area()
test.simpleDescription()
Copy the code
Experiment with creating another subclass of NamedShape, Circle, that takes a radius and a name as parameters to the initializer. Implement area() and simpleDescription() methods on the Circle class.
In addition to storing simple properties, properties can also have getters and setters.
Class triangle: NamedShape {var sideLength: Double = 0.0 init(sideLength: Double, name: String) { self.sideLength = sideLength super.init(name: name) numberOfSides = 3 } var perimeter: Double { get {return3.0 * sideLength}set{sideLength = newValue / 3.0}} Override func simpleDescription() -> String {return "An equilateral triangle with sides of length \(sideLength)."}} var triangle = triangle (sideLength: 3.1, name:"a triangle")
print(triangle.perimeter)
// Prints "9.3"Triangle. Generating = 9.9print(triangle.sideLength)
// Prints "3.3000000000000003"
Copy the code
Perimeter in setters, the implicit name for a newValue is newValue. You can provide an explicit name in parentheses after a set. Such as:
set(value) {// Explicit name of setter value sideLength = value / 3.0}Copy the code
Note that the EquilateralTriangle class initializer has three different steps:
- Sets the value of a property declared by a subclass.
- Call the initialization method of the parent class.
- Changes the value of a property defined by the parent class. Any other setup work that uses methods, getters, or setters can also be done at this point.
If you don’t need to evaluate properties, but still need to provide code that runs before and after setting new values, use willSet and didSet. Whenever the value changes outside of the initializer, the code you provide runs. For example, the following class ensures that the sides of a triangle are always the same length as the sides of a square.
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)
// Prints "10.0"
print(triangleAndSquare.triangle.sideLength)
// Prints "10.0"
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
// Prints "50.0"
Copy the code
When using optional values, you can write? Before operations such as methods, properties, and subscripts. . If? The front value is nil, right? Everything after that and the whole expression is nil. Otherwise, the optional values will be unwrapped, and? Acts on unwrapped values. In both cases, the value of the entire expression is optional.
letoptionalSquare: Square? = Square (sideLength: 2.5, name:"optional square")
letsideLength = optionalSquare? .sideLengthCopy the code
3.5 Enumerations and Structures
Create an enumeration using enum. Like classes and all other named types, enumerations can have methods associated with them.
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
let aceRawValue = ace.rawValue
Copy the code
Experimentally write a function that compares the original values of two rank values.
By default, Swift assigns raw values starting at 0 and incrementing each time by 1, but you can change this behavior by explicitly specifying values. In the example above, Ace is explicitly assigned a raw value of 1, and the remaining raw values are assigned in order. You can also use strings or floating-point numbers as primitive types for enumerations. Use the rawValue property to access the rawValue of the enumerated use case.
Use the init? The (rawValue:) initializer generates an enumerated instance from the original value. It returns the enumeration use case that matches the original value, or nil if there is no matching Rank.
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
Copy the code
The case value of the enumeration is the actual value, not just another way of writing the original value. In fact, in the absence of meaningful original values, you don’t have to provide them.
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()
Copy the code
Experiment by adding a color() method that returns “black” for spades and clubs and “red” for hearts and diamonds.
The code is as follows:
enum CardColor {
case Heitao, Meihua, Fangkuai, Hongxin
func color() -> String {
switch self {
case .Heitao:
return "black"
case .Meihua:
return "black"
case .Fangkuai:
return "red"
case .Hongxin:
return "red"}}}Copy the code
Note the two ways in which the enumerated hearts case is referred to above: when assigning a value to the hearts constant, we refer to the full suit.hearts name of the enumerated case because the constant does not explicitly specify a type. Inside switch, the enumerated case is referenced by the abbreviation.hearts, because the value of self is already known to be a suit. Abbreviations can be used as long as the type of the value is known.
If enumerations have original values, those values are part of the declaration, which means that each instance of a particular enumeration case always has the same original value. An alternative to enumeration cases is to have values associated with that case — values that are determined when the instance is created, and can be different for each instance of the enumeration case. Associated values can be thought of as storage properties similar to enumerated case instances. For example, consider requesting sunrise and sunset times from a server. The server responds either to the requested information or to a description of the error.
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)")
}
// Prints "Sunrise is at 6:00 am and sunset is at 8:09 pm."
Copy the code
Experiment by adding a third case to ServerResponse and Switch
Notice how the sunrise and sunset times are extracted from the ServerResponse value as part of the matching value with the Switch Cases.
Structs are used to create structures. Structs support many of the same behaviors as classes, including methods and initializers. One of the most important differences between structures and classes is that when structures are passed in code, they are always copied (value passed), whereas classes are passed by reference (reference passed).
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()
Copy the code
Write a function that returns an array containing a deck of cards, one for each deck’s rank and suit combination.
3.6 Protocols and Extensions
Uses protocol to define a protocol
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
Copy the code
Classes, enumerations, and structures can all adopt protocols.
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
Copy the code
Experiment with adding another requirement to the ExampleProtocol. What changes do you need to make to SimpleClass and SimpleStructure to make them still protocol compliant?
Notice that the mutating keyword is used in the declaration of SimpleStructure to flag methods that modify the structure. A SimpleClass declaration does not need to mark any of its methods as mutating, because methods on a class can always modify the class.
Use Extension to add functionality to existing types, such as new methods and computed properties. Extensions can be used to add protocol consistency to types declared elsewhere, or even imported from a library or framework.
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
// Prints "The number 7"
Copy the code
Experiment with writing an extension for the Double type to add the absoluteValue property.
You can use protocol names just like any other named type-for example, to create collections of objects that have different types but all conform to a single protocol. Methods outside the protocol definition are not available when working with values whose type is protocol type.
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// Prints "A very simple class. Now 100% adjusted."
// print(protocolValue.anotherProperty) // Uncomment to see the error
Copy the code
Even if the mutable protocolValue is of runtime type SimpleClass, the compiler treats it as an ExampleProtocol of the given type. This means that you cannot accidentally access methods or properties implemented by a class, not just its protocol conformance.
3.7 Error Handling
Errors can be expressed using any type that uses the error protocol.
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
Copy the code
Throw is used to throw an error, and throws is used to mark functions that can throw an error. If an error is thrown in a function, the function returns immediately, and the code calling the function handles the error.
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
Copy the code
There are several ways to handle errors. One way is to use a do catch. In a DO block, you can flag code that might throw an error by writing a try in front of it. In a catch block, an error is automatically named an error unless you give it a different name.
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
// Prints "Job sent"
Copy the code
Experiment with changing the printer name to “Never have Toner”, causing send(job:toPrinter:) to throw an error.
You can provide multiple catch blocks to handle specific errors. Writing patterns after catch is like writing patterns after case in Switch.
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"
Copy the code
Experiment with adding code that throws an error in a DO block. What type of error do you need to throw in order for the first catch block to handle the error? How about the second and third blocks?
Another way to handle errors is to use **try? ** Converts the result to optional. If the function throws an error, that particular error is discarded and the result is nil. Otherwise, the result is optional and contains the value returned by the function.
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
Copy the code
Use defer to write a code block that executes after all the other code in the function and just before the function returns. The code executes whether or not the function throws an error. You can use defer to write setup and cleanup code side by side, even if they need to be executed at different times.
var fridgeIsOpen = false
let fridgeContent = ["milk"."eggs"."leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
let result = fridgeContent.contains(food)
return result
}
fridgeContains("banana")
print(fridgeIsOpen)
// Prints "false"
Copy the code
3.8 generics
Write the name inside Angle brackets to make it a generic function or type.
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result = [Item]()
for _ in0.. <numberOfTimes { result.append(item) }return result
}
makeArray(repeating: "knock", numberOfTimes: 4)
Copy the code
You can create functions and methods in generic form, as well as classes, enumerations, and structures.
// Reimplement the Swift standard library's optional type enum OptionalValue
{ case none case some(Wrapped) } var possibleInteger: OptionalValue
= .none possibleInteger = .some(100)
Copy the code
Using the WHERE keyword after the type name allows you to define a list of restrictions, for example, that restrict types implement a certain protocol, or that two types are the same, or that the class inherits from a parent class.
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])
Copy the code
Experiment with modifying the anyCommonElements(_ :_ 🙂 function to return an array of elements common to any two sequences.
In the example above, you can ignore where and just write the protocol name or class name after the colon. Writing <T: Equatable> works the same as writing.
Next chapter: Basics
Swift – WELCOME TO Swift