swift 笔记

字数 3133 · 2019-01-23

#ios

Swift defines away large classes of common programming errors by adopting modern programming patterns:

  • Variables are always initialized before use.
  • Array indices are checked for out-of-bounds errors.
  • Integers are checked for overflow.
  • Optionals ensure that nil values are handled explicitly.
  • Memory is managed automatically.
  • Error handling allows controlled recovery from unexpected failures.

Simple Values

Use let to make a constant and var to make a variable.

A constant or variable must have the same type as the value you want to assign to it. However, you don’t always have to write the type explicitly.

1
2
3
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70

include values in strings: Write the value in parentheses, and write a backslash () before the parentheses.

1
2
let apples = 3
let appleSummary = "I have \(apples) apples."

Use three double quotation marks (""") for strings that take up multiple lines.

1
2
3
4
let quotation = """
Even though there's whitespace to the left,
the actual lines aren't indented.
"""

Create arrays and dictionaries using brackets ([]), and access their elements by writing the index or key in brackets.

1
2
3
4
5
6
7
8
var shoppingList = ["catfish", "water", "tulips"]
shoppingList[1] = "bottle of water"

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

To create an empty array or dictionary, use the initializer syntax.

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

Control Flow

Use if and switch to make conditionals, and use for-in, while, and repeat-while to make loops. Parentheses around the condition or loop variable are optional. Braces around the body are required.

1
2
3
4
5
6
7
8
9
10
11
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"

In an if statement, the conditional must be a Boolean expression—this means that code such as if score { ... } is an error, not an implicit comparison to zero.

1
2
3
4
5
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}

Another way to handle optional values is to provide a default value using the ?? operator. If the optional value is missing, the default value is used instead.

1
2
3
let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)"

Switches support any kind of data and a wide variety of comparison operations—they aren’t limited to integers and tests for equality.

1
2
3
4
5
6
7
8
9
10
11
12
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?"

After executing the code inside the switch case that matched, the program exits from the switch statement.
Execution doesn’t continue to the next case, so there is no need to explicitly break out of the switch at the end of each case’s code.

Use for-in to iterate over items in a dictionary by providing a pair of names to use for each key-value pair. Dictionaries are an unordered collection, so their keys and values are iterated over in an arbitrary order.

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

Use while to repeat a block of code until a condition changes. The condition of a loop can be at the end instead, ensuring that the loop is run at least once.

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

Functions and Closures

Use func to declare a function. Call a function by following its name with a list of arguments in parentheses. Use -> to separate the parameter names and types from the function’s return type.

1
2
3
4
func greet(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")

By default, functions use their parameter names as labels for their arguments. Write a custom argument label before the parameter name, or write _ to use no argument label.

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

Use a tuple to make a compound value—for example, to return multiple values from a function. The elements of a tuple can be referred to either by name or by number.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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"

Functions can be nested.

Functions are a first-class type. This means that a function can return another function as its value.

A function can take another function as one of its arguments.

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

Functions are actually a special case of closures: blocks of code that can be called later.

You can write a closure without a name by surrounding code with braces ({}). Use in to separate the arguments and return type from the body.

1
2
3
4
numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})

You have several options for writing closures more concisely.
When a closure’s type is already known, such as the callback for a delegate, you can omit the type of its parameters, its return type, or both.
Single statement closures implicitly return the value of their only statement.

1
2
3
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
// Prints "[60, 57, 21, 36]"

You can refer to parameters by number instead of by name—this approach is especially useful in very short closures.

A closure passed as the last argument to a function can appear immediately after the parentheses.

When a closure is the only argument to a function, you can omit the parentheses entirely.

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

Objects and Classes

Use class followed by the class’s name to create a class.

1
2
3
4
5
6
class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

Create an instance of a class by putting parentheses after the class name. Use dot syntax to access the properties and methods of the instance.

1
2
3
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

This version of the Shape class is missing something important: an initializer to set up the class when an instance is created. Use init to create one.

1
2
3
4
5
6
7
class NamedShape {
    var name: String

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

Use deinit to create a deinitializer if you need to perform some cleanup before the object is deallocated.

Subclasses include their superclass name after their class name, separated by a colon.

Methods on a subclass that override the superclass’s implementation are marked with override—overriding a method by accident, without override, is detected by the compiler as an error. The compiler also detects methods with override that don’t actually override any method in the superclass.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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()

In addition to simple properties that are stored, properties can have a getter and a setter.

1
2
3
4
5
6
7
8
9
10
11
12
class EquilateralTriangle: NamedShape {

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

}

In the setter for perimeter, the new value has the implicit name newValue. You can provide an explicit name in parentheses after set.

If you don’t need to compute the property but still need to provide code that is run before and after setting a new value, use willSet and didSet. The code you provide is run any time the value changes outside of an initializer.

When working with optional values, you can write ? before operations like methods, properties, and subscripting. If the value before the ? is nil, everything after the ? is ignored and the value of the whole expression is nil.

1
let sideLength = optionalSquare?.sideLength

Enumerations and Structures

Use enum to create an enumeration. Like classes and all other named types, enumerations can have methods associated with them.

Use struct to create a structure. Structures support many of the same behaviors as classes, including methods and initializers. One of the most important differences between structures and classes is that structures are always copied when they are passed around in your code, but classes are passed by reference.

Protocols and Extensions

Use protocol to declare a protocol.

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

Classes, enumerations, and structs can all adopt protocols.

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

Reference

GuidedTour - docs.swift.org