🌯🌯 Burritos

Bitrise Build Status Swift Package Manager Platform

一组经过良好测试的 Swift 属性包装器。

要求

Xcode 11 & Swift 5

安装

Swift Package Manager

Xcode 11+ 集成

  1. 打开 MenuBarFileSwift PackagesAdd Package Dependency...
  2. 粘贴包仓库 URL https://github.com/guillermomuntaner/Burritos 并点击 Next。
  3. 选择你的规则。由于此包处于预发布开发阶段,我建议你指定一个具体的标签,以避免拉取破坏性更改。

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 操作是互斥的。 可以使用包装器的 mutate 方法完成原子修改 (read-modify-write)。

@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)

由 @v_pradeilles 提供

@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 !

TODO: 重新实现

打印/日志

TODO: 一个属性包装器,用于打印/记录任何设置的值。

关于属性包装器

引用 属性包装器提案 的描述

属性包装器是一种抽象重复出现的属性实现模式的机制。

👉 你知道吗:属性包装器由 Apple 在 WWDC 2019 期间宣布。 它们是 SwiftUI 语法糖的一个基本组成部分,因此 Apple 将它们推入了最初的 Swift 5.1 beta 版,跳过了正常的 Swift Evolution 过程。 在 WWDC 之后,这个过程仍在继续,并且经过 3 次审查才在 Xcode 11 beta 4 上达到最终形式。

有趣的读物

其他语言中的等价物

许可

Burritos 在 MIT 许可 下发布。