ShannonChenCHN / ASwiftTour

A journey of learning Swift. Swift 学习之旅。
37 stars 12 forks source link

值类型(struct) VS. 引用类型(class) #3

Open ShannonChenCHN opened 4 years ago

ShannonChenCHN commented 4 years ago

Swift 中的值类型(Building Better Apps with Value Types in Swift)

2019.09.13

1. Reference Semantics

2. Immutablity 是否是一个解决方案?

3. Value Semantics

4. Value Types in Practice

see sample code

5. Mixing value types and reference types

5.1 Reference Types Often Contain Value Types

Value types generally used for “primitive” data of objects:

class Button : Control {
 
  var label: String

  var enabled: Bool

  // ...

}

5.2 A Value Type Can Contain a Reference

Copies of the value type will share the reference:

struct ButtonWrapper {
 
  var button: Button

}

Maintaining value semantics requires special considerations:

5.3 Immutable References and Equatable

Reference identity is not enough:

struct Image : Drawable {
  var topLeft: CGPoint
  var image: UIImage
}

extension Image : Equatable { 
  static func ==(lhs: Image, rhs: Image) -> Bool {
    // return lhs.topLeft == rhs.topLeft && lhs.image === rhs.image
    return lhs.topLeft == rhs.topLeft && lhs.image.isEqual(rhs.image)
  }
}

var image = Image(topLeft: CGPoint(x: 0, y: 0),
                  image: UIImage(imageNamed:"San Francisco”)!)
var image2 = image

在上面的例子中,image 属性是不可变的引用类型,所以不用担心数据的安全性。但是判断相等性时,不能以引用值是否相等作为依据,而应该将其当做值类型一样根据内容去判断对等性。

5.4 References to Mutable Objects

Unexpected mutation:

struct BezierPath: Drawable {
  var path = UIBezierPath()
  var isEmpty: Bool {
    return path.empty
  }


  func addLineToPoint(point: CGPoint) {
    path.addLineToPoint(point) // Unexpected mutation❌ 
  } 
}

在上面的例子中,因为 path 属性是可变的引用类型,所以在 addLineToPoint 方法被调用时,path 属性值可能会被改变。

5.5 Copy On Write

struct BezierPath: Drawable {
 
  private var _path = UIBezierPath()



  var pathForReading: UIBezierPath {

    return _path
 
  }



  var pathForWriting: UIBezierPath {

    mutating get {

      _path = _path.copy() as! UIBezierPath
 return _path

    }
 
  }
    

}

extension BezierPath {
 
  var isEmpty: Bool {
    return pathForReading.empty 
  }


  mutating func addLineToPoint(point: CGPoint) {
    pathForWriting.addLineToPoint(point)
  }
}

var path = BezierPath()
var path2 = path
var path2 = path
if path.empty { print("Path is empty") }
var path2 = path
path.addLineToPoint(CGPoint(x: 10, y: 20))
path.addLineToPoint(CGPoint(x: 100, y: 125))

The standard library value types uses this throughout:

struct MyWrapper {
  var _object: SomeSwiftObject
  var objectForWriting: SomeSwiftObject {
    mutating get {
     if !isKnownUniquelyReferenced(&_object)) {
        _object = _object.copy()
     }
     return _object
    }
} }

延伸阅读:

小结

参考

ShannonChenCHN commented 3 years ago

Copy on Write

参考: