Home > Software design >  How to get Swift KVO working for static member?
How to get Swift KVO working for static member?

Time:08-14

I have a UIViewController with the following code. I want to know when the value of portrait effect is changed (in control center). I have tried AVCaptureDevice.isPortraitEffectEnabled and .portraitEffectEnabled, both have the same result: observeValue() is never called. I have verified that the value itself does actually change, and the docs state that KVO is supported for this member.

What am I missing?

To test this I am toggling the value of portaitEffectEnabled by calling AVCaptureDevice.showSystemUserInterface(.videoEffects) and turning it on/off, and expecting the KVO to fire.

@objc class EventSettingsCaptureViewController : UIViewController, ... {

    required init(...) {
        super.init(nibName: nil, bundle: nil)

        if #available(iOS 15.0, *) {
            AVCaptureDevice.self.addObserver(self, forKeyPath: "portraitEffectEnabled", options: [.new], context: nil)
        }
    }

    deinit {
        if #available(iOS 15.0, *) {
            AVCaptureDevice.self.removeObserver(self, forKeyPath: "portraitEffectEnabled", context: nil)
        }
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {

        // Breakpoint set here: never hits
        if keyPath == "portraitEffectEnabled" {
            guard let object = object as? AVCaptureDevice.Type else { return }

            if #available(iOS 15.0, *) {
                WLog("isPortraitEffectEnabled changed: \(object.isPortraitEffectEnabled)")
            }

        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }

CodePudding user response:

That won’t work because the AVCaptureDevice class itself doesn’t have a portraitEffectSupported property.

The issue is that the portraitEffectSupported property is an instance property.

you can always use class_copyPropertyList to double check that the property you’re trying to observe actually exists on that object. Here's an example:

import AVFoundation

func getPropertyNames(of target: AnyObject) -> [String] {
    let itsClass: AnyClass = object_getClass(target)!
    
    var count = UInt32()
    guard let p = class_copyPropertyList(itsClass, &count) else {
        return []
    }

    defer { p.deallocate() }
    
    let properties = UnsafeBufferPointer(start: p, count: Int(count))
    
    return properties.map { String(cString: property_getName($0)) }
}

// `AVCaptureDevice` has no class properties.
let propertiesOfTheClassItself = getPropertyNames(of: AVCaptureDevice.self)
print(propertiesOfTheClassItself) // => []

// Instances of `AVCaptureDevice` have some instance properties.
let propertiesOfASampleInstance = getPropertyNames(of: AVCaptureDevice.default(for: .video)!)
print(propertiesOfASampleInstance) // => ["transportControlsSupported", "transportControlsPlaybackMode", "transportControlsSpeed", "adjustingFocus", "adjustingExposure", "adjustingWhiteBalance"]
  • Related