【IOS开发】Swift 包装器的使用
·
在 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
- 对属性进行数值范围限制
@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
- 邮箱验证
@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)
}
}
- 数据持久化
// 数据持久化
@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 }
}
}
更多推荐
所有评论(0)