Swift4 使用KVO监听音量变化

Swift 4 Using KVO to listen to volume changes

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

警告:

(Block Based KVO Violation: Prefer the new block based KVO API with keypaths when using Swift 3.2 or later. (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)")
}

Apple 声称 here 大多数属性应该是 dynamic(我知道这是 AVPlayer 而不是 AVAudioSession)。我查了一下,但在 AVPlayer 属性中找不到任何 dynamic 语句,我想知道它怎么可能工作(如果我没记错的话,KVO 需要这些才能工作)。

编辑:

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

我假设你指的是行:

You can use Key-value observing (KVO) to observe state changes to many of the player’s dynamic properties...

"dynamic" 的这种用法与 Objective-C 的 @dynamic 或 Swift 的 dynamic 不同。在这种情况下,文档仅表示 "properties that change",并且他们告诉您 AVPlayer 通常非常符合 KVO,并且旨在以这种方式进行观察。 "KVO compliant" 表示它遵循 change notification 规则。有很多方法可以实现这一点,包括自动和手动。文档只是承诺 AVPlayer 可以。

(Cocoa 区别于许多其他系统的重要一点是 Cocoa 处理很多事情 "by convention"。代码中没有办法说 "this is KVO compliant"并且编译器没有办法强制执行它,但是 Cocoa 开发人员往往非常善于遵守规则。当开发 ARC 时,它在很大程度上依赖于 Cocoa 开发人员多年的事实命名方法遵循指示如何处理内存管理的非常具体的规则。它只是增加了编译器对规则的执行 Cocoa 开发人员一直手动遵循的规则。这就是为什么 Cocoa 开发人员对命名约定非常吵闹,大写。Cocoa 的主要部分完全依赖于遵循一致的命名规则。)

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

Objective-C @dynamic 是完全不同的东西,与 KVO 的关系很弱(尽管它出现在很多 KVO 密集的上下文中,比如 Core Data)。它只是意味着 "even though you can't find an accessor implementation for this property anywhere, trust me, by the time this runs an implementation will be available." 这依赖于 ObjC 运行时动态生成实现或以程序员控制的方式分派的能力(这仍然存在于 Swift 中,通过操纵 ObjC 运行时,但它不是确实是 "Swift" 功能)。

至于 KVO 的工作原理,它是 Cocoa 中为数不多的 "magic tricks" 之一。有关快速介绍,请参阅 Key-Value Observing Implementation Details。简短版本是:

  • 当您观察一个对象时,会动态创建该对象的子class(是的,在运行时发明了一个新的class)。
  • subclass 在所有对 superclass 的 属性 访问器的调用中添加了对 willChangeValue...didChangeValue... 的调用。
  • 对象 "ISA-swizzled" 是那个新的 class。
  • 神奇! (好吧,不是真的魔术;它只是代码,但它是一个相当大的技巧。)

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

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

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)")
        }
    }
}