Swift Structures and Classes, St
2020-08-05 本文已影响0人
幸运者_Lucky
common:
- 属性定义
- 方法定义
- 定义下标 (subscripts),
[]
- 定义初始函数
- 扩展的使用
- 协议的使用
diffenrence:
- 类可以继承, 结构体不可以
- 类可以在运行时, 检查或者转换类型, 结构体不可以
- 析构函数
- 类有引用计数, 可以多个指针指向同一个对象, 结构体不可以
Swift struct 的实现
- 当结构体中包含引用类型
当结构体包含引用类型, 在 copy 的时候, 它的效率还不如 class 本身, 看下面代码,
当 b2 = b1 的时候, b2 在复制 b1 中的 a 和 aa 这两个引用类型的时候, 都会造成 a 和 aa 的引用计数加一, 如果 B 是类, 则只会 b1 这个对象的引用计数加一, 不会对它的属性造成影响, 所以说, 如果在 swift 结构体中, 使用引用类型, 还不如直接使用类的效率高.
class A {}
struct B {
let a = A()
let aa = A()
let c = 1
}
let b1 = B()
let b2 = b1
- valueBuffer 存储值
结构体的实现包含了一个 valueBuffer, 伪代码是let valueBuffer = [x1, x2, x3]
当结构的属性都是值类型, 并且数量小于等于3个的时候, 属性就会直接存放在 valueBuffer 中, 如下面 code, 当 p2 = p1 的时候, p1 会直接把 valueBuffer 中的值赋值给 p2, 到目前为止, struct 是不涉及到任何堆操作的.
struct Point {
let x: Float
let y: Float
}
let p1 = Point(x: 0, y: 0)
let p2 = p1
- valueBuffer 指向堆, Copy-On-Write
在第一点提到使用引用类型效率低的问题, 这里就是解决的办法.
当属性大于3的时候, valueBuffer 没法存储, 是通过 VWT(Value Witness Table) 来实现的, 数据存储在堆上, 通过 valueBuffer 来记录堆上的地址, 通过 VWT 来管理生命周期, 如下面的结构体 T, 就会按照这个方式来存储, 但是在发生复制的情况是如何处理的,t2 = t1
,t3 = t1
..., 按照值类型的复制概念, 每次都是深拷贝, 那会申请大量的堆内存.
结构体也有引用计数, 这是针对数量大于3这种情况来说的, 因为存储到堆上, 每次复制的时候, 只是新增了指向堆内存的指针, 但是这样当修改属性值的时候, 所有指向这个堆内存的值都会变吗? 所以在某个属性有写操作的时候, 才做深拷贝操作, 这样, 它的修改就不会应该其他的.
到这里引出了一个新概念 Copy-On-Write
如下代码: 当 t2.w = 10, 发生的时候, 首先 t2 会做深拷贝, 此时 t2 的 valueBuffer 指向了新的堆内存, 并且把 w, x, y, z 都 copy 进去了, 这样t1, t3就不会有任何问题
struct T {
var w: Float
var x: Float
var y: Float
var z: Float
}
var t1 = T(w: 0, x: 0, y: 0, z: 0)
var t2 = t1
var t3 = t1
t2.w = 10
note
: 自定义的 struct 是没有实现 Copy-On-Write
的, 大概实现的思路是下面这样的
final class Ref<T: Hashable>: Hashable {
var val : T
init(_ v : T) {val = v}
func hash(into hasher: inout Hasher) {
hasher.combine(val)
hasher.finalize()
}
static func == (lhs: Ref, rhs: Ref) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}
struct Box<T: Hashable>: Hashable {
var ref : Ref<T>
init(_ x : T) { ref = Ref(x) }
var val: T {
get { return ref.val }
set {
if (!isKnownUniquelyReferenced(&ref)) {
print("set1")
ref = Ref(newValue)
} else {
print("set2")
ref.val = newValue
}
}
}
mutating func run() {
print("isKnownUniquelyReferenced: ", isKnownUniquelyReferenced(&ref))
}
var cc = 10
}
let view = UIView()
var b = Box(view)
b.run()
var c = b
b.run()
if c == b {
print("==")
}
// print
isKnownUniquelyReferenced: true
isKnownUniquelyReferenced: false
==
isknownuniquelyreferenced
understanding-swift-copy-on-write-mechanisms
总结:
struct 不会像 class 那样, 背后有大量的代码来管理一个对象的生命周期
struct 中包含 class 属性, 同样走引用计数
struct 使用 Copy-On-Write 来优化大值的内存问题
struct 不会造成多线程唯一的问题, 和内存泄露, 不存在循环引用的问题
Swift 5 源码实现 Copy-On-Write
internal final class _MutableHandle<MutableType : NSObject>
where MutableType : NSCopying {
fileprivate var _pointer : MutableType
init(reference : __shared MutableType) {
_pointer = reference.copy() as! MutableType
}
init(adoptingReference reference: MutableType) {
_pointer = reference
}
/// Apply a closure to the reference type.
func map<ReturnType>(_ whatToDo : (MutableType) throws -> ReturnType) rethrows -> ReturnType {
return try whatToDo(_pointer)
}
func _copiedReference() -> MutableType {
return _pointer.copy() as! MutableType
}
func _uncopiedReference() -> MutableType {
return _pointer
}
}
/// Describes common operations for Foundation struct types that are bridged to a mutable object (e.g. NSURLComponents).
internal protocol _MutableBoxing : ReferenceConvertible {
var _handle : _MutableHandle<ReferenceType> { get set }
/// Apply a mutating closure to the reference type, regardless if it is mutable or immutable.
///
/// This function performs the correct copy-on-write check for efficient mutation.
mutating func _applyMutation<ReturnType>(_ whatToDo : (ReferenceType) -> ReturnType) -> ReturnType
}
extension _MutableBoxing {
@inline(__always)
mutating func _applyMutation<ReturnType>(_ whatToDo : (ReferenceType) -> ReturnType) -> ReturnType {
// Only create a new box if we are not uniquely referenced
if !isKnownUniquelyReferenced(&_handle) {
let ref = _handle._pointer
_handle = _MutableHandle(reference: ref)
}
return whatToDo(_handle._pointer)
}
}