问题 Swift 4使用KVO收听音量变化


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

警告:

(基于块的KVO违规:首选基于新块的KVO API   使用Swift 3.2或更高版本时的keypath。 (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声称 这里 大多数属性应该是 dynamic (我知道这是AVPlayer而不是AVAudioSession)。我查了一下,却找不到任何东西 dynamic AVPlayer属性中的语句,并想知道它是如何工作的(如果我没有弄错,KVO需要这些工作)。

编辑:

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


6817
2017-09-20 13:58


起源

您的尝试似乎有效,但不要忘记使用或不使用观察功能的返回值。如果你不想使用,你可以这样做 _ = audioSession.observe(\.outputVolume) { (av, change) in print("volume \(av.outputVolume)") } - abdullahselek
我试过了,但不幸的是它似乎没有触发。由于某种原因,代码无法执行。也许我错过了一些东西。 - Eternal Black
这在模拟器中有效吗?因为我已经尝试了所有东西,并且从未调用过观察回调。 - Carlo Vespa


答案:


我假设你指的是这条线:

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

这种“动态”的使用与Objective-C不同 @dynamic 或斯威夫特的 dynamic。文档只是意味着“在这种情况下改变的属性”,并且它们告诉您AVPlayer通常非常符合KVO并且打算以这种方式观察。 “KVO兼容”意味着它遵循 改变通知 规则。有很多方法可以实现这一点,包括自动和手动。文档只是承诺AVPlayer。

(关于Cocoa的一个重要观点是它与许多其他系统的不同之处在于Cocoa按照惯例处理了许多事情。在代码中没有办法说“这是符合KVO的”并且编译器没有办法强制执行它,但是Cocoa开发人员往往非常善于遵守规则。当ARC开发时,它在很大程度上依赖于Cocoa开发人员多年来根据非常具体的规则命名方法的事实,这些规则指示了如何处理内存管理。它只是增加了编译器的执行规则Cocoa开发人员总是随手操作。这就是Cocoa开发人员对命名约定和大小写非常嘈杂的原因.Cocoa的主要部分完全依赖于遵循一致的命名规则。)

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

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

至于KVO是如何工作的,它是Cocoa中为数不多的真正“魔术”之一。如需快速介绍,请参阅 键值观察实现细节。简短版本是:

  • 观察对象时,会动态创建该对象的子类(是的,在运行时发明了一个新类)。
  • 子类添加了调用 willChangeValue... 和 didChangeValue... 围绕所有调用超类的属性访问器。
  • 对象是“ISA-swizzled”成为新课程。
  • 魔法! (好吧,不是真的很神奇;它只是代码,但这是一个很巧妙的方法。)

编辑: 最初的问题从未提及它不起作用。它不起作用的原因是因为你没有分配返回的 NSKeyValueObservation 在财产;你只是扔掉它。我很惊讶那里没有任何警告;我可以开雷达。

什么时候退回 NSKeyValueObservation 取消分配,观察消失,所以这会产生观察并立即摧毁它。您需要将其存储在属性中,直到您希望观察消失为止。


8
2017-09-20 14:10



那怎么解决这个问题呢? - João Nunes
谢谢你!我一整天都在努力奋斗。我得到了应有的一切,但直到我读完你的编辑才行。一旦你知道,这似乎很明显,但是现在在互联网上可用的新Swift 4 KVO的几句话中没有提到这一点。 +1为详细说明 - rmvz3
非常感谢您的深入解答。你是完全正确的,如果你知道背后的逻辑,这是有道理的。不幸的是,我首先将结果存储在变量而不是类属性中.... - Eternal Black


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

1