Skip to content

Instantly share code, notes, and snippets.

@the-eric-kwok
Created January 16, 2026 02:54
Show Gist options
  • Select an option

  • Save the-eric-kwok/e525dc7e182cc85c4cb598b244589ae8 to your computer and use it in GitHub Desktop.

Select an option

Save the-eric-kwok/e525dc7e182cc85c4cb598b244589ae8 to your computer and use it in GitHub Desktop.
Using NSKeyValueObserving in KMP

Place this file under composeApp/src/nativeInterop/cinterop/nskeyvalueobserving.def

# https://slack-chats.kotlinlang.org/t/528792/hello-is-it-possible-to-use-in-kotlin-addobserver-on-nsobjec#968aa67c-9166-41c6-b44c-d549a377c08e
package = platform.foundation
language = Objective-C
---
#import <Foundation/Foundation.h>

@protocol NSKeyValueObserving
@required
// https://developer.apple.com/documentation/objectivec/nsobject/1416553-observevalueforkeypath
- (void) observeValueForKeyPath:(NSString *)keyPath
    ofObject:(id)object
    change:(NSDictionary<NSKeyValueChangeKey, id> *)change
    context:(void *)context;
@end;

Then in composeApp/build.gradle.kts, add this:

kotlin {
    // iOS 平台配置:支持真机和模拟器
    listOf(
        iosX64(),           // x64 模拟器
        iosArm64(),         // 真机
        iosSimulatorArm64() // Apple Silicon 模拟器
    ).forEach { target ->
        target.compilations.getByName("main") {
            // 定义文件路径:src/nativeInterop/cinterop/nskeyvalueobserving.def
            // 参考文档:https://kotlinlang.org/docs/multiplatform-dsl-reference.html#cinterops
            val nskeyvalueobserving by cinterops.creating
        }
    }
}

Usage:

import platform.darwin.NSObject
import platform.darwin.NSObjectProtocol
import platform.Foundation.addObserver
import platform.Foundation.removeObserver
import platform.Foundation.NSKeyValueObservingOptionNew
import platform.foundation.NSKeyValueObservingProtocol
import platform.AVFAudio.AVAudioSession
import platform.AVFAudio.outputVolume

class Foo {
    private val volumeObserver = object : NSObject(), NSKeyValueObservingProtocol {
        override fun observeValueForKeyPath(
            keyPath: String?,
            ofObject: Any?,
            change: Map<Any?, *>?,
            context: COpaquePointer?
        ) {
            if (keyPath == "outputVolume")
                handleVolumeChange()
        }
    }
    
    private fun setupObserver() {
        AVAudioSession.sharedInstance().addObserver(
            observer = volumeObserver,
            forKeyPath = "outputVolume",
            options = NSKeyValueObservingOptionNew,
            context = null
        )
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment