结合:不能对结构使用`.assign`-为什么?

时间:2020-04-06 09:35:11

标签: swift combine

当尝试使用Combine分配值时,我看到了一些我不真正理解的struct vs类行为。

代码:

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转换为类时,assign的版本也可以正常工作。我的问题是:为什么它仅适用于班级?最后,这两个版本(接收器和分配)确实做相同的事情,对吗?它们都更新了women上的passengers属性。

(当我将Passengers更改为一个类时,sink版本将不再可用。)

2 个答案:

答案 0 :(得分:2)

实际上,它是明确记录的-将发布者中的每个元素分配给对象上的属性。这是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
}

答案 1 :(得分:2)

Asperi 的回答是正确的,因为它解释了框架的设计。概念上的原因是,由于 passengers 是一个值类型,将它传递给 assign(to:on:) 会导致 传递给 assign 的乘客副本 被修改,这将'不要更新类实例中的值。这就是 API 阻止这种情况的原因。您想要做的是更新 passengers.womenself 属性,这就是您的闭包示例所做的:

minusButtonTapPublisher
  .map { self.passengers.women - 1 }
    // WARNING: Leaks memory!
    .assign(to: \.passengers.women, on: self)
  .store(in: &cancellables)
}

不幸的是,此版本将创建一个保留循环,因为 assign(to:on:) 持有对传递对象的强引用,而 cancellables 集合持有强引用。请参阅 How to prevent strong reference cycles when using Apple's new Combine framework (.assign is causing problems) 以获取进一步讨论,但 tl;dr:如果分配给的对象也是可取消对象的所有者,则使用基于弱自块的版本。