将较旧的 KVO 转换为 Swift 4

Converting Older KVO to Swift 4

我正在尝试将一些旧的 WWDC swift 代码转换为 Swift 4. 我认为我已经完成了所有工作,除了最后一点执行一些 KVO。很难将其缩小到最后一点,因为一切似乎都像示例代码一样运行 - 但这些 KVO 方法不会在 Swift 4 中调用。我在这里找到了:Open Radar Bug

Swift 4 表示以下内容的方法是什么?

// 使用 KVO 机制表明对 "state" 的更改也会影响其他属性

class func keyPathsForValuesAffectingIsReady() -> Set<NSObject> {
    return ["state" as NSObject]
}

class func keyPathsForValuesAffectingIsExecuting() -> Set<NSObject> {
    return ["state" as NSObject]
}

class func keyPathsForValuesAffectingIsFinished() -> Set<NSObject> {
    return ["state" as NSObject]
}

下面是示例中的变量定义:

    override var isReady: Bool {
    switch state {

        case .initialized:
            // If the operation has been cancelled, "isReady" should return true
            return isCancelled

        case .pending:
            // If the operation has been cancelled, "isReady" should return true
            guard !isCancelled else {
                return true
            }

            // If super isReady, conditions can be evaluated
            if super.isReady {
                evaluateConditions()
            }

            // Until conditions have been evaluated, "isReady" returns false
            return false

        case .ready:
            return super.isReady || isCancelled

        default:
            return false
    }
}

override var isExecuting: Bool {
    return state == .executing
}

override var isFinished: Bool {
    return state == .finished
}

如果需要更多代码,请告诉我。

如果这是一个重复的问题,请link在此处复制。我一直找不到解决方案。

  • keyPathsForValuesAffecting… 成员可以是属性而不是方法。
  • 必须声明它们 @objc 因为 KVO 系统使用 Objective-C 运行时访问属性。
  • 属性的类型应为 Set<String>
  • 如果您使用 #keyPath 指令,当您使用了无效的键路径时(例如由于拼写错误或 属性 名称更改),编译器会告诉您.

因此:

@objc class var keyPathsForValuesAffectingIsReady: Set<String> {
    return [#keyPath(state)]
}

@objc class var keyPathsForValuesAffectingIsExecuting: Set<String> {
    return [#keyPath(state)]
}

@objc class var keyPathsForValuesAffectingIsFinished: Set<String> {
    return [#keyPath(state)]
}

您还需要确保您的 state 属性 已声明 @objc dynamic

(NS)Operation 严重依赖 NSObject-KVO

最接近的Swift语法是

@objc private class func keyPathsForValuesAffectingIsReady() -> Set<String> {
    return [#keyPath(state)]
}

@objc private class func keyPathsForValuesAffectingIsExecuting() -> Set<String> {
    return [#keyPath(state)]
}

@objc private class func keyPathsForValuesAffectingIsFinished() -> Set<String> {
    return [#keyPath(state)]
}

旁注:您可能需要使 state 线程安全。

主要问题是 KVO 是使用 Objective-C 构建的,它使用 Objective-C 运行时来检测 keyPathsForValuesAffecting 方法的存在。在 Swift 4 中,如果您不在方法上包含 @objc 注释,则默认情况下方法不再公开给 Objective-C。因此,简而言之,添加 @objc 注释可能会解决您的问题。

我做的另一件事——不是绝对必要的,但它使代码看起来更好一些——是将它们声明为静态常量。 @objc 将导致这些作为 class 方法暴露给 Objective-C,所以它一切正常,而且稍微干净一些。我也喜欢把 private 放在它们上面,因为它们永远不会被 Swift 代码调用,而且没有必要弄乱你的 class 的内部 and/or public 界面.

您还需要确保您的 state 属性 符合 KVO 标准,并在更改时发送通知。您可以通过设置 属性 dynamic 来完成此操作,这将导致 KVO 系统自动为您生成通知调用,或者您可以手动调用 willChangeValue(for:)didChangeValue(for:) (或基于字符串的版本,willChangeValue(forKey:)didChangeValue(forKey:))在 willSetdidSet 处理程序中用于 属性.

最后,如果可以避免,请不要在 Swift 中使用原始字符串键路径。 #keyPath() 机制是获取基于字符串的键路径的首选方法(并且对于这些遗留的 Objective-C 方法以外的需要使用字符串的用途,您应该使用新的 KeyPath 类型更好)。但是,如果您的 state 属性 不是 Objective-C 兼容的类型,您就会被旧的字符串键路径所困(在这种情况下,您将在 willSetdidSet,如前一段所述)。或者,您可以创建一个虚拟 Any 类型的对象来镜像您的 state 属性,纯粹用于 KVO 目的。

所以,像这样:

@objc private static let keyPathsForValuesAffectingIsReady: Set<String> = [
    #keyPath(state)
]

现在,state 属性。如果它是 Objective-C 兼容类型,那很简单:

@objc dynamic var state: ...

或者,如果不是:

@objc var state: SomeNonObjCThing {
    willSet { self.willChangeValue(forKey: "state") }
    didSet { self.didChangeValue(forKey: "state") }
}

或:

@objc private var _stateKVO: Any { return self.state }
var state: SomeNonObjCThing {
    willSet { self.willChangeValue(for: \.stateKVO) }
    didSet { self.didChangeValue(for: \.stateKVO) }
}
// you can now use #keyPath(_stateKVO) in keyPathsForValuesAffecting...