A Swift Tour

1.打印 Hello, world!

print("Hello, world!")
  • 不需要引入第三方的库
  • 不需要一个 main 函数
  • 不需要在结尾处写分号

简单值

1.使用 let 声明常量。

2.使用 var 声明变量。

3.通常不需要明确指定常量、变量类型,可以让编译器推测它的类型。

4.如果初始值不能够提供足够的信息(或者没有初始化值),使用 : 类型 的方式来指定类型。

let implicitDouble = 70.0
let explicitDouble: Double = 70

5.值不能隐式的将一个类型转化为另一种类型。如果想转化为另一种类型,应明确设置。

let label = “The width is ”
let width = 94
let widthLabel = label + String(width)

6.有一种简单的方式将数字转换为字符串:写在一个括号里,在括号前加一个反斜杠。

let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fuitSummary = "I have \(apples + oranges) pieces of fruit."

7.使用三个双引号来承载多行字符串。

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

8.创建数组和字典使用方括号 [] ,在 [] 中使用索引或键访问元素。

var shoppingList = ["catfish", “water”, “tulips”]
shoppingList[1] = “bottle of water”

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

9.当添加元素时数组会自动扩充。

shoppingList.append(“blue paint”)
print(shoppingList)

10.创建一个空数组或字典,使用初始化语法。

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

控制流

1.使用 ifswitch 来做判断。

2.使用 for-in, while, 和 repeat-while 来循环。

3.围绕条件判断、循环变量的圆括号是可以省略的。

4.可选值绑定。如果可选值是 nil,则条件是 false;如果可选值是存在的,则将这个值付给 let 后的变量,添加为 true

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

5.另一种方式处理可选类型是通过使用 ?? 操作符来提供默认值。

let nickName: String? = nil
let fulName: String = “John Appleseed”
let informalGreeting = “Hi \(nickName ?? fullName)”

6.switch case 支持各种类型的数据和各种比较操作——不限于整形和相等的比较

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.”)
}

7.当 switch case 符合要求,程序将会从该 case 退出,执行不会继续到下一个 case。因此没必要明确在每一个 case 后面写 break

8.通过提供一对名字来用于每个 key-value 对, 用于 for-in 迭代 Dictionary 中的的项。字典是无序的集合,因此他们的键值在迭代时是任意的。

let interertingNumbers = [
    “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)

9.使用 while

var n = 2
while n < 100 {
    n *= 2
}
// Prints “128”

10.使用 repeat-while

var m = 2
repeat {
    m *= 2
} while m < 100
print(m)
// Prints “128”

11.使用 ..< 创建一个忽略了最大值的范围,使用 创建了一个包含所有值的范围。

var total = 0
for i in 0..<4 {
    total += i
}
print(total)
// Prints “6”

函数和闭包

1.使用 func 来定义函数。

2.funcName(parameterName) 来调用函数。

3.使用 -> 来分割参数和返回类型。

func greet(person: String, day: String) -> String {
    return “Hello \(person), today is \(day).”
}
greet(person: “Bob”, day: “Tuesday”)

4.在默认情况下,函数使用参数名来作为参数的标签。

5.自定义参数标签写在参数名前面。

6.如果没有参数标签,使用 _ 代替。

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

7.函数可以使用元组作为返回值,用于返回多个值。

8.函数可以嵌套在函数中。嵌套的函数可以访问嵌套外的变量。可以使用嵌套函数来组织长或者复杂的函数代码。

9.函数是一等类型。这意味着函数可以作为另一个函数的返回值

func makIncrementer() ->((Int) -> Int) {
    func addOne(number: Int) -> Int {
         return 1 + number
    }

    return addOne
}
var increment = makeIncrementer()
increment(7)

10.函数可以用来作为另一个函数的参数

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)

11.函数事实上是一种特殊的闭包——后续会被调用的一块代码。

12.使用 {} 创建一个没有名字的闭包。使用 in 来分割参数、返回值和函数主体。

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

13.单行声明闭包 隐式返回 仅有声明的值。

let mappedNumbers = numbers.map({ number in 3 * number })

14.还可以使用数字来代替参数名字。

15.如果函数的最后一个参数是闭包,它可以写在 () 外面。当闭包是函数的唯一一个参数,() 可以忽略。

let sortedNumber = number.sorted { $0 > $1 }
print(sortedNumbers)

对象和类

1.用 class ClassName 的方法来创建类。

2.类中常量、变量的声明和往常一样。同样的,方法和函数的声明也和往常一样。

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
         return “A shape with \(numberOfSides) sides.”
    }
}

3.创建类的实例。

var shape = Shape()

4.使用点语法来访问实例的属性和方法。

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

5.上面版本的 Shape 类没有初始化器——当实例创建时用于装配类。

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

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

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

6.初始化器参数传递和函数类似。

7.每一个属性都需要初始化——无论是在声明时,还是在初始化器里。

8.想在对象被释放前做一些清理操作,使用 deinit 来创建析构器。

9.子类继承父类使用 :

10.子类的方法可以重载父类的实现,需要使用 override 标识。父类中有此方法,子类如果没有 override 使用此方法,编译器将会报错。如果父类没有此方法,子类还使用 override ,编译器也会报错。

class Square: NameShape {
    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()

11.除了简单的用于存储的属性,属性还可以有 gettersetter

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)
// Prints “9.3”
triangle.perimeter = 9.9
print(triangle.sideLength)
// Prints “3.3000000000000003”

12.在 perimetersetter 中,新的值有一个隐式的名字 newValue。你可以在 set 后加上 (),并在 () 里明确的提供一个名字。

13.注意到 EquilateralTriangle 类的初始化器有 3 个不同的步骤: (1)设置子类定义属性的值 (2)调用父类的初始化器 (3)修改父类定义的属性值。任何额外的设置工作——使用方法、getters、setter,也在这个时机操作

14.使用 willSetdidSet

15.当操作可选值时,如果在 ? 前的值是 nil,任何在 ? 后的操作将会被忽略,整个表达式的值为 nil。否则,可选值被解开,? 后的操作作用于打开的值。在所有情况下,整个表达式的值是一个可选类型的值。

枚举和结构体

1.使用 enum 来创建枚举。

2.枚举中可以包含方法:

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

3.By default, Swift assigns the raw values starting at zero and incrementing by one each time, but you can change this behavior by explicitly specifying values. In the example above, Ace is explicitly given a raw value of 1, and the rest of the raw values are assigned in order. You can also use strings or floating-point numbers as the raw type of an enumeration. Use the rawValue property to access the raw value of an enumeration case.

4.Use the init?(rawValue:) initializer to make an instance of an enumeration from a raw value. It returns either the enumeration case matching the raw value or nil if there is no matching Rank.

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

5.The case values of an enumeration are actual values, not just another way of writing their raw values. In fact, in cases where there isn’t a meaningful raw value, you don’t have to provide one.

6.代码

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

7.使用 struct 来创建结构体。结构体像类一样支持很多特性,包括方法和初始化器。类和结构体最大的不同是,结构体在传递时经常会被拷贝出一个副本,但类在传递时使用引用。

协议和扩展

1.使用 protocol 声明一个协议

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

2.类、枚举和结构体都可以适配协议

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

3.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.

4.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.

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

5.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 protocol type, methods outside the protocol definition are not available.

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

6.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.

错误处理

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

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

2.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.

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

3.There are several ways to handle errors. One way is to use do-catch. Inside the doblock, 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.

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

4.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.

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"

5.Another way to handle errors is to use try? to convert the result to an optional. If the function throws an error, the specific error is discarded and the result is nil. Otherwise, the result is an optional containing the value that the function returned.

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

6.Use defer to write a block of code that is 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.

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"

泛型

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

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)

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

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

3.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.

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])

4.Writing <T: Equatable> is the same as writing <T> ... where T: Equatable.