对 Swift 学习 的一些总结
学习文献:
Chris Eidhof. “Swift 进阶”
类与结构体的主要不同点
- 语义:
类:
引用类型(引用语义),需要自己管理其引用计数、引用值得变化
结构体:值类型(值语义),在设计结构体时,我们可以要求编译器保证不可变性。
- 内存管理方式:
类:类的实例只能通过引用来间接地访问。类能有很多个持有者。
结构体:可以被直接持有及访问,不会被引用,但是会被复制。也就是说,结构体的持有者是唯一的。
- 共享代码:
类:通过继承来共享代码
结构体 (以及枚举):不能被继承。想要在不同的结构体或者枚举之间共享代码,我们需要使用不同的技术,比如像是组合、泛型以及协议扩展等。
值类型
值永远不会改变,它们具有不可变的特性。这 (在绝大多数情况下) 是一件好事,因为使用不变的数据可以让代码更容易被理解。不可变性也让代码天然地具有线程安全的特性,因为不能改变的东西是可以在线程之间安全地共享的。
值语义
- 结构体不能通过引用来进行比较,只能通过它们的属性来比较两个结构体。虽然可以用 var 来在结构体中声明可变的变量属性,但是这个可变性只体现在变量本身上,而不是指里面的值。改变一个结构体变量的属性,在概念上来说,和为整个变量赋值一个全新的结构体是等价的。我们总是使用一个新的结构体,并设置被改变的属性值,然后用它替代原来的结构体。
- 结构体只有一个持有者。比如,当我们将结构体变量传递给一个函数时,函数将接收到结构体的复制,它也只能改变它自己的这份复制。这叫做值语义 (value semantics) (又作复制语义)
引用语义
类对象会拥有很多持有者,这被叫做引用语义 (reference semantics)。
结构体
结构体为值类型
值类型意味着一个值变量被复制时,这个值本身也会被复制,而不只限于对这个值的引用的复制。在几乎所有的编程语言中,标量类型都是值类型。这意味着当一个值被赋给新的变量时,并不是传递引用,而是进行值的复制。
let a = 12
let b = a
a += 1
print (a) // 13
print (b) // 12
b的值没有根据a的值进行改变
结构体的赋值 特点
- 定义结构体 PY_Point 它里面有一个
let x = 12
, 以及var y
, 可以比较 y是否大于x
struct PY_Point {
let x = 12
var y = 0
func isReaterThanX() -> Bool {
return y > x
}
}
- 赋值: 下面打印结果表示 两个结构体已经不是同一个结构体了。发生了复制现象。
var point = PY_Point(y: 13)
var pointTemp = point
withUnsafePointer(to: &point) { pointAddress in
print("point 地址: \(pointAddress)")
}
withUnsafePointer(to: &pointTemp) {pointTempAddress in
print("pointTemp 地址: \(pointTempAddress)")
}
/**
point 地址: 0x00007ffeec4d7a00
pointTemp 地址: 0x00007ffeec4d7a10
*/
- 结构体(值类型)内部赋值:
可以看到,多次调用了didSet 方法,但是pointTemp的地址没有改变
“理解值类型的关键就是理解为什么这里会被调用。对结构体进行改变,在语义上来说,与重新为它进行赋值是相同的。当我们改变了结构体中某个深层次的属性时,其实还是意味着我们改变了结构体,所以 didSet 依然会被触发。
虽然语义上来说,我们将整个结构体替换为了新的结构体,但是编译器依然会原地进行变更。由于这个结构体没有其他所有者,实际上我们没有必要进行复制。不过如果有多个持有者的话,重新赋值意味着发生复制。对于写时复制的结构体,工作方式会略有不同
var pointTemp: PY_Point? = PY_Point(y: 13) {
didSet {
print("pointTemp didSet 调用了")
}
}
func valuation() {
withUnsafePointer(to: &pointTemp) {pointTempAddress in
print("pointTemp 地址: \(pointTempAddress)")
}
pointTemp?.y = -1
withUnsafePointer(to: &pointTemp) {pointTempAddress in
print("pointTemp 地址: \(pointTempAddress)")
}
}
//调用valuation()打印结果为
/**
pointTemp 地址: 0x00007ffee9379a00
pointTemp didSet 调用了
pointTemp didSet 调用了
pointTemp 地址: 0x00007ffee9379a00
pointTemp didSet 调用了
*/
结构体内建方法
- 结构体可以添加方法 ,比如比较x,y大小的方法
struct PY_Point {
let x = 12
var y = 0
func isReaterThanX() -> Bool {
return y > x
}
}
- 结构体方法中修改自身的属性值
编译器自动补充了关键词 mutating
对不可变x属性,提示了Left side of mutating operator isn’t mutable: ‘x’ is a ‘let’ constant (x为let 修饰,值不能被修改)
extension PY_Point {
mutating func y_offset1() {
y += 1
}
mutating func x_offset1() {
///报错 Left side of mutating operator isn't mutable: 'x' is a 'let' constant
x += 1
}
}
mutating / inout关键词
- inout: 函数中,我们可以将参数标记为 inout 来使其可变。就和一个普通的参数一样,值被复制并作为参数被传到函数内。不过,我们可以改变这个复制 (就好像它是被 var 定义的一样)。然后当函数返回时,原来的值将被覆盖掉。
postfix func ++ (left: inout CGFloat) {
left += 1
}
var a:CGFloat = 1
a++
print(a)
// 打印结果为 2.0
- mutating: 函数体内部你可以随时使用 self。如果我们想要改变 self,或是改变 self 自身或者嵌套的 (比如 self.y) 任何属性,我们就需要将方法标记为 mutating.
mutating 将隐式的 self 参数变为可变的,并且改变了这个变量的值。
- mutating 其实和inout做了同样的事情,它将隐式的 self 参数变为可变的,并且改变了这个变量的值
写时复制
在 Swift 标准库中, Array、Dictionary、 Set 这样的集合类型是通过一种叫做写时复制 (copy-on-write) 的技术实现的。
请参考 Swift 写时复制