结构体和类
- swift 中储存结构化的数据 可以用 结构体,枚举,类及使用闭包捕获变量。
- 类和结构体不同点: 1.结构体是值类型类是引用类型,编译器可以保证结构体的不变性,类要我们自己来保证 2.内存管理方式不同接头体可以直接持有及访问,类的实例只能通过地址间接访问,结构体是唯一的,类能有多个持有者。3.类可以代码共享,结构体不能被继承
- 值类型
- 我们经常处理一些有生命周期的类型
ViewController
有init,delloc等等,而有的则不需要生命周期例如URL
它一旦创建就不会被改变,它们结束时不需要额外操作。比较两个URL
的值时 我们不关心是否指向同一地址,而是它们的属性是否一样。这种类型我们称之为值类型。NSURL
是一个不可变对象 而URL
是一个结构体。 - 值类型具有天然的线程安全性。不可变的东西是可以多线程共享的。、
- 结构体中也能定义 var 类型 但这个可变性只体现在变量自己身上。当我们改变结构体中的属性时 它总是生成一个全新的结构体来取代
- 结构体只能有一个持有者,当我们把它传递给一个函数是它被复制了一份 函数只能改变这个副本,这被叫做 值语义。而对象传递的是地址指针,称为引用语义。
- 结构体只有一个持有者所以不会造成循环引用,除非结构体包含类属性,否则就不需要考虑引用计数问题,let声明的结构体 一个字节也不会改变
- 可变性
- 可变性是造成bug主要原因之一,而swift可以让我们写出安全代码的同时,保留可变代码的风格
var mutabelArray: NSMutableArray = [1,2,3]
for _ in mutabelArray {
mutabelArray.removeLastObject()
}
- 我们知道数组的变化会破坏迭代器的内部结构 这个是不被允许的,我们知道这一点,不会犯这种错误。 然而我们不能保证 removeLastObject 不被其他地方调用。这种错误很难被发现
swift 就避免了这种错误
var mutabelArray = [1,2,3]
for _ in mutabelArray {
mutabelArray.removeLast()
}
- 赋值问题 引用类型会改变赋值它的变量的属性 这是很强大的特性,但有时也会造成bug
var mutabelArray: NSMutableArray = [1,2,3]
var new = mutabelArray
new.add(4)
我们实现一个扫描器
class BinaryScanner {
var position: Int
let data: Data
init(data: Data) {
self.position = 0
self.data = data
}
}
extension BinaryScanner {
func scanByte() -> UInt8? {
guard position < data.endIndex else {
return nil
}
position += 1
return data[position - 1]
}
}
正常可运行
func scanRemainingBytes(scanner: BinaryScanner) {
while let byte = scanner.scanByte() {
print(byte)
}
}
有可能偶发线程不安全
for _ in 0..<Int.max {
let newScanner = BinaryScanner(data: "hi".data(using: .utf8)!)
DispatchQueue.global().async {
scanRemainingBytes(scanner: newScanner)
}
scanRemainingBytes(scanner: newScanner)
}
- 结构体
- 几乎在所有编程中 标量都是值类型
- 当我们把一个结构体赋值给另一个时,swift 自动对它进行赋值,听起来很昂贵, 但编译器会对这种赋值进行优化,称为写时复制。
struct Point {
var x: Int
var y: Int
}
let origin = Point(x: 0, y:0)
origin.x = 10 // ❎
var otherPoint = Point(x: 0, y:0)
otherPoint.x = 10 // ✅
当我们把一个结构体赋值给另一个时
var otherPoint = origin
otherPoint.x = 10 // ✅
otherPoint // x: 10, y: 0
origin // x: 0, y: 0
struct Size {
var width: Int
var height: Int
}
静态变量
extension Point {
static let origin = Point(x: 0, y: 0)
}
struct Rectangle {
var origin: Point
var size: Size
}
写在扩展中的初始化方法,系统会保留原始的初始化方法。
extension Rectangle {
init(x: Int = 0, y: Int = 0, width: Int, height: Int) {
origin = Point(x: x, y: y)
size = Size(width: width, height: height)
}
}
var screen = Rectangle(width: 320, height: 480) {
didSet {
print("screen did Changed \(screen)")
}
}
screen.origin.x = 10
我们只是改变了结构体的数量 但是它的didset会被触发
var array = [screen] {
didSet {
print("array did Changed")
}
}
array[0].origin.x = 10
数组是结构体 数组内元素的变化 数组本身 也会触发 didset
如果Rectangle是类 那么 didset就不会被触发
func + (lhs: Point, rhs: Point) -> Point {
return Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
extension Rectangle {
func translate(by offset: Point) {
origin = origin + offset
// 这个方法系统会报错 self是不可变得 需要声明mutating
}
}
extension Rectangle {
mutating func translate(by offset: Point) {
origin = origin + offset
}
}
标记了mutating 意味着 self是可变的 表现的想一个 var
当然 let 声明的结构体 依然是不可变得
很多情况下 方法都有可变和不可变两种版本 ,sort() 原地排序 sorted() 返回新的数组
extension Rectangle {
func translated(by offset: Point) {
var copy = self
copy.translate(offset: offset)
return copy
}
}
事实上 mutating 方法只是结构体上 普通的方法 只是 self 被隐士的标记为 inout 了 &
我们再来看上面的scanner 问题 如果 BinaryScanner 是 结构体那么 每个方法调用的结构体都是一个 副本这样 就可以安全的迭代了而不用担心被其他线程更改。
- 写时复制 copy-on-write
var x = [1,2,3]
var y = x
如果创建一个y 把并把x赋值它时 会发生复制
x 和 y 含有独立的结构体。
但是 数组内含有指向 元素位置的指针 在这时 x 和 y 共享了他们的部分储存 不过 当x改变时 这种共享 会被检测到,内存将被复制。
只有在 有一个 发生改变时 内存才被复制 成为写时复制
- 昂贵方式
struct Mydata {
fileprivate var _data: NSMutableData
var _dataFOrWriting: NSMutableData {
mutating get {
_data = _data.mutableCopy() as! NSMutableData
return _data
}
}
init(data: NSData) {
self._data = data.mutableCopy() as! NSMutableData
}
}
- 高效方式
在 swift 中 可以用 isKnownUniquelyReferenced(&<#T##object: T##T#>)
来检查引用的唯一性。
获取时 可以通过 是否被唯一引用来决定是否 复制
得益于写时复制和相关联的编译器优化 大量不必要的操作被移除了。
如果我们写一个 结构体 而不能保证其中属性的不变性,我们可以考虑使用类来实现。
- 写时复制陷阱
我们使用array[0] 时 直接用下标访问 是不会发生写时复制的 因为它直接访问了内存中元素的位置。而字典和set 不同
- 闭包和可变性
一个函数 每次生成一个唯一的整数直到Int.max可以将状态移动到函数外部,换句话说函数对i进行了闭合
var i = 0
func uniqueInteger() -> Int {
i += 1
return i
}
swift 函数也是引用类型
let otherFunction = uniqueInteger
传递函数它会已引用方式存在 并共享了 其中的状态
func uniqueIntegerProvider() -> () -> Int {
var i = 0
return {
i += 1
return i
}
}
返回一个从零开始的方法
也可以封装成 AnyIterator 一个整数发生器
func uniqueIntegerProvider() -> AnyIterator<Int> {
var i = 0
return AnyIterator {
i += 1
return i
}
}
- 结构体 一般被储存在栈上,而非堆上这其实是一种优化,默认结构体是在堆上的,但大多数情况下 优化都会生效
- 当一个结构体被函数闭合了 它就在堆上就算函数退出了作用域,它仍然存在,同样结构体过大 也会放在堆上
- 内存 swift 的强引用和循环引用类似于OC weak(必然是可选值) unowned [weak self]
网友评论