DSFValueBinders

Swift Package Manager

ValueBinder

ValueBinder 创建一个双向绑定对象,允许对象之间共享值。

这与 SwiftUI 中的 @Binding 有些类似,但不依赖于 SwiftUI - 这意味着它几乎可以在 Swift 可以使用的任何地方使用。

创建

您可以使用标准初始化程序来定义一个 binder。

此初始化程序还允许您提供一个回调块,该回调块在 wrappedValue 更改时触发。

let countBinder = ValueBinder<Int>(0) { newValue in
   Swift.print("countBinder changed: \(newValue)")
}
...
countBinder.wrappedValue = 4  // triggers the update callback

注册更改更新

您可以将 ValueBinding 对象传递给另一个类,该类可以提供一个代码块,以便在 ValueBinder 的包装值更改时调用该代码块。

class AnotherClass {
   init(_ binder: ValueBinder<Int>) {
      binder.register(self) { newValue in
         Swift.print("Binding detected change: \(newValue)")
      }
   }
}

此外,如果您保留 binder 对象,您的类也可以更新 ValueBinder 的值!

class AnotherClass {
   let countBinder: ValueBinder<Int> 
   init(_ binder: ValueBinder<Int>) {
      countBinder = binder
      countBinder.register(self) { newValue in
         Swift.print("Binding detected change: \(newValue)")
      }
   }
   
   func userPressed() {
      countBinder.wrappedValue += 1
   }
}

更新 ValueBinder 值

任何拥有 ValueBinder 对象的对象都可以更新包装值。

_countBinder.wrappedValue += 1

所有已注册更改回调的对象都将收到值更改的通知。

ValueBinding PropertyWrapper

ValueBindingValueBinder 类型的属性包装器实现。 感谢 Mx-Iris 分享他们的实现。

示例

@ValueBinding var countValue = 0

// Register a block for updates
$countValue.register { newValue in 
   Swift.print("countValue is now \(newValue)")
}

countValue = 4  // triggers the update callback
// prints "countValue is now 4"

// Register for combine updates
$countValue.publisher?.publisher
   .receive(on: DispatchQueue.global(qos: .background))
   .sink { newValue in
      // Do something with 'newValue'
   }
   .store(in: &subscribers)

KeyPathBinder

KeyPathBinderValueBinder 的一种特殊化,可以跟踪动态 keypath。

// The dynamic property to bind to. This might be (for example) bound to a control from interface builder.
@objc dynamic var state: NSControl.State = .on

// Our binding object
lazy var boundKeyPath: KeyPathBinder<MyViewController, NSControl.StateValue> = {
   return try! .init(self, keyPath: \.buttonState) { newValue in
      Swift.print("boundKeyPath notifies change: \(String(describing: newValue))")
   }
}()

EnumKeyPathBinder

EnumKeyPathBinder 是一个 keypath binder,用于观察 Swift enum 类型。

我遇到了这样一种情况:我试图在工具栏的大小模式上使用 KeyPathBinder,该模式的类型为 NSToolbar.SizeMode,但一直失败。 但是,绑定到控件上的 NSControl.StateValue 却可以正常工作。

结果是 NSControl.StateValue 虽然看起来一个枚举,但实际上是一个结构体,而 NSToolbar.SizeMode 是一个枚举(特别是 RawRepresentable)。 当尝试观察枚举类型更改时会出现问题,因此该类是 KeyPathBinder 的一种特殊化,专门用于观察此类枚举类型。

Combine

两种 binder 类型都公开了一个属性 publisher,您可以将其连接到您的 combine 工作流程。

如果操作系统不支持 Combine,则 publisher 属性将为 nil。

let binder = ValueBinder(0) 
...
let cancellable = binder.publisher?.sink { newValue in
   // do something with `newValue`
}

许可

MIT License

Copyright (c) 2023 Darren Ford

Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.