函数相关
可变形式参数
一个可变形式参数可以接受零或者多个特定类型的值。当调用函数的时候你可以利用可变形式参数来声明形式参数可以被传入值的数量是可变的。可以通过在形式参数的类型名称后边插入三个点符号( ...)来书写可变形式参数。
传入到可变参数中的值在函数的主体中被当作是对应类型的数组。举个栗子,一个可变参数的名字是 numbers类型是 Double...在函数的主体中它会被当作名字是 numbers 类型是 [Double]的常量数组。
下面的栗子计算了一组任意长度的数字的算术平均值(也叫做平均数)。
func arithmeticMean(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers
注意
一个函数最多只能有一个可变形式参数。
输入输出形式参数
就像上面描述的,可变形式参数只能在函数的内部做改变。如果你想函数能够修改一个形式参数的值,而且你想这些改变在函数结束之后依然生效,那么就需要将形式参数定义为输入输出形式参数。
在形式参数定义开始的时候在前边添加一个 inout关键字可以定义一个输入输出形式参数。输入输出形式参数有一个能输入给函数的值,函数能对其进行修改,还能输出到函数外边替换原来的值。
你只能把变量作为输入输出形式参数的实际参数。你不能用常量或者字面量作为实际参数,因为常量和字面量不能修改。在将变量作为实际参数传递给输入输出形式参数的时候,直接在它前边添加一个和符号 ( &) 来明确可以被函数修改。
注意
输入输出形式参数不能有默认值,可变形式参数不能标记为 inout,如果你给一个形式参数标记了 inout,那么它们也不能标记 var和 let了。
这里有一个 swapTwoInts(::)函数,它有两个输入输出整数形式参数 a和 b:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
函数 swapTwoInts(::)只是简单的将 b和 a的值进行了调换。函数将 a的值储存在临时常量 temporaryA中,将 b的值赋给 a,然后再将 temporaryA的值赋给 b。
你可以通过两个 Int类型的变量来调用函数 swapTwoInts(::)去调换它们两个的值,需要注意的是 someInt的值和 anotherInt的值在传入函数 swapTwoInts(::)时都添加了和符号。
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// prints "someInt is now 107, and anotherInt is now 3"
上边的栗子显示了 someInt 和 anotherInt的原始值即使是在函数的外部定义的,也可被函数 swapTwoInts(::)修改。
注意
输入输出形式参数与函数的返回值不同。上边的 swapTwoInts没有定义返回类型和返回值,但它仍然能修改 someInt和 anotherInt的值。输入输出形式参数是函数能影响到函数范围外的另一种替代方式。
闭包相关
涉及关键字:尾随闭包,逃逸闭包 @escaping,自动闭包 @autoclosure 闭包是引用类型
https://www.cnswift.org/closures
枚举相关
关联值
有时将其它类型的关联值与这些成员值一起存储是很有用的。这样你就可以将额外的自定义信息和成员值一起储存,并且允许你在代码中使用每次调用这个成员时都能使用它。
你可以定义 Swift 枚举来存储任意给定类型的关联值,如果需要的话不同枚举成员关联值的类型可以不同。
在Moya里面的使用场景
例如:
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
这可以读作:
“定义一个叫做 Barcode的枚举类型,它要么用 (Int, Int, Int, Int)类型的关联值获取 upc 值,要么用 String 类型的关联值获取一个 qrCode的值。”
递归枚举
关键字:indirect
示例:
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
类和结构体
类与结构体的对比
在 Swift 中类和结构体有很多共同之处,它们都能:
- 定义属性用来存储值;
- 定义方法用于提供功能;
- 定义下标脚本用来允许使用下标语法访问值;
- 定义初始化器用于初始化状态;
- 可以被扩展来默认所没有的功能;
- 遵循协议来针对特定类型提供标准功能。
类有而结构体没有的额外功能:
- 继承允许一个类继承另一个类的特征;
- 类型转换允许你在运行检查和解释一个类实例的类型;
- 反初始化器允许一个类实例释放任何其所被分配的资源;
- 引用计数允许不止一个对类实例的引用。
注意
结构体在你的代码中通过复制来传递,并且并不会使用引用计数。
结构体和枚举是值类型
值类型是一种当它被指定到常量或者变量,或者被传递给函数时会被拷贝的类型。
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
println("cinema is now \(cinema.width) pixels wide")
//println "cinema is now 2048 pixels wide"
print("hd is still \(hd.width) pixels wide")
// prints "hd is still 1920 pixels wide"
类是引用类型
类和结构体之间的选择
按照通用准则,当符合以下一条或多条情形时应考虑创建一个结构体:
- 结构体的主要目的是为了封装一些相关的简单数据值;
- 当你在赋予或者传递结构实例时,有理由需要封装的数据值被拷贝而不是引用;
- 任何存储在结构体中的属性是值类型,也将被拷贝而不是被引用;
- 结构体不需要从一个已存在类型继承属性或者行为。
合适的结构体候选者包括:
- 几何形状的大小,可能封装了一个 width属性和 height属性,两者都为 double类型;
- 一定范围的路径,可能封装了一个 start属性和 length属性,两者为 Int类型;
- 三维坐标系的一个点,可能封装了 x , y 和 z属性,他们都是 double类型。
属性
注意
- 如果被标记为 lazy 修饰符的属性同时被多个线程访问并且属性还没有被初始化,则无法保证属性只初始化一次。
- 全局常量和变量永远是延迟计算的,与延迟存储属性有着相同的行为。不同于延迟存储属性,全局常量和变量不需要标记 lazy 修饰符。
方法
在实例方法中修改值类型
关键字:mutating
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// prints "The point is now at (3.0, 4.0)"
//你不能在常量结构体类型里调用异变方法,因为自身属性不能被改变,就算它们是变量属性:
let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// this will report an error
在异变方法里指定自身
异变方法可以指定整个实例给隐含的 self属性。上文中那个 Point的栗子可以用下边的代码代替:
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
初始化
类类型的初始化器委托
为了简化指定和便捷初始化器之间的调用关系,Swift 在初始化器之间的委托调用有下面的三个规则:
指定初始化器必须从它的直系父类调用指定初始化器。
便捷初始化器必须从相同的类里调用另一个初始化器。
便捷初始化器最终必须调用一个指定初始化器。
简单记忆的这些规则的方法如下:
- 指定初始化器必须总是向上委托。
-
便捷初始化器必须总是横向委托。
两段式初始化
Swift 的类初始化是一个两段式过程。在第一个阶段,每一个存储属性被引入类为分配了一个初始值。一旦每个存储属性的初始状态被确定,第二个阶段就开始了,每个类都有机会在新的实例准备使用之前来定制它的存储属性。
两段式初始化过程的使用让初始化更加安全,同时在每个类的层级结构给与了完备的灵活性。两段式初始化过程可以防止属性值在初始化之前被访问,还可以防止属性值被另一个初始化器意外地赋予不同的值。
注意
Swift 的两段式初始化过程与 Objective-C 的初始化类似。主要的不同点是在第一阶段,Objective-C 为每一个属性分配零或空值(例如 0 或 nil )。Swift 的初始化流程更加灵活,它允许你设置自定义的初始值,并可以自如应对 0 或 nil 不为合法值的情况。
Swift编译器执行四种有效的安全检查来确保两段式初始化过程能够顺利完成:
安全检查 1
指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成。
如上所述,一个对象的内存只有在其所有储存型属性确定之后才能完全初始化。为了满足这一规则,指定初始化器必须保证它自己的属性在它上交委托之前先完成初始化。
安全检查 2
指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖。
安全检查 3
便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其它指定初始化器所覆盖。
安全检查 4
初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用 self 作为值。
直到第一阶段结束类实例才完全合法。属性只能被读取,方法也只能被调用,直到第一阶段结束的时候,这个类实例才被看做是合法的。
以下是两段初始化过程,基于上述四种检查的流程:
阶段 1
- 指定或便捷初始化器在类中被调用;
- 为这个类的新实例分配内存。内存还没有被初始化;
- 这个类的指定初始化器确保所有由此类引入的存储属性都有一个值。现在这些存储属性的内存被初始化了;
- 指定初始化器上交父类的初始化器为其存储属性执行相同的任务;
*这个调用父类初始化器的过程将沿着初始化器链一直向上进行,直到到达初始化器链的最顶部; - 一旦达了初始化器链的最顶部,在链顶部的类确保所有的存储属性都有一个值,此实例的内存被认为完全初始化了,此时第一阶段完成。
阶段 2
- 从顶部初始化器往下,链中的每一个指定初始化器都有机会进一步定制实例。初始化器现在能够访问 self 并且可以修改它的属性,调用它的实例方法等等;
- 最终,链中任何便捷初始化器都有机会定制实例以及使用 slef 。
自动初始化器的继承
子类默认不会继承父类初始化器。总之,在特定的情况下父类初始化器是可以被自动继承的。实际上,这意味着在许多场景中你不必重写父类初始化器,只要可以安全操作,你就可以毫不费力地继承父类的初始化器。
假设你为你子类引入的任何新的属性都提供了默认值,请遵守以下2个规则:
规则1
- 如果你的子类没有定义任何指定初始化器,它会自动继承父类所有的指定初始化器。
规则2
- 如果你的子类提供了所有父类指定初始化器的实现——要么是通过规则1继承来的,要么通过在定义中提供自定义实现的——那么它自动继承所有的父类便捷初始化器。
就算你的子类添加了更多的便捷初始化器,这些规则仍然适用。