基础部分
- 输出
使用print
函数输出,不再使用NSLog
,输出占位符\(常量/变量)
例如:
let name = "小明"
print("我的名字叫\(name)")
- 注释
支持多行注释
/*
第一层注释
/*第二层注释*/
*/
数据类型
以下都是结构体类型
Int, String, Double,Float,Bool,Array, Set,Dictionary自动推断数据类型
let age = 12 // 默认推断出age是Int类型
let pi = 3.14 //默认推断出pi是Double类型
- 类型转换
swift是类型安全的语言,不同数据类型的变量或常量无法进行运算或赋值,需要转换
let three = 3
let pointOneFour = 0.14
let pi = Double(three) + pointOneFour //pi会被推断为Double类型,因为其值为3.14
- 布尔类型
swift是类型安全的语言,不再像OC那样非0即true
let i = 1
if i {//i被推断为Int类型不是Bool类型,因此编译时这里就会报错
}
if i == 1 {//i == 1的结果被推断为Bool类型,所以这里能通过编译
}
- 元组类型
元组的类型可以任意组合
let error = (404, "page not found") //此时常量error是类型为`(Int, String)`的元组
//获取元组值
let (code, desc) = error
print("code = \(code), desc = \(desc)") //输出结果code = 404, desc = page not found
//或这样打印
print("code = \(error.0), desc = \(error.1)")//输出结果code = 404, desc = page not found
//也可以在定义元组时,给每一个元素命名
let error = (code:404, desc:"page not found")
print("code = \(error.code), desc = \(error.desc)")//输出结果code = 404, desc = page
- 可选类型
在swift中nil是一个值,表示缺失。而OC中nil 是一个指向不存在对象的指针
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
//此时convertedNumber的类型为`Int?`,意思是convertedNumber可能有一个Int值,也有可能
//为nil,即没有任何值
print("convertedNumber = \(convertedNumber)")
//打印:convertedNumber = Optional(123)
//可选值可以和nil比较,例如
if convertedNumber != nil {
print("convertedNumber = \(convertedNumber!)")//当确定可选值不为空时,可以使用感叹号!进行强制解包获取对应的值
}
- 可选绑定
因swift新增了可选类型引出来的概念,主要在条件语句中使用,便于我们更好使用可选类型
if let number = convertedNumber {
print("number = \(number)")
//如果possibleNumber="123",打印number = 123
} else {
print("number is nil")
//如果possibleNumber="a",打印number is nil
}
//这段代码的含义是如果可选类型convertedNumber包含一个值,就创建
//一个新常量并将值赋给它,赋值成功了就执行if后边的语句
//条件语句可以包含多个可选绑定,例如:
if let _ = Int("10"), let _ = Int("a") {
print("都可转为Int类型")
} else {
print("存在不可转为Int类型的可选值")
}
//打印存在不可转为Int类型的可选值,因为a不可转为Int类型
- 隐式解析可选类型(自动解析可选类型)
let convertedNumber = Int("123")
let num1:Int = convertedNumber! // 需要感叹号来获取值
let num2:Int! = convertedNumber // 不需要感叹号
let num3 = num2 // num3没有显式的数据类型,那么根据类型推断,它就是一个普通的可选类型Int?
//在以上的代码中,可选值 convertedNumber 在把自己的值赋给 num2 之前会被强制解析,原因是 num2 本身的类型是非可选类型的 String。
print("num1 = \(num1), num2 = \(num2), num3 = \(num3)")
//打印num1 = 123, num2 = Optional(123), num3 = Optional(123)
//可以在可选绑定中使用隐式解析
if let _ = num2 {
print("num2可转为Int")
} else {
print("num2不可转")
}
//打印num2可转为Int
注意:
如果一个变量之后可能变成 nil 的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是 nil 的话,请使用普通可选类型。
- 错误处理
使用throws抛出错误
使用do{}catch{}捕捉错误
func makeASandwich() throws {//制作三明治
}
do {
makeASandwich()//做三明治
eatASandwich()//吃三明治
}catch SandwichError.outOfCleanDishes {//盘子不干净
washDishes()//洗盘子
} catch SandwichError.missingIngredients(let ingredients) {//缺少原料
buyGroceries(ingredients)//买杂货
}
- 断言调试和先决条件
断言调试
var age = 10
assert(age >= 18, "未成年")//程序运行到此会报错并在控制台打印未成年
age = -1
if age >= 18 {
print("已成年")
} else if age > 0 {
print("未成年")
} else {
assertionFailure("年龄不能低于0")//程序运行到此会报错并在控制台打印 年龄不能低于0
}
先决条件调试
age = 10
precondition(age >= 18, "年龄未满18岁")//执行到此报错并输出 年龄未满18岁
print("成年了可以打游戏了")
注意:
断言和先决条件的不同点是,他们什么时候进行状态检测:断言仅在调试环境运行,而先决条件则在调试环境和生产环境中运行。在生产环境中,断言的条件将不会进行评估。这个意味着你可以使用很多断言在你的开发阶段,但是这些断言在生产环境中不会产生任何影响。
注意:
在 Swift 中,struct,enum,以及 tuple(元组) 都是值类型。而平时使用的 Int, Double,Float,String,Array,Dictionary,Set 其实都是用结构体实现的,也是值类型。
运算符
- swift中没有--, ++运算符,使用-=,+=代替,且复合运算符没有返回值。
let num = 10
++num// 会报错
--num// 会报错
print(num += 1) // 打印(),而不是11
- 空合运算符 ??
??首先对前面的可选类型进行判空,如果包含值则返回,否则返回后面的值
let num = Int("a")
print("num = \(num ?? 0)") //打印 num = 0,由于num为空,所以返回后面的0
//上面的空合运算符就是下面的简写
let num1 = num != nil ? num!:0
print("num1 = \(num1)")//打印 num1 = 0
适用于组件封装时,外部如果有传入属性,则适用外部的,否则使用默认的。
- 区间运算符
for i in 0...3 {//0...3闭区间
print(i)
}
//打印0123
for i in 0..<3 {//0..<3半开区间
print(i)
}
//打印012
//半侧区间
let names = ["a", "b", "c", "d", "e"]
for name in names[2...] {
print(name)
}
打印:cde
for name in names[..<3] {
print(name)
}
打印:abc
- 字符串
注意:在 Swift 中 String和其他基本数据类型都继承Struct,而Struct是值类型,所以String也是值类型。
//和OC不同,swift字符串拼接使用加号即可。
var greeting = "Hello, playground"
print("greeting = " + greeting)
//打印greeting = Hello, playground
//字符串插值——和其他非String类型的值拼接使用
var age = 20
print("my age is \(age)")
//打印my age is 20
//多行字符串
var text = """
这\
其实是\
一行
"""
print(text)
//打印结果
//这其实是一行
//多行字符串中去掉反斜杠,输出效果不一样
var text = """
这
其实是
三行
"""
print(text)
//打印结果
//这
//其实是
//三行
//多行字符串缩进匹配周围代码
var text = """
前方没有空格
前方一个空格
前方三个空格
"""
print(text)
//打印结果
//前方没有空格
// 前方一个空格
// 前方三个空格
//扩展字符串分隔符——即在该符号内的任何特殊字符都不需要转义
var text = #"""
换行符号是:\n
回车符号:\r
"""#
print(text)
//打印结果
//换行符号是:\n
//回车符号:\r
//字符串判空(空是指空字符串,不是nil)
var text = ""
print(text.isEmpty)
//打印true
//扩展字符串中使用字符串插值,则需要再字符串插值反斜杠后添加与开头和结尾数量相同扩展字符串分隔符
print(#"6 times 7 is \#(6 * 7)."#)
打印 6 times 7 is 42.
//字符串长度
var words = "apple"
print(words.count)
//打印 5
//字符串访问——索引访问
var words = "apple"
//startIndex获取一个 String 的第一个 Character 的索引
print(words[words.startIndex])//打印 a
//endIndex获取最后一个 Character 的后一个位置的索引,注意是最后一个字符后面的索引
//因此endIndex 属性不能作为一个字符串的有效下标。所以下行代码会报错
//print(words[words.endIndex])// 报错
print(words[words.index(before: words.endIndex)])//打印 e
print(words[words.index(words.startIndex, offsetBy: 3)])//打印 l
//达到便利字符串的效果
for index in words.indices {//indices是index的复数
print(words[index])
}
//字符串插入和删除
var words = "apple"
words.insert(contentsOf: "123", at: words.firstIndex(of: "l")!)
print(words)//打印 app123le
let rang = words.firstIndex(of: "1")!..
- 数组
//数组初始化
var arr1: Array = ["a","p","p","l","e"]
//或
var arr2: [String] = ["a","p","p","l","e"]
print(arr1 == arr2)//打印true
//创建一个带有默认值的数组
var arr = Array(repeating: 0, count: 5)
print(arr)//打印 [0, 0, 0, 0, 0]
//相同数据类型的数组可通过加号连接起来
var arr1 = ["a","b"]
var arr2 = ["c"]
var arr3 = arr1 + arr2
print(arr3)//打印 ["a", "b", "c"]
//修改值
var arr = ["a","b","c","d"]
arr[0] = "e"
print(arr)//打印 ["e", "b", "c", "d"]
//或
var arr = ["a","b","c","d"]
arr[1...3] = ["e","f","g"]
print(arr)//打印 ["a", "e", "f", "g"]
//插入值
var arr = ["a","b","c","d"]
arr.insert("e", at: 1)
print(arr)//打印 ["a", "e", "b", "c", "d"]
//删除值
var arr = ["a","b","c","d"]
arr.remove(at: 1)
print(arr)//打印 ["a", "c", "d"]
//便利
var arr = ["a","b","c","d"]
for letter in arr {
print(letter)
}
//或
var arr = ["a","b","c","d"]
for (index, value) in arr.enumerated() {
print("index = \(index), value = \(value)")
}
//打印
//index = 0, value = a
//index = 1, value = b
//index = 2, value = c
//index = 3, value = d
- Set
//初始化——没有简化形式
var set1 = Set()//空集合
var set2: Set = ["a","p","p","l","e"]//数组字面量创建集合
print(set2)//打印 ["p", "e", "a", "l"],因为Set元素不可重复
//插入与删除
var set: Set = ["a","b"]
set.insert("c")
print(set)//打印 ["c", "a", "b"]
set.remove("a")
print(set)//打印 ["c", "b"]
//便利
for elem in set {//无序遍历
print(elem)
}
//或
for elem in set.sorted() {//变成数组有序便利
print(elem)
}
//------------------------------
//集合关系操作
var set1: Set = ["a","b","c"]
var set2: Set = ["d","b","c"]
var set3: Set = ["x","y","z"]
//获取 set1 和 set2的 交集
var intersection = set1.intersection(set2)
print(intersection)//打印 ["b", "c"]
//获取 set1 和 set2的 对称差
var symmetricDifference = set1.symmetricDifference(set2)
print(symmetricDifference)//打印 ["a", "d"]
//获取 set1 和 set2的 所有集
var union = set1.union(set2)
print(union)//打印 ["c", "b", "a", "d"]
//获取 set1 减去 set2的 集
var subtracting = set1.subtracting(set2)
print(subtracting)//打印 ["a"]
//------------------------------
//集合关系判断
var set1: Set = ["a","b","c"]
var set2: Set = ["b","c"]
var set3: Set = ["x","y","z"]
//判断是否不相交
let isDisjoint = set1.isDisjoint(with: set3)
print(isDisjoint)//打印 true
//是否是父集合
let isSuper = set1.isSuperset(of: set2)
print(isSuper)//打印 true
//是否是子集合
let isSub = set2.isSubset(of: set1)
print(isSub)//打印 true
//是否是父集合且不相等
let isStrictSuperset = set1.isStrictSuperset(of: set2)
print(isStrictSuperset)//打印 true
//是否是子集合且相等
let isStrictSubset = set2.isStrictSubset(of: set1)
print(isStrictSubset)//打印 true
- 字典
//字典初始化
var dict1: Dictionary = ["a":"A", "b":"B", "c":"C"]
//或
var dict2: [String : String] = ["a":"A", "b":"B", "c":"C"]
var dict3: [String : Int] = [:] //类型为[String : Int]的空字典
print(dict1 == dict2)//打印true
//修改
var dict = ["name1":"A", "name2":"B", "name3":"C"]
//修改或增加元素
dict["name1"] = "Jhon1"//和下面的updateValue等价
print(dict)//打印 ["name3": "C", "name1": "Jhon1", "name2": "B"]
dict.updateValue("Jhon2", forKey: "name1")
print(dict)//打印 ["name3": "C", "name1": "Jhon2", "name2": "B"]
//删除
var dict = ["name1":"A", "name2":"B", "name3":"C"]
dict.removeValue(forKey: "name1")
print(dict)//打印 ["name2": "B", "name3": "C"]
//遍历
var dict = ["name1":"A", "name2":"B", "name3":"C"]
for (key, value) in dict {
print("key = \(key), value = \(value)")
}
//打印
//key = name1, value = A
//key = name2, value = B
//key = name3, value = C
- for in 语句
注意:
swift中没有for(Int i = 0; i < 10; i++) {}这种遍历方式。
//特殊遍历
//stride:大步走 tick Mark:刻度线
let minutes = 60
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {//每隔5打印一次,但不包含最后一次
print(tickMark)//打印 0 5 10 15 20...55
}
for tickMark in stride(from: 0, through: minutes, by: minuteInterval) {//每隔5打印一次,包含最后一次
print(tickMark)//打印 0 5 10 15 20...55 60
}
- repeat while 语句
注意:
swift没有do while,而是换成了repeat while,使用和OC一样
//语法
repeat {
statements
} while condition
- if 语句
注意:
和OC不同,swift中if的条件语句不需要括号
let age = 10
if age < 18 {
print("未成年")
}
- switch
swift中的switch可以匹配任何类型,这和只能匹配整数的OC不一样,另外swift中switch也不会贯穿,匹配完一个case之后就不会继续匹配,所以case后面不需要写break,但若是想实现以前OC的贯穿效果可以使用关键字fallthrough
//匹配小数
var pointCount = 0.4
switch pointCount {
case 0.0, 0.1, 0.2, 0.3, 0.4:
pointCount = 0
default:
pointCount = 1
}
print(pointCount)//打印 0
//匹配字符串
var letter = "a"
switch letter {
case "a":
print("a")
default:
print("other letter")
}
//打印 a
//区间匹配
let score = 120
switch score {
case 0..<60:
print("未及格")
case 60:
print("刚好及格")
case 60..<70:
print("良好")
case 70...100:
print("优秀")
default:
print("无效分数")
}
//打印 无效分数
//匹配元组
//使用下划线 _ 来匹配所有可能的值,还能使用值绑定
let point = (1, 0)
switch point {
case (0, 0):
print("在原点")
case (_, 0):
print("在x轴上")
case (0, let y):
print("在y轴上, y = \(y)")
default:
print("不在xy轴上")
}
//打印 在x轴上
//switch 还有where模式,这也是OC中没有的
let point = (-1, 1)
switch point {
case let (x, y) where x == y:
print("点在函数 x == y 线上")
case let (x, y) where x == -y:
print("点在函数 x == -y 线上")
default:
print("点在其他地方")
}
//打印 点在函数 x == -y 线上
//使用fallthrough关键字使得switch可贯穿
var str = "i like "
let fruits = "apple"
switch fruits {
case let fruit where fruit == "apple":
str.append(fruit)
fallthrough
default:
str.append(" and others")
}
print(str)//打印 i like apple and others
//带标签的语句
let score = 60
scoreJudge: switch score {//给switch语句取个名字 scoreJudge
case 0..<60:
print("未及格")
break scoreJudge //显式的使用标签名跳出置顶的switch判断
default:
print("及格")
break scoreJudge
}
- 提前退出——guard
func greet(person:[String:String]) {
guard let name = person["name"] else {
print("没有用户")
return
}
print("hello \(name)")
}
greet(person: ["name1":"Jhon"])//打印 没有用户
greet(person: ["name1":"Jhon"])//打印 hello Jhon
- 函数
1.和CO不同,swift的函数参数可以提供默认值
2.有传入参数和传出参数的概念
3.可以将函数充当参数来使用等等
//函数定义——有参有返回值
func say(name: String) -> String {
return "hello " + name
}
print(say(name: "Jhon"))//hello Jhon
//函数隐式返回值——前提是函数体是一个单行表达式,否则报错
func say(name: String) -> String {
"hello " + name
}
print(say(name: "Jhon"))//hello Jhon
//参数标签(调用者看)和名称(内部使用)
//userName就是参数的标签, name是参数名称
//不写参数标签时其标签名和参数名相同,如果不需要参数标签,可将userName换成下划线 _
func greet(userName name: String) -> String {
return "hello " + name
}
print(greet(userName: "Jhon"))//hello Jhon
//默认参数值,当参数有默认值时,调用时可忽略该参数
func introduction(yourName name: String, age: Int = 10, gender: String = "boy") -> String {
return "hello, my name is \(name), i am a \(gender), i am \(age) years old."
}
var intro = introduction(yourName: "Jhon")
print(intro)
//打印 hello, my name is Jhon, i am a boy, i am 10 years old.
intro = introduction(yourName: "Merry", age: 12, gender: "girl")
print(intro)
//打印 hello, my name is Merry, i am a girl, i am 12 years old.
//可变参数,参数类型后面添加 ... 即可。
func getMaxNumber(valus:Int...) -> Int {
return valus.max()!
}
print(getMaxNumber(valus: 10, 12, 3, 9))
// 输入输出参数——关键字inout
//首先要知道,swift中的参数时常量,所以是无法直接在函数体中修改的,因此有了输入输出参数的概念。
var value1 = 10;
var value2 = 20
func swapTwoInts(v1: inout Int, v2: inout Int) {//包含输入输出参数的函数
let temp = v1
v1 = v2
v2 = temp
}
print("before value1 = \(value1), value2 = \(value2)")
//打印 before value1 = 10, value2 = 20
swapTwoInts(v1: &value1, v2: &value2)//给输入输出参数传参时,需要再变量名前面加 & 符号
print("after value1 = \(value1), value2 = \(value2)")
//打印 after value1 = 20, value2 = 10
//函数类型——由函数的参数类型和返回类型组成
//例如:
//没有参数和没有返回值的函数:()->void
//有参数有返回值的函数:(Int, Int)->Int
var mathFnction: (Int, Int)->Int
func addTwoInts(v1: Int, v2: Int) -> Int {
return v1 + v2
}
func multiplyTwoInts(v1: Int, v2: Int) -> Int {
return v1 * v2
}
mathFnction = addTwoInts
print(mathFnction(2, 4))//打印 5
mathFnction = multiplyTwoInts
print(mathFnction(2, 4))//打印 6
//函数类型作为参数类型
func addTwoInts(v1: Int, v2: Int) -> Int {
return v1 + v2
}
func multiplyTwoInts(v1: Int, v2: Int) -> Int {
return v1 * v2
}
func handleMathResult(mathFnction: (Int, Int) -> Int, v1: Int, v2: Int) -> Int {
return mathFnction(v1, v2)
}
let result = handleMathResult(mathFnction: multiplyTwoInts, v1: 3, v2: 5)
print(result)//打印 15
//函数类型作为返回类型
func stepForward(input: Int) -> Int {
return input + 1
}
func stepBackward(input: Int) -> Int {
return input - 1
}
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
let function = chooseStepFunction(backward: false)
print(function(5))//打印 6
//嵌套函数——目前为止学习都是函数
//都是全局函数,和OC不同,swift的函数式可以嵌套的
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int {
return input + 1
}
func stepBackward(input: Int) -> Int {
return input - 1
}
return backward ? stepBackward : stepForward
}
let function = chooseStepFunction(backward: false)
print(function(8))//打印 9
- 闭包——和OC的block有点类似
Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
1、利用上下文推断参数和返回值类型
2、隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字
3、参数名称缩写
4、尾随闭包语法
下面我们跟随使用sorted(by:)(是一个排序函数)慢慢深入了解闭包的使用
//这是sorted(by:)函数最常规的使用(还没使用闭包)
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backward(s1: String, s2: String) -> Bool {//如果第一个参数
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
print(reversedNames)
//打印 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//首先了解闭包的语法,如下:
/*
{ (parameters) -> return type in
statements
}
*/
//注意:闭包的参数不能使用默认值
//下面的例子展示了之前 backward(_:_:) 函数对应的闭包表达式版本的代码:
//这是闭包最初的使用方式。
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted(by: {(s1: String, s2: String) -> Bool in
return s1 > s2
})
print(reversedNames)
//打印 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//注意:上面关键词 in 用来引入闭包函数体,表示闭包的参数
//和返回值类型定义已经完成,闭包函数体即将开始
//下面我们根据闭包的4点优化方案来优化sorted(by:)函数的调用
//闭包简化:根据上下文推断类型
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted(by: {s1, s2 in return s1 > s2})
print(reversedNames)
//打印 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//闭包简化:单表达式闭包隐式返回,可以省略return
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted(by: {s1, s2 in s1 > s2})
print(reversedNames)
//打印 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//闭包简化:参数名称缩写
//Swift 自动为内联闭包提供了参数名称缩写功能,你可以
//直接通过 $0,$1,$2 来顺序调用闭包的参数,以此类推。
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted(by: { $0 > $1})
print(reversedNames)
//打印 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//简化:运算符。严格上说不是简化,只是碰巧String类型
//提供了大于号的函数定义,该函数类型刚好是(String, String)->Bool类型
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted(by: >)
print(reversedNames)
//打印 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
//尾随闭包——只有当闭包充当最后一个参数时可以使用尾随闭包。
//尾随闭包是一个书写在函数 圆括号之后 的闭包表达式,函数
//支持将其作为最后一个参数调用。在使用尾随闭包时,你
//不用写出它的参数标签:
//带一个闭包参数的函数
func takeAClosureFunction(closure: ()->Void) {
print("我是带有一个闭包参数的函数")
closure()
}
//不适用尾随闭包进行调用
takeAClosureFunction(closure: {
print("使用普通的闭包调用")
})
//使用尾随闭包进行调用
takeAClosureFunction() {
print("使用尾随闭包调用")
}
//打印
//我是带有一个闭包参数的函数
//使用普通的闭包调用
//我是带有一个闭包参数的函数
//使用尾随闭包调用
//如果闭包表达式是函数唯一参数且当你使用尾随
//闭包时,你甚至可以把 () 省略掉,这才是sorted(by:)函数使用
//闭包(更加确切的说是尾随闭包)之后的简写效果,如下:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted { $0 > $1}
print(reversedNames)
//打印 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
小结:闭包高级吗?不高级,是为了简化函数充当参数时简化语法结构而产生出来的。
- 值捕获——该概念是针对闭包产生出来的
示例中局部变量amount和runningTotal的引用被闭包捕获,确保了
调用makeIncrementer函数之后不会消失。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementer = makeIncrementer(forIncrement: 5)
print(incrementer())//打印 5
print(incrementer())//打印 10
print(incrementer())//打印 15
//闭包是引用类型
let incrementer2 = incrementer
print(incrementer2())//打印 20
- 逃逸闭包
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。
逃逸闭包需要在类型前加@escaping
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
//此时赋值操作并未执行,因为someFunctionWithEscapingClosure函数体中,闭包
//被存放到completionHandlers数组中了
someFunctionWithEscapingClosure { self.x = 100 }
//此时赋值操作已被立刻执行,因为someFunctionWithNonescapingClosure函数体中
//直接执行的闭包
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)//打印 200
completionHandlers.first?()
print(instance.x)//打印 100
小结:逃逸闭包高级吗?不高级,不就是将闭包存放在外部嘛,等待合适的时机再去调用而已。
- 自动闭包
自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。
//闭包如何延时求值——自动闭包让你能够延迟求值
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)// 打印出 5
//remove(at:)函数会返回被移除的元素
//只有闭包被执行,闭包体中的表达式才会被执行
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)// 打印出 5
print("开始执行1 \(customerProvider())!")// 开始执行闭包,打印出 开始执行1 Chris!
print(customersInLine.count)// 打印出“4”
//将闭包作为参数传递给函数时,你能获得同样的延时求值行为。
func serve(customer customerProvider: () -> String) {
print("开始执行2 \(customerProvider())!")//打印 开始执行2 Alex!
}
serve(customer: { customersInLine.remove(at: 0) })
//下面我们使用自动闭包实现上面的功能。关键字@autoclosure
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("现在执行3 \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))//打印 现在执行3 Chris!
小结:用得多,很少实现,了解即可。
自动闭包局限性:
这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
- 枚举
和OC相比,swift中的 枚举成员 类型 更加丰富,还多了 原始值 和 关联值 的概念。
注意:和OC不同,swift的枚举成员不会被隐式的赋值为0,1,2,3...等等,swift中枚举成员的默认既是定义的枚举名类型。
//语法
//enum [枚举名] {
// case 枚举成员1,
// case 枚举成员2,
// ...
//}
//例子:
enum CompassPoint {
case north//北
case south//南
case east//东
case west//西
}
//由于有swift有类型推断的机制,所以我们在使用枚举时,有更简单的使用方式
var direction = CompassPoint.north
direction = .south //更简单的赋值方式,因为已经能推断出direction的类型了
//由于swift中枚举相比OC要强大了很多,因此呢会针对强大出来的新功能做更多的操作,例如:遍历枚举,需要枚举遵循CaseIterable协议。
enum Beverage: CaseIterable {
case coffee, tea, juice
}
for beverage in Beverage.allCases {
print(beverage)
}
//打印
//coffee
//tea
//juice
//关联值——swift中枚举成员的关联值类型可以不一样,例如:
enum Barcode {//条形码枚举
case upc(Int, Int, Int, Int)//upc格式的条形码
case qrcode(String)//二维码格式的条形码
}
//创建面包商品的条形码
var breadBarcode = Barcode.upc(8, 85909, 51226, 3)
//同一面包商品可以不同类型的条形码
breadBarcode = Barcode.qrcode("ABCDEFG")//尝试注释看执行结果
switch breadBarcode {
//case .upc(let numberSystem, let manufacturer, let product, let check):
case let .upc(numberSystem, manufacturer, product, check)://上面简写
if numberSystem == 8 && manufacturer == 85909 && product == 51226 && check == 3 {
print("是我想要的面包,产品条形码:\(numberSystem) \(manufacturer) \(product) \(check)")
} else {
print("不是我想要的面包")
}
//case .qrcode(let productCode)://上面简写
case let .qrcode(productCode):
if productCode == "ABCDEFG" {
print("是我想要的面包,产品二维码:\(productCode)")
} else {
print("不是我想要的面包")
}
}
//原始值——作为关联值的替代选择,类型必须相同。
//下面枚举为成员赋值原始值,原始值类型为Character
enum ASCIIControlCharacter: Character {
case tab = "\t"//换行 光标到行首
case lineFeed = "\n"//输出位置移动到下一行行首 新行
case carriageReturn = "\r"// 回车 输出位置移动到行首 不换行
}
//注意:在使用原始值为整数或者字符串类型的枚举时,不需要
//显式地为每一个枚举成员设置原始值,Swift 将会自动为你赋值。
//如果是Int类型,则和OC一样。
//如果是String类型,原始值就是枚举成员名称。
//枚举成员可以通过rawValue属性获取原始值,还可以通过原始值初始化枚举实例,例如:
enum CompassPoint: String {
case north//北
case south//南
case east//东
case west//西
}
let direction = CompassPoint.init(rawValue: "north")
print(direction!)//打印 north
print(direction!.rawValue)//打印 north
//递归枚举——是一种枚举类型。在枚举成员 case 前加上 indirect 来表示该成员可递归。在枚举类型 enum 前加上 indirect 关键字来表明它的所有成员都是可递归的。了解即可,感觉没啥卵用。
- Swift中结构体的功能和类极其相似,具备了几乎和类同等的功能。
注意:
1、结构体和枚举都是值类型,类是引用类型。
2、基本数据类型继承于结构体,所以基本数据类型也全部都是值类型。闭包是引用类型
//声明一个结构体
struct Resolution {
var width = 0
var height = 0
}
//声明一个类
class VideoMode {
var name: String?
var resolution = Resolution()
}
//恒等运算符——用来比较两个引用类型是否相等的运算符。
//相同(===)
//不相同(!==)
- 属性——swift中属性分计算属性和存储属性。前者用于类,结构体和枚举,后者只能用于类和结构体。(难点)
//存储属性——一般用的就是存储属性。没啥难度,字面上理解即可。
struct FixedLengthRange {
var firstValue: Int
let lenght: Int
}
var variableRange = FixedLengthRange(firstValue: 0, lenght: 3)//范围[0,1,2]
variableRange.firstValue = 3//实例为var类型,所以属性firstValue可修改
//variableRange.lenght = 5//根据结构体是值类型可知不可修改
let constRange = FixedLengthRange(firstValue: 3, lenght: 3)//范围[3,4,5]
//constRange.firstValue = 6//根据结构体是值类型知不可修改
//存储属性延迟加载,在属性修饰符前面加lazy即可——该属性必须是var类型,因
//为常量属性在构造过程完成之前必须要有初始值,因此无法声明成延时加载。
//该类将外部文件中的数据导入,该类初始化会花费很多时间
class DataImporter {
var fileName = "data.txt"
}
class DataManager {
lazy var importer = DataImporter()
var data: [String] = []
}
let dataManager = DataManager()
dataManager.data.append("some data")//到这时实例dataManager的importer属性实际还没初始化
print(dataManager.importer.fileName)//这会实例dataManager用到了importer属性,所以importer会被初始化
//计算属性——swift中特有。没啥难度,字面上理解即可。
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {//center就是计算属性
// get {
// let centerX = origin.x + size.width * 0.5
// let centerY = origin.y + size.height * 0.5
// return Point(x: centerX, y: centerY)
// }
//如果get是单一表达式,可省略return,简化如下:
get {
Point(x: origin.x + size.width * 0.5,
y: origin.y + size.height * 0.5)
}
// set (newCenter) {
// origin.x = newCenter.x - size.width * 0.5
// origin.y = newCenter.y - size.height * 0.5
// }
//set可以不定义新值得参数名,使用默认的newValue,set可简化如下:
set {
origin.x = newValue.x - size.width * 0.5
origin.y = newValue.y - size.height * 0.5
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 10.0))
print(square.center)//打印 Point(x: 5.0, y: 5.0)
square.center = Point(x: 10, y: 10)
print(square.origin)//打印 Point(x: 5.0, y: 5.0)
//只读属性——如果计算属性没有set,那就变成了只读计算属性,只读属性可以去掉get关键字,例如:
struct Cuboid {//长方体
var width = 0.0, height = 0.0, dept = 0.0
var volume: Double {//体积只读计算属性
width*height*dept
}
}
let cuboid = Cuboid(width: 2, height: 3, dept: 4)
print(cuboid.volume)
//属性观察器——监控和响应属性值的变化。有willset和didset两种观察器
class StepCounter {//计步器
var totalSteps: Int = 0 {
willSet {
print("将把 totalSteps 值设置为 \(newValue)")
}
didSet {
if totalSteps > oldValue {
print("增加了 \(totalSteps - oldValue) 步")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 1
//打印
//将把 totalSteps 值设置为 1
//增加了 1 步
stepCounter.totalSteps = 5
//打印
//将把 totalSteps 值设置为 5
//增加了 4 步
- 属性包装器(难点)
属性包装器在 管理属性 如何 存储 和 定义属性 的代码之间添加了一个分隔层。举例来说,如果你的属性需要线程安全性检查或者需要在数据库中存储它们的基本数据,那么必须给每个属性添加同样的逻辑代码。当使用属性包装器时,你只需在定义属性包装器时编写一次管理代码,然后应用到多个属性上来进行复用。
//定义一个属性包装器
@propertyWrapper
struct TwelveOrLess {
private var number: Int = 0
var wrappedValue: Int {
get { number }
set { number = min(newValue, 12) }
}
}
//定义小矩形结构体
struct SmallRectangle {
//通过在属性之前写上包装器名称,确保小矩形的长宽均小于等于 12
@TwelveOrLess var width: Int//包装器应用到一个属性上
@TwelveOrLess var height: Int//包装器应用到一个属性上
}
var rectangle = SmallRectangle()
print(rectangle.height)//打印 0
rectangle.height = 10
print(rectangle.height)//打印 10
rectangle.height = 20
print(rectangle.height)//打印 12
//如果包装器不应用到一个属性上,SmallRectangle的代码是下面这样子的:
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
}
小结:属性包装器其实就是一套代码业务逻辑,用于给属性做限制的逻辑代码。
- 设置被包装属性的初始值
学习了上面的属性包装器之后,发现SmallRectangle定义时没法给width和height讴歌初始值,为此我们来学习通过构造器为其初始值。
//定义一个属性包装器
@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { number }
set { number = min(newValue, maximum) }
}
//与先前的TwelveOrLess多了下面几个构造函数
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)
}
}
//使用带有 属性包装器 构造器 的 属性包装器
//当你把包装器 应用于属性且没有设定初始值时,Swift 使用 init() 构造器来设置包装器
struct ZeroRectangle {
@SmallNumber var width: Int
@SmallNumber var height: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.width, zeroRectangle.height)// 打印 0 0
//当你为属性指定初始值时,Swift 使用 init(wrappedValue:) 构造器来设置包装器
struct ZeroRectangle {
@SmallNumber var width: Int = 1
@SmallNumber var height: Int = 1
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.width, zeroRectangle.height)// 打印 1 1
//当你在自定义特性后面把实参写在括号里时,Swift 使用接受这些实参的构造器来设置包装器
struct ZeroRectangle {
@SmallNumber(wrappedValue: 2, maximum: 5) var width: Int
@SmallNumber(wrappedValue: 3, maximum: 4) var height: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.width, zeroRectangle.height)// 打印 2 3
zeroRectangle.width = 100
zeroRectangle.height = 100
print(zeroRectangle.width, zeroRectangle.height)// 打印 5 4
- 从属性包装器中呈现一个值(难点)
属性包装器可以通过定义被呈现值暴露出其他功能,例如那个数值是否被调整过
@propertyWrapper
struct SmallNumber {
private var number = 0
//变量名projectedValue是固定的,不可修改
var projectedValue = "没有修改值"
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
projectedValue = "值被修改了"
} else {
number = newValue
projectedValue = "没有修改值"
}
}
}
}
struct SomeStructure {
@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber)// 没有修改值
someStructure.someNumber = 55// 值被修改了
print(someStructure.$someNumber)
- 全局变量和局部变量
全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
注意:
全局的常量或变量都是延迟计算的,跟 延时加载存储属性 相似,不同的地方在于,全局的常量或变量不需要标记 lazy
修饰符。局部范围的常量和变量从不延迟计算。
可以在局部存储型变量上使用属性包装器,但不能在全局变量或者计算型变量上使用。
- 实例属性和类型属性(就是成员属性和全局属性)
实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。
你也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是类型属性。
//定义实例属性,使用关键字 static 来定义类型属性。
//在为类定义计算型类型属性时,可以改用关键字 class 来支持子类对父类的实现进行重写。
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
//获取和设置类型属性的值
print(SomeStructure.storedTypeProperty)
// 打印“Some value.”
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 打印“Another value.”
print(SomeEnumeration.computedTypeProperty)
// 打印“6”
print(SomeClass.computedTypeProperty)
// 打印“27”
- 方法
实例方法的语法与函数完全一致,与OC不同,swift中枚举和结构体都可以定义方法(实例方法和类型方法)。
注意:
结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。如果想可以为这个方法选择 可变(mutating)行为,关键字 mutating 放到方法的 func 关键字之前就可以了。
struct Point {
var x: Double = 0.0
var y: Double = 0.0
mutating func move(x: Double, y: Double) {
self.x += x
self.y += y
}
}
var point = Point()
print(point)//打印 Point(x: 0.0, y: 0.0)
point.move(x: 2, y: 3)
print(point)//打印 Point(x: 2.0, y: 3.0)
//可在可变方法中给 self 赋予全新的实例
struct Point {
var x: Double = 0.0
var y: Double = 0.0
mutating func move(x: Double, y: Double) {
self = Point(x: self.x + x, y: self.y + y)
}
}
var point = Point()
print(point)//打印 Point(x: 0.0, y: 0.0)
point.move(x: 3, y: 5)
print(point)//打印 Point(x: 2.0, y: 3.0)
//枚举的可变方法可以把 self 设置为同一枚举类型中不同的成员
enum TriStateSwitch {//三态开关
case off, low, high
mutating func next() {
switch self {
case .off:
self = .low
case .low:
self = .high
case .high:
self = .off
}
}
}
var ovenLight = TriStateSwitch.low //ovenLight:路灯
print(ovenLight)//打印 low
ovenLight.next()
print(ovenLight)//打印 high
ovenLight.next()
print(ovenLight)//打印 off
//类型方法——在方法的 func 关键字之前加上关键字 static。如果即重写类型方法。
//如果类的类型方法允许子类重写,那么就将 static 替换成 class即可。
class Object {//三态开关
class func ability() {
print("具备创建新对象得能力")
}
}
class SubObject: Object {
override class func ability() {
super.ability()
print("子类也具备创建新对象的能力")
}
}
Object.ability()//打印 具备创建新对象得能力
SubObject.ability()//打印 具备创建新对象得能力 和 打印 子类也具备创建新对象的能力
- 下标
定义在类、结构体和枚举中,是访问集合、列表或序列中元素的快捷方式,可以使用下标的索引,设置和获取值。
常见的下标使用方式有数组Array[index],字典Dictionary[key]。
一个类型可以定义多个下标且不限于一维。
语法类似于实例方法语法和计算型属性语法,定义下标使用 subscript 关键字,与定义实例方法类似,都是指定一个或多个输入参数和一个返回类型
//下标语法
//可读可写的下标
struct TimesTable {//时间表
var multiplier: Int//倍增器
subscript(index: Int) -> Int {
get {
return index * multiplier
}
set {
multiplier = newValue
}
}
}
var timesTable = TimesTable(multiplier: 3)
print(timesTable[4])//打印 12 (3 * 4 = 12)
timesTable[0] = 8//将multiplier设置为8,由于设置时索引没使用上,因此索引可以填写任意Int类型值
print(timesTable[3])//打印 24 (3 * 8 = 24)
//只读下标(可以像只读属性那样简写)
struct TimesTable {//时间表
let multiplier: Int//倍增器
subscript(index: Int) -> Int {
index * multiplier
}
}
let timesTable = TimesTable(multiplier: 3)
print(timesTable[5])
//下标选项——意思就是下标可以接受任意数量的入参,并且这些入参可以是任何类型。一般都是用一个参数的下标,了解即可。
//类型下标——顾名思义,就是定义在类型上的下标,需要在 subscript 前加 static来修饰。同样在类中,如果允许下标被子类修改,就将 subscript 改成 class 即可。
- 类
注意:
Swift 中的类并不是从一个通用的基类继承而来的。如果你不为自己定义的类指定一个超类的话,这个类就会自动成为基类。
你可以通过把方法,属性或下标标记为 final 来防止它们被重写,例如 final func,final var,final class func 以及 final subscript。
被 final 修饰的类和OC一样,不可被继承。
- 构造过程
简单概括就是实例创建之后,得确保属性都有值。因此在不自定义构造器的前提下,swift会自动产生一个构造器,用来给所有的属性赋值。
//类型(类,结构体,枚举)没有任何属性时,默认构造器时这样的
class Person {
//init() {}//这是默认提供的,可不显式写出来
}
//有一个非可选参数的默认构造器是这样的
class Person {
var IDCard: String
//必须提供这么一个构造器,因为需要给非可选类型赋值
init(IDCard: String) {
self.IDCard = IDCard
}
}
//可选参数的默认构造器
class Person {
var name: String?
//默认构造器如下,也可不显式写出来
// init() {
// name = nil
// }
}
枚举和结构体的构造器和类的构造器基本一样。
- 析构过程
每个类有且只有一个析构器,作用同OC的dealloc一样。
class Person {
deinit {
print("处理后事")
}
}
- 可选连
因为有可选值类型,所以多个可选值的调用关系形成了可选链。看例子即可,难点
class Person {
var residence: Residence?
}
class Residence {//住宅
var rooms: [Room] = []
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
get {
return rooms[i]
}
set {
rooms[i] = newValue
}
}
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
var address: Address?
}
class Room {
let name: String
init(name: String) { self.name = name }
}
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if buildingName != nil {
return buildingName
} else if let buildingNumber = buildingNumber, let street = street {
return "\(buildingNumber) \(street)"
} else {
return nil
}
}
}
let john = Person()
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
//判断可选链上判断方法是否执行成功
if john.residence?.printNumberOfRooms() != nil {
print("可以打印房间的数量")
} else {
print("无法打印房间的数量")
}
// 打印 无法打印房间的数量
//判断可选链上属性赋值可否成功
if (john.residence?.address = someAddress) != nil {
print("可以设置地址")
} else {
print("不可以设置地址")
}
//打印 不可以设置地址
//判断可选链上下标访问是否成功
if let firstRoomName = john.residence?[0].name {
print("第一个房间的名字是 \(firstRoomName).")
} else {
print("无法检索第一个室名称")
}
//打印 无法检索第一个室名称
//判断可选链上下标赋值是否成功
if (john.residence?[0] = Room(name: "Bathroom")) != nil {
print("房间名设置成功")
} else {
print("没有房间")
}
//打印 没有房间
注意:下标中如果返回值是可选类型,那么在使用时,问号?应当放在中括号[]的后面。
- 错误处理
Swift 在运行时提供了抛出、捕获、传递和操作可恢复错误(recoverable errors)的一等支持(first-class support)。
//表示与抛出错误——在 Swift 中,错误用遵循 Error 协议的类型的值来表示。
enum VendingMachineError: Error {//自动贩卖机错误
case invalidSelection //选择无效
case insufficientFunds(coinsNeeded: Int) //金额不足
case outOfStock //缺货
}
//抛出异常,使用关键字throw,例如:
throw VendingMachineError.outOfStock
//异常处理
//1.1你可以把函数抛出的错误传递给调用此函数的代码。关键词throws
//1.2调用会抛出异常的函数,需要在调用前使用try。
//2.用 do-catch 语句处理错误
//3.将错误作为可选类型处理
//4.断言此错误根本不会发生
struct Item {
var price: Double
var count: Int
}
class VendingMachine {//售卖机
var inventory = [//库存
"糖果": Item(price: 12, count: 7),
"薯条": Item(price: 10, count: 4),
"饼干": Item(price: 7, count: 11) ]
var coinsDeposited = 0.0;//金币余量
//1.1你可以把函数抛出的错误传递给调用此函数的代码。关键词throws
func vend(name: String) throws {
guard let item = inventory[name] else {
throw VendingMachineError.invalidSelection//选择无效
}
guard item.count > 0 else {
throw VendingMachineError.outOfStock//缺货
}
guard item.price <= coinsDeposited else {
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)//金额不足
}
coinsDeposited -= item.price
var newItem = item
newItem.count -= 1
inventory[name] = newItem
print("售卖一份了\(name)")
}
}
let favoriteSnacks = [
"Alice": "糖果",
"Bob": "薯条",
"Eve": "话梅",
]
func buySnack(persion: String, vendingMachine: VendingMachine) throws {//买零食
//传入的用户如果没有喜欢的零食,那么默认为喜欢糖果
let snackName = favoriteSnacks[persion] ?? "糖果"
//1.2调用会抛出异常的函数,需要在前面使用 try。
try vendingMachine.vend(name: snackName)
}
//2.用 do-catch 语句处理错误
ar vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try buySnack(persion: "Alice", vendingMachine: vendingMachine)
} catch VendingMachineError.invalidSelection {
print("选择无效")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
print("金币不足,缺少 \(coinsNeeded) 个")
} catch VendingMachineError.outOfStock {
print("缺货")
} catch {
print("未知异常")
}
// 打印 金币不足,缺少 4.0 个
//注意:可以将多个异常类型值放在一个case后面,用逗号隔开。
//3.将错误作为可选类型处理——使用 try? 通过将错误转换成一个可选值来处理错误。
//在下面的代码中,x 和 y 有着相同的数值和等价的含义
func someThrowingFunction() throws -> Int {
return 0
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
}catch {
y = nil
}
//4.断言此错误根本不会发生(禁用错误传递)
//有时你知道某个 throwing 函数实际上在运行时是不会抛出错误的,在这种
//情况下,你可以在表达式前面写 try! 来禁用错误传递,这会把调用包装
//在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得
//到一个运行时错误。
//例如,当可以确定someThrowingFunction不会抛出异常时,可以这样,这时
//的x,y,z值一样。
let z = try! someThrowingFunction()
- 指定清理操作
可以使用 defer 语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,或是由于诸如 return、break 的语句。例如,你可以用 defer 语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。
func processFile(fileName: String) throws {
if exists {
let file = open(fileName)
}
defer {
close(file)
}
while let line = try file.readline() {
//处理文件
}
// close(file) 会在这里被调用,即作用域的最后。
}
- 类型转换
和OC不一样,Swift的类型转换更加严格一些,使用 is 和 as 操作符实现类型检查和类型转换
//使用 is 做类型检查
class MediaItem {//媒体类型
var name: String
init(name: String) {
self.name = name
}
}
class Movie: MediaItem {//电影
var director: String//导演
init(director: String, name: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {//音乐
var artist: String//艺术家
init(artist: String, name: String) {
self.artist = artist
super.init(name: name)
}
}
let library = [
Movie(director: "A", name: "a"),
Movie(director: "B", name: "b"),
Movie(director: "C", name: "c"),
Song(artist: "D", name: "d"),
Song(artist: "E", name: "e")
]
var moviceCount = 0
var songCount = 0
for item in library {
if item is Movie {
moviceCount += 1
} else if item is Song {
songCount += 1
}
}
print("moviceCount = \(moviceCount), songCount = \(songCount)")
//打印 moviceCount = 3, songCount = 2
//使用 as? 或 as! 向下转型,as? 返回一个你试图向下转成的
//类型的可选值,强制形式 as! 把试图向下转型和强制解包转换结果
//结合为一个操作。
for item in library {
if let movie = item as? Movie {
print("movie name = \(movie.name), movie director = \(movie.director)")
} else if let song = item as? Song {
print("song name = \(song.name), song artist = \(song.artist)")
}
}
//打印
//movie name = a, movie director = A
//movie name = b, movie director = B
//movie name = c, movie director = C
//song name = d, song artist = D
//song name = e, song artist = E
//类型强转
var mediaItem = MediaItem(name: "A")
mediaItem = Movie(director: "B", name: "b")
let movie = mediaItem as! Movie
print("movie name = \(movie.name), movie director = \(movie.director)")
//打印
//movie name = b, movie director = B
//Any 和 AnyObject 的类型转换
//Any表示任意类型,AnyObject表示任意类型的实例
var everyThings: [Any] = []
everyThings.append(0)
everyThings.append(0.1)
everyThings.append("0")
everyThings.append(true)
everyThings.append([1, 2])//数组
everyThings.append(["A":"a"])//字典
everyThings.append((200, "success"))//元组
everyThings.append(Movie(director: "A", name: "a"))//自定义类型
everyThings.append({(name: String) -> String in "hello, \(name)"})//闭包
//使用 as 向上转型
let optionalNumber: Int? = 3
everyThings.append(optionalNumber as Any)//不向上转型会有警告
嵌套类型
和OC不同,Swift 允许你定义嵌套类型,可以在支持的类型中定义嵌套的枚举、类和结构体。扩展
可以给一个现有的类,结构体,枚举,还有协议添加新的功能。例如添加属性,方法等。
//语法:
extension TypeName {
//扩展的功能
}
//为类型扩展计算型属性(注意:不能扩展存储型属性)
extension Double {
var km: Double { return self * 1_000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
}
let oneInch = 2.54.cm//一英寸等于2.54厘米
print("一英寸等于\(oneInch)厘米")//打印 一英寸等于0.0254厘米
let oneKiloemeter = 1.0.km
print("一千米等于\(oneKiloemeter)米")//打印 一千米等于1000.0米
let total = 2.3.km + 1000.m + 23.cm
print("total = \(total)")//打印 total = 3300.23
//为类型扩展新的构造器
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var size: Size = Size()
var origin: Point = Point()
}
extension Rect {
//给结构体扩展构造方法
init(center: Point, size: Size) {
let originX = center.x - size.width * 0.5
let originY = center.y - size.height * 0.5
let origin = Point(x: originX, y: originY)
self.init(size: size, origin: origin)
}
}
let rect = Rect(center: Point(x: 10, y: 10), size: Size(width: 10, height: 10))
print(rect.origin)//打印 Point(x: 5.0, y: 5.0)
//给类型扩展方法
extension Int {
func repetitions(task:() -> Void) {
for _ in 0.. Void in
print("hello")
})
//打印
//hello
//hello
//hello
var number = 3
number.square()
print(number)//打印 9
//给类型添加下标嵌套类型(感觉用处不大)
- 协议
相当于OC中的代理,只不过swift中类、结构体或枚举都可以遵循协议。
//协议语法
protocol TypeName {
// 这里是协议的定义部分
}
//遵循协议——写在类型后面,中间用冒号隔开,遵循多个
//协议时,协议之间用逗号隔开。
//注意:若是一个类拥有父类,应该将父类名放在遵循的协议名之前
class SomeClass: SuperClass, SomeProtocol1, SomeProtocol1 {
}
//swift中协议可要求遵从协议的类型提供 特定名称 和 类型 的属性
protocol FullyName {
//{ get }表示该属性只读,{ set get }表示属性可读可写
var fullyName: String { get }
}
struct Person: FullyName {
var fullyName: String
}
let person = Person(fullyName: "San Zhang")
print(person.fullyName)
//swift中协议可要求遵从协议的类型提供 指定的方法
protocol SayHello {
func say()
}
struct Person: SayHello {
func say() {
print("hello world!")
}
}
let person = Person()
person.say()
//打印 hello world!
//如果协议中要求实现构造器方法,那么实现时构造方法前面要加required,使用 required 修饰符可以确保所有子类也必须提供此
//构造器实现,从而也能遵循协议。
protocol SomeProtocol {
init(param: Int)
}
class Person: SomeProtocol {
required init(param: Int) {//不加 required 会报错
}
}
//在扩展添加协议遵循
protocol SomeProtocol {
//定义协议
}
extension Int: SomeProtocol {
//实现协议
}
//有条件的遵循协议
protocol SomeProtocol1 {
//定义协议
}
protocol SomeProtocol2 {
//定义协议
}
extension Array: SomeProtocol1 where Element: SomeProtocol2 {
//实现协议
}
//声明协议采纳
//当一个类已经实现协议所有要求,但是没采纳协议,这时可以通过
//扩展来让它采纳该协议。
protocol SomeProtocol {
var someProperty: String { get }
}
struct SomeStruct {
var someProperty: String {
return "some value"
}
}
extension SomeStruct: SomeProtocol{}
//协议能够继承其他协议且可以多继承
//当一个协议继承AnyObject,该协议就只能被类类型遵循,不能被结构体和枚举遵循
//协议合成——使用&符号
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {//祝生日快乐
print("\(celebrator.name)生日快乐,你\(celebrator.age)岁了")
}
wishHappyBirthday(to: Person(name: "小明", age: 10))
//检查是否遵循某协议,使用 is 和 as
//is 用来检查实例是否遵循某个协议,若遵循则返回 true,否则返回 false;
//as? 返回一个可选值,当实例遵循某个协议时,返回类型为协议类型的可选值,否则返回 nil;
//as! 将实例强制向下转换到某个协议类型,如果强转失败,将触发运行时错误。
protocol HasArea {
var area: Double { get }
}
class Circle: HasArea {
let pi = 3.1415927
var radius: Double
var area: Double { return pi * radius * radius }
init(radius: Double) { self.radius = radius }
}
class Country: HasArea {
var area: Double
init(area: Double) { self.area = area }
}
class Animal {
var legs: Int
init(legs: Int) { self.legs = legs }
}
let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
for object in objects {
if let objectWithArea = object as? HasArea {
print("面积是:\(objectWithArea.area)")
} else {
print("不存在面积这种东西的事物")
}
}
//打印
//面积是:12.5663708
//面积是:243610.0
//不存在面积这种东西的事物
//协议中定义不要求遵循对象一定要实现的方法
//如果是和OC交互的协议,只需在协议和方法之前加上@objc,这类协议只能给OC类遵循
@objc protocol SomeProtocol {
@objc optional func someMethod(forCount count: Int) -> Int
@objc optional var someProperty: Int { get }
}
//如果想实现swift中协议方法不一定要实现,可以在扩展中提供默认实现
protocol SomeProtocol {
func someMethod()
}
extension SomeProtocol {
func someMethod() {
print("execute someMethod")
}
}
class SomeClass: SomeProtocol {
}
SomeClass().someMethod()//打印 execute someMethod
//为协议扩展添加限制条件
extension Collection where Element: Equatable {
func allEqual() -> Bool {
for element in self {
if element != self.first {
return false;
}
}
return true
}
}
- 并发