在 Swift 中,包装器和泛型是两个强大的特性,它们经常结合使用来创建灵活且类型安全的代码。让我为你详细介绍这两个概念及其结合使用。包装器是 Swift 5.1 引入的特性,允许你在属性访问逻辑上添加一层封装。

包装器的作用

1.1 核心作用

(1) 代码封装:将属性访问逻辑封装在统一的地方
(2) 行为抽象:为属性添加额外的行为而不修改使用方的代码
(3) 代码复用:在多个属性间共享相同的访问逻辑

1.2 提升代码可维护性

(1) 单一职责:每个包装器只关注一个特定的行为
(2) 易于测试:包装器逻辑可以独立测试
(3) 易于修改:修改逻辑只需改动包装器实现

技术实现

2.1 基本结构

对于属性包装器(Property Wrapper)类型,有两个要求:
(1)必须使用属性@propertyWrapper进行定义。
(2)它必须具有wrappedValue属性。

@propertyWrapper
struct MyWrapper<T> {
    // 必须实现的属性
    var wrappedValue: T
    
    // 可选的投影值
    var projectedValue: SomeType { ... }
    
    // 初始化器
    init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }
    
    // 其他自定义初始化器
    init(customParam: String) {
        // 自定义初始化逻辑
    }
}

demo

  1. 对属性进行数值范围限制
@propertyWrapper
struct Clamped {
    private var value: Double
    private let range: ClosedRange<Double>
    
    init(wrappedValue: Double, _ range: ClosedRange<Double>) {
        self.range = range
        self.value = Self.clamp(wrappedValue, range: range)
    }
    
    var wrappedValue: Double {
        get { value }
        set { value = Self.clamp(newValue, range: range) }
    }
    
    private static func clamp(_ value: Double, range: ClosedRange<Double>) -> Double {
        return min(max(value, range.lowerBound), range.upperBound)
    }
}

// 使用包装器
struct Temperature {
    @Clamped(0...100) var celsius: Double = 0
}

//test
var temp = Temperature()
temp.celsius = 150  // 会被限制为 0-100
print(temp.celsius) // 输出: 100.0
temp.celsius = -10  // 会被限制为 0-100
print(temp.celsius) // 输出: 0.0
  1. 邮箱验证
@propertyWrapper
struct Email {
    private var value: String = ""
    
    var wrappedValue: String {
        get { value }
        set {
            if isValidEmail(newValue) {
                value = newValue
            } else {
                print("Invalid email format")
            }
        }
    }
    
    private func isValidEmail(_ email: String) -> Bool {
        let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        return NSPredicate(format: "SELF MATCHES %@", pattern).evaluate(with: email)
    }
}
  1. 数据持久化
// 数据持久化
@propertyWrapper
struct UserDefaultsStorage<T> {
    private let key: String
    private let defaultValue: T
    
    init(key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }
    
    var wrappedValue: T {
        get {
            UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

注意事项

Property wrappers并非没有限制。 他们强加了许多限制:
(1)带有包装器的属性不能在子类中覆盖。
(2)具有包装器的属性不能是lazy,@NSCopying,@NSManaged,weak或unowned。
(3)具有包装器的属性不能具有自定义的set或get方法。
wrappedValue,init(wrappedValue :)和projectedValue必须具有与包装类型本身相同的访问控制级别
(3)不能在协议或扩展中声明带有包装器的属性。

3.1 性能考虑

// 避免在包装器中执行昂贵操作
@propertyWrapper
struct ExpensiveWrapper {
    var wrappedValue: String {
        didSet {
            // ❌ 避免:每次设置都执行昂贵操作
            expensiveOperation()
        }
    }
    
    private func expensiveOperation() {
        // 耗时操作
    }
}

// 改进版本
@propertyWrapper
struct LazyExpensiveWrapper {
    private var value: String
    private var needsUpdate = false
    
    var wrappedValue: String {
        get { value }
        set {
            value = newValue
            needsUpdate = true
        }
    }
    
    // 提供显式更新方法
    func updateIfNeeded() {
        if needsUpdate {
            expensiveOperation()
            needsUpdate = false
        }
    }
}

3.2 线程安全

import Foundation

@propertyWrapper
struct ThreadSafe<T> {
    private var value: T
    private let lock = NSLock()
    
    var wrappedValue: T {
        get {
            lock.lock()
            defer { lock.unlock() }
            return value
        }
        set {
            lock.lock()
            defer { lock.unlock() }
            value = newValue
        }
    }
    
    // 提供原子操作
    mutating func atomically(_ transform: (inout T) -> Void) {
        lock.lock()
        defer { lock.unlock() }
        transform(&value)
    }
}

3.3 继承和覆盖

class BaseClass {
    @UserDefault("setting", defaultValue: "default")
    var setting: String
}

class SubClass: BaseClass {
    // ❌ 不能覆盖包装器属性
    // override var setting: String = "new"
    
    // ✅ 解决方案:使用计算属性
    override var setting: String {
        get { super.setting }
        set { super.setting = newValue }
    }
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐