Hello, is it possible to use in kotlin addObserver...
# kotlin-native
i
Hello, is it possible to use in kotlin addObserver on NSObject? I want to observe the property value of native object without success. I also tried to implement my own NSObject using this swift example as reference https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift. Is anyone have more success and want to share with me?
l
I started to explore it, but paused it because of priorities. The following thread might still help you: https://kotlinlang.slack.com/archives/C3SGXARS6/p1561321574260700?thread_ts=1561321574.260700&cid=C3SGXARS6
i
Thanks
s
NSObject observing is not supposed to work for properties declared in Kotlin.
i
Thanks for the answer. Is it possible to work with properties defined in objective c. Like in this example: https://developer.apple.com/documentation/avfoundation/media_assets_playback_and_editing/responding_to_playback_state_changes
s
I don’t know anything preventing this. Could you try and check?
i
I tried without success. Maybe I did not quite understand how this should be implemented.
Hi @svyatoslav.scherbina, sorry for the late reply, I didn’t have time for a detail explanation. The main problem is how to override
observeValueForKeyPath
function for catching new values. Idea warns me that ‘observeValueForKeyPath’ overrides nothing. Do you know how to do that? Kotlin code
Copy code
import kotlinx.cinterop.CValue
import platform.AVFoundation.*
import platform.CoreGraphics.CGRect
import platform.CoreGraphics.CGRectMake
import platform.Foundation.NSKeyValueObservingOptionNew
import platform.Foundation.NSURL
import platform.Foundation.addObserver
import platform.UIKit.UIView
import platform.darwin.NSObject

class PlayerOK {

    private var observer = Observer()
    private var asset: AVAsset
    private var player: AVPlayer
    private var playerItem: AVPlayerItem
    private var playerLayer: AVPlayerLayer = AVPlayerLayer()
    private val gcRect: CValue<CGRect> = CGRectMake(height = 100.0, width = 100.0, x = 20.0, y = 20.0)
    val view: UIView = UIView(gcRect)
    private val requiredAssetKeys = listOf("playable", "hasProtectedContent")

    init {
        val url =
            NSURL(string = "<https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8>")

        asset = AVAsset.assetWithURL(URL = url)

        // Create a new AVPlayerItem with the asset and an
        // array of asset keys to be automatically loaded
        playerItem = AVPlayerItem(asset = asset, automaticallyLoadedAssetKeys = requiredAssetKeys)

        // Register as an observer of the player item's status property
        playerItem.addObserver(observer, forKeyPath = "status", options = NSKeyValueObservingOptionNew, context = null)

        // Associate the player item with the player
        player = AVPlayer(playerItem = playerItem)

        playerLayer.player = player
        playerLayer.frame = view.bounds

        view.layer.addSublayer(playerLayer)
        player.play()
    }
}

class Observer : NSObject() {
    fun observeValueForKeyPath(
        keyPath: String?,
        ofObject: Any?,
        change: Map<Any?, *>?,
        context: kotlinx.cinterop.COpaquePointer?
    ) {
        println("keyPath $keyPath")
        println("ofObject $ofObject")
        println("change $change")
        println("context $context")
    }
}
Using in swift
Copy code
class ViewController: UIViewController {
    var c: PlayerOK?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        c = PlayerOK()
        self.view.addSubview(c!.view)
    }
}
Xcode error:
Copy code
iosApp[3203:1352905] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<sample.Observer: 0x2801446d0>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: status
Observed object: <AVPlayerItem: 0x28014ba00, asset = <AVURLAsset: 0x280368d00, URL = <https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8>>>
Change: {
    kind = 1;
    new = 1;
}
Context: 0x0'
*** First throw call stack:
(0x209d7127c 0x208f4b9f8 0x209c7b4b0 0x20a7bc9b4 0x20a7bcb08 0x20a7bee9c 0x20a7bc3bc 0x20fca9f18 0x102cab6f0 0x102cacc74 0x102cba6fc 0x209d02c1c 0x209cfdb54 0x209cfd0b0 0x20befd79c 0x23652c978 0x1027c989c 0x2097c28e0)
libc++abi.dylib: terminating with uncaught exception of type NSException
Also working example in swift:
Copy code
import Foundation
import AVFoundation
import AVKit

class PlayerOK {
    var observer:Observer = Observer()
    var url: URL?
    var asset: AVAsset?
    var player: AVPlayer?
    var playerItem: AVPlayerItem?
    var playerLayer: AVPlayerLayer?
    var view: UIView!
    
    // Key-value observing context
    private var playerItemContext = 3
    
    let requiredAssetKeys = [
        "playable",
        "hasProtectedContent"
    ]
    
    init(controller: UIViewController) {
        let rect = CGRect(x: 0, y: 100, width: 100, height: 100)
        view = UIView(frame: rect)
        // Create the asset to play
        url = URL(string: "<https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8>")
        asset = AVAsset(url: url!)
        
        // Create a new AVPlayerItem with the asset and an
        // array of asset keys to be automatically loaded
        playerItem = AVPlayerItem(asset: asset!,
                                  automaticallyLoadedAssetKeys: requiredAssetKeys)
        
        // Register as an observer of the player item's status property
        playerItem?.addObserver(observer,
                                forKeyPath: #keyPath(AVPlayerItem.status),
                                options: [.old, .new],
                                context: &playerItemContext)
        
        // Associate the player item with the player
        player = AVPlayer(playerItem: playerItem)
        
        playerLayer = AVPlayerLayer(player: player)
        playerLayer?.frame = view.bounds
        
        view.layer.addSublayer(playerLayer!)
        player?.play()
    }
}

class Observer: NSObject {
    override func observeValue(forKeyPath keyPath: String?,
                               of object: Any?,
                               change: [NSKeyValueChangeKey : Any]?,
                               context: UnsafeMutableRawPointer?) {
        
        if keyPath == #keyPath(AVPlayerItem.status) {
            let status: AVPlayerItem.Status
            if let statusNumber = change?[.newKey] as? NSNumber {
                status = AVPlayerItem.Status(rawValue: statusNumber.intValue)!
            } else {
                status = .unknown
            }
            
            // Switch over status value
            switch status {
            case .readyToPlay:
                print("readyToPlay")
                break
            // Player item is ready to play.
            case .failed:
                print("failed")
                break
            // Player item failed. See error.
            case .unknown:
                print("unknown")
                break
            // Player item is not yet ready.
            @unknown default:
                print("Error")
            }
        }
        
    }
}
ViewController:
Copy code
import UIKit

class ViewController: UIViewController {    
    
    var c: PlayerOK?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        c = PlayerOK(controller: self)
        self.view.addSubview(c!.view)
    }
}
s
Overriding
observeValueForKeyPath
method could be a bit tricky. It is originally declared as category method in Objective-C, thus imported as Kotlin extension which can’t be overridden. To workaround this, you can configure interop with custom
.def
file containing declaration of a protocol with this method, e.g. `observer.def`:
Copy code
language = Objective-C
---
#import <Foundation/Foundation.h>

@protocol Observer
@required
- (void)observeValueForKeyPath:(NSString *)keyPath 
    ofObject:(id)object 
    change:(NSDictionary<NSKeyValueChangeKey, id> *)change 
    context:(void *)context;
@end;
and then inherit this protocol in Kotlin.
i
Thanks, I will try it
It’s working. Thanks a lot
s
Thanks for experiments!
l
Can we do the same for the
dealloc
method in
NSObject
?
s
I don’t think so. Overriding
dealloc
is not possible for different reason.
l
What's this reason? Is there no way to use Obj-C KVO safely without memory leaks in Kotlin/Native? Only alternative is to use Obj-C?
s
Consider using Objective-C wrapper with weak reference to Kotlin object.
a
@Ivan Ilic how did your implementation end up, I'm still getting the
message was received but not handled
.
i
It had worked like is described. But it is before 4 years, I’m not sure that on this kotlin version works same.
a
Yeah it still works. Thanks
780 Views