组合:不能将 .assign 与结构一起使用 - 为什么?
Combine: can't use `.assign` with structs - why?
在尝试使用 Combine 赋值时,我看到了一些结构与 class 的行为,但我并不真正理解。
代码:
import Foundation
import Combine
struct Passengers {
var women = 0
var men = 0
}
class Controller {
@Published var passengers = Passengers()
var cancellables = Set<AnyCancellable>()
let minusButtonTapPublisher: AnyPublisher<Void, Never>
init() {
// Of course the real code has a real publisher for button taps :)
minusButtonTapPublisher = Empty<Void, Never>().eraseToAnyPublisher()
// Works fine:
minusButtonTapPublisher
.map { self.passengers.women - 1 }
.sink { [weak self] value in
self?.passengers.women = value
}.store(in: &cancellables)
// Doesn't work:
minusButtonTapPublisher
.map { self.passengers.women - 1 }
.assign(to: \.women, on: passengers)
.store(in: &cancellables)
}
}
我得到的错误是 Key path value type 'ReferenceWritableKeyPath<Passengers, Int>' cannot be converted to contextual type 'WritableKeyPath<Passengers, Int>'
。
使用 sink
而不是 assign
的版本工作正常,当我将 Passengers
变成 class 时,assign
版本也工作正常.我的问题是:为什么它只适用于 class?这两个版本(sink 和 assign)最终确实做了同样的事情,对吧?他们都在 passengers
.
上更新 women
属性
(当我将 Passengers
更改为 class 时,sink
版本将不再有效。)
实际上它已明确记录 - 将发布者中的每个元素分配给对象上的属性。这是 Assign
订阅者的一项功能和设计 - 仅适用于引用类型。
extension Publisher where Self.Failure == Never {
/// Assigns each element from a Publisher to a property on an object.
///
/// - Parameters:
/// - keyPath: The key path of the property to assign.
/// - object: The object on which to assign the value.
/// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream.
public func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable
}
就解释框架设计而言,Asperi 的回答是正确的。概念上的原因是,由于 passengers
是一个值类型,将其传递给 assign(to:on:)
会导致 传递给 assign
的 passengers 的副本被修改,这不会更新 class 实例中的值。这就是 API 阻止这种情况的原因。您想要做的是更新 self
的 passengers.women
属性,这就是您的闭包示例所做的:
minusButtonTapPublisher
.map { self.passengers.women - 1 }
// WARNING: Leaks memory!
.assign(to: \.passengers.women, on: self)
.store(in: &cancellables)
}
不幸的是,这个版本将创建一个保留周期,因为assign(to:on:)
持有对传递对象的强引用,而cancellables
集合持有强引用背部。请参阅 进一步讨论,但 tl;dr:如果被分配给的对象也是可取消对象的所有者,请使用基于弱自块的版本。
在尝试使用 Combine 赋值时,我看到了一些结构与 class 的行为,但我并不真正理解。
代码:
import Foundation
import Combine
struct Passengers {
var women = 0
var men = 0
}
class Controller {
@Published var passengers = Passengers()
var cancellables = Set<AnyCancellable>()
let minusButtonTapPublisher: AnyPublisher<Void, Never>
init() {
// Of course the real code has a real publisher for button taps :)
minusButtonTapPublisher = Empty<Void, Never>().eraseToAnyPublisher()
// Works fine:
minusButtonTapPublisher
.map { self.passengers.women - 1 }
.sink { [weak self] value in
self?.passengers.women = value
}.store(in: &cancellables)
// Doesn't work:
minusButtonTapPublisher
.map { self.passengers.women - 1 }
.assign(to: \.women, on: passengers)
.store(in: &cancellables)
}
}
我得到的错误是 Key path value type 'ReferenceWritableKeyPath<Passengers, Int>' cannot be converted to contextual type 'WritableKeyPath<Passengers, Int>'
。
使用 sink
而不是 assign
的版本工作正常,当我将 Passengers
变成 class 时,assign
版本也工作正常.我的问题是:为什么它只适用于 class?这两个版本(sink 和 assign)最终确实做了同样的事情,对吧?他们都在 passengers
.
women
属性
(当我将 Passengers
更改为 class 时,sink
版本将不再有效。)
实际上它已明确记录 - 将发布者中的每个元素分配给对象上的属性。这是 Assign
订阅者的一项功能和设计 - 仅适用于引用类型。
extension Publisher where Self.Failure == Never {
/// Assigns each element from a Publisher to a property on an object.
///
/// - Parameters:
/// - keyPath: The key path of the property to assign.
/// - object: The object on which to assign the value.
/// - Returns: A cancellable instance; used when you end assignment of the received value. Deallocation of the result will tear down the subscription stream.
public func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable
}
就解释框架设计而言,Asperi 的回答是正确的。概念上的原因是,由于 passengers
是一个值类型,将其传递给 assign(to:on:)
会导致 传递给 assign
的 passengers 的副本被修改,这不会更新 class 实例中的值。这就是 API 阻止这种情况的原因。您想要做的是更新 self
的 passengers.women
属性,这就是您的闭包示例所做的:
minusButtonTapPublisher
.map { self.passengers.women - 1 }
// WARNING: Leaks memory!
.assign(to: \.passengers.women, on: self)
.store(in: &cancellables)
}
不幸的是,这个版本将创建一个保留周期,因为assign(to:on:)
持有对传递对象的强引用,而cancellables
集合持有强引用背部。请参阅