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
咱们能够运用 assert
和 precondition
检测条件是否满意,它们两者的差异是 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
guard
和 if
的差异在于,它要求条件有必要为 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) 继续缩写以上调用,因为 >
或 <
在字符串中被界说为接受两个字符串参数回来值为布尔类型的办法,正好契合 sorted
中 by
接纳的办法类型,因而,咱们能够直接传递操作符:
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) 等,当导入一个模块后,你就能够运用其间任何公共的办法和数据模型了。
虽然 struct
和 class
能够界说在同一个文件中,可是我觉得最好还是创立一个新的模块,用独自的文件保存,因而,先介绍一下模块的常识,关于拜访控制 (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
struct
和 class
在 Swift 都是组织代码的根本结构,它们有很多共同点,比方:
- 都能够界说特点、办法和 subscripts
- 都能够界说初始化器
- 都能够被扩展增加功用
- 都能够完成 protocols 来完成特定用处
不同之处在于,类比较结构有更多特别功用:
- 类能够被承继
- 类支撑类型转化和运转时类型查看
- 能够在毁掉办法中释放资源
- 能够运用引证计数,一个类实例能够有多个引证
除此之外,struct
和 class
的用法根本共同,界说一个 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()
作为值类型的 struct
和 enum
在 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
咱们还能够在特点上设置监听器,从而在特点设值之前和设值之后做一些操作。关于存值特点,咱们经过 willSet
和 didSet
办法监听;关于核算后特点,则能够经过 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
之外,struct
和 enum
类中也能界说办法。
实例办法
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
class
和 struct
的结构器办法是 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…