现在Swift API 设计
1、 类、结构体、枚举
- 前者为引用类型、后两者为值类型。
- 前者拷贝是是拷贝引用,修改值相互影响,后两者是值拷贝,修改值不会相互影响。
注意: 类和结构体的使用没有一个硬性规定,但有一些笼统的指南,总的来说你们应该倾向于使用结构体而不是类,除非你有一个使用类的很好的理由。
2、如何处理值类型拷贝引用的问题?
当结构体中存在引用类型,在拷贝结构体时如何避免引用类型仅仅是拷贝引用,导致两个结构体实例公用一个引用的问题?例如下面这个结构体:
struct Material {
public var roughness: Float
public var color: Color
public var texture: Texture
}
class Texture { ... }
最早的处理方式是做一个防御性拷贝(defensive copy),只需将texture存储属性转为private,然后创建一个计算后的属性,我们复制texture对象,这就避免了问题的发生。
struct Material {
public var roughness: Float
public var color: Color
private var _texture: Texture
public var texture {
get { _texture }
set { _texture = Texture(copying: newValue) }
}
}
但是没从根本上解决问题,因为我们可以通过get方法改变它。让我们考虑一下另一种方式,完全不显示引用类型。只是显示了我们希望反应在对象上的属性,作为计算生成的属性。
struct Material {
public var roughness: Float
public var color: Color
private var _texture: Texture
public var isSparkly: Bool {
get { _texture.isSparkly }
set {
//检查对象是否被唯一引用,如果不是则完全复制对象
if !isKnownUniquelyReferenced(&_texture) {
_texture = Texture(copying: _texture)
}
_texture.isSparkly = newValue
}
}
}
3、协议和泛型(protocol and generic)
与OC不同,在Swift中可以向结构体和枚举添加协议,这意味着你可以共享代码,通过使用泛型可以跨越很多值类型,所以当你觉得需要你需要共享一些不同类型的代码,并不是一定要创建类继承关系使其中的父类有共享功能。
4、学习了第3个知识点(协议和泛型),如果想将texture上的所有属性使用写时复制语义,可以使用动态成员查找来实现
@dynamicMemberLookup
struct Material {
public var roughness: Float
public var color: Color
private var _texture: Texture
public subscript(dynamicMember keyPath: ReferenceWritableKeyPath) -> T {
get { _texture[keyPath: keyPath] }
set {
if !isKnownUniquelyReferenced(&_texture) { _texture = Texture(copying: _texture) }
_texture[keyPath: keyPath] = newValue
}
}
}
5、属性包装器(swift 5.1)
例如:lazy——懒加载,就属于属性包装器。
//没有使用属性包装器时
public struct MyType {
var imageStorage: UIImage? = nil
public var image: UIImage {
mutating get {
if imageStorage == nil {
imageStorage = loadDefaultImage()
}
return imageStorage!
}
set {
imageStorage = newValue
}
}
}
//使用属性包装器后
public struct MyType {
public lazy var image: UIImage = loadDefaultImage()
}
@LateInitialized——延迟初始化包装器。
//使用延迟初始化包装器之前
public struct MyType {
var textStorage: String? = nil
public var text: String {
get {
guard let value = textStorage else {
fatalError("text has not yet been set!")
}
return value
}
set {
textStorage = newValue
}
}
}
//之后
public struct MyType {
@LateInitialized public var text: String
}
//属性包装器的定义
@propertyWrapper
public struct LateInitialized {
private var storage: Value?
public init() {
storage = nil
}
public var value: Value {
get {
guard let value = storage else {
fatalError("value has not yet been set!")
}
return value
}
set {
storage = newValue
}
}
}
//使用属性包装器,编译之后的代码如下
//编译器会将代码翻译成两个分开的属性
public struct MyType {
//@LateInitialized public var text: String
var $text: LateInitialized = LateInitialized()
public var text: String {
get { $text.value }
set { $text.value = newValue }
}
}
6、通过学习第5点知识,我们思考下为什么不构建一个包装器来防止值类型中包含引用属性导致的一些问题?
假设定义如下防御性拷贝的属性包装器
@propertyWrapper
public struct DefensiveCopying {
private var storage: Value
public init(initialValue value: Value) {
storage = value.copy() as! Value
}
public var value: Value {
get { storage }
set {
storage = newValue.copy() as! Value
}
}
}
使用防御性拷贝属性包装器
public struct MyType {
@DefensiveCopying public var path: UIBezierPath = UIBezierPath()
}
//编译器翻译之后的代码
public struct MyType {
//@DefensiveCopying public var path: UIBezierPath = UIBezierPath()
// Compiler-synthesized code...
var $path: DefensiveCopying =
DefensiveCopying(initialValue: UIBezierPath())
public var path: UIBezierPath {
get { $path.value }
set { $path.value = newValue }
}
}
我们甚至可以在创建属性包装器时传递默认值
//属性包装器扩展
extension DefensiveCopying {
public init(withoutCopying value: Value) {
storage = value
}
}
//简化前
public struct MyType {
@DefensiveCopying public var path: UIBezierPath
public init() {
$path = DefensiveCopying(withoutCopying: UIBezierPath())
}
}
//简化后
public struct MyType {
@DefensiveCopying(withoutCopying: UIBezierPath())
public var path: UIBezierPath
}
还有@UserDefault、@ThreadSpecific、@Option等属性包装器,在SwiftUI中我们广泛地使用属性包装器,例如:@State、@Binding、@Environment等