Swift编程一(快速之旅)

案例代码下载

关于Swift

Swift通过采用现代编程模式来定义大类常见的编程错误:
- 变量在使用前始终初始化。
- 检查数组索引是否存在越界错误。
- 检查整数是否溢出。
- Optionals确保nil明确处理值。
- 内存自动管理。
- 错误处理允许从意外故障中受控恢复。

Swift将强大的类型推理和模式匹配与现代轻量级语法相结合,允许以清晰简洁的方式表达复杂的想法。因此,代码不仅更容易编写,而且更易于阅读和维护。

版本兼容性

注意

当Swift 4.2编译器使用Swift 3代码时,它将其语言版本标识为3.4。因此,您可以使用条件编译块来编写与多个版本的Swift编译器兼容的代码。#if swift(>=3.4)

使用Xcode 9.2构建Swift 3代码时,大多数新的Swift 4功能都可用。也就是说,以下功能仅适用于Swift 4代码:
- 子串操作返回该Substring类型的实例,而不是String。
- 该@objc属性隐式添加在较少的位置。
- 对同一文件中的类型的扩展可以访问该类型的私有成员。

Swift之旅

print("Hello, world!")

这行代码是一个完整的程序。无需为输入/输出或字符串处理等功能导入单独的库、在全局范围编写的代码用作程序的入口点,不需要main()函数,也不需要在每个语句的末尾写分号。

简单的值

使用let声明一个常数,var声明一个变量。在编译时不需要知道常量的值,但必须为其分配一次值。这意味着您可以使用常量来命名您确定一次但在许多地方使用的值。

var myVariable = 42
myVariable = 50
let myConstant = 42

常量或变量必须与要分配给它的值具有相同的类型。但是,并不总是必须明确地写入类型。在创建常量或变量时提供值可让编译器推断其类型。

如果初始值未提供足够的信息(或者没有初始值),请通过在变量后面写入来指定类型,用冒号分隔。

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

如果初始值未提供足够的信息(或者没有初始值),请通过在变量后面写入来指定类型,用冒号分隔。

let intCons: Int
let stringCons: Double = 5

练习:创建一个Float类型值为4的常量

let exV: Float = 4

值永远不会隐式转换为其他类型。如果需要将值转换为其他类型,请显式创建所需类型的实例。

let label = "宽度是"
let width = 8
var widthLabel = label + String(width)

练习:
尝试删除上面一行的String显式转换,看得到什么错误。

widthLabel = label + width

得到错误:Binary operator ‘+’ cannot be applied to operands of type ‘String’ and ‘Int’

有一种简单的方法可以在字符串中包含值:在括号中写入值,并在括号前写入\。

let apples = 3
let oranges = 5
let appleSummary = "我有\(apples)个苹果"
let orangeSummary = "我有个\(oranges)橘子"

//: 对于占用多行的字符串,请使用三个双引号,每个引用行开头的缩进都会被删除,只要它与右引号的缩进相匹配即可(字符串必须另起一行,结束的三个引号也必须另起一行)

let quotation = """
我有\(apples)个苹果
我还有\(oranges)个橘子
"""

使用方括号([])创建数组和字典,并通过在括号中写入索引或键来访问它们的元素。最后一个元素后面允许逗号。

var shoppingList = ["鱼", "狗", "人", "猪",]
shoppingList[0] = "草鱼"

var occupations = ["张三": "厨师", "李四": "iOS程序员", ]
occupations["lis"] = "前台"

创建空数组或字典,请使用初始化程序语法。

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

如果可以推断类型信息,则可以将空数组写为[]空字典写为[:]

shoppingList = []
occupations = [:]
let emptyStringArray: [String] = []
let emptyStringDictionary: [String: String] = [:]

控制流

使用if和switch制作条件语句和使用for- in,while和repeat- 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)

在if语句中,条件必须是布尔表达式 - 这意味着代码例如if score { … }是错误的。

for score in individualScores {
    if score {//('Int' is not convertible to 'Bool')
        teamScore += 2
    }
}

类型后面写一个问号,将值标记为可选。可选值包含值或包含nil,ke以使用if和let一起处理可能缺少的值。

var optionalString: String? = "你好"
print(optionalString == nil)

var optionalName: String? = "张三"
var greeting = "你好!"
if let name = optionalName {
    print("你好, \(name)")
} else {
    print("不存在这个人")
}

如果是可选值nil,则条件为false执行else中的代码。否则,将解包可选值并将其分配给let常量,成为在代码块内可用的展开值。

处理可选值的另一种方法是使用??运算符提供默认值。如果缺少可选值,则使用默认值。

let nickName: String? = nil
let fullName = "张三"
let informalGreeting = "你好,\(nickName ?? fullName)"

Switches支持任何类型的数据和各种各样的比较操作

let vegetable = "辣椒"
switch vegetable {
case "土豆":
    print("今天吃土豆丝")
case "辣椒", "肉":
    print("今天吃辣椒炒肉")
case let x where x.hasPrefix("剁")://注意可以使用let where将匹配条件的值赋给常量
    print("剁辣椒很辣的")
default:
    print("今天没吃东西")
}

如果移除default分支,会得到错误:Switch must be exhaustive(Switch语句必须是完整的)
switch case中执行代码后,程序退出switch语句。不会继续执行下一种情况,因此不需要在每个案例代码的末尾添加break

可以使用for- in通过提供一对放在()中用于表示每个键值对的名称来迭代字典中的项目。字典是无序集合,因此它们的键和值以任意顺序迭代。

let interestingNumbers = [
    "Prime": [2, 3, 5, 7, 9, 11],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25]
]
var name = ""
var largest = 0
for (kind, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            name = kind
            largest = number
        }
    }
}
print("最大的数在\(name)中,最大数为\(largest)")

使用while可以循环一个代码块直到条件不成立。也可以用repeat {} while 形式将条件放在后面,来确保循环体至少被执行一次

var n = 2
while n < 2 {
    n *= 2
}
print(n)

var m = 2
repeat {
    m *= 2
} while m < 2
print(m)

使用while可以循环一个代码块直到条件不成立。也可以用repeat {} while 形式将条件放在后面,来确保循环体至少被执行一次

var n = 2
while n < 2 {
    n *= 2
}
print(n)

var m = 2
repeat {
    m *= 2
} while m < 2
print(m)

使用..<表示范围内

var total = 0
for index in 0..<4 {
    total += index
}
print(total)

使用…表示包涵的所有范围

total = 0
for index in 0...10 {
    total += index
}
print(total)

函数和闭包

使用func声明函数。通过在括号中使用参数列表跟随其名称来调用函数。使用->的参数名称和类型与函数的返回类型分开。

func greet(person: String, day: String) -> String {
    return "你好 \(person),今天是\(day)"
}
print(greet(person: "张三", day: "星期四"))

默认情况下,函数使用其参数名称作为其参数的标签。在参数名称前写入自定义参数标签,或者写入_不使用参数标签。

func greetOther(_ person: String, on day: String) -> String {
    return "你好 \(person),今天是\(day)"
}
print(greetOther("李四", on: "星期五"))

使用元组创建复合值 - 例如,从函数返回多个值。元组的元素可以通过名称或数字来引用。

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 < min {
            min = score
        } else {
            max = score
        }
        sum += score
    }

    return (min, max, sum)
}
print(calculateStatistics(scores: [1, 2, 5, 19]))

函数可以嵌套。嵌套函数可以访问外部函数中声明的变量。您可以使用嵌套函数来组织长或复杂函数中的代码。

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

    return y
}
print(returnFifteen())

函数可以返回另一个函数作为其返回值

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

    return addOne
}
print(makeIncrementer()(7))

函数可以将另一个函数作为其参数之一

func hasAnyMatches(list: [Int], condition: ((Int) -> Bool)) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }

    return false
}

var numbers = [1, 2, 3, 4]
hasAnyMatches(list: numbers) { (p) -> Bool in
    return p > 5
}

函数实际上是闭包的一种特殊情况:可以在以后调用的代码块。闭包中的代码可以访问创建闭包的作用域中可用的变量和函数,即使闭包在不同作用域执行。使用大括号({})来编写没有名称的闭包。使用in分离的参数、返回类型与闭包体。

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

可以通过几种方式更简洁地编写闭包。当已知闭包的类型(例如委托的回调)时,可以省略其参数的类型,返回类型或两者。单个语句闭包隐式返回其唯一语句的值。

numbers.map({number in number*3 })

可以按编号而不是按名称来引用参数 - 这种方法在非常短的闭包中特别有用。作为函数的最后一个参数传递的闭包可以在括号后面立即出现。当闭包是函数的唯一参数时,可以完全省略括号。

numbers.sort { $0 > $1 }

类和对象

使用class后跟类的名称来创建一个类。类中的属性声明与常量或变量声明的编写方式相同,只是它位于类的上下文中。同样,方法和函数声明以相同的方式编写。

class Shape {
    var numbersOfSides = 0

    func simpleDescription() -> String {
        return "\(numbersOfSides)边形"
    }
}

通过在类名后面加括号来创建类的实例。使用点语法访问实例的属性和方法。

var shape = Shape()
shape.numbersOfSides = 4
print(shape.simpleDescription())

每个属性都需要一个赋值 - 在其声明中(如同numberOfSides)或在初始化器中
如果想要在对象销毁时清理一些东西,就使用deinit
子类在其类名后面包含它们的父类名称,用冒号分隔。类不需要子类化任何标准根类,因此可以根据需要包含或省略超类。
覆盖父类的实现的方法在子类上用override标记 - 意外地覆盖方法,而不使用override会被编译器检测为错误。编译器还检测具有override的方法实际上不覆盖父类中的任何方法。

class NameShape: Shape {
    var name: String
    init(name: String) {
        self.name = name
    }
    override func simpleDescription() -> String {
        return "我叫\(name),我是\(numbersOfSides)边形"
    }
}

class Square: NameShape {
    var sideLength: Double
    init(name: String, length: Double) {
        sideLength = length
        super.init(name: name)
        numbersOfSides = 4
    }

    func area() -> Double {
        return sideLength*sideLength
    }

    override func simpleDescription() -> String {
        return "我叫\(name),我是边长为\(sideLength)的正方形"
    }
}
let square = Square(name: "测试", length: 3.0)
print("正方形\(square.name)的面积为\(square.area())")

属性还可以包含getter和setter

class EquilateralTriangle: NameShape {
    var sideLength: Double
    init(name: String, length: Double) {
        sideLength = length
        super.init(name: name)
        numbersOfSides = 3
    }
    var perimeter: Double {
        get {
            return 3*sideLength
        }
        set {
            sideLength = newValue/3.0
        }
    }
    override func simpleDescription() -> String {
        return "我叫\(name),我是边长为\(sideLength)的等边三角形"
    }
}

注意初始化程序的三个基本步骤
_设置子类属性
_初始化父类
_更改父类属性以及做一些其他操作

在setter中,新值具有隐式名称newValue,也可以在set后面括号中提供显式名称
不需要计算属性但仍需要提供在设置新值之前和之后运行的代码,使用willSet和didSet。提供的代码在初始化程序之外值更改时运行。

class TriangleAndSquare {
    var triangle: EquilateralTriangle {
        willSet (value) {
            square.sideLength = value.sideLength
        }
    }
    var square:Square {
        willSet (value) {
            triangle.sideLength = value.sideLength
        }
    }

    init(size: Double, name: String) {
        triangle = EquilateralTriangle.init(name: name, length: size)
        square = Square.init(name: name, length: size)
    }
}
TriangleAndSquare(size: 50.0, name: "test")

枚举和结构

用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)
        }
    }
}

默认情况下,Swift从0开始并每次递增1为枚举分配原始值,但可以通过显式指定值来更改此行为。还可以使用字符串或浮点数作为枚举的原始类型。使用该rawValue属性可以访问枚举实例的原始值。
使用init?(rawValue:)初始化程序从原始值创建枚举的实例。它返回与原始值匹配的枚举,或者返回nil如果没有匹配的情况的话。

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

枚举的值是实际值,而不仅仅是编写其原始值的另一种方式。实际上,在没有有意义的原始值的情况下,不必提供原始值。

enum Suit {
    case spades, hearts, clubs, diamonds
    func simpleDescription() -> String {
        switch self {
        case .spades:
            return "黑桃"
        case .hearts:
            return "红心"
        case .clubs:
            return "梅花"
        case .diamonds:
            return "方块"
        }
    }
    func color() -> String {
        switch self {
        case .spades, .clubs:
            return "black"
        case .hearts, .diamonds:
            return "red"
        }
    }
}
let hearts = Suit.hearts
print(hearts.simpleDescription())

如果枚举具有原始值,则这些值将作为声明的一部分确定,这意味着特定枚举实例的具有相同的原始值。再就是可以使值与案例相关联 - 这些值在创建实例时确定,并且对于枚举案例的每个实例它们可以不同。您可以将关联值视为与枚举案例实例的存储属性相似。

enum ServerResponse {
    case result(String, String)
    case failure(String)
}
let success = ServerResponse.result("6:00 am", "7:00 pm")
let failure = ServerResponse.failure("Out of cheese")
switch success {
case let .result(sunrise, sunset):
    print("\(sunrise)太阳升起来啦,\(sunset)太阳下山了")
case let .failure(message):
    print("请求失败了,原因是:\(message)")
}

struct创建的结构。结构支持许多与类相同的行为,包括方法和初始化器,但是不能继承。结构和类之间最重要的区别之一是结构在代码中传递时总是被复制通过值传递,但类是通过引用传递的。

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "\(suit.simpleDescription())\(rank.simpleDescription())"
    }
}

错误处理

用遵循Error协议的类型表示错误

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

用throw抛出错误,throws标记可以抛出错误的函数。如果在函数中抛出错误,函数会立即返回,并且调用该函数的代码会处理错误。

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

    return "Job sent"
}

有几种方法可以处理错误:

1、使用do- catch。在do块中,通过在其前面写入try来标记可能引发错误的代码。在catch块内,错误会自动给出error名称,除非给它一个不同的名称

do {
    let printerResponse = try send(job: 1024, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}

可以提供多个catch处理特定错误的块

do {
    let printerResponse = try send(job: 1024, toPrinter: "Bi Sheng")
    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)
}

2、用try?将结果转换为可选的。如果函数抛出错误,则丢弃特定错误,结果为nil。否则,结果是一个包含函数返回的值的可选值。

let printerSuccess = try? send(job: 1002, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1003, 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(food: "banana")
print(fridgeIsOpen)

泛型

在尖括号内写一个名称以制作泛型

func makeArray<Item>(item: Item, count: Int) -> [Item] {
    var result: [Item] = []
    for _ in 0..<count {
        result.append(item)
    }

    return result
}
print(makeArray(item: "eggs", count: 5))

泛型可以在函数、类、枚举、结构体中制作

enum OptionValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionValue<Int> = .none
possibleInteger = .some(10)

where在正文之前使用以指定需求列表 - 例如,要求类型实现协议,要求两个类型相同,或要求类具有特定的超类

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool where T.Iterator.Element: Equatable, T.Iterator.Element == U.Iterator.Element {
    for rhsItem in rhs {
        for lhsItem in lhs {
            if rhsItem == lhsItem {
                return true
            }
        }
    }

    return false
}
anyCommonElements([1, 2, 3], [3])

与… where T: Equatable两种写法效果一样

猜你喜欢

转载自blog.csdn.net/jiuchabaikaishui/article/details/81281048