我正在尝试创建 SwiftUI 支持的自定义属性包装器,这意味着对相应属性值的更改会导致 SwiftUI 视图的更新。这是我所拥有的简化版本:
@propertyWrapper
public struct Foo: DynamicProperty {
@ObservedObject var observed: SomeObservedObject
public var wrappedValue: [SomeValue] {
return observed.value
}
}
Run Code Online (Sandbox Code Playgroud)
我看到即使 myObservedObject包含在我的自定义属性包装器中,SwiftUI 仍然会捕获更改,SomeObservedObject只要:
DynamicProperty不幸的是,文档很少,我很难判断这是否仅适用于当前的 SwiftUI 实现。
DynamicProperty(在 Xcode 中,而不是在线)的文档似乎表明这样的属性是从外部更改导致视图重绘的属性,但是无法保证当您使自己的类型符合此协议时会发生什么。
我可以期待这在未来的 SwiftUI 版本中继续工作吗?
为了实现应用程序代码的简洁外观,我为每个包含逻辑的视图创建了 ViewModel。
一个普通的 ViewModel 看起来有点像这样:
class SomeViewModel: ObservableObject {
@Published var state = 1
// Logic and calls of Business Logic goes here
}
Run Code Online (Sandbox Code Playgroud)
并像这样使用:
struct SomeView: View {
@ObservedObject var viewModel = SomeViewModel()
var body: some View {
// Code to read and write the State goes here
}
}
Run Code Online (Sandbox Code Playgroud)
当视图父级未更新时,这可以正常工作。如果父级的状态发生变化,这个视图会被重绘(在声明性框架中很正常)。但是ViewModel 也会被重新创建,并且之后不会保存状态。与其他框架(例如:Flutter)相比,这是不寻常的。
在我看来,ViewModel 应该保留,或者 State 应该保留。
如果我用一个@State属性替换 ViewModel并int直接使用(在本例中)它会保持持久化并且不会重新创建:
struct SomeView: View {
@State var state = 1
var body: some …Run Code Online (Sandbox Code Playgroud) I'm using Swift property wrappers to define something like:
@MyWrapper var foo: Int
Run Code Online (Sandbox Code Playgroud)
And in the implementation of the property wrapper, I'd like to access the name of the variable, foo, as a string. Something like this:
@propertyWrapper
public struct MyWrapper<Type> {
init() {
// Get access to "foo" -- name of var as String
}
}
Run Code Online (Sandbox Code Playgroud)
Suggestions?
我知道这@Binding是一个属性包装器,我相信这Binding<Type>是一种类型转换的形式,但实际上有什么区别?
例如声明一个 var,如下所示:
@Binding
var hidden: Bool
Run Code Online (Sandbox Code Playgroud)
相对
var hidden: Binding<Bool>
Run Code Online (Sandbox Code Playgroud) 我最近一直在试验 swift 属性包装器,想知道是否有任何方法可以将它们组合在一起以实现更加模块化的架构。例如:
@WrapperOne @WrapperTwo var foo: T
Run Code Online (Sandbox Code Playgroud)
查看文档一无所获。关于如何做到这一点的唯一参考是在这个 GitHub 页面(https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md)(下面的引用),这似乎在说有可能的。其他文章说他们很难撰写,但没有解释如何去做。但是,我无法理解它,如果有人可以向我展示一些关于如何实现它的示例代码(见帖子底部),我将不胜感激。
当为给定属性提供多个属性包装器时,包装器组合在一起以获得两种效果。例如,考虑的组合物
DelayedMutable和Copying:Run Code Online (Sandbox Code Playgroud)@DelayedMutable @Copying var path: UIBezierPath在这里,我们有一个可以延迟初始化的属性。当我们确实设置了一个值时,它将通过
NSCopying的 copy 方法进行复制。组合是通过将较晚的包装类型嵌套在较早的包装类型中来实现的,其中最内层的嵌套类型是原始属性的类型。对于上面的示例,后备存储将是类型DelayedMutable<Copying<UIBezierPath>>,路径的合成 getter/setter 将查看 .wrappedValue 的两个级别:Run Code Online (Sandbox Code Playgroud)private var _path: DelayedMutable<Copying<UIBezierPath>> = .init() var path: UIBezierPath { get { return _path.wrappedValue.wrappedValue } set { _path.wrappedValue.wrappedValue = newValue } }请注意,这种设计意味着属性包装器组合不是可交换的,因为属性的顺序会影响嵌套的执行方式:@DelayedMutable @Copying var path1: UIBezierPath // _path1 has type DelayedMutable> @Copying @DelayedMutable var path2: UIBezierPath / / 错误:_path2 有格式错误的类型 Copying> 在这种情况下,类型检查器会阻止第二次排序,因为
DelayedMutable不符合NSCopying …
我希望将自定义属性包装器应用于已包装在 中的变量@Published,将它们像
(A) @Custom @Published var myVar或
(B) @Published @Custom var myVar
一样嵌套
(注意包装器的应用顺序)。
在(A)的情况下,我收到错误
'wrappedValue' is unavailable: @Published is only available on properties of classes
对于(B)
error: key path value type 'Int' cannot be converted to contextual type 'Updating<Int>'
两者都不是特别有帮助。有什么想法如何让它发挥作用吗?
import Combine
class A {
@Updating @Published var b: Int
init(b: Int) {
self.b = b
}
}
@propertyWrapper struct Updating<T> {
var wrappedValue: T {
didSet {
print("Update: \(wrappedValue)")
}
}
} …Run Code Online (Sandbox Code Playgroud) 我在 Swift 中创建了一个“锁”,并为我的 Swift 类创建了一个使用该锁的 Atomic 属性包装器,因为 Swift 缺少 ObjC 的atomic属性属性。
当我在启用线程清理器的情况下运行我的测试时,它总是捕获使用我的 Atomic 属性包装器的属性上的数据竞争。
唯一有效的是将属性包装器的声明更改为一个类而不是一个结构体,这里的主要问题是:为什么它有效!
我print在属性包装器中添加了s 并添加了 lockinit来跟踪创建的对象数量,它与 struct/class 相同,尝试在另一个项目中重现该问题,但也没有用。但是我会添加与问题类似的文件,并让我知道它为什么起作用的任何猜测。
锁
public class SwiftLock {
init() { }
public func sync<R>(execute: () throws -> R) rethrows -> R {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
return try execute()
}
}
Run Code Online (Sandbox Code Playgroud)
原子属性包装器
@propertyWrapper struct Atomic<Value> {
let lock: SwiftLock
var value: Value
init(wrappedValue: Value, lock: SwiftLock=SwiftLock()) {
self.value = wrappedValue
self.lock = …Run Code Online (Sandbox Code Playgroud) 我正在尝试制作一个类似于 的属性包装器Combine(Published满足我的项目需求),但能够通过向发布者发送存储在 中的值来修改包装的属性projectedValue,如下所示:
// in class
@PublishedMutable var foo = "foo"
$foo.send("bar")
// ...
Run Code Online (Sandbox Code Playgroud)
这是属性包装器的代码:
@propertyWrapper
struct PublishedMutable<Value> {
static subscript<T: ObservableObject>(
_enclosingInstance instance: T,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<T, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<T, Self>
) -> Value {
get {
instance[keyPath: storageKeyPath].storage
}
set {
let publisher = instance.objectWillChange
// This assumption is definitely not safe to make in
// production code, but it's fine for this demo purpose:
(publisher as? ObservableObjectPublisher)?.send()
instance[keyPath: storageKeyPath].storage = …Run Code Online (Sandbox Code Playgroud) swift combine property-wrapper property-wrapper-published swift-property-wrapper
我们现在有了一种创建惰性变量的新方法。它在swift-evolution/proposals/0258-property-wrappers.md 中有描述:
@propertyWrapper
enum Lazy<Value> {
case uninitialized(() -> Value)
case initialized(Value)
init(wrappedValue: @autoclosure @escaping () -> Value) {
self = .uninitialized(wrappedValue)
}
var wrappedValue: Value {
mutating get {
switch self {
case .uninitialized(let initializer):
let value = initializer()
self = .initialized(value)
return value
case .initialized(let value):
return value
}
}
set {
self = .initialized(newValue)
}
}
}
Run Code Online (Sandbox Code Playgroud)
它是线程安全的实现吗?如果没有,如何重现非线程安全行为?
generics lazy-evaluation lazy-initialization swift property-wrapper
我将模型作为已发布的变量保留在 ViewModel 中,并从视图中观察它。
\n当模型进程进入后台线程时,如果您发布模型值,Xcode 线程检查器将会做出反应。
\n\n\n不允许从后台线程发布更改;确保在模型更新时从主线程发布值 \xe2\x80\x8b\xe2\x80\x8b (通过像 receive (on :) 这样的运算符)。
\n
已发出。
\n@StateObject var viewModel = ViewModel()\nRun Code Online (Sandbox Code Playgroud)\nclass ViewModel: ObservableObject {\n\n @Published var model = Model()\n var thisValue:String {\n return model.thisValue // I want to use this value in view\n }\nRun Code Online (Sandbox Code Playgroud)\nstruct Model {\n var thisValue:String = "value" // I want to change this value on background threads.\nRun Code Online (Sandbox Code Playgroud)\n我想知道如何在主线程中接收模型值,但是\n我不太明白并提出了一个问题。
\n如果你能告诉我,我会很高兴。
\n