Swift初窥

基本内容

常量和变量

使用关键字let声明常量,使用关键字var声明变量.
在一行中声明多个变量或者常量时,其间用逗号分隔.

使用let定义的变量具有不变性。
针对值类型具有值的不变性,而对于引用类型值保证引用的不变性(不能给引用赋新值,但是引用可以所指的对象是可变的)。

类型标注

在声明变量或者常量是在名字后加冒号,空格最后加上使用的类型名称,用来确定变量或者常量能够存储值的类型.
如果在定义常量或者变量时未设置类型标注,其会在设置初始值时进行类型推断.

类型安全和类型推断

Swift会在编译阶段进行类型检查,任何不匹配的类型都会报错.
如果没有为所需要的值进行类型声明,Swift会使用类型推断功能推断出合适的类型.

类型别名

可以使用typealias关键字为类型定义别名.

元组

元组把多个值合并成单一的复合型的值.
元组内的值可以是任何类型,而且不必是同一类型.

元组分解时,不需要的数据可以使用_代替.还可以使用索引值去除对应的元素值.

可选型

某个类型可以有值,也可以根本没有值的特殊类型(nil).

Swift中的nil和Objective-C 中的nil不同.
后者是一个指向不存在对象的指针.
前者不是指针,是值缺失的一种特殊类型.

强制展开:如果确定可选型中包含值,可以在名字后使用!强制展开来获取值.

可选型绑定:使用可选型绑定来判断可选型是否包含值,如果包含就把值赋给一个临时的常量或者变量.

if let constantName = someOptional {
    statements
}

隐式展开可选型:如果确定类型会一直有值,可以在其声明后加上!来表示隐式展开可选型.

扩展:可选类型完美解决占位问题

断言和先决条件

使用全局函数assert(_:_:)来写断言,来断定一个条件为真.
使用recondition(_:_:file:line:)函数来写先决条件.

使用fatalError(_:file:line:)函数用来表示致命错误的断言,且该断言在Release环境下也会生效.
该函数还可以用来加在方法中表示不希望别人随意调用,但是又不得不实现的方法.

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

基本运算符

赋值运算符

赋值运算符可以跟其他操作符结合组成组合赋值运算符.(+=等)

算术运算符

常见的四则运算符.

Swift中算术运算符默认不允许值溢出.(详见溢出运算符)

比较运算符

相等运算符(==)用来判断两个对象的值是否相等或相同.
等价运算符(===!==)用来判断两个对象的引用是否一致.

合并空值运算符(a ?? b)

// a ?? b
// 如果a有值则展开,如果没有值则是nil,返回b
a != nil ? a! : b

区间运算符

逻辑运算符

在复杂的混合逻辑运算中,可以使用显示括号改变逻辑运算优先级.

字符串和字符

字符串是值类型,进行赋值等操作传递的是拷贝份.

集合类型

数组(Array<Element>),集合(Set<Element>)和字典(Dictionary<Key, Value>).

集合中存储值必须都是可哈希的.

集合是通过判断hash值是否相等去除重复值。

需要将自定义类型存储在集合类型中时,类型需要遵循Hashable协议、还需要对==运算符进行重载。
自定义类型的内容改变后,其hash值就会发生改变。

// 自定义结构体Person
struct Person {
    var name: String
    var zipCode: Int
    var birthDay: Date
}

// 重载运算符==
extension Person: Equatable {
    static func ==(lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name && lhs.zipCode == rhs.zipCode && lhs.birthDay == rhs.birthDay
    }
}

// 保证对象可哈希
extension Person: Hashable {
    var hashValue: Int {
        // 使用异或计算hash值
        // 自定义类型的内容改变后,其hash值就会发生改变
        return name.hashValue ^ zipCode.hashValue ^ birthDay.hashValue
    }
}

常见的集合操作:

控制流

常见的流程控制语句包括:while循环和for-in循环,分支选择if,guardswitch语句,和流程跳转breakcontinue语句.

可以使用??运算符为可选值提供默认值.

在控制流语句中可以使用 where 语句和 let 语句。

循环遍历

使用for-in循环来遍历序列.

遍历字典类型时,获取的对象是键值对组成的元组成员((key, value)).
还可以遍历由区间运算符组成的序列.

还可以使用whilerepeat-while进行循环遍历.

条件语句

if语句和switch语句.

在Swift中的switch语句不再默认从每个case末尾贯穿到下一个case中,因此不再需要显式地使用break语句.

let anotherPoint = (2, 0)
switch anotherPoint {
// 使用了元组进行匹配
// 将匹配的值使用值绑定为常量
case (let x, 0):
    print("on the x-axis with an x value of \(x)")
case (0, let y):
    print("on the y-axis with a y value of \(y)")
// 使用where语句对匹配值进行检查,筛选出符合条件的值
case let (x, y) where x == y:
    print("somewhere else at (\(x), \(y))")
default:
    print("somewhere else at (\(x), \(y))")
}

控制转移语句

// 判断代码可执行版本,检查API可用性
if #available(iOS 10, macOS 10.12, *) {
    // Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS
} else {
    // Fall back to earlier iOS and macOS APIs
}

函数

使用func关键字声明函数,函数由函数名,参数列表(形参),返回值组成.

函数类型

它由形式参数类型和返回值类型组成.((Int, Int) -> Int)
函数也是一等公民,可以对函数类型进行相关操作.

参数列表

函数参数列表可以为空,还可以指定实际参数标签或省略实际参数标签(使用_).
函数中还可以指定形式参数标签的默认值.
函数中还可以使用...关键字设置可变形式参数.在函数体内部可变参数传入的值被当做对应类型的数组. 函数中可以通过inout关键字设置输入输出形式参数.

函数调用传入inout类型参数时需要使用&取得对应变量地址.
此时传入的参数需要确保已经初始化,因为无法确定在何时会从指针中读取数据.

返回值

函数可以无返回值,可以拥有多个返回值(元组/可选元组),还可以设置函数类型的返回值.

闭包

闭包是可以在你的代码中被传递和引用的功能性独立模块.
闭包能够捕获和存储定义在上下文的任意常量和变量的引用.

闭包和函数都是引用类型.它们赋值时,实际上都是设置相关引用.

全局函数是一个有名字但不会捕获任何值的闭包,
内嵌函数是一个有名字且能从其上层函数捕获值的闭包,
闭包表达式可以捕获其上下文中常量或变量值的没有名字的闭包.

闭包表达式

闭包表达式是一种在简短行内就能写完闭包的语法.

// parameters支持常量形式参数,变量形式参数和输入输出形式参数,但是不支持默认参数值.
{ (parameters) -> (return type) in
    statements
}

Swift中的闭包能够根据实际参数来推断形式参数类型和返回类型,在实际应用中可以省略形式参数类型和返回类型.
单表达式的闭包还可以省略return关键字来隐式返回单个表达式结果.
Swift自动支持对行内闭包提供简写实际参数名($0,$1,$2等).
还可以使用系统支持的运算符函数对闭包进行简化.

let names = ["Chris","Alex","Ewa","Barry","Daniella"]
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
// 从语境中推断出参数类型,并省略掉形式参数类型和返回类型
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
// 单表达式闭包的隐式返回.省略掉return字段.
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
// 简写实际参数名
reversedNames = names.sorted(by: { $0 > $1 } )
// 使用系统提供的运算符函数简化
reversedNames = names.sorted(by: >)

尾随闭包

尾随闭包是一种被书写在函数形式参数的括号外边(后边)的闭包表达式.

// 将闭包写在函数方法括号外边
reversedNames = names.sorted() { $0 > $1 }
// 将闭包写在函数名后.由于函数只有一个参数,且使用了尾随闭包的语法,不需要在函数名后写圆括号.
reversedNames = names.sorted { $0 > $1 }

捕获值

闭包能够从上下文捕获已被定义的常量和变量.

嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量.

逃逸闭包

当闭包作为参数传到一个函数中,但是这个闭包在函数返回后才会被执行,就称该闭包从函数中逃逸.
在定义接受闭包作为参数时,可以在参数名前使用@escaping来允许闭包逃逸出该函数.

如果将闭包标记为@escaping就必须在闭包中显式地引用self.

逃逸闭包常用在异步操作的函数中:

  1. 将闭包保存在函数外部定义的变量中.
  2. 异步函数接受一个闭包参数.
  3. 异步函数开始后闭包会立即返回,但是闭包会在异步操作完成后再调用.
// 定义一个保存闭包对象的数组
var completionHandlers: [() -> Void] = []
// 函数接受一个闭包作为参数,在参数名前使用@escaping允许闭包逃逸出该函数
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    // 将获取的闭包添加到数组中
    completionHandlers.append(completionHandler)
}

// 定义非逃逸闭包
func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        // 逃逸闭包必须显式的使用self
        someFunctionWithEscapingClosure { self.x = 100 }
        // 非逃逸闭包可以隐式的使用self
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
// 逃逸闭包未被执行
instance.doSomething()
print(instance.x)
// 打印出 "200"

// 执行逃逸闭包
completionHandlers.first?()
print(instance.x)
// 打印出 "100"

自动闭包

自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式.
通过将参数标记为@autoclosure来接收一个自动闭包.

自动闭包可以延迟求值,直到调用这个闭包时代码段才会被执行.

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// 打印出 "5"

// customerProvider的类型为:() -> String
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出 "5"

// customerProvider调用后才会执行闭包中的语句
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出 "4"

枚举

在Swift中枚举是一等公民(first-class),使用关键字enum创建并定义枚举.
他们可以含有计算属性,实例方法,构造函数,协议等(类似).

枚举值

  1. 不必为每个枚举成员设置值,
  2. 可以为枚举成员设置字符串,字符,整型,浮点型等初始值.
  3. 在设置枚举值和switch-case语句中可以省略枚举类型名

Swift中枚举成员在创建后不会被赋予一个默认的整型值(不同于在OC中的实现).
而这些枚举成员本身已经是定义好的枚举对应值.

关联值

Swift中枚举类型可以用来存储任意类型的关联值.(类似于存储了一个方法在枚举中,需要时调用就好)

// 枚举类型为Barcode
enum Barcode {
    // 类型关联值为upc
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}
// 通过不同的关联值类型设置枚举值
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

// 使用switch-case时,必须覆盖所有case,或者使用default代替未尽case
switch productBarcode {
    // 可以省略枚举类型Barcode
    case .upc(let numberSystem, let manufacturer, let product, let check):
        print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
    case .qrCode(let productCode):
        print("QR code: \(productCode).")
}
// QR code: ABCDEFGHIJKLMNOP.

原始值

枚举成员可以在定义时被默认值(原始值)预填充.此时原始值的类型必须相同.

原始值的隐式赋值:在使用原始值为整数(从0开始)或者字符串类型(与枚举成员名相同)的枚举时,不必为每个枚举成员设置默认值.
使用枚举成员的rawValue属性可以访问该枚举成员的原始值,还可以使用这个初始化方法创建一个新的枚举实例.

// 指定枚举成员的类型为Character
enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}
// possibleCharacter为可选型
let possibleCharacter = ASCIIControlCharacter(rawValue: "1")

递归枚举

递归枚举有一个或者多个枚举类型作为关联值.
枚举成员前加indirect字段来表示该成员可递归,还可以在枚举类型前添加字段表示所有成员都是可递归的.

// 使用indirect表示枚举成员都可以递归
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
// (5 + 4) * 2
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

// 定义计算公式,传入枚举类型的表达式
func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let .number(value):
        return value
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}
print(evaluate(product))
// 18

类和结构体

struct Resolution {
    var width = 0
    var height = 0
}
let vga = Resolution(width:640, height: 480)
// 默认构造通过为所有存储属性设置默认值实现
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    // 可选型默认值为nil
    var name: String?
}
enum CompassPoint {
    case North, South, East, West
}

注意:

恒等运算符:
引用类型具有同一性(identity)。
可以使用等价于===,不等价于!==)来判断两个类类型(class type)的常量或者变量引用的是否为同一个类实例

== ===
结构相等 指针相等或引用相等
两个变量的值是否相等 两个变量是否持有相同的引用
- (BOOL)isEqual:(id)other {  
    if (other == self)
        return YES;
    if (!other || ![other isKindOfClass:[self class]])
        return NO;
    return [self isEqualToWidget:other];
}

// 逐步遍历对象的各个属性是否相等
- (BOOL)isEqualToWidget:(MyWidget *)aWidget {
    if (self == aWidget)
        return YES;
    if (![(id)[self name] isEqual:[aWidget name]])
        return NO;
    if (![[self data] isEqualToData:[aWidget data]])
        return NO;
    return YES;
}

属性

存储属性

指在特定的类或者结构体的实例中的一个常量(let)或变量(var).
还可以在定义属性时指定默认值,也可以在构造工程中修改其值.

延迟存储属性

指当第一次被调用时才会计算其初始值的属性,在属性前添加lazy关键字标识.

常用于耗时的操作,避免不必要的过早初始化,
其针对的是变量属性,不能对常量属性使用,
不是线程安全的,需要考虑多线程同步的问题.

计算属性

其不存储值,而是使用setter,getter来获取,设置属性值.

struct Point {
    // 存储属性
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}

struct Rect {
    // 存储属性
    var origin = Point()
    var size = Size()
    // 计算属性
    var center : Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        // 可以不声明newCenter,使用newValue代替
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}

var square = Rect(origin: Point(x: 10.0, y: 11.0),size: Size(width: 20.0,height: 30.0))
print("square.origin \(square.origin.x),\(square.center.x)")
// square.origin 10.0,20.0

只读计算属性

其总是返回一个值,只有getter方法而没有setter方法.

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        // 在书写上可以简化,去掉get关键字和括号
        return width * height * depth
    }
}

属性观察器

内部用来监听和响应属性值的变化,每次属性值的变化都会调用(即使新值与当前值相等).

class StepCounter {
    var totalSteps = 0 {
        willSet {
            print("totalSteps:\(totalSteps),new count:\(newValue)")
        }
        didSet {
            if totalSteps > oldValue {
                print("old count:\(oldValue),increase count:\(totalSteps - oldValue)")
            }
        }
    }
}
let counter = StepCounter()
counter.totalSteps = 100
counter.totalSteps = 120
/*
totalSteps:0,new count:100
old count:0,increase count:100
totalSteps:100,new count:120
old count:100,increase count:20
*/

属性使用in-out方式传入函数也会调用willSetdidSet方法.输入输出参数

类型属性

常用来定义某个类型的所有实例的共享数据,比如所有实例都能使用的常量/变量.

struct AudioChannel {
    // 设置最大值
    static let maxAudioLevel = 10
    // 设置历史最大值
    static var historyMaxAudioLevel = 0
    var currentLevel = 0 {
        didSet {
            // 使用类型本身访问,而不是某个实例
            if currentLevel > AudioChannel.maxAudioLevel {
                currentLevel = AudioChannel.maxAudioLevel
            }
            if currentLevel > AudioChannel.historyMaxAudioLevel {
                AudioChannel.historyMaxAudioLevel = currentLevel
            }
        }
    }
}

var leftChannel = AudioChannel()
leftChannel.currentLevel = 11
print(leftChannel.currentLevel)
// 10

方法

实例方法

实例方法要写在它所属的类型的前后大括号之间.

实例方法能够隐式访问它所属类型的所有实例方法和属性,
实例方法只能在现存的实例中调用.

class Counter {
    var count = 0
    func increment() {
        // self等价于实例本身
        self.count += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}
let counter = Counter()
counter.increment()
counter.increment(by: 5)
counter.reset()

在实例方法中修改值类型:如果需要修改结构体或者枚举的属性,需要在方法前增加mutating修饰.

struct Point {
    var x = 0.0, y = 0.0
    // 方法定义时增加mutating,允许修改属性
    mutating func moveByX(deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// "The point is now at (3.0, 4.0)"

类型方法

类型方法是写在类型本身上的方法,在方法func前加static关键字来指定.(类比类型属性)

类型方法方法体中可以直接调用本类的其他类型方法/类型属性,
而在实例方法方法体中调用时需要添加类型名称

struct LevelTracker {
    // 类型属性,在属性前加static关键字
    static var highestUnlockedLevel = 1
    var currentLevel = 1
    // 类型方法,在方法前加static关键字
    static func unlock(_ level: Int) {
        // 在类型方法中,可以直接访问类型属性
        // 在此方法体内,self指向的是类型本身,而不是某个实例
        if level > highestUnlockedLevel {
            highestUnlockedLevel = level
        }
    }
    static func isUnLocked(_ level: Int) -> Bool {
        return level <= highestUnlockedLevel
    }
    // 在实例方法中修改内部属性值,在方法前加mutating关键字
    mutating func advance(to level:Int) -> Bool {
        // 在实例方法中调用类型属性/类型方法
        if LevelTracker.isUnLocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}

class Player {
    var tracker = LevelTracker()
    let playerName : String
    func complete(level: Int) {
        // 解锁关卡
        LevelTracker.unlock(level + 1)
        // 调至某关
        tracker.advance(to: level + 1)
    }
    init(name: String) {
        playerName = name
    }
}

var player1 = Player(name: "张三")
// 完成第1关,最高未解锁为第2关
player1.complete(level: 1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")

var palye2 = Player(name: "李四")
// 新建对象直接调至10关,提示失败
if palye2.tracker.advance(to: 10) {
    print("player is now on level 10")
} else {
    print("level 10 has not yet been unlocked")
}
// 调用类方法判断第10关解锁状态
if LevelTracker.isUnLocked(10) {
    print("player is now on level 10")
} else {
    print("level 10 has not yet been unlocked")
}
/*
highest unlocked level is now 2
level 10 has not yet been unlocked
level 10 has not yet been unlocked
*/

下标

下标语法支持在实例名称后的方括号传入一个/多个索引值来对索引值进行存取.

  1. 定义下标语法时需要使用subscript字段,指定一个/多个输入参数和返回类型.
  2. 下标方法可以设置读写权限(使用settergetter实现,有点类似计算型属性).
  3. 下标方法支持下标的重载(通过入参的数量和类型区分).
    下标方法可以接受任意数量的入参,入参的类型也可以是任意类型.返回值的类型也可以是任意类型.
    下标方法可以有多个不同的实现,使用时根据不同的入参进行区分.
struct Matrix {
    let rows: Int,columns: Int
    // 使用一维数组保存二维数组数据
    var grids: [Double]
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grids = Array(repeating: 0.0, count: rows * columns)
    }
    func indexsIsAvailable(row: Int, column: Int) -> Bool {
        return row >= 0 && column >= 0 && row < rows && column < columns
    }
    // 使用关键字subscript修饰
    subscript(row: Int, column: Int) -> Double {
        // 使用setter和getter方法设置读写权限
        set {
            // 判断数组是否越界
            assert(indexsIsAvailable(row: row, column: column),"Index out of range")
            grids[(row * columns + column)] = newValue
        }
        get {
            assert(indexsIsAvailable(row: row, column: column),"Index out of range")
            return grids[(row * columns + column)]
        }
    }
    // 下标的重载(通过入参的数量和类型区分)
    subscript(valueOfIndex: Int) -> Double {
        assert(valueOfIndex < grids.count, "index out of girds.count")
        return self.grids[valueOfIndex]
    }
}

var matrix = Matrix(rows: 2, columns: 3)
print(matrix)
matrix[1, 1] = 1.0
print(matrix)
print("二维数组第5个元素为:\(matrix[4])")
matrix[1, 3] = 1.0
/*
Matrix(rows: 2, columns: 3, grids: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
Matrix(rows: 2, columns: 3, grids: [0.0, 0.0, 0.0, 0.0, 1.0, 0.0])
Assertion failed: Index out of range
*/

继承

一个类可以继承另一个类的方法,属性和其他特性.

// 基类Vehicle
class Vehicle {
    var currentSpeed = 0.0
    var description: String {
        return "traveling at \(currentSpeed) miles per hour"
    }
    func makeNoise() {
        // 什么也不做-因为车辆不一定会有噪音
    }
}

class Train: Vehicle {
    // 重写父类方法
    override func makeNoise() {
        print("Choo Choo")
    }
}
let train = Train()
train.makeNoise()

class Car: Vehicle {
    var gear = 1
    // 重写父类属性
    override var description: String {
        // 使用super修饰符访问父类属性
        return super.description + " in gear \(gear)"
    }
}
let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")

class AutomaticCar: Car {
    // 重写父类属性观察器
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
/*
Choo Choo
Car: traveling at 25.0 miles per hour in gear 3
AutomaticCar: traveling at 35.0 miles per hour in gear 4
*/

构造过程

构造过程是指使用类,结构体和枚举类型等的实例之前的准备过程,主要包括设置实例中存储类型的初值和其他必须的设置初始化工作.

存储属性初始赋值

类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值.
可以在构造器中设置,也可以在定义时为其设置默认值.

当为存储属性设置初值时,不会触发属性观察器.
构造过程可以为常量属性设置一个确定值,且一旦设置成功后不能再被修改.

构造器

构造器在创建创建某个特定类型的新实例时被调用,最简单的形式是以关键字init命名.

init(parameters) {
    statements
}
// 使用convenience关键字修饰
convenience init(parameters) {
    statements
}

类的构造器代理规则:

  1. 指定构造器必须调用其直接父类的的指定构造器
  2. 便利构造器必须调用同类中定义的其它构造器
  3. 便利构造器必须最终导致一个指定构造器被调用

类的构造器对比

注意:
值类型的自定义构造器会覆盖默认构造器(结构体类型还无法访问逐一成员构造器).
如果希望默认构造器,逐一成员构造器和用户自定义构造器共存,都能用来创建实例,可以将用户自定义构造器写到扩展(extension)中.

两段式构造过程

两段式构造过程可以防止属性值在初始化前被修改,也可以防止属性被另一个构造器意外的赋值.

为确保两段式构造过程执行而进行的4项安全检查:

  1. 指定构造器确保其所在类中的所有属性都已初始化完成,之后再将其它构造任务向上代理给父类中的构造器.

    对象的内存空间只有在其所有的存储型属性被确定后才能完全初始化.

  2. 指定构造器必须先向上代理调用父类构造器,然后才能为继承的属性设置新值.

    如果不这么做,子类为继承属性设置的新值会被父类构造器赋值操作覆盖.

  3. 便利构造器必须先代理调用同一类中的其他构造器,然后再为任意属性赋新值.

    否则,便利构造器赋予的新值会被同一类中的其他指定构造器赋值操作所覆盖.

  4. 构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用self作为一个值.

    在第一阶段构造完成前,类实例并不是有效的.进行相关调用会导致报错.

两段式构造过程的构造流程展示:

  1. 阶段1
    • 某个指定构造器或者便利构造器被调用.
    • 此时已经完成新实例内存的分配,但是内存还未被初始化.
    • 指定构造器确保所在类中引入的所有存储型属性都已被赋值,存储型属性所属的内存完成初始化.
    • 指定构造器调用父类的构造器,完成父类属性的初始化.
    • 沿着调用父类构造器的过程一直向上执行,直到到达构造器链的最顶部.
    • 当到达了构造器链式最顶部,且已确保所有实例包含的存储属性都被赋值.至此,这个实例的内存被认为已经完全初始化.

两段式构造过程1

构造过程从子类的便利构造器调用开始,此时便利构造器无法修改任何属性,它把构造任务代理给同一类中的指定构造器.
指定构造器确保所有子类的属性都有值,然后调用父类的指定构造器,并沿着构造器链一直向上完成父类的构造过程.

父类的指定构造器来确保所有父类的属性都有值.当没有更多的父类需要初始化时就无需继续向上代理.
此时父类中的所有属性都有了初始值,实例的内存被认为是完全初始化.

  1. 阶段2
    • 从顶部构造器链一直往下,每个构造器链中类的指定构造器进一步定制.此时构造器可以访问self,修改属性或调用实例方法等.
    • 最后,任意构造器链中的便利构造器都可以定制实例和使用self.

两段式构造过程2

父类中指定构造器反过来进行实例定制.
父类中的指定构造器完成调用后,子类中的指定构造器就可以执行更多的定制操作.
子类的指定构造器完成调用后,最开始调用的便利构造器就可以执行更多的定制操作.

参考资料:两段式构造过程

构造器的继承和重写

Swift中的子类默认是不会继承父类的构造器.子类可以手动继承或重写父类构造器(使用override关键词修饰,即使是重写系统的默认构造器).

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")

class Bicycle: Vehicle {
    // 重写父类默认构造器
    override init() {
        // 需要调用父类的默认构造器
        super.init()
        numberOfWheels = 2
    }
}
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
/*
Vehicle: 0 wheel(s)
Bicycle: 2 wheel(s)
*/

构造器的自动继承

子类在默认情况下不会继承父类的构造器,某些特殊情况下父类构造器是可以被自动继承的.

  1. 子类中没有任何指定构造器,他会自动继承所有父类的指定构造器.
  2. 子类提供了所有父类指定构造器的实现.

可失败构造器

一个类,结构体或枚举类型(是否含有原始值)的对象在构造过程中有可能失败.可失败构造器会创建一个类型为自身类型的可选类型对象.其语法为在init关键字后面添加问号(init?)(也可以使用init!代表该类型为隐式解包可选类型).
可失败构造器可以传递,可以进行向上代理(父类的可失败构造器),横向代理(类型中的其他可失败构造器).

class Product {
    let name: String
    // 可失败构造器
    init?(name: String) {   
        if name.isEmpty { return nil }
        self.name = name
    }
}

class CartItem: Product {
    let quantity: Int
    init?(name: String, quantity: Int) {
        if quantity < 1 { return nil }
        self.quantity = quantity
        super.init(name: name)
    }
}

必要构造器

在类的构造器前添加required修饰符表示该类的子类必须实现该构造器方法.
且子类重写父类必要构造器前必须添加该修饰符(重写父类的必要构造器时不需要添加override修饰符).

使用闭包或者函数设置属性的默认值:每当某个属性所在类型的新实例被创建时,对应的闭包或函数会被调用(需要在大括号结尾添加()),而它们的返回值会当做默认值赋值给这个属性.

struct Checkerboard {
    let boardColors: [Bool] = {
        var temporaryBoard = [Bool]()
        var isBlack = false
        for i in 1...8 {
            for j in 1...8 {
                temporaryBoard.append(isBlack)
                isBlack = !isBlack
            }
            isBlack = !isBlack
        }
        return temporaryBoard
        // 闭包结尾需要添加大括号使Swift立马执行此包.
    }()
    func squareIsBlackAtRow(row: Int, column: Int) -> Bool {
        return boardColors[(row * 8) + column]
    }
}

let board = Checkerboard()
// 每次调用squareIsBlackAtRow内部执行
print(board.squareIsBlackAtRow(row: 0, column: 1))
print(board.squareIsBlackAtRow(row: 7, column: 7))

析构过程

析构器只适用于类类型,当一个类的实例被释放之前析构器会被立即调用.用关键字deinit来标示.
在类的定义中,一个类有且只有一个析构器.且析构器不带任何参数(类似于dealloc方法).

class Bank {
    // 类型属性
    static var coinsInBank = 10_000
    // 类型方法
    static func distribute(coins numberOfCoinsRequested: Int) -> Int {
        // 超过余额后,取余额
        let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
        coinsInBank -= numberOfCoinsToVend
        return numberOfCoinsToVend
    }
    static func receive(coins: Int) {
        coinsInBank += coins
    }
}
class Player {
    var coinsInPurse: Int
    init(coins: Int) {
        coinsInPurse = Bank.distribute(coins: coins)
    }
    func win(coins: Int) {
        coinsInPurse += Bank.distribute(coins: coins)
    }
    // 在资源释放前调用
    deinit {
        Bank.receive(coins: coinsInPurse)
    }
}

var playerOne: Player? = Player(coins: 100)
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
print("There are now \(Bank.coinsInBank) coins left in the bank")
playerOne!.win(coins: 2_000)
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
print("The bank now only has \(Bank.coinsInBank) coins left")
// 设置释放playerOne资源,回收金币
playerOne = nil
print("PlayerOne has left the game")
print("The bank now has \(Bank.coinsInBank) coins")
/*
A new player has joined the game with 100 coins
There are now 9900 coins left in the bank
PlayerOne won 2000 coins & now has 2100 coins
The bank now only has 7900 coins left
PlayerOne has left the game
The bank now has 10000 coins
*/

自动引用计数

引用计数仅仅应用于的实例.结构体枚举值类型,不通过引用进行存储和传递.

循环强引用

如果两个类实例互相持有对方的强引用,从而每个实例都让对方一直存在.

解决方法:使用弱引用(weak reference)或者无主引用(unowned reference).

弱引用

弱引用不会对其引用的实例保持强引用,因而不会阻止ARC销毁被引用的实例.在声明属性或者变量时,在其前添加weak来表明弱引用.

ARC会控制引用的实例被销毁后自动置为nil,因此弱引用的对象应该定义为可选型变量.
当ARC设置设置弱引用为nil时,属性观察期不会触发.

无主引用

无主引用不会牢牢保持住引用的实例.在声明属性或者变量时,在其前添加unowned来表明无主引用.

无主引用通常都被期望拥有值.
使用无主引用,你必须确保引用始终指向一个未销毁的实例.
在实例被销毁后,无主引用不能被设为nil.

// 一个客户可能有或者没有信用卡,但是一张信用卡总是关联着一个客户
class Customer {
    let name: String
    // 客户可以没有信用卡,card允许为nil
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
    let number: UInt64
    // 每张信用卡一定是对应一个客户的
    // 使用unowned将customer对象设置为无主引用,避免循环强引用
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}

var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
// john释放后,Customer实例被销毁,再也没有指向CreditCard实例的强引用,该实例也就被销毁.
john = nil
/*
John Appleseed is being deinitialized
Card #1234567890123456 is being deinitialized
*/

无主引用以及隐式解析可选属性:使用隐式解析可选值意味着满足了类的构造函数的两个构造阶段的要求.通常通过在类型结尾处加上感叹号(!)处理.

// 每个国家必须有首都,每个城市必须属于一个国家.
class Country {
    let name: String
    // 隐式解析可选属性:capitalCity默认值为nil,但是不需要展开它的值就可以访问.
    var capitalCity: City!
    init(name: String, capitalName: String) {
        // 当在构造方法中将name属性赋值后,初始化过程就已经完成,就可以调用隐式的self.
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}
class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

// 可以通过一条语句同时创建Country和City的实例,而不会产生循环强引用.
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// Canada's capital city is called Ottawa

闭包引起的循环强引用

把闭包赋值给类实例的某个属性,且闭包体内又使用了该类实例时,就会存在循环强引用.

闭包和类一样,都是引用类型.闭包的赋值是将闭包的引用进行赋值.
闭包的循环强引用和类实例的循环强引用不同.闭包的循环强引用一个是闭包,一个是类实例.

class HTMLElement {
    let name: String
    let text: String?
    
    // 闭包赋值给了asHTML属性
    // 实例对asHTML有强引用
    lazy var asHTML: Void -> String = {
        // 闭包体中捕获了self,持有了HTMLElement的强引用
        // 将self捕获为无主引用
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    deinit {
        print("\(self.name) is beging deinit")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello world")
print(paragraph!.asHTML())
// 将paragraph对象置为nil时,析构函数未被调用,证明该对象没有被销毁释放
paragraph = nil

解决方案:可以使用闭包捕获列表解决闭包引起的循环强引用.

在定义闭包时,同时定义闭包捕获列表作为闭包的一部分.
捕获列表定义了闭包体内捕获一个或者多个引用类型的规则.

捕获列表中的每一项都有一对元素组成,一个元素是weakunowned关键字,一个是类实例的引用或初始化过的变量.这些项在方括号中有逗号分隔.

闭包和被捕获实例总是相互引用且同时销毁时,将闭包中的捕获定义为无主引用.
当被捕获的引用可能变为nil时,将闭包内的捕获定义为弱引用.

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // 这里是闭包的函数体
}

可选链式调用

可选链式调用是一种可以在当前值为nil的可选值上请求和调用属性,方法及下标的方法.
可选链式调用的返回值结果和原始返回结果具有相同的类型,但是被包装成了一个可选值.如果可选值有值,那么调用就会成功.
多个调用在一起可以形成一个调用链.其中任何一个节点为nil,整个调用链都会失败.

链式调用的展开:在可选值后使用?代替!来强制展开值.
可选链式调用可以用在属性调用,可选类型的访问下标(判断可选类型是否有值)和方法的可选返回值中.

当可选值为空时,可选链式调用只会失败,而强制展开则会触发运行时错误.
当连接多层可选链式调用时,返回值仍为原始可选型.

class Person {
    var residence: Residence?
}
class Residence {
    var numberOfRooms = 1
}

let john = Person()
// 使用去强制解包会抛出运行时错误
// john.residence展开是没有值的,无法调用对象的numberOfRooms属性
let roomCount = john.residence!.numberOfRooms
print("rooms count:\(roomCount)")
// 使用问号代替感叹号,表明residence对象可能为空.可选链式会返回Int?类型
if let roomCount = john.residence?.numberOfRooms {
    print("rooms count:\(roomCount)")
} else {
    print("unable to get rooms")
}

john.residence = Residence ()
if let roomCount = john.residence?.numberOfRooms {
    print("rooms count:\(roomCount)")
} else {
    print("unable to get rooms")
}
// unable to get rooms
// rooms count:1

// 在可选类型数组变量的下标前使用
if let firstRoomName = john.residence?[0].name {
    print("The first room name is \(firstRoomName).")
} else {
    print("Unable to retrieve the first room name.")
}

var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// "Dave" 数组现在是 [91, 82, 84],"Bev" 数组现在是 [80, 94, 81]

// 返回的johnsStreet为String?可选类型
if let johnsStreet = john.residence?.address?.street {
    print("John's street name is \(johnsStreet).")
} else {
    print("Unable to retrieve the address.")
}

// 在方法的可选返回值后进行可选链式调用
// buildingIdentifier()是方法的调用,在其后加?是判断其可选返回值
if let beginsWithThe =
    john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
    if beginsWithThe {
        print("John's building identifier begins with \"The\".")
    } else {
        print("John's building identifier does not begin with \"The\".")
    }
}

错误处理

错误处理是指响应错误,并作出相应处理的过程.Swift提供了对错误的抛出,捕获,传递和操作等一等公民.
在Swift中使用符合Error协议类型的值表示.使用throw关键字抛出错误.

Swift中错误处理并不涉及解除调用栈(代价较高),因此throw语句的性能很高.

// 使用枚举类型定义错误类型
enum VendingMachineError: Error {
    case invalidSelection                    //选择无效
    case insufficientFunds(coinsNeeded: Int) //金额不足
    case outOfStock                          //缺货
}
// 使用throw直接抛出错误
throw VendingMachineError. insufficientFunds(coinsNeeded: 5)

错误处理方式:

使用throwing函数传递参数

throwing函数是指在函数声明的参数列表后加上throws关键字的函数.
throwing函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域(其他函数抛出错误后无法在外部处理).

// 指定返回值类型时,throws的位置要在返回值类型前
func canThrowErrors() throws -> String

// 无返回值函数中throws的位置有所不同
func vend(itemNamed name: String) throws {
        // 使用guard语句来做提前退出方法
        guard let item = inventory[name] else {
            // 抛出错误
            throw VendingMachineError.invalidSelection
        }

        guard item.count > 0 else {
            throw VendingMachineError.outOfStock
        }

        guard item.price <= coinsDeposited else {
            throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
        }

        coinsDeposited -= item.price

        var newItem = item
        newItem.count -= 1
        inventory[name] = newItem

        print("Dispensing \(name)")
    }

使用Do-Catch处理错误

可以使用Do-Catch的运行一段闭包代码处理错误.
如果在do子语句的代码中抛出一个错误,这个错误就会和catch子句做匹配并做出处理.

在内部未作处理的错误会传递到上一级,被相应的语句捕捉处理.

var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
    // 在调用的方法前加try关键字
    try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
} catch VendingMachineError.invalidSelection {
    print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
    print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
    print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
}

将错误转换为可选值

使用try?将错误转换为一个可选值进行处理(未抛出错误时就是nil).

func fetchData() -> Data? {
    // 返回的data为Data?
    // 如果抛出错误,data=nil,否则data有值
    if let data = try? fetchDataFromDisk() { return data }
    if let data = try? fetchDataFromServer() { return data }
    return nil
}

禁用错误传递

某个函数在运行时不会有错误抛出,适合使用禁用错误传递.
在表达式前面写try!来禁用错误传递,会将调用包装在一个不会有错误抛出的运行时断言中,真的抛出错误就会是运行时错误.

let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")

指定清理操作

使用defer语句在即将离开当前代码块时执行一系列操作.

延迟执行语句由关键字defer和要被延迟执行的语句组成.延迟执行语句中不能含有控制转移语句或者错误(即逻辑分支).

延迟执行操作的执行顺序是按照声明顺序从后向前.

func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            // 关闭文件.在处理文件后执行.
            close(file)
        }
        while let line = try file.readline() {
            // 处理文件.
        }
    }
}

常见的类型处理

Swift常见类型详见->类型

类型转换

类型转换可以判断实例的类型,也可以将实例看做是父类或者子类的实现.
类型转换在Swift中使用isas关键字实现.

类型检查:使用is来检查实例是否是某一特定子类型.

向下转型:常量或者变量在底层可能属于一个子类,可以使用类型转换操作符(as?或者as!)向下转到它的子类型.

AnyAnyObject两种特殊的类型别名:

嵌套类型

要在一个类型中嵌套另一个类型,需要将嵌套的定义写在其外部类型的{}作用域内.
在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀.

struct BlackjackCard {
    enum Suit: Character {
        case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
    }
    
    enum Rank: Int {
    }
    
    // 声明属性rank和suit
    let rank: Rank, suit: Suit
    var description: String {
        return "output"
    }
}
// 引用嵌套类型
let heartsSymbol = BlackjackCard.Suit.hearts.rawValue

扩展

扩展就是为已有类,结构体,枚举类型或者协议类型添加新功能.其中包括在没有权限获取原始源代码的情况下扩展类型的能力(逆向建模).
扩展时使用extension关键字.

扩展是为了避免出现类继承的情况.
扩展是向类型中添加新的功能,不是重写已有功能.
扩展定义会改变该类型的所有实例,不论其创建顺序.

NOTICE:Swift 4.0不再允许重载extension中的方法(包括instance,static,class方法).

Swift中的扩展支持:

extension Double {
    var km: Double { return self*1_000.0 }
    var m: Double { return self }
    var mm: Double { return self/1_000.0 }
    var ft: Double { return self/3.28084 }
}
// 设置计算型属性扩展
let oneInch = 25.4.mm
print("one inch is \(oneInch) meters")
let threeFeet = 3.ft
print("three feet is \(threeFeet) meters")
let aMarathon = 42.km + 195.m
print("a marathon is \(aMarathon) meters")
// one inch is 0.0254 meters
// three feet is 0.914399970739201 meters
// a marathon is 42195.0 meters
extension Int {
    func repetitions(task: () -> Void) {
        for _ in 0..<self {
            task()
        }
    }
}
3.repetitions {
    print("hello world")
}

extension Int {
    // 在实例方法中修改属性,需要在方法前加mutating
    mutating func square() {
        self = self * self
    }
}
var someInt = 3
someInt.square()
// someInt 的值现在是 9

通过扩展为已有类型增加新的构造器.但是只能添加便利构造器,不能添加新的指定构造器析构器(这两种构造器必须由原始类实现).

extension Rect {
    // 结构体为center,size等存储属性提供了默认值.
    // 已经存在了默认构造器,可以增加新的构造器.
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

扩展可以为已有类型添加新的下标.

extension Int {
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 0..<digitIndex {
            decimalBase *= 10
        }
        return (self / decimalBase) % 10
    }
}

746381295[0]
// 返回 5
746381295[1]
// 返回 9

协议

,结构体枚举都可以遵循协议,并可以为协议定义提供具体实现.
要让自定义类型遵循某个协议时,要在类型名称后加协议名称,并用:分割,多个协议用,分割.

协议不指定属性是存储属性还是计算型属性,只指定属性的名称和类型.还可以指定属性的可读写限制.

协议中使用var关键字来声明变量属性,在类型声明后加上set/get来控制属性的读写.
也可以使用static/class关键字作为前缀来声明类型属性.

// 任何遵守SomeProtocol协议的类型,都必须有个可读可写的mustBeSettable属性和只读的doesNotNeedToBeSettable属性
protocol SomeProtocol {
    // 可读可写
    var mustBeSettable: Int { get set }
    // 只读
    var doesNotNeedToBeSettable: Int { get }
}

协议可以要求遵循协议的类型实现某些指定的实例方法或者类方法.
也可以使用static/class关键字做前缀来声明类型方法.

协议中定义的方法不支持默认参数值.

protocol RandomNumberGenerator {
    func random() -> Double
}

Mutating方法允许在方法中改变方法所属的实例.
常在func关键字前加mutating关键字作为前缀,表示可以在该方法中修改所属实例以及实例的任意属性值.

protocol Togglable {
    // toggle方法在定义时使用mutating关键字,表示支持改变该实例属性
    mutating func toggle()
}

enum OnOffSwitch: Togglable {
    case off,on
    mutating func toggle() {
        switch self {
        case .off:
            self = .off
        case .on:
            self = .on
        }
    }
}

var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch 现在的值为 .On

构造器可以在遵循协议的类型中实现指定的构造器.

在类中实现构造器时,必须为构造器实现前加required修饰符.
子类重写父类的指定构造器时,需要在构造器前加override修饰符.

protocol SomeProtocol {
    init()
}

class SomeSuperClass {
    init() {
        // 这里是构造器的实现部分
    }
}

class SomeSubClass: SomeSuperClass, SomeProtocol {
    // 因为遵循协议,需要加上 required
    // 因为继承自父类,需要加上 override
    required override init() {
        // 这里是构造器的实现部分
    }
}

协议的常见使用场景:

  1. 作为函数,方法 或者构造器中的参数类型或者返回值类型.
  2. 作为常量,变量或者属性的类型.
  3. 作为数组,字典或者其他容器中的元素类型.

委托允许类或者结构体将一些需要它们负责的功能委托给其他类型的实例处理.
可以定义协议来封装需要被委托的功能,就可以确保遵循协议的类型能够提供这些功能.

协议能够继承一个或者多个其他协议,可以在继承协议的基础上增加新的需求.

在协议的继承列表中,通过添加class关键字来限制协议只能被类类型遵循,而其他类型不能遵循该协议.

当需要同时遵循多个协议时,可以将多个协议采用&进行组合,称为协议合成.

使用isas操作符来检查协议的一致性,还可以转换到指定的协议类型(类型转换).

通过使用optional关键字作为前缀来定义可选要求,使得遵循协议的类型可以选择是否实现这些要求.
使用@objc关键字可以标记协议只能被继承自Objective-C的类或者@objc类遵循.

使用可选要求时,它们的类型会变成可选型(例如整个函数类型变为可选型).

协议可以通过扩展为遵循协议的类型提供属性,方法以及下标的实现.

协议扩展可以使用自定义实现代替扩展中的默认实现.

扩展协议时可以指定一些限制条件,只有满足这些限制条件时,才能获得协议扩展提供的默认实现.
限制条件写在协议名后,使用where关键字来描述.可以使用Self来表示约束泛型,类似于OC中的instancetype.

泛型

泛型函数可以用于任何类型.
泛型函数:通过在函数名后跟上一对尖括号(<>),括号内是类型的形式参数名.

// 交换两个int值
func swapTwoInts(_ a: inout Int, _ b: inout Int)
// 交换两个值,在<>内标出支持的类型
// T是占位符类型,表示任意类型
func swapTwoValues<T>(_ a: inout T, _ b: inout T)

类型形式参数

swapTwoValues(_:_:)函数中,占位符类型T就是一个类型形式参数.
类型形式参数指定并命名一个占位符类型,紧挨着函数名后的一对尖括号中(<T>).

另一种常见的类型形式参数是Dictionary<Key, Value>,其中KeyValue都是用来标识占位符类型.

泛型类型

Swift还允许自定义泛型类型.

// 定义泛型类型Stack,指定Element为类型形式参数,又称占位符类型名.
struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}
// 调用时传入String类型.
var stackOfStrings = Stack<String>()

对泛型类型进行扩展时,可以直接使用泛型类型的占位符类型名.

extension Stack {
    // 可以直接使用原占位符类型Element.
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

类型约束

在一个类型形式参数名称后面放置一个类或者协议作为形式参数列表的一部分,并用冒号隔开以写一个类型约束.

// 指定T的类型为SomeClass,U的类型为SomeProtocol
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // function body goes here
}

// 使用T标识任意类型
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

关联类型

关联类型是给协议中用到的类型占位符名称,通过associatedtype关键字指定.

// 关联类型针对的是协议对象
protocol Container {
    // 使用associatedtype自定义占位符类型名称
    associatedtype ItemType
    mutating func append(_ item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

struct IntStack: Container {
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // 把ItemType抽象类型转换为具体的Int类型
    typealias ItemType = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    // 重写getter方法
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

泛型下标

泛型Where分句以where关键字开头,写在一个类型或函数体的左半个大括号前面,后接关联类型的约束或类型和关联类型一致的关系.
泛型where分句还可以用在扩展中,关联类型中.

// 自定义Container为泛型形式参数名
// 传入两个参数C1和C2
// 使用泛型where语句限制C1的ItemType必须和C2的ItemType相同,且C1的ItemType必须遵循Equatable协议
func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
    return false
}

下标也可以是泛型,可以包括泛型where分句.

extension Container {
    // 在下标操作中使用泛型where语句
    subscript<Indices: Sequence>(indices: Indices) -> [Item]
        where Indices.Iterator.Element == Int {
            var result = [Item]()
            for index in indices {
                result.append(self[index])
            }
            return result
    }
}

访问控制

Access Control 权限控制的黑与白

访问控制和protected

模块是单一的代码分配单元,一个框架或者应用程序会作为独立的单元构建和发布并且可以使用Swift中的import关键字导入到另一个模块.
源文件是一个模块中当个Swift源代码文件(常指应用程序或者是框架中的单个文件).

访问控制限制其他源文件和模块对你的代码的访问,允许你隐藏代码的实现细节,并指定一个偏好的接口让其他代码可以访问和使用.

访问级别

Swift中访问级别遵循的原则:实体不可以被更低(限制更多)访问级别的实体定义.

Swift中提供了五种不同的访问级别:

open访问和public访问:

各种类型的访问控制

可以在定义类型时,给类型指定访问级别.

高级运算符

在Swift中可以自由的定义中缀,前缀,后缀和赋值运算符,以及相应的优先级和结合性.

Swift中算术运算符默认是不会溢出的.
所有的溢出行为都会被系统捕捉并报告为错误.

位运算符

溢出运算符

在默认情况下,当数值超出它的容量时就会产生溢出,Swift就会报出错误,而不是生成一个无效的数值.
对数值溢出的常见处理有:溢出加法&+,溢出减法&-,溢出乘法&*.

数值溢出:数值上溢时,会从最大值变为最小值;数值下溢时,会从最小值变为最大值.

优先级和结合性

运算符都具有优先级,可以使用括号改变结合性达到想要的优先级.

运算符函数

在类和结构体中可以为现有的运算进行自定义实现,即运算符重载.

// 自定义前缀运算符
prefix operator +++
// 自定义运算符的优先级
infix operator +-: AdditionPrecedence

语法结构

Swift中的语法结构描述了能够构成该语言的合法符号的字符序列.
一个字符序列常由标识符,关键字,标点符号,字面量运算符组成.

类型

Swift语言中的类型有命名型类型复合型类型.

命名类型:指定义时可以指定类型名字,其包括类,结构体,枚举,协议等.

复合类型:指没有名字的类型,它由Swift系统定义.Swift中有两种复合类型,函数类型和元组类型.

类型注解:显式地指出指定一个变量或表达式的值类型.

类型标识符:引用命名型类型,还可以引用命名型或复合型类型的别名.

常见的几种类型

Swift里面的类型分为两种:
值类型与引用类型

只有当元组中元素个数大于等于2时才可以命名元组元素.

Void是空元组类型()的别名.

函数类型表示一个函数,方法或闭包的类型,它由参数类型和返回值类型组成,中间用箭头(->)隔开.

函数类型可以使用autoclosure关键字自动将参数表达式改为闭包类型,表达式结果就是闭包返回值.
可以拥有可变长参数作为参数类型中的最后一个参数.
可以在参数类型前加inout指定参数读写类型.

定义使用命名类型Array[element]或者[类型].

定义使用命名类型Dictionary<Key, Value>或者[键类型 : 值类型].

用后缀?代替命名类型Optional<Wrapped>.

Optional<Wrapped>是枚举值,有nonesome(Wrapped)两个值.用来表示可能有值也可能没有值.

如果在声明或定义可选变量或属性时未指定初始值,它的值会自动赋为默认值nil.

用后缀!代替命名类型Optional<Wrapped>来实现自动解包功能.

指符合协议列表中每个指定协议的类型.协议合成类型可能会在类型注解和泛型参数中(协议).

元类型是指类型的类型,包括有类类型,结构体类型,枚举类型和协议类型.
协议类型使用.Protocol获取元类型,其它类型则通过.Type获取元类型.
还可以使用.self表示元类型.

用来指定一个命名类型继承自那个类(继承)或遵循那些协议.

允许省略代码中变量和表达式的类型或者部分类型(类型标注).

造个类型不是梦-白话Swift类型创建(Bool类)

表达式

前缀/后缀表达式

前缀表达式由可选的前缀运算符和一个表达式组合而成.(基本运算符,高级运算符)

一个try表达式由一个try运算符和一个可抛出错误的表达式组成.

// 如果表达式不能抛出错误,那么可选try表达式的值就是一个包含了表达式值的可选项.
try? expression

二元表达式

二元表达式由一个中缀二元运算符和两个表达式作为左实际参数和右实际参数.

实际解析时,通常会按照运算符的优先级把表达式转换为树.

三元条件运算符

三元条件运算符会基于条件的值来对两个给定值中的一个进行计算.

类型转换运算符

常见的有is运算符,as运算符,as?运算符和as!运算符.

字面量表达式

当作为函数或是方法的默认值时,特殊字面量的值取决于默认值表达式调用的时候.

表达式 表示意义
#file 所在的文件名
#line 所在的行数
#column 所在的列数
#function 所在的声明的名字
#selector 可以引用属性的setter,getter方法或者方法的选择器
#keyPath 可以访问Objective-C中引用属性的字符串,用来使用键值编码和键值观察
[] 数组字面量
[:] 字典字面量

在Swift 4.0中selector的适配问题:

  1. 确保工程的Build Settings中的Swift 3 @objc inference设置为Default.
  2. 使用#selector参数指定实例方法时(不论该方法是否是private修饰)必须使用@objc修饰.

元组表达式:使用括号括起来用逗号分隔的表达式组成.

通配符表达式:可以在赋值时显示地忽略一个值,使用_关键字标记.

Self表达式和父类表达式

self表达式用来明确当前类型或实例类型的显式引用(self).

父类表达式使类能够与它的父类互动.子类可以在它们的成员,下标和初始化器的实现中使用父类表达式,来使用父类的实现(super).

闭包表达式

闭包表达式主要用于创建闭包,也就是lambda或匿名函数.

闭包表达式可以进行相关省略:

更多参考:闭包表达式

可选链表表达式和强制取值表达式

使用后缀运算符?会根据表达式形成可选链但是不会改变其值,
使用后缀运算符!对可选项展开后返回非可选的相关类型,否则就是运行时错误.

语句

常见的几种语句

详见控制流

编译器控制语句

Swift有两种编译器控制语句:编译配置语句线路控制语句.

编译配置语句:编译配置语句可以根据一个或多个配置来有条件地编译代码.一个编译配置语句都以#if开始,#endif结束.

#if 编译配置1
    如果编译配置1成立则执行这部分代码
#elseif 编译配置2
    如果编译配置2成立则执行这部分代码
#else
    如果编译配置均不成立则执行这部分代码
#endif

行控制语句:行控制语句可以为被编译的源代码指定行号和文件名,从而改变源代码的定位信息,以便进行分析和调试.

#sourceLocation(file: 文件名 , line:行号)
#sourceLocation()

可用性条件:可用性条件可作为if,while,guard语句的条件,可以在运行时基于特定的平台参数来查询API的可用性.

if #available(平台名称 版本, ..., *) {    
    如果 API 可用,则执行这部分语句    
} else {    
    如果 API 不可用,则执行这部分语句
}
// 表示此方法仅在iOS 11及其以后的系统可用
@available(iOS 11.0, *)
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
}

声明

常见的声明关键词

关键字字段 含义 备注
import 导入声明  
let/var 常量声明/变量声明  
override 重写标记  
static 声明类型变量属性/类型方法  
typealias 类型别名  
func 函数声明  
enum 枚举声明  
indirect 标记对象可以被递归调用  
set/getwillSet/didSet 计算型属性和存储型属性  
struct/class/protocol/extension 结构体/类/协议/扩展声明  
init/deinit 构造器/析构器初始化  
subscript 下标声明  
operator 运算符声明  
inout/_/... 可以在方法体内部修改外部值的参数/显式忽略参数/可变参数.实际接收参数类型为序列. 特殊参数处理
mutating 在实例对象中修改相关值  
throws/catch 抛出错误/捕捉错误 错误处理方法
infix/prefix/postfix 中缀/前缀/后缀 运算符声明
dynamic/final/lazy/optional/required 动态属性标记/类不能被继承,属性不能在子类中被重写/懒加载/属性方法等可选型标记/属性方法等必选标记 声明修饰符
public/internal/private 可以被同模块和导入声明的模块中访问/只能被同模块的代码访问/只能被所在源文件的代码访问 访问控制级别

特性

符号@后跟特性的名称和特性接收的任何参数来指定一个特性.

@ 特性名
@ 特性名(特性参数)

声明特性

声明属性只能应用于声明中.

@objc主要是处理OC和Swift混编时方法的调用以及属性获取问题.Swift 4.0将去掉了相关的隐式类型推断,因此需要手动来管理.

Interface Builder中的声明特性有:IBAction(修饰类的方法),IBDesignable(修饰类的声明),IBOutletIBInspectable(修饰类的属性声明).

类型特性

类型特性只能用于修饰类型.

模式

模式是代表单个值或者复合值的结构.

Swift下的模式分为两类:

通配符模式

使用通配符表达式_来忽略任何值.

标识符模式

标识符模式匹配任何值,并将匹配的值和一个变量或常量绑定起来.

值绑定模式

值绑定模式把匹配到的值绑定给一个变量(var)或常量(let).

元组模式

元组模式是由逗号分隔的,具有零个或多个模式的列表,并由一对圆括号括起来.
可以使用类型标注来限制元组能够匹配到那种元组类型.

枚举用例模式

枚举用例模式匹配现有的某个枚举类型的某个用例.

可选模式

可选模式由一个标识符模式和紧随其后的一个问号组成.

表达式模式

表达式模式代表表达式的值.表达式模式只出现在switch语句中的case标签中.

类型转换模式

类型转换有两种类型转换模式,is模式和as模式.

is 类型
模式 as 类型

Swift 使用 Tips

Swift 中的良好编程风格

Swift 中的宏定义

由于命名空间的缘故,Swift 中是不能使用宏定义的。但是可以使用其他方法达到宏定义的目的。

// 使用let常量定义无参数的宏定义
let kScreenHeight = UIScreen.main.bounds.height
let kScreenWidth = UIScreen.main.bounds.width

// 使用函数定义有参数的宏定义
func KRGBColor(_ red:CGFloat, green:CGFloat, blue:CGFloat) -> UIColor {
    return UIColor.init(red:red , green: green, blue: blue, alpha: 1.0)
}
func KRandomColor() -> UIColor {
    return KRGBColor(CGFloat(Double(exactly: arc4random_uniform(256))!/255.0),
                     green: CGFloat(Double(exactly: arc4random_uniform(256))!/255.0),
                     blue: CGFloat(Double(exactly: arc4random_uniform(256))!/255.0))
}

参考资料:swift中的宏定义

NSRange 和 Range 的互转

// Swift 4.0

// NSRange -> Range
let range = Range(nsrange, in: str)

// Range -> NSRange
let nsrange = NSRange(range!, in: str)

// Swift 3.0
extension String {
    func toNSRange(_ range: Range<String.Index>) -> NSRange {
        guard let from = range.lowerBound.samePosition(in: utf16), let to = range.upperBound.samePosition(in: utf16) else {
            return NSMakeRange(0, 0)
        }
        return NSMakeRange(utf16.distance(from: utf16.startIndex, to: from), utf16.distance(from: from, to: to))
    }
    func toRange(_ range: NSRange) -> Range<String.Index>? {
        guard let from16 = utf16.index(utf16.startIndex, offsetBy: range.location, limitedBy: utf16.endIndex) else {
            return nil
        }
        guard let to16 = utf16.index(from16, offsetBy: range.length, limitedBy: utf16.endIndex) else {
            return nil
        }
        guard let from = String.Index(from16, within: self) else {
            return nil
        }
        guard let to = String.Index(to16, within: self) else {
            return nil
        }
        return from ..< to
    }
}

[Methods to convert between Swift string ranges (Range) and NSString ranges (NSRange)](https://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index/30404532#30404532)

substring 方法/求子串的使用

// modelString.substring(with: range)
// 'substring(with:)' is deprecated: Please use String slicing subscript.

// Substring
let newStr = str[..<index]
// String
let newStr = String(str[..<index])

How can I use String slicing subscripts in Swift 4?

Swift和Objective-C对比

两种语言的区别

// TODO: 区别

两种语言的混编

参考资料:

Objective-C Mix Swift Configuration:混编设置

Swift与C语言指针友好合作

The Swift Programming Language 中文版(Swift 3.0)

Swift 编程语言(Swift 4.0)


⬆️ 回到顶部 ⬆️