About Swift — The Swift Programming Language (Swift 5.6)

欢迎运用swift

关于swift

swift是一门极好的编写软件的言语, 不管是用来写手机软件、桌面软件、服务器或许是其他跑代码的东西。它是安全、快速、交互的编程言语, 在现代编程言语中结合了最好的、来自于广泛的苹果工程文明和其开源社区的各种奉献。编译器为功用进行了优化, 言语为开发做了优化, 不将就。

swift对于新手来说是友好的。它是一门具有工业品质的编程言语, 同时也是富有表现力和令人愉快的脚本言语。在playground编写swfit让你体会编程并能够马上看到成果, 不需求build和run的开销

swift经过选用现代编程形式, 避免了常规编程的常见过错

  • 变量永远在运用之前初始化
  • 数组的索引会做越界检查
  • Integer会做溢出检查
  • 可选项确保nil会被显现处理
  • 自动内存办理
  • 过错处理答应从反常中进行可控的恢复

swift代码为了能充分利用现代硬件,是编译和优化过的。语法和标准库被规划建立在引导原则上, 依照提示的办法去书写你的代码得到最佳运转效果。它结合了安全和速度, 是从hello world到完好的可操作系统的构建的不错的挑选

swift结合了强大的类型接口和现代的、轻量级的语法、答应杂乱的主意以简洁明了的办法完成的形式. 代码不单单更简略书写并且更简略阅览和掌握

swift现已开展多年, 也在持续地进化出新功用和才能。咱们对于swift充满信心。现已等不及看你运用swift进行创作了。

版别兼容性

书中描绘的是swift5.6, XCode13默认的swift版别, 你也能够运用XCode13构建swift4.2或许swift4。当你运用XCode13构建swift4、swift4.2的代码时, 多数的swift5.6的功用是可用的。以下改变只针对运用swift5.6及以后的代码有用

  • 回来不透明类型的函数需求swift5.1 runtime
  • try?对于现已回来可选项的表达式不再需求引进一个可选值
  • large integer初始化的时分会揣度为正确的integer类型

并发需求swift5.6或之后的版别, 以及一个供给了应对并发类型的swift标准库。在苹果平台, 设置方针运转环境最低为iOS15、macOS12、tvOS15 或许 watchOS8.0

一个swift5.6的项目能够依赖于一个swift4.2或许swift4的项目, 反之亦然。假如你存在一个大的项目区分成了多个frameworks, 你能够一次将代码从swift4.0迁移到swift5.6

总览

Hello World

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

这一行代码直接就能履行
不需求为输入输出功用或许字符串处理导入单独的一个库。在全局规模编写的代码作为程序的入口, 所以也不需求main(), 你也不需求在每句代码后边加分号

  • 建议合作playground运用这一章节, 它能够让你在写代码的时分就看见成果

简略的值

let标识常量, var标识变量。常量的值在编译的时分不需求被知道它的类型, 但你有必要声明时给它赋值。你能够声明一个常量然后在其他当地运用

var myVariable = 42
myVariable = 50
let myConstant = 42

常量和变量有必要和你想给它赋的值是同一个类型。你不需求显式地指定类型, 在你给他们赋值的时分编译器会指定他们的类型。

假如初始值不能供给足够的信息或许没有初始值, 在变量名后边加个冒号, 显式指定类型

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70

值不会隐式地转化为其他类型, 你需求操作不同类型的值的时分需求显现转化

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

对于字符串来说有愈加简便的转化办法, 字符串内运用\加括号包裹值

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

运用三个引号”””包裹住string, string能够占多行, 每个引证行的最初缩进被移除, 只需匹配右引号的锁进

let quotation = """
I said "I have \(apples) apples."
And then I said "I have \(apples + oranges) pieces of fruit."
"""

创立字典和数组运用方阔号, 经过[index]或许[key]去拜访某一个值

var shoppingList = ["catfish", "water", "tulips"]
shoppingList[1] = "bottle of water"
var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"

当你向数组里增加元素的时分数组自动增加

shoppingList.append("blue paint")
print(shoppingList)

运用初始化器语法创立一个空的字典或许数组

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

假如类型信息能够被揣度出来, 你能够用以下的办法去写一个空的数组、空的字典,

shoppingList = []
occupations = [:]

控制流

运用ifswitch去构建条件句子, 运用for-inwhilerepeat-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)
// Prints "11"

在if句子傍边, 条件值有必要是布尔值表达式, 这意味着if score {...}这样的代码是过错的, 它不会将其和0值进行比较。

你能够运用if和let一同处理或许缺失的值, 这个或许缺失的值是一个可选项, 一个可选项的值能够是一个值, 也能够是nil表明这个值是缺失的。在变量的类型后边加上问号表明这个变量是一个可选项。

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

假如可选项为nil, 上面的条件值是false, 大括号里边的代码不会履行。假如可选项不为nil, 则会被解包并赋值给let常量, 在大括号内, 这个let常量是有用的。

别的一种处理可选项的办法是运用??供给一个默认值。假如可选项的值是缺失的, 默认的值会替代被运用

let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"

switch支撑任何类型的数据和广泛品种的比较操作。不再约束成integer, 而是检测值是否持平

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

留意上面let常量是如安在形式中被运用的, 用来将一个匹配形式的值赋值给常量。

履行完switch内某一个匹配的switch之后, 程序会从switch句子中退出, 不会持续履行下一个case, 你不再需求显现地去在每一个case的末尾break;

你能够运用for-in去遍历字典里边的每个item, 经过供给一对姓名去运用每一个键值对。字典是一个无序的调集, 所以键值对依照一个任意的顺序被遍历。

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 (_, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)
// Prints "25"

运用while去履行代码直到条件产生改动, 也能够将while放到后边, 确保循环体至少履行一次

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"

有index的循环, 合作..<约束规模

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

..<表明不包括右边的值, ...表明包括右边的值

函数和闭包

运用func声明一个函数, 运用函数的时分, 办法名 + 括号包裹的参数列表。在声明一个函数的时分, 运用->切割参数列表和回来类型

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

默认的函数运用参数名作为参数外部标签, 你能够在参数名前面设置自界说的参数标签, 也能够运用_表明不需求外部标签

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "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)
// Prints "120"
print(statistics.2)
// Prints "120"

函数能够被嵌套, 嵌套的函数能够拜访在函数外部的变量, 你能够运用嵌套函数去组织较长的或许杂乱函数的代码

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

函数是first-class类型, 一个函数能够作为一个函数的回来值

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

一个函数能够是另一个函数的参数

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)

函数是特别类型的闭包, 闭包是一段能够在之后被调用的代码, 闭包内的代码能够拜访到闭包创立环境下的变量和函数, 即便闭包在别的一个彻底不同的环境中被履行,比方上边的嵌套函数。你能够经过用花括号包裹去写一个匿名闭包, 运用in去切割参数和回来类型 以及 闭包主体

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

你有好几种办法去简化闭包。当一个闭包的类型现已被承认, 比方delegate的回调, 你能够省掉参数的类型, 或许回来值的类型, 或许都省掉。单个句子的闭包会隐式地回来这个仅有的句子的值

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

你能够经过数字替代参数名去拜访参数, 这种办法对于小的闭包来说是非常有用的。一个闭包假如是函数的最终一个参数, 能够直接跟在参数列表圆括号后边。假如是仅有的参数, 那么圆括号能够省掉

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

对象和类

运用class后边跟着class的姓名去发明一个类。类傍边特点声明写法和常量/变量声明共同, 除了特点的声明坐落类的语境傍边。同样地, 办法和函数声明也是同样的写法

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

在类名后边加一对圆括号能够创立一个类的实例, 运用点语法去拜访实例的特点和办法。

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

类创立一个实例的时分会运用初始化办法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."
    }
}

留意上边在init办法里边经过self. 区分了特点和初始化的参数name。每一个特点都需求初始值, 能够在声明特点的时分给到, 也能够在初始化器里边给到。

运用deinit去创立一个去初始化器, 假如你需求在对象deallocated之前做一些整理。

子类需求在自己的class姓名后跟上父类的姓名, 运用冒号:分隔。没有必要让一个类显式声明为某一标准根类的子类, 你能够依照自己的需求增加或许省掉父类声明。

子类的办法假如需求覆盖父类的完成, 需求运用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")
test.area()
test.simpleDescription()

除了简略的存储性特点, 特点能够有getter和setter办法

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"

在setter傍边, newValue作为隐式的传入的参数名, 你能够运用()跟在set后边供给一个显式的名称

在上边等边三角形的初始化傍边有3个不同的过程:

  • 设置子类声明的特点值

  • 调用父类的初始化办法

  • 改动父类傍边的界说的特点的值, 其他额定的运用getter、setter办法的设置作业都能够在这一步进行

    假如你不需求核算特点, 可是想要在设置一个新值之前或许之后运转一段代码, 能够运用willSetdidSet。被供给的代码将在每次特点发送改变的时分被运转, 除了初始化以外。以下代码确保了正方形和三角形的边长一向保持共同

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"

当运用可选项的时分, 能够在可选项的办法、特点、下标之前写上?。假如?前面值是nil, 任何?后边的都会被疏忽, 并且整个表达式的值是nil。不写上?的话, 可选项会被解包, 之后的一切?作用于被解包的值。两种办法来说, 整个表达式都是可选项.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength

枚举和结构体

运用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
let aceRawValue = ace.rawValue

默认地, swift枚举初始值从0开端顺次增大, 可是你能够经过显式地指定值去改动这一点。上面的类型傍边运用1作为Ace的初始值, 剩下的初始值顺次增大。你也能够运用字符串或许浮点数作为枚举的初始值。运用rawValue特点去拜访一个枚举项的初始值。

运用init?(rawValue:)初始化器去用一个初始值发明一个枚举实例。回来对应值的枚举项, 或许没有对应枚举项, 回来nil

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

留意上面hearts的枚举项的两种推导办法, 将hearts枚举项给到常量的时分, 运用的是Suit.hearts, 由于常量没有显式指定类型。而在枚举内部的Description傍边, 直接运用了.hearts, 由于self现已被认定为是Suit的枚举。在现已知道类型的情况下, 你能够运用缩写。

假如一个枚举设定了初始值, 这些值被确定为声明的一部分, 这就意味着每一种枚举项的实例都是相同的初始值。别的一种办法便是让枚举项和值进行相关, 只要在你创立实例的时分才会确定, 同一枚举项的值能够是不同的。你能够以为相关的值类似于枚举项实例的特点。比方, 从服务器恳求日出、日落时刻, 服务器要么同时回来这两个时刻, 要么回来犯错信息

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

运用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()

协议和扩展

运用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()
b.adjust()
let bDescription = b.simpleDescription

留意到上面结构体傍边运用了mutating, 去标识一个将会修改结构体的办法。而类不需求这个标识, 由于类傍边的办法总是能够修改类。

运用extension去对现已存在的类型增加功用, 比方新的办法和核算型特点。你能够运用扩展对声明在任何其他位置的类型增加协议的完成, 甚至是你从库或许framework傍边导入的类型。

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

像其他命名类型相同, 你能够运用协议名表明一个类型, 比方创立一个类型不同可是遵从同一协议的调集。可是当你操作这些类型为协议类型的对象的时分, 协议以外的办法是不能够运用的

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

虽然上面的protocolValue运转时类型是SimpleClass, 可是编译器会将其作为ExampleProtocoll来处理, 这意味着你不能拜访除了在协议里边规定的特点和办法

过错处理

你能够运用遵从了Error协议的任何类型来代表errors

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的代码块傍边, 你需求在或许抛出过错的代码前面标识try。在catch代码块傍边, 过错会运用error作为过错名

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

你能够供给多个catch代码块去处理特定的过错, 一个catch紧接上一个catch, 类似于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"

别的一种处理过错的办法是运用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
}
fridgeContains("banana")
print(fridgeIsOpen)
// Prints "false"

泛型

运用尖括号去构造一个范型函数或许类型

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)

函数、办法、类、枚举和结构体都支撑范型

// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
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
}
anyCommonElements([1, 2, 3], [3])

<T: Equatable>是等价于<T>... where T: Equatable