HeadFirstSwift

Swift 是在 2014 年苹果开发者大会上面世的新言语,比较 Objective-C,它的语法更简练,也更现代化。

这篇学习笔记主要参阅自 Swift 官方文档。

The Basics

Swift 是一门现代化的言语,它的语法和 Kotlin 十分类似,因而,我这儿只会整理出一些特别的当地。

常量和变量

Swift 中运用 let 关键字声明常量,var 关键字声明变量。

let constant: String = "Swift"
var version = "5.7.1"
print("Hello, \(constant) \(version)!")

不同于 Objective-C,Swift 不需求一个 main 办法作为程序入口,咱们能够直接将上述代码保存成文件,比方 basics.swift,然后运用下面的命令运转它:

swift basics.swift

类型别号

给已有的类型增加别号,用关键字 typealias 表明。

// 界说别号 unsigned UInt 16
typealias AudioSample = UInt16
// 运用别号调用原来类型上的特点或办法
var maxAmplitudeFound = AudioSample.min

运用别号能够增加代码的可读性,咱们能够在某些情况下,依据当时上下文为类增加别号。

Tuple

元组是 Swift 中独有的数据类型,用于表明一组值。

// 用 () 创立元组
let http404Error = (404, "Not Found")
// 解构赋值
let (statusCode, statusMessage) = http404Error
// 依据下标拜访元组
print(http404Error.1)
// 也能够给元素命名
let http200Success = (code: 200, result: "OK")
print("The result is \(http200Success.result))

元组最大的用处是在办法中作为回来值,当咱们需求回来多个不同类型的数据时就能够运用元组。

Optionals

类似于 Kotlin 中的 nullable 和 Dart 中的 null safe,用于表明一个变量或许没有值。

用法也根本共同,不同的当地在于,它需求运用 ! 转化 (unwrap) 后才干运用:

var serverResponseCode: Int? = nil
if serverResponseCode != nil {
	  print("response code: \(serverResponseCode!)")
}
Optional Binding

咱们能够运用 optional binding 的语法查看某个 optional 是否有值,语法如下:

if let constantName = someOptional {
    // now you can use `constantName`
}

需求留意的是,经过这种语法创立的常量,只要在该句子下才干运用。

Implicitly Unwrapped Optionals

当咱们确定某个可选变量一定有值时,能够运用隐式转化,使得一个可选变量能够直接拜访而不用再进行转化。

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString

Assertions and Preconditions

咱们能够运用 assertprecondition 检测条件是否满意,它们两者的差异是 assert 只在 debug 阶段运转,而 precondition 则在 debug 和 production 下都会运转。

var index = 3
precondition(index > 3)
assert(index > 3)

String

Swift 中的字符串用法和 Kotlin 根本共同,比方多行字符串等。

扩展分隔符

当字符串中包括多个转义字符的时分,咱们能够运用扩展分隔符,语法是在字符串前后都加上 #

print(#"Write an "extended delimiters" string in Swift using #\n"."#)

字符串插值

Kotlin 中能够运用 $ 在字符串中引证变量,Swift 中运用 \()

let someString = "some string literals \(3*2)\n"
print("some string = \(someString)")

字符串常用办法

长度和字符串下标

经过 count 获取字符串长度。因为 Swift 中字符串中的每个字符或许运用不同数量的内存空间,所以无法直接运用 int 下标拜访,而有必要经过字符串下标 (String.Index):

var str = "Swift"
print("count: \(str.count)")
let endIndex = str.endIndex
let startIndex = str.startIndex
var lastCharIndex = str.index(before: endIndex)
print("first: \(str[startIndex]), last: \(str[lastCharIndex])")
var charIndex = str.index(startIndex, offsetBy: 2)
print("char at \(charIndex.utf16Offset(in: str)): \(str[charIndex])")

上面的比方中,startIndex 表明字符串中榜首个字符的下标,endIndex 表明最终一个字符之后的下标。因而,当字符串为空时,这两个值相同。

咱们运用 index 办法获取最终一个字符的下标,然后经过 [indexValue] 语法获取字符串中的字符。

增加和删去
str.insert("!", at: endIndex)
print("insert !: \(str)")
str.insert(contentsOf: "Hello ", at: startIndex)
print("insert Hello: \(str)")
str.remove(at: str.index(before: str.endIndex))
print("remove last char: \(str)")
let range = startIndex..<str.firstIndex(of: "S")!
str.removeSubrange(range)
print("remove first word: \(str)")

Substring

Substring 是字符串的一个切片,它是一个独立的类型,可是运用办法和字符串根本共同,并且一切的操作都很高效,因为 Substring 同享原有字符串的存储空间。

let greeting = "Hi, Swift!"
let start = greeting.firstIndex(of: "S")!
let end = greeting.firstIndex(of: "!")!
let substring = greeting[start...end]
print("substring =\"\(substring)\"")
var substringToNewString = String(substring))

Collections

Swift 中有三种根本的调集类型,Array / Set / Dictionary。

Arrays

var someInts: [Int] = [0, 1]  // 类型可省略
var threeInts = Array(repeating: 2, count: 3)
var twoInts = [Int](repeating: 3, count: 2)
var combinedInts = someInts + threeInts + twoInts
print(combinedInts)
var basket = ["apple", "banana", "orange"]
basket.insert("blueberry", at: 1)
print(basket)
basket.removeLast();
print(basket)
for (index, value) in basket.enumerated() {
    print("Item at \(index): \(value)")
}

Set

var langs: Set<String> = ["kotlin", "dart", "swift"]
if langs.contains("swift") {
    print("Hi, Swift!")
}

Dictionary

var namesOfIntegers: [Int: String] = [:]
namesOfIntegers[1] = "one" // 运用 subscript 拜访或赋值
print(namesOfIntegers)
var languageBirth = ["Kotlin": 2011, "Dart": 2011, "Swift": 2014]
var keys = [String] (languageBirth.keys) // to array
print("languages: \(keys)")
for (lang, year) in languageBirth {
    print("\(lang) is first published at \(year)")
}
languageBirth["Dart"] = nil // remove item
print(languageBirth)

Control Flow

For-In Loops

// closed range operator
for index in 1...3 {
    print("\(index) times 5 is \(index * 5)")
}
// half-open range operator
for tick in 1..<3 {
    print("count to \(tick)")
}
// stride(from:through/to:by:) function
for tick in stride(from: 3, through: 12, by: 3) {
    print("upgrade to \(tick)")
}

While Loops

var gameRound = 1
while gameRound <= 3 {
    print("Playing round \(gameRound)")
    gameRound += 1
}
print("Game over")
let daysInWeek = 5
var workingDays = 0
repeat {
    print("Day \(workingDays + 1): Today you're going to work")
    workingDays += 1
} while workingDays < daysInWeek

Switch

Swift 中的 switch 语法和其它言语根本相同,不同的是在 case 判别中支撑更多的形式匹配,比方支撑多个值判别、区间判别、元组、临时赋值等。具体请看 control_flow.swift

Optional Chaining

咱们能够链式调用可为空的值:

if let hasRmb = person.wallert?.rmb?.notEmpty {
    print("You can pay with RMB.")
} else {
    print("Insufficient funds.")
}

guard

guardif 的差异在于,它要求条件有必要为 true 才干履行后面的代码,并且总是包括一个 else 句子。

func greet(person: [String: String]) {
    guard let name = person["name"] else {
        // else 中有必要运用 return 或者 throw 抛出反常中断后续代码的履行
        return
    }
  	// 运用 guard 后,optional binding 中的变量在后面的作用域中也可用了
    print("Hello \(name)!") // 将 guard 改成 if 后再试试
    guard let location = person["location"] else {
        print("I hope the weather is nice near you.")
        return
    }
    print("I hope the weather is nice in \(location).")
}

available

咱们能够在履行代码前判别 API 是否可用:

func chooseBestColor() -> String {
    guard #available(macOS 10.12, *) else {
        return "gray"
    }
    let colors = ColorPreference()
    return colors.bestColor
}

Function

Swift 中的办法相同是 first-class 的,办法能够作为参数,也能够作为回来值。

一个典型的 Swift 办法界说如下:

// 办法称号是:greeting(name:)
func greeting(name: String) -> String {
    return "Hi, \(name)!"
}

Swift 中的办法参数与 Objective-C 中相同,是办法称号的一部分,参数分为标签 (argument label) 和称号 (parameter name) 两部分。假如咱们没有界说标签,则默许运用参数称号作为标签,假如想要运用不签字参数,则能够运用 _ 作为标签。

// 办法称号是:greeting(for:),参数标签和参数称号不同
func greeting(for name: String) -> String {
    return "Hi, \(name)!"
}
// 办法称号是:greeting(_:),参数无标签
func greeting(_ name: String) -> String {
    return "Hi, \(name)"
}

此外,办法的参数还支撑默许值、动态参数列表 (variadic parameters) 等。和 Kotlin 不同,Swift 中的参数还能够运用 inout 关键字来支撑修改参数的值,具体请看 function.swift

办法类型

咱们能够将办法作为数据类型运用:

// 作为变量
var mathFunction: (Int, Int) -> Int = addTwoInts
// 作为参数
func printMathResult(_ mathFunction: (Int, Int) -> Int) {
    mathFunction(1, 2)
}
// 作为回来值
func produceMathFunction() -> (Int, Int) -> Int {
    return addTwoInts
}

Closure

Swift 中的 closure 类似于 Kotlin 中的 lambda 办法以及 Objective-C 中的 blcoks。

let languages = ["Kotlin", "Dart", "Swift"]
let sortedWithClosure = languages.sorted(by: {
  (s1: String, s2: String) -> Bool in s1 < s2
})

以上比方中,sorted 办法的 by 参数接纳的是一个办法类型,因而咱们能够创立一个 closure 去完成调用。一个 closure 的完好语法如下:

{ (parameters) -> returnType in
    statements
}

当参数和回来值的类型能够被推断出来时,能够简写成以下的形式:

let sortedWithClosureOneLiner = languages.sorted(by: { s1, s2 -> in s1 < s2 })

另外,在 closure 中,参数能够缩写成 $index 的形式,因而,以上调用能够进一步缩写成:

let sortedWithClosureNoArgs = languages.sorted(by: { $0 < $1 })

咱们甚至能够运用操作符办法 (Operator Methods) 继续缩写以上调用,因为 >< 在字符串中被界说为接受两个字符串参数回来值为布尔类型的办法,正好契合 sortedby 接纳的办法类型,因而,咱们能够直接传递操作符:

let sortedWithClosureOperator = languages.sorted(by: <)
Trailing Closure

类似于 Kotlin 中的 trailing lambda,当办法最终一个参数是一个 closure 时,咱们能够将 closure 调用写在 () 之外。而当假如 closure 是调用的唯一的参数时,则能够省略 ()

上面的比方用拖尾 closure 表明便是:

let sortedWithTrailingClosure = languages.sorted { $0 < $1 }

不同之处在于,Swift 中的拖尾表达式能够链式调用:

// 模拟下载办法
func download(_ file: String, from: String) -> String? {
    print("Downloading \(file) from \(from)")
    return Int.random(in: 0...1) > 0 ? file : nil
}
// 下载图片办法,包括两个办法类型的参数
func downloadPicture(name: String, onComplete: (String) -> Void, onFailure: () -> Void) {
    if let picture = download(name, from: "picture sever") {
        onComplete(picture)
    } else {
        onFailure()
    }
}

运用链式 closure 调用该办法:

downloadPicture(name: "photo.jpg") { picture in
    print("\"\(picture)\" is now downloaded!")
} onFailure: {
    print("Couldn't download the picture.")
}
Capturing Values

当咱们运用 closure 时,有时会发生捕获 closure 作用域之外的值的现象:

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    // 捕获外部作用域的值
    func incrementer() -> Int {
        // incrementer 办法内部会保留 runningTotal 和 amount 的引证,
        // 直到该办法不再被运用
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

Enumeration

枚举值语法:

enum CompassPoint {
    case north
    case south
    case east
    case west
}
// or
enum CompassPoint {
    case north, south, east, west
}

运用枚举类:

var direction = CompassPoint.north
// 枚举变量界说后修改能够不写枚举类
direction = .west
switch direction {
case .north:
    print("Lots of planets have a north")
case .south:
    print("Watch out for penguins")
case .east:
    print("Where the sun rises")
case .west:
    print("Where the skies are blue")
}
// 承继 CaseIterable 后就能够遍历枚举类的一切值了
enum CompassPoint: CaseIterable { ... }
// 运用 allCases 特点获取一切枚举值
print("There're \(CompassPoint.allCases.count) directions, and they are:")
// 遍历
for direction in CompassPoint.allCases {
    print(direction)
}
// 枚举类默许是没有原始数据类型 (raw type) 的,可是咱们能够经过承继自指定类型后取得
// 比方承继 Int,则原始数据类型便是随界说方位自增的 Int,承继自 String 便是对应的字符串
enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
// 运用 rawValue 拜访原始数据
print("Earth is No.\(Planet.earth.rawValue) in the solar system.")
// enum 类也能够保存值 (associated value)
enum ListType {
    case vertical(Int)
    case horizontal(Int, String)
}
// 获取值
switch(listType) {
case .vertical(let column):
    print("It's a vertical list with \(column) columns")
case let .horizontal(row, id):
    print("It's a horizontal list with \(row) rows, id = \(id)")
default:
    print("Unknow type")
}

Structures and Classes

模块

到目前为止,以上一切代码履行环境都能够视为在脚本中履行,可是,当咱们开始构建大型项目之后,咱们不或许将一切的代码都写在一个文件中,咱们需求将代码抽象化成结构和类以复用代码。除此之外,咱们还或许需求将自己的代码封装成一个模块 (module) 给其他人调用。

在 Swift 中,一个模块是一个最小的代码分发单元,它能够表明一个库 (library)、结构 (framework)、可履行包 (executable package)、应用 (application) 等,当导入一个模块后,你就能够运用其间任何公共的办法和数据模型了。

虽然 structclass 能够界说在同一个文件中,可是我觉得最好还是创立一个新的模块,用独自的文件保存,因而,先介绍一下模块的常识,关于拜访控制 (Access Control) 相关的常识之后再慢慢介绍。

Swift Package Manager

咱们能够运用 Swift 包管理器创立和发布库 (library) 和可履行包 (executable package)。

mkdir MySwiftPackage
cd MySwiftPackage
# 在 MySwiftPackage 文件夹下创立一个可履行包
swift package init --type executable
# 运转
swift run
  • 根本运用:Using the Package Manager
  • 具体用法:swift-package-manager
  • API 文档:Package Description API

Struct vs Class

structclass 在 Swift 都是组织代码的根本结构,它们有很多共同点,比方:

  • 都能够界说特点、办法和 subscripts
  • 都能够界说初始化器
  • 都能够被扩展增加功用
  • 都能够完成 protocols 来完成特定用处

不同之处在于,类比较结构有更多特别功用:

  • 类能够被承继
  • 类支撑类型转化和运转时类型查看
  • 能够在毁掉办法中释放资源
  • 能够运用引证计数,一个类实例能够有多个引证

除此之外,structclass 的用法根本共同,界说一个 struct:

struct Resolution {
    var width = 0
    var height = 0
}

界说一个类:

class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

创立实例:

// struct 有默许的初始化办法 (memberwise initializer),能够在其间为一切特点赋值
var hd = Resolution(width: 1920, height: 1080)
// 类只要一个默许结构器
var videoMode = VideoMode()

作为值类型的 structenum

在 Swift 中,变量和常量分为两种类型,一种是作为值类型,一种是作为引证类型,根本数据类型 Int Float Double Bool String,以及调集类型 Array Set Dictionary 都是值类型,它们背面都是根据 struct 完成的。也便是说,当把它们作为变量传递时,传递的都是值的复制,而不是引证。

enum 实例也是相同:

var direction = CompassPoint.north
var newDirection = direction
direction = .west
print(newDirection) // still north!

因而,当咱们需求改动值类型时,一般需求在实例办法上增加 mutating 关键字:

mutating func changeDirection(_ newDirection: CompassPoint) {
    self = newDirection
}

作为引证类型的 class

不同于 struct,类的实例是作为引证类型运用的,这点毋庸置疑。

var normal = VideoMode()
normal.name = "Normal"
normal.resolution = sd
normal.frameRate = 60
print("\(normal.name!)-> resolution: \(normal.resolution)")
var blueray = normal
blueray.name = "Blueray"
normal.resolution = hd
normal.frameRate = 90
print("\(blueray.name!)-> resolution: \(blueray.resolution)")

以上比方中,blueray 复制了 normal 的引证,所以当咱们修改 normal 的特点时,blueray 的特点也会得到修改。

为了判别两个类的实例是否持平,Swift 中引入了共同性判别操作符 (Identity Operator) ===!==

if normal === blueray {
    print("They are the same.")
}

Properties

依据是否保存值来看,特点能够分红两种,分别是存值特点 (Stored Properties) 和核算后特点 (Computed Properties)。

Stored Properties

存值特点通常在类 (class) 和结构 (struct) 中作为常量 (var) 和变量 (let) 运用。

struct SomeStruct {
    let someConstant = 0
    var someVar = 0
    // 推迟初始化特点
  	lazy var importer = DataImporter()
}
Computed Properties

核算后特点除了能够在类和结构中运用外,也能够在枚举类中运用,它不保存值,可是会提供 getter 办法和可选的 setter 办法从其它特点或值间接地核算或设值后得到。

class SomeClass {
    var computed: Int = {
        get {
            // 依据其它特点核算得到回来值
        }
        // optional
        set(newValue) {
            // 更新相相关的特点,从而核算新值
        }
        // 也能够不写 setter,直接运用回来值作为 getter
        // 那么,该核算后特点便是 read-only 的
        // return 10
    }
}
Property Observers

咱们还能够在特点上设置监听器,从而在特点设值之前和设值之后做一些操作。关于存值特点,咱们经过 willSetdidSet 办法监听;关于核算后特点,则能够经过 set 办法监听。

struct SomeStruct {
    var someVar: Int {
        willSet(newValue) {
            print("Before set new value")
        }
        didSet {
            // 旧值用 oldValue 表明
            print("new value = \(someVar), old value = \(oldValue)")
        }
    }
}
Property Wrappers

当咱们界说了怎么存储一个特点时,为了复用代码,咱们能够将它作为模板运用。

@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

比方以上界说了一个约束最大值的 property wrapper 之后,能够这样运用:

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

这样,运用了该 property wrapper 的特点最大值会被约束为 12。

咱们也能够在 property wrapper 上设置初始化值:

@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, maximum) }
    }
    init() {
        maximum = 12
        number = 0
    }
    init(wrappedValue: Int) {
        maximum = 12
        number = min(wrappedValue, maximum)
    }
    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        number = min(wrappedValue, maximum)
    }
}

经过增加结构器办法,使得咱们能够指定初始值。

struct MixedRectangle {
    @SmallNumber(maximum: 20) var width: Int = 2
    @SmallNumber var height: Int = 1
}
Global and Local Variables

咱们能够像特点相同设置全局变量,不过全局变量总是会被推迟初始化。咱们也能够为全局变量或者本地变量设置监听器,可是不能够为全局变量或核算后变量设置 property wrapper。

var globalVariable: String = "Swift"
func gloablFunc() {
    @SmallNumber var version: Int = 5
    print("\(globalVariable) \(version)")
}
Type Properties

类型特点也便是静态特点,不需求经过创立实例而是能够直接经过类型拜访的特点。

Methods

办法分为实例办法 (instance method) 和类型办法 (type methods),类型办法也便是 Objective-C 中的类办法。比较 C 和 Objective-C,Swift 最大的不同在于,除了 class 之外,structenum 类中也能界说办法。

实例办法
func getResolution() -> String {
  return "width: \(width), height: \(height)"
}
类型办法

在 Swift 中,相同运用 static 关键字声明类型办法,关于类而言,还能够运用 class func 关键字,这样子类就能够承继和覆写父类中的类型办法了。

class SomeClass {
    class func someTypeMethod() {
        // do something
    }
}
struct SomeStruct {
		static func someTypeMethod() {
        // do something
    }
}

Subscripts

Subscript 是创立对调集、列表等的成员进行拜访和赋值的语法。咱们能够经过下标拜访数组、字典等都是因为其内部完成了 subscript 办法。

咱们能够在恣意的类、结构或枚举类中运用 subscript,其语法如下:

subscript(index: Int) -> Int {
    get {
        // 回来值
    }
    set(newValue) {
        // 赋值
    }
}

subcript 能够指定恣意的参数和恣意类型的回来值,也能够为参数设置默许值和运用可变参数列表,可是不能够运用 inout 参数。

Type Subscript

类型 subscript 和类型办法类似,是直接作用于类型之上的办法。

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
    // 增加类型 subscript,直接经过下标创立枚举类实例
    static subscript(n: Int) -> Planet {
        return Planet(rawValue: n)!
    }
}
var mars = Planet[4]

Initialization

classstruct 的结构器办法是 init

init(argumentLabel parameterName: Type, ...) {
    // 初始化常量和特点等
  	self.someValue = someValue
}

假如初始化参数标签和特点名相同,能够运用 self 引证类中的特点,类似于其他言语中的 this

required init

咱们能够将某个结构器办法标记为 required

class SomeClass {
    required init(param: Type) {
        // initializer implementation goes here
    }
}

这样的话,子类就有必要完成它:

class SomeSubclass: SomeClass {
    required init(param: Type) {
        // subclass implementation of the required initializer goes here
    }
}

Deinitialization

类比较 struct 还多了一个毁掉的办法 deinit,在类实例被毁掉之前被调用:

deinit {
    // 释放资源等
}

毁掉办法会自动调用父类毁掉办法,即便子类没有界说毁掉办法也相同,所以咱们不用担心父类毁掉办法得不到调用。

Type Casting

Swift 中的类型查看相同运用 is 关键字:

if instance is Type {
    print("It's the type")
}
if !(instance is Type) {
		print("Not the type")
}

向下转型则运用 as?as!

// optional 转化
if let movie = item as? Movie {
    print("Movie: \(movie.name), dir. \(movie.director)")
}
// 强制类型转化
var song = item as! Song

Nested Types

咱们能够在类、枚举类型、结构中界说嵌套类型。

struct Manager {
    enum JobTitle {
        case CEO, CFO, CTO, CMO, CHRO, CCO
        case vacant
    }
    let name: String = ""
  	var jobTitle = JobTitle.vacant
    func attendMeeting(meetingName: String) {
        print("\(name)(\(jobTitle)) is attending <\(meetingName)> meeting.")
    }
}

Concurrency

并发是个杂乱的话题,所以这儿不会具体翻开。Swift 中的并发用法和 Kotlin 或 Dart 根本共同,背面都是根据 thread 完成,咱们只需求运用几个简单的关键字就能完成同步和异步履行代码。

static func fetchUserId() async -> Int {
    print("fetching user id...")
    try? await Task.sleep(nanoseconds: 1000_000_000)
    return Int.random(in: 1..<5)
}
static func fetchUserName(userId: Int) async -> String {
    print("fetching user name...")
    try? await Task.sleep(nanoseconds: 1000_000_000)
    return "User\(userId)"
}
public static func main() async {
    let userId = await fetchUserId()
    async let userName = fetchUserName(userId: userId)
    print("User name = \(await userName)")
}

Protocols

和 Objective-C 相同,Swift 中也有 protocol 的概念,咱们用它来界说一组功用,然后交给子类完成,类似于其他言语中的接口概念。在 Swift 中,enum / struct / class 都能够完成 protocol 来增强本身的功用。

语法:

protocol SomeProtocol {
	  static var someTypeProperty: Type { get set }
    var mustBeSettable: Type { get set }
    var doesNotNeedToBeSettable: Type { get }
    init(parameterName: Type)
  	static func someTypeMethod(argumentLabel parameterName: Type) -> returnValue
  	func someMethod(argumentLabel parameterName: Type) -> returnValue
}

完成:

class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
    // 完成特点和办法
}

Mutate 办法

关于值类型 (struct & enum),假如想要改动特点或本身的实例,需求在办法前增加 mutating 关键字。

假如你确定 protocol 中的某个办法有或许会改动值且有或许会被值类型运用,则能够用 mutating 润饰:

protocol Togglable {
    mutating func toggle()
}

子类:

enum OnOffSwitch: Togglable {
    case off, on
    // 假如是被 class 完成则不需求写 mutating
    mutating func toggle() {
        switch self {
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}
初始化办法

protocol 中的初始化办法都是 required 的,子类有必要完成:

// 假如 protocol 中界说了 init() 办法
class SomeClass: SomeProtocol {
    required init() {
    }
}

假如子类完成同时承继了父类和完成了 protocol,则需求运用 required override 关键字:

class SomeSubClass: SomeSuperClass, SomeProtocol {
    // "required" from SomeProtocol conformance; "override" from SomeSuperClass
    required override init() {
        // initializer implementation goes here
    }
}

Extensions

Swift 中的 extension 类似于 Objective-C 中的 category,用于扩展 enum / struct / class / protocol 的功用,比方:

  • 增加核算后实例变量和类型变量(computed properties
  • 增加实例办法 (instance methods) 和类型办法 (type methods)
  • 增加新的结构器办法 (initializers)
  • 增加 subcript 办法
  • 增加和运用新的嵌套类型 (nested types)
  • 完成新的 protocol

增加实例变量和实例办法的比方:

extension VendingMachine {
    // computed instance property
    var singleSongPrice: Int {
        return 10
    }
    /// instance method
    func playSong(name: String) throws {
        guard coinsDeposited > singleSongPrice  else {
            throw VendingMachineError.insufficientFunds(coinsNeeded: singleSongPrice)
        }
        // access instance properties and methods
        coinsDeposited -= 5
        refundRemaining()
        print("Thanks for your coins, now playing \(name)...")
    }
}

留意,增加新的实例变量有必要是 computed properties。

Generics

Swift 中相同能够运用泛型。

泛型办法

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

运用:

var var1 = "var1"
var var2 = "var2"
swapTwoValues(var1, var2)
print("var1 = \(var1), var2 = \(var2)")

类型约束

咱们能够为泛型设置类型约束 (Type Constraints) 来清晰泛型的运用边界。

func someFunc<T: SomeClass, M: SomeProtocol>(someT: T, someM: M) {
    someT.doSomething()
    someM.doSomething()
}

Associated Types

在界说 protocol 时,咱们有时会需求界说一个相关的数据类型,类似于泛型类中的泛型数据类型。

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

上面界说了 Container 的 protocol,内部运用 associatedtype 界说了一个 Item 类作为相关类型,并且部分办法中运用到该类型。接下来咱们定一个完成类:

struct IntStack: Container {
    // 指定 Container 中的 Item 类型为 Int
    typealias Item = Int
    var items: [Int] = []
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

当然,咱们也能够运用泛型类来完成 Container

// 标准库中 Stack 的完成
struct Stack<Element>: Container {
    var items: [Element] = []
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

另外,在相关类型上也能够增加类型约束。

associatedtype Item: Equatable

最终,关于泛型上 where 关键字的运用,请看文档。

Opaque Types

咱们有时或许不想要回来具体的类型,此刻就需求用到含糊类型。比方下面的比方:

protocol Shape {
    func draw() -> String
}
struct Triangle: Shape {
    var size: Int
    func draw() -> String {
        var result: [String] = []
        for length in 1...size {
            result.append(String(repeating: "*", count: length))
        }
        return result.joined(separator: "\n")
    }
}
struct Square: Shape {
    var size: Int
    func draw() -> String {
        let line = String(repeating: "*", count: size)
        let result = Array<String>(repeating: line, count: size)
        return result.joined(separator: "\n")
    }
}
struct JoinedShape<T: Shape, U: Shape>: Shape {
    var top: T
    var bottom: U
    func draw() -> String {
        return top.draw() + "\n" + bottom.draw()
    }
}

咱们界说了一个 Shape protocol,其间主要包括一个 draw 办法用来表明绘制本身形状,然后完成了两个形状类以及一个由两个形状拼接而成的合成形状。接下来咱们界说一个运用这些形状的办法:

func makeTrapezoid() -> some Shape {
    let top = Triangle(size: 2)
    let bottom = Square(size: 2)
    let joinedShape = JoinedShape(
        top: top,
        bottom: bottom
    )
    return joinedShape
}

上面的比方中,咱们运用了 some 关键字,这样咱们就对外部隐藏了具体的形状类型,可是并不影响调用 draw 办法。

let trapezoid = makeTrapezoid()
trapezoid.draw()

Error Handling

Swift 中的反常用 Error 代表,它是一个内容为空的 protocol,咱们能够运用它来自界说咱们的反常,通常运用 enum 类来完成。

enum VendingMachineError: Error {
    case invalidSelection
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
}

抛出反常的语法:

throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

在 Swift 中,当遇到一个反常时,咱们有 4 种处理办法:

  • 向外传递反常 throws
  • 运用 do..catch 语法捕捉反常
  • 将反常作为可选值处理 try?
  • 判定反常不会呈现 try!

下面一一介绍。

向外传递反常

当某个办法不去处理反常时,能够运用 throws 关键字将反常向外抛出:

func vend(name: String) throws {
		guard let item = inventory[name] else {
        throw VendingMachineError.invalidSelection
    }
  	...
}

捕捉反常

语法:

do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
} catch pattern 3, pattern 4 where condition {
    statements
} catch {
    statements
}

比方:

do {
    try vendingMachine.vend(name: name)
    print("Enjoy your \(name)!")
} catch {
    // 捕捉任何反常,运用 error 获取反常类
    print("Something went wrong: \(error)")
}

用可选值接纳

当咱们只是想要获取结果时,能够不对有或许抛出的反常进行处理,而是用可选值接纳:

let result = try? funcMightThrows()

上面运用 try? 调用了一个有或许抛出反常的办法,调用成功则会取得结果,抛出反常则结果为 nil

判别反常不会呈现

当咱们坚信不会抛出反常时,能够强制完成调用,将反常放到运转时抛出。

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() {
            // Work with the file.
        }
        // close(file) is called here, at the end of the scope.
    }
}

一个办法中能够有多个 defer 表达式,可是履行次序是从下往上的,也便是最终界说的 defer 表达式最先履行。并且即便不抛出反常的办法,仍然能够运用 defer 表达式。

内容来历:github.com/aJIEw/HeadF…