16

我刚刚更新到 Swift 4 和 Xcode 9,并收到以下代码的 (swiftlint) 警告,告诉我现在应该使用 KVO:

警告:

(基于块的 KVO 违规:在使用 Swift 3.2 或更高版本时,更喜欢带有键路径的新的基于块的 KVO API。(block_based_kvo))

旧代码:

override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?) {
    if keyPath == "outputVolume"{
        guard let newKey = change?[NSKeyValueChangeKey.newKey] as? NSNumber else {
            fatalError("Could not unwrap optional content of new key")
        }

        let volume = newKey.floatValue

        print("volume " + volume.description)
    }
}

我尝试修复:

let audioSession = AVAudioSession.sharedInstance()
    audioSession.observe(\.outputVolume) { (av, change) in
        print("volume \(av.outputVolume)")
}

苹果在这里声称大多数属性应该是dynamic(我知道这是 AVPlayer 而不是 AVAudioSession)。我查了一下,但在 AVPlayer 属性中找不到任何dynamic语句,并且想知道它是如何工作的(如果我没记错的话,这些是 KVO 工作所必需的)。

编辑:

我不确定它是否不会触发,因为它根本不起作用,或者是由于我尝试存档的原因。一般来说,我希望收到有关通过推动硬件音量摇杆触发的音量变化的通知。

4

2 回答 2

16

我假设您指的是以下行:

您可以使用键值观察 (KVO) 来观察玩家的许多动态属性的状态变化...

这种“动态”的使用与 Objective-C@dynamic或 Swift 的dynamic. 在这种情况下,文档仅表示“发生变化的属性”,他们告诉您 AVPlayer 通常非常符合 KVO 并打算以这种方式观察。“KVO 兼容”意味着它遵循变更通知规则。有很多方法可以实现这一点,包括自动和手动。文档只是承诺 AVPlayer 会这样做。

(关于 Cocoa 与许多其他系统的区别的一个重要点是 Cocoa “按约定”处理许多事情。没有办法在代码中说“这是 KVO 兼容的”,编译器也无法强制执行它,但是Cocoa 开发人员往往非常善于遵守规则。在开发 ARC 时,它在很大程度上依赖于 Cocoa 开发人员多年来命名方法遵循非常具体的规则来指示如何处理内存管理的事实。它只是添加了编译器强制执行规则 Cocoa 开发人员一直手动遵循。这就是为什么 Cocoa 开发人员对命名约定和大小写非常关注的原因。Cocoa 的主要部分完全依赖于遵循一致的命名规则。)

记住 AVPlayer 接口是一个恰好桥接到 Swift 的 Objective-C API,dynamic在这种情况下没有等效的 Swift 关键字。这是一个关键字,它告诉 Swift 这个属性可以被观察到,因此它的访问器不能被优化为静态调度。这不是 Objective-C 需要的(或可以做的;从这个意义上说,所有 ObjC 属性都是“动态的”)。

Objective-C@dynamic是完全不同的东西,只与 KVO 微弱相关(尽管它出现在很多 KVO 密集的上下文中,比如 Core Data)。它只是意味着“即使您无法在任何地方找到此属性的访问器实现,相信我,当它运行时,实现将可用。” 这依赖于 ObjC 运行时动态生成实现或以程序员控制的方式分派的能力(这在 Swift 中仍然存在,通过操纵 ObjC 运行时,但它并不是真正的“Swift”特性)。

至于 KVO 的工作原理,它是 Cocoa 中为数不多的真正“魔术”之一。如需快速介绍,请参阅Key-Value Observing Implementation Details。简短的版本是:

  • 当您观察一个对象时,会动态创建该对象的子类(是的,在运行时发明了一个新类)。
  • 子类添加对超类属性访问器的所有调用willChangeValue...及其didChangeValue...周围的调用。
  • 该对象是“ISA-swizzled”成为那个新类。
  • 魔法!(好吧,这不是真正的魔法;它只是代码,但它是一个技巧。)

编辑:最初的问题从未提到它不起作用。它不起作用的原因是您没有NSKeyValueObservation在属性中分配返回的值;你只是把它扔掉。我很惊讶没有警告。我可能会打开雷达。

当返回的NSKeyValueObservation解除分配时,观察消失,所以这会创建一个观察并立即销毁它。您需要将其存储在一个属性中,直到您希望观察消失。

于 2017-09-20T14:10:14.097 回答
7

OP的解决方案。

它需要存储在属性中。不是变量,_而是属性。否则它将无法正常工作。像这样:

class YourViewController: UIViewController {

    var obs: NSKeyValueObservation?

    override func viewDidLoad() {
        super.viewDidLoad()

        let audioSession = AVAudioSession.sharedInstance()
        self.obs = audioSession.observe( \.outputVolume ) { (av, change) in
            print("volume \(av.outputVolume)")
        }
    }
} 
于 2018-07-20T07:27:22.120 回答