Swift学习笔记
Swift学习笔记
[toc]
常量和变量
常量的值一旦设定就不能改变,而变量的值可以随意更改。
声明常量和变量
let height = 188
var weight = 120
这两行代码可以理解为,声明一个名字为 height 的常量,并给它赋值为 188,然后声明一个名字为 weight 的变量,并给它赋值为 120。
如果你的代码中有不需要改变的值,使用 let
关键字将它声明为常量,只将需要改变的值声明为变量,这样会使你的代码更加合理。
与变量不同,常量的值一旦被确定就不能更改了。尝试这样做会导致编译时报错:
let height = 188
height = 199
// 这会报编译时错误 - height 不可改变
你可以在一行中声明多个常量或者多个变量,用逗号隔开:
var x = 0, y = 1, z = 2
类型注解
当你声明常量或者变量的时候可以加上 类型注解(type annotation),说明常量或者变量中要存储的值的类型。如果要添加类型注解,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。
这个例子给 height
变量添加了类型注解,表示这个变量可以存储 Int
类型的值:
let height: Int = 188
可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型注解:
var x, y, z: Double
一般来说很少需要写类型注解。如果在声明常量或者变量的时候赋了一个初始值,Swift 可以推断出这个常量或者变量的类型
输出常量和变量
你可以用 print(_:separator:terminator:)
函数来输出当前常量或变量的值。
separator
和 terminator
参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给 terminator
参数。例如,print(someValue, terminator:"")
print("第一行")
print("第二行")
print("第三行",terminator: "")
print("也是第三行",terminator: "")
// 第一行
// 第二行
// 第三行也是第三行
Swift 用 字符串插值(string interpolation) 的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义:
let nickName = "beyhuang"
print("The boy nickName is \(nickName)")
// The boy nickName is beyhuang
整数和浮点数
整数范围
你可以访问不同整数类型的 min
和 max
属性来获取对应类型的最小值和最大值:
let minValue = UInt8.min // minValue 为 0,是 UInt8 类型
let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型
Int
一般来说,你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型 Int
,长度与当前平台的原生字长相同:
在32位平台上,Int
和 Int32
长度相同。
在64位平台上,Int
和 Int64
长度相同。
除非你需要特定长度的整数,一般来说使用 Int
就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,Int
可以存储的整数范围也可以达到 -2,147,483,648
~ 2,147,483,647
,大多数时候这已经足够大了。
UInt
Swift 也提供了一个特殊的无符号类型 UInt
,长度与当前平台的原生字长相同:
在32位平台上,UInt
和 UInt32
长度相同。
在64位平台上,UInt
和 UInt64
长度相同。
注意
尽量不要使用
UInt
,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用Int
,即使你要存储的值已知是非负的。统一使用Int
可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断
浮点数
浮点数是有小数部分的数字,比如 3.14159
、0.1
和 -273.15
。
浮点类型比整数类型表示的范围更大,可以存储比 Int
类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:
Double
表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。
Float
表示32位浮点数。精度要求不高的话可以使用此类型。
注意
Double
精确度很高,至少有 15 位小数,而Float
只有 6 位小数。选择哪个类型取决于你的代码需要处理的值的范围,在两种类型都匹配的情况下,将优先选择Double
。
类型安全和类型推断
Swift 是一个 类型安全(type safe) 的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个 String
,你绝对不可能不小心传进去一个 Int
。
由于 Swift 是类型安全的,所以它会在 编译 你的代码时进行 类型检查(type checks),并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。
当你要处理不同类型的值时,类型检查可以帮你避免错误。然而,这并不是说你每次声明常量和变量的时候都需要显式指定类型。如果你没有显式指定类型,Swift 会使用 类型推断(type inference)来选择合适的类型。有了类型推断,编译器可以在编译代码的时候自动推断出表达式的类型。原理很简单,只要检查你赋的值即可。
因为有类型推断,和 C 或者 Objective-C 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。
当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的值,比如 42
和 3.14159
。)
例如,如果你给一个新常量赋值 42
并且没有标明类型,Swift 可以推断出常量类型是 Int
,因为你给它赋的初始值看起来像一个整数:
let meaningOfLife = 42
// meaningOfLife 会被推测为 Int 类型
同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是 Double
:
let pi = 3.14159
// pi 会被推测为 Double 类型
当推断浮点数的类型时,Swift 总是会选择 Double
而不是 Float
。
如果表达式中同时出现了整数和浮点数,会被推断为 Double
类型:
let anotherPi = 3 + 0.14159
// anotherPi 会被推测为 Double 类型
原始值 3
没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为 Double
类型。
数值型类型转换
不同整数类型的变量和常量可以存储不同范围的数字
let cannotBeNegative: UInt8 = -1
// UInt8 类型不能存储负数,所以会报错
let tooBig: Int8 = Int8.max + 1
// Int8 类型不能存储超过最大值的数,所以会报错
不同的数值类型之间运算是需要进行类型转换的
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
整数和浮点数的转换必须显式指定类型
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi 等于 3.14159,所以被推测为 Double 类型
布尔值
swift 中的布尔值需要注意的是和其他能够进行隐式转换类型的语言不同,当一个 Int 的值为 1 的时候,在进行 if 判断时,swift 并不会将这个值转换为布尔值。
如果你在需要使用 Bool
类型的地方使用了非布尔值,Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误:
let i = 1
if i {
// 这个例子不会通过编译,会报错
}
然而,下面的例子是合法的:
let i = 1
if i == 1 {
// 这个例子会编译成功
}
i == 1
的比较结果是 Bool
类型,所以第二个例子可以通过类型检查。类似 i == 1
这样的比较
和 Swift 中的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图总是清晰的
元组
元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型
let http404Error = (404, "Not Found")
// http404Error 的类型是 (Int, String),值是 (404, "Not Found")
你可以把任意顺序的类型组合成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为 (Int, Int, Int)
或者 (String, Bool)
或者其他任何你想要的组合的元组。
你可以将一个元组的内容分解(decompose)成单独的常量和变量,然后你就可以正常使用它们了:
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// The status code is 404
print("The status message is \(statusMessage)")
// The status message is Not Found
如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线(_
)标记:
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// The status code is 404
此外,你还可以通过下标来访问元组中的单个元素,下标从零开始:
print("The status code is \(http404Error.0)")
// The status code is 404
print("The status message is \(http404Error.1)")
// The status message is Not Found
你可以在定义元组的时候给单个元素命名:
let http200Status = (statusCode: 200, description: "OK")
给元组中的元素命名后,你可以通过名字来获取这些元素的值:
print("The status code is \(http200Status.statusCode)")
// The status code is 200
print("The status message is \(http200Status.description)")
// The status message is OK
作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 (Int, String)
元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。
可选类型
使用 可选类型(optionals)来处理值可能缺失的情况。可选类型表示两种可能:值缺失或者有值, 你可以解析可选类型访问这个值, 或者根本没有值。
注意
C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回
nil
,nil
表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值( 比如NSNotFound
)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示 任意类型 的值缺失,并不需要一个特殊值。
来看一个例子。Swift 的 Int
类型有一种构造器,作用是将一个 String
值转换成一个 Int
值。然而,并不是所有的字符串都可以转换成一个整数。字符串 "123"
可以被转换成数字 123
,但是字符串 "hello, world"
不行。
下面的例子使用这种构造器来尝试将一个 String
转换成 Int
:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"
因为该构造器可能会失败,所以它返回一个可选类型(optional)Int
,而不是一个 Int
。一个可选的 Int
被写作 Int?
而不是 Int
。问号暗示包含的值是可选类型,也就是说可能包含 Int
值也可能不包含值。(不能包含其他任何值比如 Bool
值或者 String
值。只能是 Int
或者什么都没有。)
nil
你可以给可选变量赋值为 nil
来表示它没有值:
var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404
serverResponseCode = nil
// serverResponseCode 现在不包含值
注意
nil
不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。
如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 nil
:
var surveyAnswer: String?
// surveyAnswer 被自动设置为 nil
注意
Swift 的
nil
和 Objective-C 中的nil
并不一样。在 Objective-C 中,nil
是一个指向不存在对象的指针。在 Swift 中,nil
不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为nil
,不只是对象类型。
可选绑定
使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 if
和 while
语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。
像下面这样在 if
语句中写一个可选绑定:
if let constantName = someOptional {
statements
}
你可以像上面这样使用可选绑定来
if let actualNumber = Int(possibleNumber) {
print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
} else {
print("\'\(possibleNumber)\' could not be converted to an integer")
}
// '123' has an integer value of 123
重写这个例子
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"
空和运算符
空和运算符(Nil Coalescing Operator)
空合运算符( a ?? b
)将对可选类型 a
进行空判断,如果 a
包含一个值就进行解包,否则就返回一个默认值 b
。表达式 a
必须是 Optional 类型。默认值 b
的类型必须要和 a
存储值的类型保持一致。
空合运算符是对以下代码的简短表达方法:
a != nil ? a! : b
字符和字符串
多行字符串字面量
如果你需要一个字符串是跨越多行的,那就使用多行字符串字面量 — 由一对三个双引号包裹着的具有固定顺序的文本字符集:
let quotation = """
The White Rabbit put on his spectacles. "Where shall I begin,
please your Majesty?" he asked.
"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""
你可以用在行尾写一个反斜杠(\
)作为续行符。
let softWrappedQuotation = """
The White Rabbit put on his spectacles. "Where shall I begin, \
please your Majesty?" he asked.
"Begin at the beginning," the King said gravely, "and go on \
till you come to the end; then stop."
"""
// The White Rabbit put on his spectacles. "Where shall I begin, please your Majesty?" he asked.
// "Begin at the beginning," the King said gravely, "and go on till you come to the end; then stop."
初始化空字符串
要创建一个空字符串作为初始值,可以将空的字符串字面量赋值给变量,也可以初始化一个新的 String
实例:
var emptyString = "" // 空字符串字面量
var anotherEmptyString = String() // 初始化方法
// 两个字符串均为空并等价。
你可以通过检查 Bool
类型的 isEmpty
属性来判断该字符串是否为空:
if emptyString.isEmpty {
print("Nothing to see here")
}
// Nothing to see here
计算字符数量
如果想要获得一个字符串中 Character
值的数量,可以使用 count
属性:
let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie has \(unusualMenagerie.count) characters")
// unusualMenagerie has 40 characters
注意在 Swift 中,使用可拓展的字符群集作为 Character
值来连接或改变字符串时,并不一定会更改字符串的字符数量。
例如,如果你用四个字符的单词 cafe
初始化一个新的字符串,然后添加一个 COMBINING ACTUE ACCENT
(U+0301
)作为字符串的结尾。最终这个字符串的字符数量仍然是 4
,因为第四个字符是 é
,而不是 e
:
var word = "cafe"
print("the number of characters in \(word) is \(word.count)")
// the number of characters in cafe is 4
word += "\u{301}" // 拼接一个重音,U+0301
print("the number of characters in \(word) is \(word.count)")
// the number of characters in café is 4
插入和删除
调用 insert(_:at:)
方法可以在一个字符串的指定索引插入一个字符,调用 insert(contentsOf:at:)
方法可以在一个字符串的指定索引插入一段字符串。
var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// hello!
welcome.insert(contentsOf:" there", at: welcome.index(before: welcome.endIndex))
// hello there!
调用 remove(at:)
方法可以在一个字符串的指定索引删除一个字符,调用 removeSubrange(_:)
方法可以在一个字符串的指定索引删除一个子字符串。
welcome.remove(at: welcome.index(before: welcome.endIndex))
// hello there
let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// hello
注意
你可以使用
insert(_:at:)
、insert(contentsOf:at:)
、remove(at:)
和removeSubrange(_:)
方法在任意一个确认的并遵循RangeReplaceableCollection
协议的类型里面,如上文所示是使用在String
中,你也可以使用在Array
、Dictionary
和Set
中。
子字符串
当你从字符串中获取一个子字符串 —— 例如,使用下标或者 prefix(_:)
之类的方法 —— 就可以得到一个 Substring
的实例,而非另外一个 String
。Swift 里的 Substring
绝大部分函数都跟 String
一样,意味着你可以使用同样的方式去操作 Substring
和 String
。然而,跟 String
不同的是,你只有在短时间内需要操作字符串时,才会使用 Substring
。当你需要长时间保存结果时,就把 Substring
转化为 String
的实例:
let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning 的值为 "Hello"
// 把结果转化为 String 以便长期存储。
let newString = String(beginning)
就像 String
,每一个 Substring
都会在内存里保存字符集。而 String
和 Substring
的区别在于性能优化上,Substring
可以重用原 String
的内存空间,或者另一个 Substring
的内存空间(String
也有同样的优化,但如果两个 String
共享内存的话,它们就会相等)。这一优化意味着你在修改 String
和 Substring
之前都不需要消耗性能去复制内存。就像前面说的那样,Substring
不适合长期存储 —— 因为它重用了原 String
的内存空间,原 String
的内存空间必须保留直到它的 Substring
不再被使用为止。
前缀/后缀相等
通过调用字符串的 hasPrefix(_:)
/hasSuffix(_:)
方法来检查字符串是否拥有特定前缀/后缀,两个方法均接收一个 String
类型的参数,并返回一个布尔值。
let string4 = "i after j"
print(string4.hasPrefix("i"))
print(string4.hasSuffix("j"))
print(string4.hasSuffix("r"))
// true
// true
// false
集合类型
注意:
字符串中的
insert(_:at:)
、insert(contentsOf:at:)
、remove(at:)
和removeSubrange(_:)
方法在任意一个确认的并遵循RangeReplaceableCollection
协议的类型里面都可以使用,如上文所示是使用在String
中,你也可以使用在Array
、Dictionary
和Set
中。
所以这里就不加篇幅去举例子了,可以自己去尝试一下
数组
// 创建一个空数组
var someInts: [Int] = []
// 创建一个带有默认值的数组
var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0]
// 用数组字面量构造数组
var shoppingList: [String] = ["Eggs", "Milk"]
// 利用下标来一次改变一系列数据值
shoppingList[2...4] = ["Bananas", "Apples"]
// 移除数组中的最后一项
let apples = shoppingList.removeLast()
// 同时遍历每个数据项的值和索引值
for (index, value) in shoppingList.enumerated() {
print("Item \(String(index + 1)): \(value)")
}
集合
集合 用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。
Swift 中的集合类型被写为 Set<Element>
,这里的 Element
表示集合中允许存储的类型。和数组不同的是,集合没有等价的简化形式。
// 创建和构造一个空的集合
var letters = Set<Character>()
print("letters is of type Set<Character> with \(letters.count) items.")
// letters is of type Set<Character> with 0 items.
letters.insert("a")
// letters 现在含有1个 Character 类型的值
letters = []
// letters 现在是一个空的 Set,但是它依然是 Set<Character> 类型
// 用数组字面量创建集合
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres 被构造成含有三个初始值的集合
// 使用 contains(_:) 方法去检查集合中是否包含一个特定的值
print(favoriteGenres.contains("Funk"))
集合操作
使用
intersection(_:)
方法根据两个集合的交集创建一个新的集合。使用
symmetricDifference(_:)
方法根据两个集合不相交的值创建一个新的集合。使用
union(_:)
方法根据两个集合的所有值创建一个新的集合。使用
subtracting(_:)
方法根据不在另一个集合中的值创建一个新的集合。
let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits.intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]
字典
字典 是一种无序的集合,它存储的是键值对之间的关系,其所有键的值需要是相同的类型,所有值的类型也需要相同。每个值(value)都关联唯一的键(key),键作为字典中这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。你在需要通过标识符(键)访问数据的时候使用字典。
// 创建一个空字典
var namesOfIntegers: [Int: String] = [:]
// namesOfIntegers 是一个空的 [Int: String] 字典
// 用字典字面量创建字典
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
// 给字典添加新的数据项
airports["LHR"] = "London"
// 改变特定键对应的值
airports["LHR"] = "London Heathrow"
// “LHR”对应的值被改为“London Heathrow”
作为一种替代下标语法的方式,字典的 updateValue(_:forKey:)
方法可以设置或者更新特定键对应的值。就像上面所示的下标示例,updateValue(_:forKey:)
方法在这个键不存在对应值的时候会设置新值或者在存在时更新已存在的值。和下标的方式不同,updateValue(_:forKey:)
这个方法返回更新值之前的原值。这样使得你可以检查更新是否成功。
updateValue(_:forKey:)
方法会返回对应值类型的可选类型。举例来说:对于存储 String
值的字典,这个函数会返回一个 String?
或者“可选 String
”类型的值。如果有值存在于更新前,则这个可选值包含了旧值,否则它将会是 nil
:
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
print("The old value for DUB was \(oldValue).")
}
// 输出“The old value for DUB was Dublin.”
你也可以使用下标语法来在字典中检索特定键对应的值。因为有可能请求的键没有对应的值存在,字典的下标访问会返回对应值类型的可选类型。如果这个字典包含请求键所对应的值,下标会返回一个包含这个存在值的可选类型,否则将返回 nil
:
if let airportName = airports["DUB"] {
print("The name of the airport is \(airportName).")
} else {
print("That airport is not in the airports dictionary.")
}
// 打印“The name of the airport is Dublin Airport.”
还可以使用下标语法通过将某个键的对应值赋值为 nil
来从字典里移除一个键值对:
airports["APL"] = "Apple Internation"
// “Apple Internation”不是真的 APL 机场,删除它
airports["APL"] = nil
// APL 现在被移除了
此外,removeValue(forKey:)
方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有对应值的情况下返回 nil
:
if let removedValue = airports.removeValue(forKey: "DUB") {
print("The removed airport's name is \(removedValue).")
} else {
print("The airports dictionary does not contain a value for DUB.")
}
// 打印“The removed airport's name is Dublin Airport.”
扩展
扩展 可以给一个现有的类,结构体,枚举,还有协议添加新的功能。它还拥有不需要访问被扩展类型源代码就能完成扩展的能力。扩展和 Objective-C 的分类很相似,与 Objective-C 分类不同的是,Swift 扩展是没有名字的。
Swift 中的扩展可以:
- 添加计算型实例属性和计算型类属性
- 定义实例方法和类方法
- 提供新的构造器
- 定义下标
- 定义和使用新的嵌套类型
- 使已经存在的类型遵循(conform)一个协议
注意
扩展可以给一个类型添加新的功能,但是不能重写已经存在的功能。
// 使用 extension 关键字声明扩展
extension SomeType {
// 在这里给 SomeType 添加新的功能
}
// 扩展可以扩充一个现有的类型,给它添加一个或多个协议
extension SomeType: SomeProtocol, AnotherProtocol {
// 协议所需要的实现写在这里
}
方法
扩展可以给现有类型添加新的实例方法和类方法。在下面的例子中,给 Int
类型添加了一个新的实例方法叫做 repetitions
:
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
3.repetitions {
print("Hello!")
}
// Hello!
// Hello!
// Hello!
可变实例方法
通过扩展添加的实例方法同样也可以修改(或 mutating(改变))实例本身。结构体和枚举的方法,若是可以修改 self
或者它自己的属性,则必须将这个实例方法标记为 mutating
,就像是改变了方法的原始实现。
在下面的例子中,对 Swift 的 Int
类型添加了一个新的 mutating 方法,叫做 square
,它将原始值求平方:
extension Int {
mutating func square() {
self = self * self
}
}
var someInt = 3
someInt.square()
// someInt 现在是 9
下标
扩展可以给现有的类型添加新的下标。下面的例子中,对 Swift 的 Int
类型添加了一个整数类型的下标。下标 [n]
返回从数字右侧开始的第 n
位数字:
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
746381295[2]
// 返回 2
746381295[8]
// 返回 7
如果操作的 Int
值没有足够的位数满足所请求的下标,那么下标的现实将返回 0
,将好像在数字的左边补上了 0:
746381295[9]
// 返回 0,就好像你进行了这个请求:
0746381295[9]
函数
函数的创建和调用
func isMoreThanTen(count: Int) -> Bool {
if count > 10 {
return true
} else {
return false
}
}
isMoreThanTen(count: 8)
isMoreThanTen(count: 24)
// 无参函数
func myFun1() -> String {
return "kobe"
}
函数的返回值
func myFuncReturnTest() {
print("func return test")
}
myFuncReturnTest()
print(myFuncReturnTest())
// func return test
// func return test
// ()
注意
严格上来说,虽然没有返回值被定义,sayGoodbye(_:)
函数依然返回了值。没有定义返回类型的函数会返回特殊的值,叫Void
。它其实是一个空的元组(tuple),没有任何元素,可以写成()
。
函数参数名
函数的参数名在各个编程语言中都有不同的特点,OC中的函数参数名是隐含于函数名称中的
- (void)getDataFromDataID:(NSString *) dataID {
}
[self getDataFromDataID:@"1101"];
虽然这样很容易推断出该函数的意义,但是当参数多了之后,函数名称变得非常冗长,很吓人,会使人觉得很繁琐,不过看久用多了也就习惯了
在 Java 中,参数名是直接添加到参数列表中的
private void getMyData(String dataID) {
}
getMyData("1101");
Swift 借鉴了这两者的优点,引入了参数内部命名和外部命名的概念,内部命名在函数实现时使用,外部命名在函数调用时使用,如果不设置外部命名,则默认函数参数的外部命名和内部命名相同。
// 多参数函数
// 函数体内只有一行代码,省略 return
func myFunc1(param1: Int, param2: Int, param3: Int) {
param1 + param2 + param3
}
myFunc1(param1: 1, param2: 2, param3: 3)
// 添加额外命名
func myFunc2(out1 param1: Int, out2 param2: Int, out3 param3: Int) {
param1 + param2 + param3
}
myFunc2(out1: 1, out2: 2, out3: 4)
// 使用下划线可以省略函数参数外部名称
func myFunc3(_ param1: Int, _ param2: Int, _ param3: Int) {
param1 + param2 + param3
}
myFunc3(1, 2, 4)
默认参数值
func myFunc4(param1: Int, param2: Int = 10, param3: Int = 88) {
param1 + param2 + param3
}
myFunc4(param1: 3)
myFunc4(param1: 5, param2: 42)
func myFunc5(param1: Int, param2: Int = 10, param3: Int) {
param1 + param2 + param3
}
myFunc5(param1: 10, param3: 5)
不定数量参数
在swift中,在某个参数类型后面追加符号 ...
就会将此参数设置为数量可变。在函数内部,开发者传递的值会被包装成一个集合类型赋值给对应的参数
func myFunc6(param: Int...) {
var sum = 0
for count in param {
sum += count
}
print(sum)
}
myFunc6(param: 1, 2, 3)
myFunc6(param: 1, 2, 3, 4, 5)
inout输入输出参数
swift函数中,如果传递的是值类型的参数,那么参数值在传递进去函数内部时会将原值复制为一份常量,且在函数内不可以修改。
错误示范
func myFunc7(param: Int) {
param += 1
}
// error: left side of mutating operator isn't mutable: 'param' is a 'let' constant
// param += 1
如果需要在函数内部修改传递进去的变量的值,可以将此参数声明为 inout 类型
func myFunc7(param: inout Int) {
param += 1
}
var para = 10
myFunc7(param: ¶)
print(para)
&
符号传递参数变量的内存地址
闭包
闭包 是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)比较相似。
先来看看用闭包怎么表示函数
func myFunc(param: Int) -> Int {
return param * param
}
用闭包来表示
let myClosures = { (param: Int) -> Int in
return param * param
}
myFunc(param: 10)
myClusures(9)
闭包在语法上有这样的标准结构
{(参数列表) -> 返回值 in 闭包体}
关键字 in 前面为闭包的参数列表和返回值,其书写规则和函数一致,in 后面为闭包体
与函数不一样的是,闭包的返回值是可以省略的,在闭包体中,如果有 return 返回,则闭包会自动将 return 的数据类型作为返回值类型,所以上面的闭包表达式可以简写为
let myClosures = { (param: Int) in
return param * param
}
闭包用于排序方法
Swift 标准库提供了名为 sorted(by:)
的方法,它会基于你提供的排序闭包表达式的判断结果对数组中的值(类型确定)进行排序。一旦它完成排序过程,sorted(by:)
方法会返回一个与旧数组类型大小相同类型的新数组,该数组的元素有着正确的排序顺序。原数组不会被 sorted(by:)
方法修改
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
// 提供排序闭包函数的一种方式是撰写一个符合其类型要求的普通函数,并将其作为 sorted(by:) 方法的参数传入
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
闭包表达式语法
{ (parameters) -> return type in
statements
}
上面的排序例子可以用闭包进行简化
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
根据上下文推断类型
因为排序闭包函数是作为 sorted(by:)
方法的参数传入的,Swift 可以推断其参数和返回值的类型。sorted(by:)
方法被一个字符串数组调用,因此其参数必须是 (String, String) -> Bool
类型的函数。这意味着 (String, String)
和 Bool
类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(->
)和围绕在参数周围的括号也可以被省略:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
参数名称缩写
Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 $0
,$1
,$2
来顺序调用闭包的参数,以此类推。
如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。闭包接受的参数的数量取决于所使用的缩写参数的最大编号。in
关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
reversedNames = names.sorted(by: { $0 > $1 } )
尾随闭包
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,将这个闭包替换成为尾随闭包的形式很有用。尾随闭包是一个书写在函数圆括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
func someFunctionThatTakesAClosure(closure: () -> Void) {
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
}
逃逸闭包
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping
,用来指明这个闭包是允许“逃逸”出这个函数的。
一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
自动闭包
swift 中有一种语法,可以实现对简单闭包的自动生成,这种闭包通常被称为自动闭包,自动闭包的参数的使用有严格条件的,首先此闭包不能够有参数,其次在调用参数传参时,闭包的实现只能由一句表达式组成,闭包的返回值即为此表达式的值,自动闭包参数由 @autoclosure
来声明
// 将闭包参数声明为自动闭包
func myFunc(closure: @autoclosure () -> Bool) {
}
myFunc(1 + 2 + 3 > 10)
自动闭包默认是非逃逸的,如果要使用逃逸类型的闭包参数,需要声明如下
func myFunc(closure: @autoclosure @escaping () -> Bool) {
}
类和结构体
Swift 中结构体和类有很多共同点。两者都可以:
- 定义属性用于存储值
- 定义方法用于提供功能
- 定义下标操作用于通过下标语法访问它们的值
- 定义构造器用于设置初始值
- 通过扩展以增加默认实现之外的功能
- 遵循协议以提供某种标准功能
与结构体相比,类还有如下的附加功能:
- 继承允许一个类继承另一个类的特征
- 类型转换允许在运行时检查和解释一个类实例的类型
- 析构器允许一个类实例释放任何其所被分配的资源
- 引用计数允许对一个类的多次引用
结构体类型的成员逐一构造器
所有结构体都有一个自动生成的 成员逐一构造器,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中:
let vga = Resolution(width: 640, height: 480)
与结构体不同,类实例没有默认的成员逐一构造器
注意:结构体和枚举是值类型,类是引用类型
恒等运算符
因为类是引用类型,所以多个常量和变量可能在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。)
判定两个常量或者变量是否引用同一个类实例有时很有用。为了达到这个目的,Swift 提供了两个恒等运算符:
- 相同(
===
) - 不相同(
!==
)