组合:不能将 .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 阻止这种情况的原因。您想要做的是更新 selfpassengers.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:如果被分配给的对象也是可取消对象的所有者,请使用基于弱自块的版本。