Swift学习笔记
基本运算符
let (x, y) = (1, 2)
//当a为负数
print(-9 % 4) //-1
//当b为负数时它的正负号被忽略掉了。这意味着 a % b 与 a % -b 能够获得相同的答案。
print("result is:", 9 % -4) //1
print(-9 % -4) //-1
b = -3
//一元减号运算符( - )直接在要进行操作的值前边放置,不加任何空格。
var c = -b
//尽管一元加号运算符实际上什么也不做,你还是可以对正数使用它来让你的代码对一元减号运算符来说显得更加对称。
c = +b
Swift 同时也提供两个等价运算符( === 和 !== ),可以用它们判断两个对象的引用是否相同。
可以比较拥有同样数量值的元组,只要元组中的每个值都是可比较的。比如说, Int 和 String 都可以用来比较大小,也就是说 (Int,String) 类型的元组就可以比较。一般来说, Bool 不能比较,这意味着包含布尔值的元组不能用来比较大小。
(1, "zebra") < (2, "apple") // true because 1 is less than 2
(3, "apple") < (3, "bird") // true because 3 is equal to 3, and "apple" is less than "bird"
(4, "dog") == (4, "dog") // true because 4 is equal to 4, and "dog" is equal to "dog"
合并空值运算符 ( a ?? b )如果可选项 a 有值则展开,如果没有值,是 nil ,则返回默认值 b 。表达式 a 必须是一个可选类型。表达式 b 必须与 a 的储存类型相同。
合并空值运算符是下边代码的缩写:
a != nil ? a! : b //equals: a ?? b
三元条件运算符强制展开( a! )储存在 a 中的值,如果 a 不是 nil 的话,否则就返回 b 的值。
Swift 包含了两个 区间运算符:
闭区间运算符( a...b )定义了从 a 到 b 的一组范围,并且包含 a 和 b 。 a 的值不能大于 b 。
for index in 1...5 {
print("index is \(index)")
}
半开区间运算符( a..<b )定义了从 a 到 b 但不包括 b 的区间。
let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
print("index is \(i)")
}
单侧区间:
for name in names[2...] {
print(name)
}
for name in names[1...3] {
print(name)
}
for name in names[..<2] {
print(name)
}
let range = ...5
range.contains(7) // false
range.contains(4) // true
range.contains(-1) // true
逻辑运算符:
let allowedEntry = false
if !allowedEntry {
print("ACCESS DENIED")
}
使用的是短路计算!(前面的a ?? b
也是,如果a是非空的就不会考虑b)
如果第一个值是 false ,那么第二个值就会被忽略掉了,因为它已经无法让整个表达式再成为 true 。
a && b || c || d
等价于 (a && b) || c || d
字符串和字符
Swift 的 String类型桥接到了基础库中的 NSString类。Foundation 同时也扩展了所有 NSString 定义的方法给 String 。也就是说,如果你导入 Foundation ,就可以在 String 中访问所有的 NSString 方法,无需转换格式。
更多在 Foundation 和 Cocoa 框架中使用 String的内容,参见 与 Cocoa 和 Objective-C 一起使用 Swift (Swift 4)(官网链接)。
字符串字面量是被双引号( ")包裹的固定顺序文本字符。
使用字符串字面量作为常量或者变量的初始值:
let someString = "Some string literal value"
多行字符串字面量是用三个双引号引起来的一系列字符:
let quotation = """
I said, "aaaaa",
so emmm
you know.
"""
print(quotation)
当你的代码中在多行字符串字面量里包含了换行,那个换行符同样会成为字符串里的值。如果你想要使用换行符来让你的代码易读,却不想让换行符成为字符串的值,那就在那些行的末尾使用反斜杠( \ ):
let quotation = """
I said, "aaaaa",\
so emmm \
\
you know.
"""
print(quotation) //I said, "aaaaa",so emmm you know.
如果在某行的空格超过了结束的双引号( """
),那么这些空格会被包含。
字符串字面量能包含以下特殊字符:
- 转义特殊字符
\0
(空字符),\\
(反斜杠),\t
(水平制表符),\n
(换行符),\r
(回车符),\"
(双引号) 以及\'
(单引号); - 任意的 Unicode 标量,写作
\u{n}
,里边的 n是一个 1-8 个与合法 Unicode 码位相等的16进制数字。
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imagination is more important than knowledge" - Einstein
let dollarSign = "\u{24}" // $, Unicode scalar U+0024
let blackHeart = "\u{2665}" // ♥, Unicode scalar U+2665
let sparklingHeart = "\u{1F496}" // 💖️, Unicode scalar U+1F496
扩展字符串分隔符:
你可以在字符串字面量中放置扩展分隔符来在字符串中包含特殊字符而不让它们真的生效。通过把字符串放在双引号( " )内并由井号( # )包裹。比如说,打印字符串字面量 #"Line 1\nLine 2"# 会打印出换行符 \n
而不是打印出两行。
let threeMoreDoubleQuotationMarks = #"""
Here are three more double quotes: """
and \n
"""#
print(threeMoreDoubleQuotationMarks)
初始化空字符串:
var str1 = ""
var str2 = String()
if str1.isEmpty {
print("Nothing to see here")
}
Swift 的 String类型是一种值类型。每一次赋值和传递,现存的 String值都会被复制一次,传递走的是拷贝而不是原本。值类型在结构体和枚举是值类型一章当中有详细描述。
Swift 的默认拷贝 String行为保证了当一个方法或者函数传给你一个 String值,你就绝对拥有了这个 String值,无需关心它从哪里来。你可以确定你传走的这个字符串除了你自己就不会有别人改变它。
let ch = "p"
let ch1: Character = "p"
let catCharacters: [Character] = ["C", "a", "t", "!", "🐱️"]
let catString = String(catCharacters)
print(catString)
给String
追加字符:直接+
或s.append(ch)
。
index:
let str1=""
print(str1.startIndex)
print(str1.endIndex)
//Index(_rawBits: 1)
//Index(_rawBits: 1)
str1.append(contentsOf: "preccrep")
print(str1.count)
print(str1.startIndex)
print(str1.endIndex)
print(str1[str1.index(str1.startIndex, offsetBy: 3)]) //c
print(str1[str1.index(before: str1.endIndex)]) //p
使用 indices属性来访问字符串中每个字符的索引:
for index in str1.indices {
print("\(str1[index])", terminator: "!")
}
//p!r!e!c!c!r!e!p!
你可以在任何遵循了 Indexable 协议的类型中使用 startIndex 和 endIndex 属性以及 index(before:) , index(after:) 和 index(_:offsetBy:) 方法。这包括这里使用的 String ,还有集合类型比如 Array , Dictionary 和 Set 。
Substring:
let greeting = "Hello, world!"
let index = greeting.index(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning is "Hello"
// Convert the result to a String for long-term storage.
let newString = String(beginning)
字符串比较可以使用==
和!=
.
let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."
if quotation == sameQuotation {
print("These two strings are considered equal")
}
// prints "These two strings are considered equal"
s.hasPrefix("xxx")
s.hasSuffix("xxx")
集合类型
Swift 提供了三种主要的集合类型——数组、合集、字典,用来储存值的集合。数组是有序的值的集合。合集是唯一值的无序集合。字典是无序的键值对集合。
集合的可变性:如果你创建一个数组、合集或者一个字典,并且赋值给一个变量,那么创建的集合就是可变的。这意味着你随后可以通过添加、移除、或者改变集合中的元素来改变(或者说异变)集合。如果你把数组、合集或者字典赋值给一个常量,则集合就成了不可变的,它的大小和内容都不能被改变。
在集合不需要改变的情况下创建不可变集合是个不错的选择。这样做可以允许 Swift 编译器优化你创建的集合的性能。
Swift 的 Array类型被桥接到了基础框架的 NSArray类上。
Swift 数组的类型完整写法是 Array<Element>
, Element是数组允许存入的值的类型。你同样可以简写数组的类型为 [Element]
。
var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]
print(type(of: threeDoubles))
//Array<Double>
//but if I change 0.0 to 0, it will be like this:
var threeDoubles = Array(repeating: 0, count: 3)
print(type(of: threeDoubles))
//Array<Int>
两个数组的连接可以直接用+
.
使用字面量创建数组:
var shoppingList: [String] = ["Eggs", "Milk"]
数组的索引从零开始。
如果你想要移除数组最后一个元素,使用 removeLast()方法而不是 remove(at:)方法以避免查询数组的 count属性。
s.remove()
和s.removeLast()
都会返回被删除的元素,但s.insert()
不会返回任何东西。
遍历数组:
如果你需要每个元素以及值的整数索引,使用 enumerated()方法来遍历数组。 enumerated()方法返回数组中每一个元素的元组,包含了这个元素的索引和值。
let myList: [String] = ["Milk", "Apple", "Banana", "Pear"]
for (index, value) in myList.enumerated() {
print("\(index) is \(value)")
}
Swift 的 Set类型桥接到了基础框架的 NSSet类上。
为了能让类型储存在合集当中,它必须是可哈希的——就是说类型必须提供计算它自身哈希值的方法。哈希值是Int值且所有的对比起来相等的对象都相同,比如 a == b
,它遵循 a.hashValue == b.hashValue
。
所有 Swift 的基础类型(比如 String, Int, Double, 和 Bool)默认都是可哈希的,并且可以用于合集或者字典的键。没有关联值的枚举成员值(如同枚举当中描述的那样)同样默认可哈希。
你可以使用你自己自定义的类型作为合集的值类型或者字典的键类型,只要让它们遵循 Swift 基础库的 Hashable协议即可。遵循 Hashable协议的类型必须提供可获取的叫做 hashValue的 Int属性。通过 hashValue属性返回的值不需要在同一个程序的不同的执行当中都相同,或者不同程序。
因为 Hashable协议遵循 Equatable,遵循的类型必须同时一个“等于”运算符 ( ==)的实现。 Equatable协议需要任何遵循 ==的实现都具有等价关系。就是说, ==的实现必须满足以下三个条件,其中 a, b, 和 c是任意值:
- a == a (自反性)
- a == b 意味着 b == a (对称性)
- a == b && b == c 意味着 a == c (传递性)
更多对协议的遵循信息,见协议。
Swift 的合集类型写做 Set<Element>
,这里的 Element
是合集要储存的类型。不同与数组,合集没有等价的简写。
var letters = Set<Character>()
letters.insert("a")
print(letters.count)
letters = []
print(letters.count)
var genres: Set<String> = ["Rock", "Classical", "Hip hop"]
合集类型不能从数组字面量推断出来,所以 Set类型必须被显式地声明,但是Set中元素的类型不必。
var genres: Set = ["Rock", "Classical", "Hip hop"]
合集当中所有的元素可以用 removeAll()一次移除。
你可以通过调用合集的 remove(_:)方法来从合集当中移除一个元素,如果元素是合集的成员就移除它,并且返回移除的值,如果合集没有这个成员就返回 nil。
if let removedGenre = favoriteGenres.remove("Rock") {
print("\(removedGenre)? I'm over it.")
}
要检查合集是否包含了特定的元素,使用 contains(_:)方法。
if favoriteGenres.contains("Funk") {
print("I get up on the good foot.")
}
Swift 的 Set类型是无序的。要以特定的顺序遍历合集的值,使用 sorted()方法,它把合集的元素作为使用 < 运算符排序了的数组返回。
for genre in favoriteGenres.sorted() {
print("\(genre)")
}
var nums: Set = [1,2,3,1,32,1,2,3321,23,1,6,4,3]
print("version 1:")
for num in nums {
print(num)
}
print("version 2:")
for num in nums.sorted() {
print(num)
}
print("version 3:")
for num in nums.sorted(by: { (x: Int, y: Int) -> Bool in
return x > y
}) {
print(num)
}
//output:
version 1:
3
6
2
1
32
3321
4
23
version 2:
1
2
3
4
6
23
32
3321
version 3:
3321
32
23
6
4
3
2
1
var nums: Set = [1,2,3,1,32,1,2,3321,23,1,6,4,3]
print("version 1:")
print(nums)
print("version 2:")
print(nums.sorted())
print("version 3:")
print(nums.sorted(by: { (x: Int, y: Int) -> Bool in
return x > y
}))
//output:
version 1:
[3321, 4, 2, 32, 6, 23, 3, 1]
version 2:
[1, 2, 3, 4, 6, 23, 32, 3321]
version 3:
[3321, 32, 23, 6, 4, 3, 2, 1]
集合的四个基本操作:
- 使用 intersection(_:)方法来创建一个只包含两个合集共有值的新合集;
- 使用 symmetricDifference(_:)方法来创建一个只包含两个合集各自有的非共有值的新合集;
- 使用 union(_:)方法来创建一个包含两个合集所有值的新合集;
- 使用 subtracting(_:)方法来创建一个两个合集当中不包含某个合集值的新合集。
集合的比较:
- 使用“相等”运算符 ( == )来判断两个合集是否包含有相同的值;
- 使用 isSubset(of:) 方法来确定一个合集的所有值是被某合集包含;
- 使用 isSuperset(of:)方法来确定一个合集是否包含某个合集的所有值;
- 使用 isStrictSubset(of:) 或者 isStrictSuperset(of:)方法来确定是个合集是否为某一个合集的子集或者超集,但并不相等;
- 使用 isDisjoint(with:)方法来判断两个合集是否拥有完全不同的值。
print(farmAnimals.isDisjoint(with: cityAnimals))
Swift 的 Dictionary桥接到了基础框架的 NSDictionary类。
Swift 的字典类型写全了是这样的: Dictionary<Key, Value>,你同样可以用简写的形式来写字典的类型为 [Key: Value]。
字典的 Key类型必须遵循 Hashable协议,就像合集的值类型。
创建(空)字典:
var namesOfIntegers = [Int: String]()
namesOfIntegers[16] = "sixteen"
// namesOfIntegers now contains 1 key-value pair
namesOfIntegers = [:]
// namesOfIntegers is once again an empty dictionary of type [Int: String]
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
作为下标脚本的代替,使用字典的 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).")
}
//如果使用下标脚本语法:
if let airportName = airports["DUB"] {
print("The name of the airport is \(airportName).")
} else {
print("That airport is not in the airports dictionary.")
}
你可以使用下标脚本语法给一个键赋值 nil来从字典当中移除一个键值对:
airports["APL"] = nil
也可以使用 removeValue(forKey:)来从字典里移除键值对。这个方法移除键值对如果他们存在的话,并且返回移除的值,如果值不存在则返回 nil。
将键或值保存为一个数组:
let airportCodes = [String](airports.keys)
switch
值绑定:
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)")
case let (x, y):
print("somewhere else at (\(x), \(y))")
}
// prints "on the x-axis with an x value of 2"
三个 switch 情况都使用了常量占位符 x 和 y ,它会从临时 anotherPoint 获取一个或者两个元组值。第一个情况, case(let x, 0) ,匹配任何 y 的值是 0 并且赋值坐标的x到临时常量 x 里。类似地,第二个情况, case(0,let y) ,匹配让后 x 值是 0 并且把 y 的值赋值给临时常量 y 。
在临时常量被声明后,它们就可以在情况的代码块里使用。
注意这个 switch 语句没有任何的 default 情况。最后的情况, case let (x,y) ,声明了一个带有两个占位符常量的元组,它可以匹配所有的值。结果,它匹配了所有剩下的值,然后就不需要 default 情况来让 switch 语句穷尽了。
在上边的栗子中, x 和 y 被 let 关键字声明为常量,因为它们没有必要在情况体内被修改。总之,它们也可以用变量来声明,使用 var 关键字。如果这么做,临时的变量就会以合适的值来创建并初始化。对这个变量的任何改变都只会在该case函数体内有效。
switch 情况可以使用 where 分句来检查额外的情况。
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
print("(\(x), \(y)) is just some arbitrary point")
}
// prints "(1, -1) is on the line x == -y"
复合情况:
let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
print("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
print("\(someCharacter) is a consonant")
default:
print("\(someCharacter) is not a vowel or a consonant")
}
// Prints "e is a vowel"
复合情况同样可以包含值绑定。所有复合情况的模式都必须包含相同的值绑定集合,并且复合情况中的每一个绑定都得有相同的类型格式。这才能确保无论复合情况的那部分匹配了,接下来的函数体中的代码都能访问到绑定的值并且值的类型也都相同。
let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
print("On an axis, \(distance) from the origin")
default:
print("Not on an axis")
}
// Prints "On an axis, 9 from the origin"
上边的 case 拥有两个模式: (let distance, 0) 匹配 x 轴的点以及 (0, let distance) 匹配 y 轴的点。两个模式都包含一个 distance 的绑定并且 distance 在两个模式中都是整形——也就是说这个 case 函数体的代码一定可以访问 distance 的值。
控制转移语句在你代码执行期间改变代码的执行顺序,通过从一段代码转移控制到另一段。Swift 拥有五种控制转移语句:
continue
break
fallthrough
return
throw
因为 Swift 的 switch 语句是穷尽且不允许空情况的,所以有时候有必要故意匹配和忽略一个匹配到的情况以让你的意图更加明确。要这样做的话你可以通过把 break 语句作为情况的整个函数体来忽略某个情况。
Swift 中的 Switch 语句不会从每个情况的末尾贯穿到下一个情况中。相反,整个 switch 语句会在第一个匹配到的情况执行完毕之后就直接结束执行。如果你确实需要 C 风格的贯穿行为,你可以选择在每个情况末尾使用 fallthrough 关键字。
guard 语句,类似于 if 语句,基于布尔值表达式来执行语句。使用 guard 语句来要求一个条件必须是真才能执行 guard 之后的语句。与 if 语句不同, guard 语句总是有一个 else 分句—— else 分句里的代码会在条件不为真的时候执行。
你可以在 if 或者 guard 语句中使用一个可用性条件来有条件地执行代码,基于在运行时你想用的哪个 API 是可用的。当验证在代码块中的 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 greeting(for person: String) -> String {
"Hello, " + person + "!"
}
func anotherGreeting(for person: String) -> String {
return "Hello, " + person + "!"
}
https://www.cnswift.org/functions#spl-9
实际参数标签(可省略):
在提供形式参数名之前写实际参数标签,用空格分隔:
func someFunction(argumentLabel parameterName: Int) {
// In the function body, parameterName refers to the argument value
// for that parameter.
}
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// In the function body, firstParameterName and secondParameterName
// refer to the argument values for the first and second parameters.
}
someFunction(1, secondParameterName: 2)
如果你为一个形式参数提供了实际参数标签,那么这个实际参数就必须在调用函数的时候使用标签。默认情况下,形式参数使用它们的形式参数名作为实际参数标签。
实际参数标签的使用能够让函数的调用更加明确,更像是自然语句,同时还能提供更可读的函数体并更清晰地表达你的意图。
测试(错误的用法):
正确的用法:
测试代码:
func testFunc1(param1: Int, param2: String) -> Void {
//
}
func testFunc2(_ param1: Int, param2: String) -> Void {
//
}
func testFunc3(_ param1: Int, label2 param2: String) -> Void {
//
}
func testFunc4(label1 param1: Int, label2 param2: String) -> Void {
//
}
func testFunc5(_ param1: Int, _ param2: String) -> Void {
//
}
func testFunc6(_ param1: Int..., label2 param2: String) -> Void {
//
}
//func testFunc7(_ param1: Int..., _ param2: String) -> Void {
// //
//}
func testFunc8(_ param1: Int..., param2: String) -> Void {
//
}
testFunc1(param1: 12, param2: "aaa")
testFunc2(12, param2: "aaa")
testFunc3(12, label2: "aaa")
testFunc4(label1: 12, label2: "aaa")
testFunc5(12, "aaa")
testFunc6(1,2,3,4,5, label2: "aaa")
testFunc8(1,2,3,4,5, param2: "aaa")
默认形式参数值:
func someFunction(parameterWithDefault: Int = 12) {
// In the function body, if no arguments are passed to the function
// call, the value of parameterWithDefault is 12.
}
someFunction(parameterWithDefault: 6) // parameterWithDefault is 6
someFunction() // parameterWithDefault is 12
可变形式参数:可以接受零个或者多个特定类型的值。当调用函数的时候你可以利用可变形式参数来声明形式参数可以被传入值的数量是可变的。可以通过在形式参数的类型名称后边插入三个点符号( ...
)来书写可变形式参数。
传入到可变参数中的值在函数的主体中被当作是对应类型的数组。举个栗子,一个可变参数的名字是 numbers类型是 Double...在函数的主体中它会被当作名字是 numbers 类型是 [Double]的常量数组。
函数可拥有多个可变形式参数。可变形式参数后的第一个形式参数必须有实际参数标签。实际参数标签可以与作为可变形式参数的实际参数去岐义。
输入输出形式参数:
可变形式参数只能在函数的内部做改变。如果你想函数能够修改一个形式参数的值,而且你想这些改变在函数结束之后依然生效,那么就需要将形式参数定义为输入输出形式参数。
在形式参数定义开始的时候在前边添加一个 inout关键字可以定义一个输入输出形式参数。输入输出形式参数有一个能输入给函数的值,函数能对其进行修改,还能输出到函数外边替换原来的值。
你只能把变量作为输入输出形式参数的实际参数。你不能用常量或者字面量作为实际参数,因为常量和字面量不能修改。在将变量作为实际参数传递给输入输出形式参数的时候,直接在它前边添加一个和符号 ( &) 来明确可以被函数修改。
输入输出形式参数不能有默认值,可变形式参数不能标记为 inout。
func swap(_ a: inout Int, _ b: inout Int) {
let tmp = a
a = b
b = tmp
}
var a1 = 13, b1 = 9
swap(&a1, &b1)
print(a1, b1)
每一个函数都有一个特定的函数类型,它由形式参数类型,返回类型组成。如 (Int, Int) -> Int
和 () -> Void
。
你可以像使用 Swift 中的其他类型一样使用函数类型。例如,你可以给一个常量或变量定义一个函数类型,并且为变量指定一个相应的函数。
var aliasSwap: (inout Int, inout Int) -> Void = swap
aliasSwap(&a1, &b1)
这里的 aliasSwap 中不能省略 inout
,否则报错。
另外,aliasSwap 终究只是个变量,因此可以赋值为任意相同类型的函数。
也可以在初始化时不指定类型,而是直接赋值一个函数,让Swift自己去推断。
你也可以在函数的内部定义另外一个函数。这就是内嵌函数。
内嵌函数在默认情况下在外部是被隐藏起来的,但却仍然可以通过包裹它们的函数来调用它们。包裹的函数也可以返回它内部的一个内嵌函数来在另外的范围里使用。
func inBuilt(_ a: Int, _ b: String) -> (Int) -> Int {
func ans(input: Int) -> Int {
return input + 1
}
func inStr(_ s: String) {
print(s)
}
inStr(b)
return ans
}
var autoAns = inBuilt(12, "aaa")
print(autoAns(12))
闭包
闭包是可以在你的代码中被传递和引用的功能性独立代码块。
闭包能够捕获和存储定义在其上下文中的任何常量和变量的引用,这也就是所谓的闭合并包裹那些常量和变量,因此被称为“闭包”。
写一个类似C++里的cmp函数:
import Foundation
func cmp(_ a: Int, _ b: Int) -> Bool {
return a > b
}
var arr: [Int] = [1,4,9,2,3,6,7]
arr = arr.sorted(by: cmp)
print(arr)
struct Node {
var id: Int
var name: String
func toPrint() {
print("\(id) --> \(name)")
}
}
var arr1: [Node] = []
arr1.append(Node(id: 11, name: "aaa"))
arr1.append(Node(id: 2, name: "aaa"))
arr1.append(Node(id: 5, name: "bbb"))
arr1.append(Node(id: 5, name: "bab"))
arr1.append(Node(id: 7, name: "aaa"))
print(arr1.count)
var arr2 = arr1.sorted(by: { (x: Node, y: Node) -> Bool in
if x.id != y.id {
return x.id < y.id
} else {
return x.name < y.name
}
})
for itm in arr2 {
itm.toPrint()
}
闭包表达式语法有如下的一般形式:
{ (parameters) -> (return type) in
statements
}
从语境中推断类型:
a2 = a1.sorted(by: { s1, s2 -> Bool in return s1 > s2 })
从单表达式闭包隐式返回:单表达式闭包能够通过从它们的声明中删掉 return 关键字来隐式返回它们单个表达式的结果。sorted(by:) 函数类型的实际参数已经明确必须通过闭包返回一个 Bool 值。因为闭包的结构包含返回 Bool 值的单一表达式 (s1 > s2),因此没有歧义, return 关键字可省略。
a2 = a1.sorted(by: { s1, s2 in s1 > s2 })
简写实际参数名:
Swift 自动对行内闭包提供简写实际参数名,你也可以通过 $0 , $1 , $2 等名字来引用闭包的实际参数值。你可以在闭包的实际参数列表中忽略对其的定义,并且简写实际参数名的数字和类型将会从期望的函数类型中推断出来。 in 关键字也能被省略,因为闭包表达式完全由它的函数体组成:
a2 = a1.sorted(by: { $0 > $1 })
这里, $0 和 $1 分别是闭包的第一个和第二个 String 实际参数。
运算符函数:
Swift 的 String 类型定义了关于大于号( >)的特定字符串实现,让其作为一个有两个 String 类型形式参数的函数并返回一个 Bool 类型的值。这正好与 sorted(by:) 方法的第二个形式参数需要的函数相匹配。因此,你能简单地传递一个大于号,并且 Swift 将推断你想使用大于号特殊字符串函数实现:
a2 = a1.sorted(by: >)
尾随闭包:
可以参考:https://juejin.cn/post/6966175930783891463
如果闭包表达式作为函数的唯一实际参数传入,而你又使用了尾随闭包的语法,那你就不需要在函数名后边写圆括号了:
func travel(action: () -> Void) {
print("I'm ready to go.")
action()
print("I'm arrived!")
}
travel {
print("I'm driving in my car.")
}
//output:
//I'm ready to go.
//I'm driving in my car.
//I'm arrived!
上面travel
函数的调用实际上是这样的:
travel(action: {
print("I'm taking the bus.")
})
当闭包很长以至于不能被写成一行时尾随闭包就显得非常有用了。
举个栗子,Swift 的 Array 类型中有一个以闭包表达式为唯一的实际参数的 map(😃 方法。数组中每一个元素调用一次该闭包,并且返回该元素所映射的值(有可能是其他类型)。具体的映射方式和返回值的类型由你传入 map(:)的闭包来指定。在给数组中每一个元素应用了你提供的闭包后, map(_:)方法返回一个新的数组,数组中包涵与原数组一一对应的映射值。
总之,使用带有尾随闭包的 map(_😃 方法将包含 Int 值的数组转换成包含 String 值的数组。
let digitNames = [
0: "Zero",1: "One",2: "Two", 3: "Three",4: "Four",
5: "Five",6: "Six",7: "Seven",8: "Eight",9: "Nine"
]
let numbers = [16,58,510]
let strings = numbers.map { (number) -> String in
var number = number
var output = ""
repeat {
output = digitNames[number % 10]! + output
number /= 10
} while number > 0
return output
}
// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]
如果函数接收多个闭包,你可省略第一个尾随闭包的实际参数标签,但要给后续的尾随闭包写标签。比如说,下面的函数给照片墙加载图片:
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
if let picture = download("photo.jpg", from: server) {
completion(picture)
} else {
onFailure()
}
}
当你调用这个函数来加载图片,你需要提供两个闭包。第一个闭包是图片下载成功后的回调。第二个闭包是报错回调,给用户显示错误。
loadPicture(from: someServer) { picture in //第1个尾随闭包completion可以省略标签
someView.currentPicture = picture
} onFailure: { //第2个尾随闭包要写标签
print("Couldn't download the next picture.")
}
不省略第一个标签:
UIView.animate(withDuration: 1.5, animations: {
print("animation block")
}, completion: { (finished: Bool) in
print("I'm getting ready to go \(finished)")
})
闭包参数:
func travel(action: (String,String) -> String) {
print("I'm getting ready to go.")
let description = action("Beijing","London")
print(description)
print("I arrived!")
}
travel{(a: String, b: String) in
return "I'm going to \(a) and \(b) in my car"
}
捕获值:
一个闭包能够从上下文捕获已被定义的常量和变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值。
func testFunc1() -> Int {
let a: Int = 8
func testInFunc() -> Int {
let b: Int = a + c
return b
}
let c: Int = 9
return testInFunc()
}
print(testFunc1()) //17
作为一种优化,如果一个值没有改变或者在闭包的外面,Swift 可能会使用这个值的拷贝而不是捕获。
闭包是引用类型:
无论你什么时候赋值一个函数或者闭包给常量或者变量,你实际上都是将常量和变量设置为对函数和闭包的引用。
逃逸闭包:
当闭包作为一个实际参数传递给一个函数的时候,我们就说这个闭包逃逸了,因为它是在函数返回之后调用的。当你声明一个接受闭包作为形式参数的函数时,你可以在形式参数前写 @escaping 来明确闭包是允许逃逸的。
Swift 的声明和定义实际上是在一块儿的,所以你需要在定义的时候同时指定形式参数和实际参数。 每一个参数按照 “ 形式参数 实际参数: 类型 ” 的格式来写。 如果形式参数是空的,那么就用 “ _ ” 表示。
枚举
遍历枚举情况:
对于某些枚举来说,如果能有一个集合包含了枚举的所有情况就好了。你可以通过在枚举名字后面写 : CaseIterable 来允许枚举被遍历。Swift 会暴露一个包含对应枚举类型所有情况的集合名为 allCases 。下面是例子:
enum Letters: CaseIterable {
case alpha, beta, gamma
}
let latinLetter = Letters.beta
switch latinLetter {
case .alpha:
print("qqq")
print("aaaa")
case .beta:
print("aaaaaaaa")
print("ddddd")
case .gamma:
print("llllll")
}
let latinSet = Letters.allCases
print(latinSet.count)
for l in latinSet {
print(l)
}
和以往一样,不同的条码类型可以用 switch 语句来检查。这一次,总之,相关值可以被提取为 switch 语句的一部分。你提取的每一个相关值都可以作为常量(用 let前缀) 或者变量(用 var前缀)在 switch的 case中使用:
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."
如果对于一个枚举成员的所有的相关值都被提取为常量,或如果都被提取为变量,为了简洁,你可以用一个单独的 var或 let在成员名称前标注:
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."
实际上,当整数值被用于作为原始值时,每成员的隐式值都比前一个大一。如果第一个成员没有值,那么它的值是 0 。
下面的枚举是先前的 Planet枚举的简化,用整数原始值来代表从太阳到每一个行星的顺序:
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
在上面的例子中, Planet.mercury
有一个明确的原始值 1 , Planet.venus
的隐式原始值是 2
,以此类推。
你可以用 rawValue属性来访问一个枚举成员的原始值:
let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3
从原始值初始化:
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.Uranus
这个例子从它的原始值 7来辨认出 uranus 。
不是所有可能的 Int
值都会对应一个行星。因此原始值的初始化器总是返回可选的枚举成员。在上面的例子中, possiblePlanet的类型是 Planet? ,即“可选项 Planet”。
继承
Swift 类不会从一个通用基类继承。你没有指定特定父类的类都会以基类的形式创建。
When an instance property is defined in Swift using var
, a getter and setter is automatically generated:
class Notes {
var canSave = false
}
notes = Notes()
notes.canSave // Getter is available
notes.canSave = true // Setter is available
When an instance property is defined in Swift using let
, only a getter is available:
class Store {
let canOrder = false
}
store = Store()
store.canOrder // Getter is available
// This will not compile and cause the error:
// "Cannot assign to property 'canSave'
// is a 'let' constant"
store.canOrder = true
One way to implement a variable property without an exposed setter is to indicate a variable as private(set)
:
class Store {
private(set) var canOrder = false
}
// Create a store instance
store = Store()
store.canOrder // Getter is available
// This will not compile and cause the error:
// "Cannot assign to property: 'canOrder'
// setter is inaccessible"
store.canOrder = true
Another way to implement a variable property without an exposed setter is to use get
. Often get
is used to expose a getter to a private property:
class Store {
private var _canOrder = false
var canOrder: Bool {
get { return _canOrder }
}
}
// Create a store instance
store = Store()
store.canOrder // Getter is available
// This will not compile and cause the error:
// "Cannot assign to property: 'canOrder'
// is a get-only property"
store.canOrder = true
Starting in Swift 5.5, throws
is available for getters defined with get
:
class Notes {
func isDatabaseAvailable() throws -> Bool {
/* ... */
}
var canSave: Bool {
get throws {
return try isDatabaseAvailable()
}
}
}
notes = Notes()
do {
if try notes.canSave {
// Handle logic
}
}
catch {
// Handle error
}
属性初始化设值:不会触发 willSet 和 didSet
属性设值时:先触发 willSet 然后触发 didSet
willSet 有个newVlue 参数 代表这次要设置的新值, didSet 有个 oldValue 的参数, 代表上次属性值
即使设置的值和上次的值一样, 也会调用 willSet 和 didSet
var name: String = "He is " {
didSet {
print("Old name: \(oldValue)")
}
willSet {
print("New name: \(newValue)")
}
}
print("test 1:")
name = "Hikaru"
print(name)
print("test 2:")
name = "Hikari"
print(name)
//output:
test 1:
New name: Hikaru
Old name: He is
Hikaru
test 2:
New name: Hikari
Old name: Hikaru
Hikari