本篇博客作为个人笔记用,无特别指导意义。若有错误,欢迎指出,不胜感激。主要记录的是java中不同的语法或者没有的概念。
主要参考:
https://docs.swift.org/swift-book/
https://swiftgg.gitbook.io/swift/huan-ying-shi-yong-swift
目录
字符串
-
swift中有多行字符串字面量(字面量就可以理解成字符串)
多行字符串字面量用3个双引号表示开头或结尾。 -
在多行字面量中如果不想换行,在字面量中用反斜杠\
-
关闭引号"""之前的空白字符串会表示会忽略多少个空字符串
-
转义自负和其他语音一样
\0(空字符)、\(反斜线)、\t(水平制表符)、\n(换行符)、\r(回车符)、"(双引号)、’(单引号) -
字符串在操作时会对已有字符串创建新副本
-
for-in
在swift中for已经被弃用 -
字形群
let eAcute: Character = "\u{E9}" // é
let combinedEAcute: Character = "\u{65}\u{301}" // e 后面加上 ́
// eAcute 是 é, combinedEAcute 是 é
第一种是单一的swift Character值,同时代表了一个可扩展的字形群,是一个单一标量;第二种情况下是两个标量的字形群。
同时复习一下:swift中定义一个变量 变量名:变量类型 = xxxx
8. 只要可扩展的字形群的语义信息相等,就认为字符串是相等的。
但是要注意不同语言中 长相相同的字符,这种语义是不同的。
let latinCapitalLetterA: Character = "\u{41}"
let cyrillicCapitalLetterA: Character = "\u{0410}"
if latinCapitalLetterA != cyrillicCapitalLetterA {
print("These two characters are not equivalent")
}
- 字形群和字符是不同的概念。字形群是由unicode标量组成的。count函数是用来计算字符的个数。
- 字符串索引
字符串不能直接通过[数字]来访问对应位置的字符, 应该使用 _.index(谓词: 索引) - 子字符串SubString
swift有子字符串subString是用的String的内存空间,有益于提高性能优化。
集合类型
Array、Sets、Dictionary
1. Array和java差不多
2. Sets(单一、无序)
sets多了如下逻辑操作:
3. 和map差不多,没什么特殊的api
可以分别对keys和values进行操作
let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]
houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isStrictSuperset(of: houseAnimals)
// true 严格判断是否为父集合并且并不相等
farmAnimals.isDisjoint(with: cityAnimals)
// true
控制流
- 一个和控制流无关的tips:
swift中和java不同的是,在print()中可以用\(变量名)来引用对象,类似于c中的&变量名 - for 和java不同的语法:
let minuteCount = 5
for ticktok in 0…< minuteCount {
print(“should print (ticktok)”)
}
和if一样,不需要在()写控制条件,并且,当minuteCount这个循环条件为int型的时候,要在前面加上0…这样的数字控制 - while和java不同的语法:
java中的do… while变成了
repeat {
statements
} while condition
(其中应该很少用就是了) - switch:
· 不需要写break了!swift中匹配到之后会自动终止;如果使用了break,会跳出switch的代码块以执行代码之后的第一行代码.
· 匹配成功后,如果不希望终止,想要进行额外的判断和处理,使用贯穿(fallthrough):
let integerToDescribe = 5
var description = "The number \(integerToDescribe) is"
switch integerToDescribe {
case2, 3, 5, 7, 11, 13, 17, 19:
description += " a prime number, and also"
fallthrough
default:
description += " an integer."
}
print(description)
// 输出“The number 5 is a prime number, and also an integer.”
同时,条件中可以使用元组来进行条件判断,例如:
下划线(_)可以匹配所有的值
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("\(somePoint) is at the origin")
case (_, 0):
print("\(somePoint) is on the x-axis")
case (0, _):
print("\(somePoint) is on the y-axis")
case (-2...2, -2...2):
print("\(somePoint) is inside the box")
default:
print("\(somePoint) is outside of the box")
}
· 可以使用临时变量(变量将作为占位符,会被自动赋值)
· 可以使用where来追加判断条件
· 可以将多个case合并以使用复合型case
5. 上一个笔记中提到了元组,这里记一下相关概念:
元组是在圆括号()中,用逗号隔开的一组值,其中,值可以带有名称。
• 一旦声明,可以改值,但不能添加/删除元素
• 不能更改已经声明的元素名称
• 已声明的名称可以省略,但未声明名称的元组,不可添加名称:
参考:
https://juejin.cn/post/6844903936630112269
- 条件控制可以带标签(可以理解为给一次条件控制命名)
- guard
必须有else,用于进行 “是-否”场景的条件控制 - 检测API可用性
if#available(iOS 10, macOS 10.12, *) {
// 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
} else{
// 使用先前版本的 iOS 和 macOS 的 API
}
理解了这段代码的作用。知但是不道具体怎么使用,avaliable里面做了哪些判断?判断的api是写在哪里?谷歌上面查是可以使用注解的:
https://www.hackingwithswift.com/new-syntax-swift-2-availability-checking
可以在类或者方法上面加**@available**
枚举
- 定义还是使用let或者var
- 可以存储不同类型的关联值
enum Barcode {
case upc (Int, Int, Int, Int)
case qrCode (String)
}
- 原始值-即是在定义枚举的同时就指定了类型,该类型就被称为原始值
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
若原始值为int型,在不指定的情况下,每个枚举成员的原始值会递增
结构体和类
c++中也有结构体和类的区别:
1.类析构时能释放所有资源
2. 类有继承
3. 传递方式不同,结构体是值传递,类的传递是引用传递,也就是说:
结构体 a = 结构体 b, 实际上是复制了b再传给a,这保证了安全性。所以对a的改变不会影响b。
但是类 a = b, 实际上是把b的引用传递给a,对a的改变就是对引用的改变,同时因为b指向了同一个引用,所以b的值也会改变。
函数
- 函数的定义
swift中的函数定义比较独特,用func来表示表示要定义函数,传参中要生命名称和类型,后面跟**->指定返回类型**(如果没有饭回来行则可以不指定)。在调用函数的时候要在声明中指定传参的名字。(更正一下,这里传参的正确叫法应该是 参数标签,没有指定标签,所以默认就是参数名)
func getSum(para1: Int, para2: Int) -> Int {
return para1 + para2
}
//return 11
print(getSum(para1:5, para2:6))
- 返回类型可以是元组
可以将元组作为返回值,元组的成员在函数的返回类型中命名,想查看对应的元组成员直接像返回对象一样用.进行访问就可以了。
func minMax(array: [Int]) -> (min: Int, max: Int) {
varcurrentMin = array[0]
varcurrentMax = array[0]
forvalue inarray[1..<array.count] {
ifvalue < currentMin {
currentMin = value
} elseifvalue > currentMax {
currentMax = value
}
}
return(currentMin, currentMax)
}
let result= minMax(array: [5,6,7])
print(result.max, result.min)
- 返回元组可选类型
下面的代码在返回类型后面多了一个?符号,代表饭回来的类型可能为nil,也可能不为nil,可以理解成java8的Optional包装类。
func minMax(array: [Int]) -> (min: Int, max: Int)? {
varcurrentMin = array[0]
varcurrentMax = array[0]
forvalue inarray[1..<array.count] {
ifvalue < currentMin {
currentMin = value
} elseifvalue > currentMax {
currentMax = value
}
}
return(currentMin, currentMax)
}
这时候即便传入一个空对象也不会报错:
let result2= minMax(array:)
let result= minMax(array: [5,6,7])
print(result!.max)
- Value of optional type ‘(min: Int, max: Int)?’ must be unwrapped to refer to member ’
在用了3的代码后,前面的代码出现了这条错误,意思是option type的变量必须要解包以后才能指向成员。这是因为不熟悉swift的?和!导致的。
在swift中,有?和!两个符号,其中?符号代表把变量包装成optional,!则代表把包装类强制解包(如果内部值为nil这时候会报错 - 空指针异常),在使用option元组的成员时必须先解包。用上面的代码为例,还有一种方法可以避免报错,就是做一次非空判断,如下:
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
print("min is \(bounds.min) and max is \(bounds.max)")
}
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
print("min is \(bounds.min)and max is \(bounds.max)")
}
// 打印“min is -6 and max is 109”
-
函数指定了返回类型后,可以隐式返回。(类似于lambda表达式,如果不写{}就不需要写return)
-
传参的艺术
· 可以在传参前加一个传参标签(argument label),可以不加,也可以用 _ 来明确不加
· 可以在传参数时给默认值,如果传参有值会覆盖掉默认值(c++也可以,java不清楚)
· 传递可变参数(和js一样):
func arithmeticMean(_numbers: Double...) -> Double{
vartotal: Double= 0
fornumber innumbers {
total += number
}
returntotal / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// 返回 3.0, 是这 5 个数的平均数。
arithmeticMean(3, 8.25, 18.75)
// 返回 10.0, 是这 3 个数的平均数。
· 希望对传参进行修改时(swift默认传参出参是个常量):
和c++又有点类似了,在传递的时候要加&符号,并且要在希望改变的参数前加上inout关键字
func swapTwoInts(_a: inout Int, _b: inout Int) {
lettemporaryA = a
a = b
b = temporaryA
}
var someInt3 = 3
var anotherInt107 = 107
swapTwoInts(&someInt3, &anotherInt107)
print("someInt3 is now \(someInt3), and anotherInt107 is now \(anotherInt107)")
// 打印“someInt is now 107, and anotherInt is now 3”
如下代码就会出错,**因为没有加inout关键字,而出参默认是个常量**:
funcarithmeticMean(_numbers: Double...) -> Double{
vartotal: Double= 0
fornumber innumbers {
total += number
}
returntotal / Double(numbers.count)
}
testConstant = 2
//Cannot assign to value: 'testConstant' is a 'let' constant
- 函数类型(java8的函数式接口?)
个人理解:函数类型和其他基本类型一样,都是一个类型,可以当作变量来赋值,类似java的函数式接口思想:把函数表达式当作一个传参。当函数类型入参和出参一样的时候,可以用参数来表示函数。如下:
func addTwoInts(a:Int, b:Int) -> Int{
a+b
}
func printHelloWorld() {
print("hello world")
}
var myFunctional:(Int,Int) -> Int= addTwoInts(a:b:)
myFunctional(5,6)
var myFunctional2:()-> Void= printHelloWorld
myFunctional2()
functional1表示有入参和出参的情况,functional2表示没有出参的情况下的函数类型参数。
同时,函数类型也可以被当作函数的传参。
8. 函数类型作为返回值
funcstepForward(_input: Int,_input2: Int) -> Int{
returninput + input2 - 1
}
funcstepBackward(_input: Int, _input2: Int) -> Int{
returninput + input2 - 1
}
func chooseStepFunction(backward: Bool) -> (Int, Int) -> Int {
returnbackward ? stepBackward : stepForward
}
var currentValue1 = 3
var currentValue2 = 3
let moveToZero = chooseStepFunction(backward: currentValue1 > 0)
let testResultTmp = moveToZero(currentValue1, currentValue2)
**//输出5,这时moveToZero就是一个函数类型表达式**
闭包表达式
即匿名函数
- 一般形式
{(parameters) -> return type in
statements
}
由关键字in引入
var testArrays: [Int] = [1,2,3,4,5]
var sortedTestArrays= testArrays.sorted(by: { (item1: Int, item2:Int) -> Boolin
returnitem1 > item2
})
print(sortedTestArrays)
因为有类型推断,所以表达式可以简化成:
var sortedTestArrays2= testArrays.sorted(by: {item1, item2 initem1 > item2})
内联闭包内部提供了参数名称缩写,所以表达式可以更近一步简化为,$0和$1都可以理解为第一个和第二个参数的引用占位符:
var sortedTestArrays3= testArrays.sorted(by: {$0 > $1})
swift中有内置的实现,甚至可以仅用一个>符号:
var sortedTestArrays4 = testArrays.sorted(by: >)
- 尾随闭包
当需要将一个很长的闭包表达式作为最后一个参数传递给函数的时候,将这个闭包替换为尾随闭包(不需要写参数标签,直接打{ })
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
下面是一个实例(map和stream的map功能一样,话说为什么要用number=number - 使用值传递,就可以对局部变量number进行修改而不影响原本的number,我觉得最好还是换个名字免得引起歧义):
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
}
//output: "OneSix" "FiveEight" "FiveOneZero"
- 值捕获
闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在。(没太明白,回头再多看看)
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int{
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen= makeIncrementer(forIncrement: 10)
let testResult1 = incrementByTen()
print(testResult1)
-
闭包是引用类型
上面的例子中,incrementByTen本身是常量没错,但是这个常量被设置称为了闭包的引用。(C++中有一个也是这个概念,忘记是什么了) -
逃逸闭包
即->不在函数作用域中执行的闭包 -
自动闭包(联想Supplier接口->没有参数但有返回值)
不接受任何参数,返回被包装在其中的表达式的值,并且只有当闭包被调用时,里面的表达式才会执行,如果表达式不调用,闭包里面的表达式将永远不会执行。
如下例子:
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出“Now serving Alex!”
serve中的(customer为参数标签,customerProvider为参数名,接受一个函数类型的闭包)
闭包的定义是放在了使用func serve的时候,定义了customer这个闭包.
// customersInLine i= ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_customerProvider: @autoclosure@escaping() -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
//这里如果是一个闭包表达式的话,301行的声明是做什么呢?-> 301行是声明了一个空的函数类型的数组!
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")
// 打印“Collected 2 closures.”
forcustomerProvider incustomerProviders{
print("Now serving \(customerProvider())!")
}
// 打印“Now serving Barry!”
// 打印“Now serving Daniella!”
这是一个逃逸自动闭包,customerProviders是一个空函数类型数组,coolectCustomerProviders接受一个自动闭包作为函数类型。每次调用customerProviders都把闭包表达式存在了customerProviders这个数组中。
自动闭包和逃逸闭包更像是一种思想,而不是一个单独的概念
属性
- 延时加载存储属性
延时加载属性是等实例构造完成之后需要计算的时候再初始化,且必须是变量。(因为常量属性在构造完成之前必须要有初始值),关键字为lazy,加在属性名称之前.
class DataImporter {
/*
DataImporter 是一个负责将外部文件中的数据导入的类。
这个类的初始化会消耗不少时间。
*/
var fileName = "data.txt"
// 这里会提供数据导入功能
}
class DataManager {
lazyvarimporter= DataImporter()
vardata: [String] = []
// 这里会提供数据管理功能
}
let manager= DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 实例的 importer 属性还没有被创建
懒加载在多线程情况下无法保证只创建一次。
2. 计算属性
计算属性是那种不直接存储值的属性,通过getter和setter来间接设置属性的值。
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{
get{
let centerX = origin.x + (size.width/ 2)
let centerY = origin.y + (size.height/ 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x= newCenter.x - (size.width/ 2)
origin.y= newCenter.y - (size.height/ 2)
}
}
}
varsquare= Rect(origin: Point(x: 0.0, y:0.0),
size: Size(width: 10.0, height: 10.0))
print("before----1 \(square.origin.x), \(square.origin.y)")
let initialSquareCenter = square.center //这里用了getter
print(initialSquareCenter.x, initialSquareCenter.y)
print("before----2 \(square.origin.x), \(square.origin.y)")
square.center= Point(x: 15.0, y: 15.0) //这里用了setter
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// 打印“square.origin is now at (10.0, 10.0)”
center就是一个计算属性
可以简化getter和setter,简化setter默认不指定入参名为new value;简化getter默认返回语句,和函数的隐式返回类似.
struct CompactRect {
var origin= Point()
var size= Size()
var center: Point{
get{
Point(x: origin.x + (size.width/ 2),
y: origin.y + (size.height/ 2))
}
set{
origin.x= newValue.x - (size.width/ 2)
origin.y= newValue.y - (size.height/ 2)
}
}
}
- 属性观察器
每次在属性发生变化的时候,观察器就会发生响应。作用于以下3种属性:
· 存储类型
· 继承的存储类型
· 继承的计算类型
有willSet(默认newValue)和didSet(默认oldValue)两种观察器,oldValue默认使用一千的值作为输入。
class StepCounter {
var totalSteps: Int= 0{
willSet(newTotalSteps) {
print("将 totalSteps 的值设置为 \(newTotalSteps)")
}
didSet{
iftotalSteps > oldValue {
print("增加了 \(totalSteps - oldValue) 步")
}
}
}
}
let stepCounter= StepCounter()
stepCounter.totalSteps = 200
// 将 totalSteps 的值设置为 200
// 增加了 200 步
stepCounter.totalSteps = 360
// 将 totalSteps 的值设置为 360
// 增加了 160 步
stepCounter.totalSteps = 896
// 将 totalSteps 的值设置为 896
// 增加了 536 步
在这个例子中totalSteps就被设置了属性观察器,并且两种都设置了,所以每次发生变化的时候,会触发两种观察器。
4. 属性包装器
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get{ returnnumber}
set{ number= min(newValue, 12) }
}
}
struct SmallRectangle {
@TwelveOrLess varheight: Int
@TwelveOrLess varwidth: Int
}
varrectangle= SmallRectangle()
print(rectangle.height)
// 打印 "0"
rectangle.height = 10
print(rectangle.height)
// 打印 "10"
rectangle.height = 24
print(rectangle.height)
// 打印 "12"
属性包装器需要定义包装器wrappedValue,这里number的值不能小于12,同时在smallrectangle内对height以及width属性之前通过注解使用该包装器使它生效.
· 设置属性包装器的初始值:
@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get{ returnnumber}
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)
}
}
重载了三个init(),包装属性的初始值根据传参来调用哪个才是需要使用的初始化构造器。
- 类型属性
switft中的第三种属性,用static修饰,类型属性是通过类型碑身访问,而不是实例。
方法
- selft就是c++和java的this
- 修改枚举和结构体的属性
在属性前面加mutating关键字,这样就可变了(依稀记得c++中有可变常量mutable?关键字,怎么swift里面很多东西都要重新命名。。) - 方法种类
实例方法:一般的方法
类型方法:static修饰
下标
怎么swift的下标这么麻烦的。。。除了索引功能,swift还能够自定义只读下标,如下所示:
- subscript关键字:
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int{
returnmultiplier* index
}
}
letthreeTimesTable= TimesTable(multiplier: 3)
print(“six times three is (threeTimesTable[6])”)
// 打印“six times three is 18”
- 类型下表->针对类型的
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
staticsubscript(n: Int) -> Planet {
returnPlanet(rawValue: n)!
}
}
let mars= Planet[4]
print(mars)
继承
和java的不同:
- 继承的语法不同,不需要关键字,直接在类后面跟冒号+父类名称即可
例如: class Bicycle : Vehicle {
xxxx : xxx
} - 重写必须在func前面加override关键字
- 属性观察器也是可以重写的
加上final可以防止被重写
构造器
-
swift的构造器名称叫做init()
-
指定构造器 和 便利构造器
便利构造器:
convenience init(para) {
statement
}
• 指定构造器必须总是向上代理
• 便利构造器必须总是横向代理
-
安全检查
swift的构造分为两个阶段:第一阶段会为所有的存储属性赋初始值;第二阶段在实例化之前自定义存储型属性
1)指定构造器必须保证所在类的所有存储属性必须先初始化完成,才能将构造任务交给父亲的构造器
2)指定构造器必须在为继承的属性设置值之前先去向上代理父类构造器,否则就会先设置值,在调用父类构造器的时候之前的值就被覆盖了
3)便利构造器赋值之前会代理调用其他构造器,否则它赋值会被覆盖
4)在第一阶段构造完成之前,不能调用任何实例方法,第一阶段完成后,类的实例才有效,才能访问属性和方法。 -
可失败构造器init?()
不能与其他非可失败的构造器具有同样的参数名和参数类型
有啥用啊?构造失败返回一个nil,本来构造失败也会报错啊? -
必要构造器
构造器前面加上required关键字,这种构造器要求所有子类都必须实现 -
通过闭包或者函数设置属性的默认值
class SomeClass {
let someProperty: SomeType = {
// 在这个闭包中给 someProperty 创建一个默认值
// someValue 必须和 SomeType 类型相同
return someValue
}()
}
必须要加小括号,加上小括号就代表了将返回值进行赋值,否则就是赋值了一个闭包表达式;
除此之外,闭包的构造其他的属性还没有初始化完成,所以不能用其他属性,包括self属性。
析构
使用关键字deinit定义析构行为 {
statement
}
可选链式调用
- 可选链式调用是一种可以在当前值可能为 nil 的可选值上请求和调用属性、方法及下标的方法。
- 多层可选链式调用
如果访问的值是不可选的,可选链式调用会返回
如果访问的值是不可选的,可选链式调用不会使返回值变得更可选(不会出现java8的Optional<Optional>的情况)
错误处理
- swift的错误处理遵循Error协议的类型值来表示
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
//创建枚举来提供错误信息
2. 4种错误处理的方式
· 错误传递给调用此函数的代码
在函数名称括号后()添加throws关键字。(在返回类型的箭头之前),表明该函数可以抛出错误,只有throwing函数能抛出错误,其他函数只能在内部对错误进行处理。
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
let snackName = favoriteSnacks[person] ?? "Candy Bar"
try vendingMachine.vend(itemNamed: snackName)
}
这个例子中的??代表啥?
构造器也是函数,所以它也能用throws关键字
· do-catch语句处理错误
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-catch的所有场景,没啥特别的,看例子即可。和java的语法比同样是没有括号
· 错误作为可选类型处理
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { returndata }
if let data = try? fetchDataFromServer() { returndata }
return nil
}
在try后面加?来表示将错误作为可选类型处理,如果后面的函数抛出了异常,那么就会返回nil。
· 断言错误不会发生
在确定不会抛出错误的业务场景下,在调用throwing函数时用try!来禁用错误传递(这种情况下如果抛出了错误,会得到运行时错误)
- 指定清理操作
defer{
statement
}
defer会在即将离开代码块时执行其内部语句,有点finally的味道,但是defer不能够使用break或者return,或者抛出异常。可以用来进行一些内存释放,文件描述符关闭的操作。
其次,定义了多个defer,执行顺序是从后往前,第一个定义的defer语句会最后一个执行。
并发:异步和并行
1. 异步和并行->两个概念一起被叫做并发
异步:同一时间只能有一段代码被执行,代码可以被挂起和继续执行。代码在执行耗时较长的任务中抽空进行一些快速操作。
并行:多段代码同时执行。(比如4核cpu就能够同时运行4段代码)
2. 异步的调用和定义
异步定义在方法括号后,关键字为async, 使用await标记悬点,程序执行到await标记的代码时会让出资源挂起代码,给其他并行代码执行的机会,直到函数返回。
func listPhotos(inGalleryname: String) async-> [String] {
await Task.sleep(2 * 1_000_000_000) // 两秒
return ["IMG001", "IMG99", "IMG0404"]
}
上面的例子中sleep函数是和js中的sleep一样就是什么都不做xx秒,模拟网络请求。
3. 异步序列
for await in,在循环时每次都可能会因为等待下一个元素而挂起当前的代码执行,这样会在每次收到一个元素后对其进行处理,而不是一次接收所有元素。
4. 关于并行的调用异步方法的tips:
• 代码中接下来的几行需要依赖异步函数的结果时,需要使用 await 来调用异步函数。这样产生的结果是有序的。
• 短时间内并不需要异步函数的结果时,需要使用 async-let 来调用异步函数。这样产生的任务是并发的。
• await 和 async-let 都允许其他任务在他们被挂起的时候执行。
• 在两种情况下,都需要用 await 标记可能的悬点,以表明代码在这些点在需要的情况下会被挂起,直到异步函数执行结束
5. 任务和任务组,结构和非结构化并发
这一部分感觉,只有在上手之后才能理解。
6. actors
actor是一个引用类型,和类的作用相似。它能够保证同一时间只有一个任务能够访问它的状态。在访问actor对象的属性时,要加await关键字,因为同一时刻可能有其他的任务在访问actor对象,所以要标记悬点以保证可能的等待。
类型转换
类型转换可以判断实例的类型。操作符 is 和 as
1. 在继承结构中,swift会自动进行类型转换。比如:基类A、子类B和C。可以用数组来同时存储字类B和C,因为B和C来自于同一个父类。虽然存储的类型确实为B和C,但是迭代取出的实例会是基类A类型。
2. 类型检查
用is关键字, <对象> is 进行类型检查,返回bool类型的值
3. 向下转型
向下转型就是父类->子类。用as关键字来进行。比如1中的例子,在数组中取出来的实例都是基类A类型,这时候迭代时用as?来判断是否为子类B或者子类C,**如果是就会新开一个临时变量并做返回。**用一个变量接受后,这时候可以访问子类特有的属性和方法了。
for item inlibrary {
if let movie = item as? Movie{
print("Movie: \(movie.name), dir. \(movie.director)")
} else if letsong = item as? Song{
print("Song: \(song.name), by \(song.artist)")
}
}
// Movie: Casablanca, dir. Michael Curtiz
// Song: Blue Suede Shoes, by Elvis Presley
// Movie: Citizen Kane, dir. Orson Welles
// Song: The One And Only, by Chesney Hawkes
// Song: Never Gonna Give You Up, by Rick Astley
4. Any和AnyObject
swift中可以用Any表示任何类型(包括函数类型),可以用AnyObject表示任何实例
扩展
扩展是给一个现有的类、结构体、枚举或者协议添加新的功能。不能重写
• 添加计算型实例属性和计算型类属性
• 定义实例方法和类方法
• 提供新的构造器
• 定义下标
• 定义和使用新的嵌套类型
• 使已经存在的类型遵循(conform)一个协议
关键字 extension 后接类型{
statement
}
协议 协议规定了用来实现特定任务或者功能的方法、属性、以及其他需要的东西。类、结构体和枚举都能够遵循协议,根据协议定义的要求提供具体实现。
1. 关键字 protocol
下面是一个定义协议,并且让类遵循协议的例子。类或者结构体遵循协议也是使用:符号
protocol someProtocol {
//statement
}
class someClass: someProtocol {
//statement
}
2. 协议-属性要求
协议能够指定的是属性的名称、类型、可读与可写(可读可写定义在{get 代表可读 set代表可写 }):
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
3. 协议-构造器
协议中可以声明构造器的入参和类型,但是不写实现。类在遵循协议实现构造器的时候必须要在构造器前面写上required。
4. 协议可以作为一种类型
• 作为函数、方法或构造器中的参数类型或返回值类型
• 作为常量、变量或属性的类型
• 作为数组、字典或其他容器中的元素类型
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init (sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int{
return Int(generator.random() * Double(sides)) + 1
}
}
这里的generator就遵循了RandomNumberGenerator这个协议。在创建Dice实例时,要传入遵循了RandomNumberGenerator协议的实例。
5. 委托
委托是一种设计模式,允许类或者结构体讲一些需要它们负责的功能委托给其他类型的实例。委托的实现:定义协议来封装需要被委托的功能,确保遵循协议的类型能提供这些功能。
6. 扩展类使其遵从协议
举例:
extension SnakesAndLadders: TextRepresentable {
var textualDescription: String {
return "A game of Snakes and Ladders with \(finalSquare)squares"
}
}
print(game.textualDescription)
// 打印 “A game of Snakes and Ladders with 25 squares”
7. 有条件地遵循协议
关键字where 写在协议后面
8. 当某个类已经实现了协议中的所有要求,没有声明协议,可以写一个空扩展使其采纳协议。
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
写一个空扩展:
extension Hamster: TextRepresentable {}
let simonTheHamster= Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// 打印 “A hamster named Simon”
9. 合成实现(Synthesized Implementation)
合成实现是指,swift对一些简单的内置协议提供了自动的功能实现,在遵循这些协议的时候,无需手动地去实现协议的功能。这样的协议有:Equatable、Hashable、Comparable。
- 以Equatable为例:可以自动获得== 、!=的功能实现
· 遵循 Equatable 协议且只有存储属性的结构体。
· 遵循 Equatable协议且只有关联类型的枚举
· 没有任何关联类型的枚举 - Hashable:可以自动获得hash(into:)的功能实现
· 遵循 Hashable 协议且只有存储属性的结构体。
· 遵循 Hashable 协议且只有关联类型的枚举
· 没有任何关联类型的枚举 - Comparable: 同时包含<、<=、 >、 >=的功能实现
· 没有原始值的枚举类型
enum SkillLevel: Comparable {
case beginner
case intermediate
case expert(stars: Int)
}
var levels= [SkillLevel.intermediate, SkillLevel.beginner,
SkillLevel.expert(stars: 5), SkillLevel.expert(stars: 3)]
for level in levels.sorted() {
print(level)
}
// 打印 "beginner"
// 打印 "intermediate"
// 打印 "expert(stars: 3)"
// 打印 "expert(stars: 5)"
10. 协议类型的集合
协议类型可以在数组和集合中使用,和存储基类的数组类似,只要遵循了同一协议,就可以放在同一个数组中。只不过,实例本身是实现类的类型,取出来的时时候仍然是协议类型。
-
协议的继承
协议也是可以继承的,和类的继承声明一样。协议在继承后也要遵循父类的协议。 -
类类型专属协议
就是创建只能够被类类型采纳的协议,需要在协议的名字后加上: AnyObject,这样,枚举和结构体就不能遵循该协议了。 -
合成协议
同时遵循多个协议时使用&链接 -
检查协议一致性
关键字和类型转换中的检查类型一样:is、as?、as!
• is 用来检查实例是否遵循某个协议,若遵循则返回 true,否则返回 false;
• as? 返回一个可选值,当实例遵循某个协议时,返回类型为协议类型的可选值,否则返回 nil;
• as! 将实例强制向下转换到某个协议类型,如果强转失败,将触发运行时错误。 -
协议的扩展
协议的扩展允许使用extension关键字对已有的协议进行扩展补充,需要注意的是,在扩展协议的时候,需要写上扩展协议的实现。同时,遵循了该协议的实现类就会自动地获得这个扩展以后的内容。通过扩展提供默认实现:
除此之外,可以通过扩展来为协议提供默认的实现。(如果实现类也写了实现,以实现类的为准)通过扩展添加限制条件
在协议名后面添加where关键字,只有满足了where后的条件的实现类才能够获得扩展协议。感觉协议和接口很像,只不过协议可以通过扩展自己给自己写默认的实现。
范围
关于范型,swift的学习手册写的:Generics are one of the most powerful features of Swift, and much of the Swift standard library is built with generic code.
1. 类型约束
类型约束指的是对范型类型做一些限制。范型函数或者范型需要遵循指定的类或者协议,这在一些场景下十分有用。语法为:
func someFunction <T: SomeClass, U: SomeProtocol> (someT: T, someU: U) {
// 这里是泛型函数的函数体部分
}
T需要继承自SomeClass,U需要遵循SomeProtocol协议。
2. 关联类型
关键字associatedtype,swift会去启动通过associatetype去实现类中自动推断对应的属性类型。
关联类型也能够添加约束、指定协议。
3. 范型where语句
范型where语句能够提供很多操作!属于核心知识点。
通过范型where子句能够让关联类型服从特定的要求。
func allItemsMatch <C1: Container, C2: Container>
(_someContainer: C1, _anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable{
// 检查两个容器含有相同数量的元素
if someContainer.count!= anotherContainer.count{
returnfalse
}
// 检查每一对元素是否相等
for i in 0.. <someContainer.count{
if someContainer[i] != anotherContainer[i] {
returnfalse
}
}
// 所有元素都匹配,返回 true
return true
}
上面的allItemsMatch范型函数中,通过where指定了两个参数的要求:
- C1 必须符合 Container 协议
- C2 必须符合 Container 协议
- C1 的 Item类型 和 C2 的 Item类型 必须相等
- C1 必须遵循 Equatable 协议
4. 下标也可以是范型,也可以用where来加上要求
不透明类型
具有不透明返回类型的函数或方法会隐藏返回值的类型信息。函数不再提供具体的类型作为返回类型,而是根据它支持的协议来描述返回值。
- 返回不透明类型不意味着就能返回多个类型,当返回多个类型是会报错;虽然协议能够返回多种类型,但是不能做具体的操作(比如判断==,因为会发生类型擦除,永远也不能用==来判断返回的两个协议类型是否相等。)
当我们希望底层返回唯一类型的时候,就使用不透明类型。关键字是在返回类型上面加some,见下方的makeTrapezoid例子。
protocol Shape {
funcdraw() -> String
}
struct Triangle: Shape {
var size: Int
func draw() -> String{
var result: [String] = []
for length in1...size {
result.append(String(repeating: "*", count: length))
}
return result.joined(separator: "\n")
}
}
let smallTriangle= Triangle(size: 3)
print(smallTriangle.draw())
//翻转图形
struct FlippedShape<T: Shape>: Shape {
var shape: T
func draw() -> String{
let lines = shape.draw().split(separator: "\n")
return lines.reversed().joined(separator: "\n")
}
}
let flippedTriangle= FlippedShape(shape: smallTriangle)
print(flippedTriangle.draw())
//拼接图形
struct JoinedShape<T: Shape, U: Shape>: Shape {
var top: T
var bottom: U
func draw() -> String{
return top.draw() + "\n"+ bottom.draw()
}
}
let joinedTriangles= JoinedShape(top: smallTriangle, bottom: flippedTriangle)
print(joinedTriangles.draw())
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")
}
}
//返回不透明类型
func makeTrapezoid() -> some Shape {
let top = Triangle(size: 2)
let middle = Square(size: 2)
let bottom = FlippedShape(shape: top)
let trapezoid = JoinedShape(
top: top,
bottom: JoinedShape(top: middle, bottom: bottom)
)
return trapezoid
}
let trapezoid= makeTrapezoid()
print(trapezoid.draw())
自动引用计数的工作机制(ARC)
- 当对象不再被任何变量使用的时候就会回收这个类的内存空间。
2. 类实例之间的循环强引用
当两个实例,互相持有指向对方类型的属性时,这时候就形成了类实例之间的循环强引用,如下:
//类实例的循环强引用
class Citizen {
let name: String
init (name: String) { self.name= name }
var apartment: Apartment?
deinit { print("\(name)is being deinitialized") }
}
class Apartment {
let unit: String
init (unit: String) { self.unit= unit }
var tenant: Citizen?
deinit { print ("Apartment \(unit)is being deinitialized") }
}
var malco: Citizen?
var unit4A: Apartment?
malco = Citizen(name: "malco")
unit4A = Apartment(unit: "unit4A")
malco!.apartment = unit4A
unit4A!.tenant = malco
创建了malco和单元楼unit4A时,相互之间持有指向对方对象的引用,这个时候他强引用关系如下图:
即便这个时候把malco和unit4A置为nil,引用关系也没有结束,如下图所示:
由于类的实例仍然被使用,析构器不会调用,类所占用的资源就不会释放,这个时候就造成了内存泄漏。
- 使用弱引用解决循环强引用问题
在Apartment中的tenant属性前加上关键字weak,这个时候tenant指向Person实例就变成了弱引用。
class Person {
letname: String
init(name: String) { self.name= name }
varapartment: Apartment?
deinit { print("\(name)is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit= unit }
weakvar tenant: Person?
deinit { print("Apartment \(unit)is being deinitialized") }
}
这个时候再释放malco和unit4A过程就变成了这样:
-
使用无主引用解决循环强引用的问题
无主引用使用unowned来声明。和弱引用不同的地方在:无主引用期望对象一定是有一个实例的(不为nil),当指向对象为nil时,会报错->所以将值标记为无主引用不会将它变成可选类型,ARC也不会将无主引用的值设置为nil。 -
无主可选引用
4里面说了标记为无主引用不会将它变为可选类型。所以,当使用无主可选引用时,需要保证引用的对象必须是合法的,或者是nil。下面是一个无主可选引用的例子:
class Department {
var name: String
var courses: [Course]
init(name: String) {
self.name= name
self.courses = []
}
}
class Course {
var name: String
unownedvar department: Department
unownedvar nextCourse: Course?
init(name: String, indepartment: Department) {
self.name= name
self.department = department
self.nextCourse = nil
}
}
let department = Department(name: "Horticulture")
let intro = Course(name: "Batman Begins", in: department)
let intermediate = Course(name: "Batman Dark Kight", in: department)
let advanced = Course(name: "Batman Dark Kight Rise", in: department)
intro.nextCourse = intermediate
intermediate.nextCourse = advanced
department.courses = [intro, intermediate, advanced]
Course的nextcourse为无主可选引用,这种情况下:需要保证确保 nextCourse 总是引用一个还没被析构的课程。假如需要从 department.courses 里删除一个课程,同时也需要在其他可能引用它的课程里移除它
6. 隐式解包可选
和隐式解包可选值类似,在变量类型后面加上!
7. 闭包的循环强引用
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String= {
if let text = self.text{
return"<\(self.name)>\(text)</\(self.name)>"
} else{
return"<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name= name
self.text= text
}
deinit{
print("\(name)is being deinitialized")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// 打印“<p>hello, world</p>”
这时就产生了一个闭包和实例的循环强引用问题:
实例的 asHTML 属性持有闭包的强引用。但是,闭包在其闭包体内使用了 self(引用了 self.name 和 self.text),因此闭包捕获了 self,这意味着闭包又反过来持有了 HTMLElement 实例的强引用。这样两个对象就产生了循环强引用。
8. 解决闭包循环强引用:
1)定义捕获列表 - 这种有点麻烦,需要用的时候再回来看吧
2)使用弱引用或者无主引用
当不会出现nil的时候在闭包内声明self为无主引用,否则声明为弱引用:
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String= {
[unowned self] in
if let text = self.text{
return"<\(self.name)>\(text)</\(self.name)>"
} else{
return"<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name= name
self.text= text
}
deinit{
print("\(name)is being deinitialized")
}
}
这时的引用关系如图所示:
内存访问冲突
1. in-out参数访问冲突
in-out为长期写,一般的内存冲突的发生都和写有关,当两个不同操作同时访问同一个地址的时候就会访问冲突
2. 方法里的self访问冲突
一个平分玩家血量的功能:
struct Player {
var name: String
var health: Int
var energy: Int
staticlet maxHealth= 10
mutating func restoreHealth() {
health= Player.maxHealth
}
}
func balance(_x: inoutInt, _y: inoutInt) {
let sum = x + y
x = sum / 2
y = sum - x
}
extension Player {
mutating func shareHealth(withteammate: inoutPlayer) {
balance(&teammate.health, &health)
}
}
var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria) // 正常
这种情况下不会出现访问冲突:
但是如果传入oscar对象,就会出错:
3. 属性的访问冲突:
遵循如下访问原则:
• 你访问的是实例的存储属性,而不是计算属性或类的属性
• 结构体是本地变量的值,而非全局变量
• 结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获了
访问控制
1. swift有5种权限:
open、public、internal(默认)、fileprivate、private
- open 和 public 级别可以让实体被同一模块源文件中的所有实体访问,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,你会使用 open 或 public 级别来指定框架的外部接口。open 和 public 的区别在后面会提到。
- internal 级别让实体被同一模块源文件中的任何实体访问,但是不能被模块外的实体访问。通常情况下,如果某个接口只在应用程序或框架内部使用,就可以将其设置为 internal 级别。
- fileprivate 限制实体只能在其定义的文件内部访问。如果功能的部分实现细节只需要在文件内使用时,可以使用 fileprivate 来将其隐藏。
- private 限制实体只能在其定义的作用域,以及同一文件内的 extension 访问。如果功能的部分细节只需要在当前作用域内使用时,可以使用 private 来将其隐藏。
open 为最高访问级别(限制最少),private 为最低访问级别(限制最多)。
2**. 模块和源文件**
模块:独立的代码单元,一个模块就是用import导入的单位。(暂时理解成一个module)
源文件:模块内的一个swift源代码文件。(暂时理解成一个file)
-
元组的访问级别由内部元素中访问级别最严格的元素决定
-
函数的访问级别根据最严格的参数类型或者返回类型的访问级别决定(如果访问级别不符合函数定义所在环境的默认访问级别,需要显式指出函数的访问级别)
-
子类
子类的访问级别不能高于父类 -
getter setter
和参数的访问级别相同 -
类型和协议
类型可以遵循访问级别比自己低的协议,但是类型对协议的所有实现必须保证至少<=协议的访问等级。