【问题标题】:Swift 4 Using KVO to listen to volume changesSwift 4 使用 KVO 监听音量变化
【发布时间】:2018-03-01 15:14:22
【问题描述】:

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

警告:

(基于块的 KVO 违规:更喜欢新的基于块的 KVO API 使用 Swift 3.2 或更高版本时的键路径。 (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 工作所必需的)。

编辑:

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

【问题讨论】:

  • 您的尝试似乎有效,但不要忘记使用或不使用观察函数的返回值。不想用的可以这样_ = audioSession.observe(\.outputVolume) { (av, change) in print("volume \(av.outputVolume)") }
  • 我试过了,但不幸的是它似乎没有触发。由于某种原因,代码没有被执行。也许我错过了什么。
  • 这在模拟器中有效吗?因为我已经尝试了所有方法,并且从未调用过观察回调。
  • 不,这在 siuulator 中不起作用。在设备上为我工作。
  • 我终于得到了这个工作(iOS 14/SDK 12)。正如其他人指出的那样,您需要在observe() 之前致电AVAudioSession.sharedInstance().setActive(true)。但是,就我而言,仅此一项并不能解决问题。我不得不在两个电话之间放置一些分隔。在init() 中设置会话处于活动状态,然后在稍后的方法中设置observe()。我相信dispatch_after 也会解决它。

标签: ios key-value-observing swift4 xcode9 swiftlint


【解决方案1】:

我假设你指的是这条线:

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

“动态”的这种用法与 Objective-C 的 @dynamic 或 Swift 的 dynamic 不同。在这种情况下,文档仅表示“发生变化的属性”,他们告诉您 AVPlayer 通常非常符合 KVO 并打算以这种方式观察。 “KVO 兼容”意味着它遵循change notification 规则。有很多方法可以实现这一点,包括自动和手动。文档只是承诺 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-heavy 上下文中,比如 Core Data)。它只是意味着“即使您无法在任何地方找到此属性的访问器实现,相信我,当它运行时,实现将可用。”这依赖于 ObjC 运行时动态生成实现或以程序员控制的方式分派的能力(这在 Swift 中仍然存在,通过操纵 ObjC 运行时,但它并不是真正的“Swift”特性)。

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

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

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

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

【讨论】:

  • 那么他是如何解决这个问题的呢?
  • 谢谢罗!我已经为此苦苦挣扎了一整天。我拥有应有的一切,但直到我阅读了您的编辑,它才起作用。一旦您知道,这似乎很明显,但在互联网上现在可用的关于新 Swift 4 KVO 的几句话中并没有提到这一点。 +1 详细说明
  • 非常感谢您的深入回答。你是完全正确的,如果有人知道背后的逻辑,这是有道理的。不幸的是,我起初将结果存储在变量中而不是类属性中......
【解决方案2】:

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

【讨论】:

  • 这在模拟器中也可以工作还是只在设备上工作?
  • @AdamFreeman KVO 通常在模拟器上工作。关于模拟器上的具体AVAudioSession.outputVolume,我从未尝试过:答案是由永恒的黑写的。
  • (如果在模拟器上不起作用,请随时向 Apple 报告)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-22
  • 1970-01-01
相关资源
最近更新 更多