🌯 🌯 Burritos
一组经过良好测试的 Swift 属性包装器。
- @AtomicWrite
- @Clamping
- @Copying
- @DefaultValue
- @DynamicUIColor
- @EnvironmentVariable
- @Expirable
- @LateInit
- @Lazy
- @LazyConstant
- @Trimmed
- @UndoRedo
- @UserDefault
- 更多即将到来...
要求
Xcode 11 & Swift 5
安装
Swift 包管理器
Xcode 11+ 集成
- 打开
MenuBar
→File
→Swift Packages
→添加包依赖...
- 粘贴仓库 URL
https://github.com/guillermomuntaner/Burritos
并点击下一步。 - 选择您的规则。由于此包处于预发布开发阶段,建议您指定一个具体的标签以避免拉取破坏性变更。
Package.swift
如果您已经有了 Package.swift 或您正在构建自己的包,请添加一个新的依赖项。
dependencies: [
.package(url: "https://github.com/guillermomuntaner/Burritos", from: "0.0.3")
]
Cocoapods
将 Burritos 添加到您的 Podfile 中。
pod 'Burritos', '~> 0.0.3'
每个包装器都是一个子模块,因此您只需添加所需的那些。
pod 'Burritos/Copying', '~> 0.0.3'
pod 'Burritos/UndoRedo', '~> 0.0.3'
pod 'Burritos/UserDefault', '~> 0.0.3'
@AtomicWrite
一种属性包装器,可以为包装的属性提供原子的写入访问。读取访问不是原子的,但与写入和修改操作是互斥的。可以通过包装器的 mutate
方法执行原子修改(读取-修改-写入)。
@Atomic var count = 0
// You can atomically write (non-derived) values directly:
count = 99
// To mutate (read-modify-write) always use the wrapper method:
DispatchQueue.concurrentPerform(iterations: 1000) { index in
_count.mutate { $0 += 1 }
}
print(count) // 1099
@Clamping
一种属性包装器,它会自动将其包装值钳位到指定的范围内。
@Clamping(range: 0...1)
var alpha: Double = 0.0
alpha = 2.5
print(alpha) // 1.0
alpha = -1.0
print(alpha) // 0.0
@Copying
一种属性包装器,它围绕 NSCopying
来进行复制,不仅在初始化时复制值,在重新分配时也复制。如果您厌倦了调用 .copy() as! X
,那么您一定会喜欢这个。
@Copying var path: UIBezierPath = .someInitialValue
public func updatePath(_ path: UIBezierPath) {
self.path = path
// You don't need to worry whoever called this method mutates the passed by reference path.
// Your stored self.path contains a copy.
}
@DefaultValue
一种属性包装器,它围绕一个隐式非可选值,它降级到给定的默认值。
@DefaultValue(default: 0)
var count
count = 100
// or
@DefaultValue(default: 0)
var count = 100
// Assigning nil resets to the default value
print(count) // 100
count = nil
print(count) // 0
@DynamicUIColor
这是一个围绕UIColor的属性包装器,支持深色模式。
默认情况下,在iOS >= 13中,它使用新的系统级界面样式特性和动态UIColor构造函数来支持深色模式,无需额外努力。在之前的iOS版本中,它默认为浅色。
@DynamicUIColor(light: .white, dark: .black)
var backgroundColor: UIColor
// The color will automatically update when traits change
view.backgroundColor = backgroundColor
为了支持旧版iOS版本和自定义逻辑(例如,应用设置中的开关),构造函数可以接受一个额外的style
闭包,该闭包动态指定要使用哪种颜色。返回一个nil
值将导致先前的默认行为。通过这样做可以更容易地实现向后兼容性:
let color = DynamicUIColor(light: .white, dark: .black) {
if #available(iOS 13.0, *) { return nil }
else { return Settings.isDarkMode ? .dark : .light }
}
view.backgroundColor = color.value
// On iOS <13 you might need to manually observe your custom dark
// mode settings & re-bind your colors on changes:
if #available(iOS 13.0, *) {} else {
Settings.onDarkModeChange { [weak self] in
self?.view.backgroundColor = self?.color.value
}
}
原始想法来源于@bardonadam
@EnvironmentVariable
一个用于设置和获取系统环境变量值的属性包装器。
@EnvironmentVariable(name: "PATH")
var path: String?
// You can set the environment variable directly:
path = "~/opt/bin:" + path!
@Expirable
围绕可以过期的值的一个属性包装器。在给定持续时间或过期日期之后获取值将返回nil。
@Expirable(duration: 60)
var apiToken: String?
// New values will be valid for 60s
apiToken = "123456abcd"
print(apiToken) // "123456abcd"
sleep(61)
print(apiToken) // nil
// You can also construct an expirable with an initial value and expiration date:
@Expirable(wrappedValue: "zyx987", expirationDate: date, duration: 60)
var apiToken: String?
// or just update an existing one:
_apiToken.set("zyx987", expirationDate: date)
@LateInit
使用属性包装器重新实现了Swift的隐式可选。
var text: String!
// or
@LateInit var text: String
// Note: Accessing it before initializing will result in a fatal error:
// print(text) // -> fatalError("Trying to access LateInit.value before setting it.")
// Later in your code:
text = "Hello, World!"
@Lazy
一个属性包装器,它将实例化推迟到第一次读写访问。它是一个使用属性包装器重新实现的Swift lazy
修饰符。
@Lazy var result = expensiveOperation()
...
print(result) // expensiveOperation() is executed at this point
在lazy
之上,它还提供将包装器重置为其“未初始化”状态的功能。
@LazyConstant
等同于@Lazy,并防止包装值的更改或修改。
@LazyConstant var result = expensiveOperation()
...
print(result) // expensiveOperation() is executed at this point
result = newResult // Compiler error
注意:该包装器防止重新分配包装属性的值,但不能重新分配包装器本身。重新分配包装器(例如:_value = LazyConstant(wrappedValue: "Hola!")
)是可能的,由于包装器本身需要声明为变量,无法防止。
@Trimmed
一个包装器,在初始化和重新分配时自动修剪字符串。
@Trimmed
var text = " \n Hello, World! \n\n "
print(text) // "Hello, World!"
// By default trims white spaces and new lines, but it also supports any character set
@Trimmed(characterSet: .whitespaces)
var text = " \n Hello, World! \n\n "
print(text) // "\n Hello, World! \n\n"
@UndoRedo
一个包装器,自动存储历史,并支持撤销和重做操作。
@UndoRedo var text = ""
text = "Hello"
text = "Hello, World!"
_text.canUndo // true
_text.undo() // text == "Hello"
_text.canRedo // true
_text.redo() // text == "Hello, World!"
您可以使用canUndo
和canRedo
属性随时检查是否可以撤销或重做,这两个属性可能特别有助于启用/禁用用户界面按钮。
原始想法来自@JeffHurray
@UserDefault
提供了对UserDefaults
的安全访问,并支持默认值。
@UserDefault("test", defaultValue: "Hello, World!")
var test: String
默认情况下,它使用标准用户默认值。您可以通过其构造函数传递任何其他您希望使用的UserDefaults
实例,例如在您使用应用程序组时。
let userDefaults = UserDefaults(suiteName: "your.app.group")
@UserDefault("test", defaultValue: "Hello, World!", userDefaults: userDefaults)
var test: String
@Cached
待办事项
@Dependency (服务定位器模式)
待办事项
线程安全
待办事项
命令行参数
待办事项
属性观察器 -> willSet, didSet !
待实现
打印/日志
待实现:打印/记录设置的任何值。
关于属性包装器
引用 属性包装器提议 描述
属性包装器是一种机制,用于抽象反复出现的属性实现模式。
有趣的阅读材料
- Swift 进化属性包装器提案
- SwiftLee:Swift 中的属性包装器以减少样板代码
- Majid's:理解 SwiftUI 中的属性包装器
- Sundell 的 Swift:为 SwiftUI API 提供动力的 Swift 5.1 特性
- NSHipster 文章
其他语言中的等效项
- Kotlin 有 委托属性
许可证
Burritos 采用了 MIT 许可协议。