Подтвердить что ты не робот

Добавление наблюдателя для KVO без указателей с использованием Swift

В Objective-C я обычно использовал бы что-то вроде этого:

static NSString *kViewTransformChanged = @"view transform changed";
// or
static const void *kViewTransformChanged = &kViewTransformChanged;

[clearContentView addObserver:self
                       forKeyPath:@"transform"
                          options:NSKeyValueObservingOptionNew
                          context:&kViewTransformChanged];

У меня есть два перегруженных метода на выбор для добавления наблюдателя для KVO с той лишь разницей, что это аргумент контекста:

 clearContentView.addObserver(observer: NSObject?, forKeyPath: String?, options: NSKeyValueObservingOptions, context: CMutableVoidPointer)
 clearContentView.addObserver(observer: NSObject?, forKeyPath: String?, options: NSKeyValueObservingOptions, kvoContext: KVOContext)

Когда Swift не использует указатели, я не уверен, как разыменовать указатель на использование первого метода.

Если я создаю свою собственную константу KVOContext для использования со вторым методом, я завершаю ее с просьбой:

let test:KVOContext = KVOContext.fromVoidContext(context: CMutableVoidPointer)

EDIT: В чем разница между CMutableVoidPointer и KVOContext? Может ли кто-нибудь дать мне пример, как использовать их оба, и когда я буду использовать один над другим?

РЕДАКТИРОВАТЬ № 2: Dev в Apple просто разместил это на форумах: KVOContext уходит; используя глобальную ссылку, так как ваш контекст - это путь прямо сейчас.

4b9b3361

Ответ 1

Теперь, когда KVOContext ушел в Xcode 6 beta 3, вы можете сделать следующее. Определите глобальное (т.е. Не свойство класса) так:

let myContext = UnsafePointer<()>()

Добавить наблюдателя:

observee.addObserver(observer, forKeyPath: …, options: nil, context: myContext)

В наблюдателе:

override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: [NSObject : AnyObject]!, context: UnsafePointer<()>) {
    if context == myContext {
        …
    } else {
        super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
    }
}

Ответ 2

В настоящее время существует техническая поддержка рекомендованная в документации, которая заключается в создании частной изменяемой переменной и использования его адрес как контекст.

(Обновлено для Swift 3 на 2017-01-09)

// Set up non-zero-sized storage. We don't intend to mutate this variable,
// but it needs to be `var` so we can pass its address in as UnsafeMutablePointer.
private static var myContext = 0
// NOTE: `static` is not necessary if you want it to be a global variable

observee.addObserver(self, forKeyPath: …, options: [], context: &MyClass.myContext)

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
    if context == &myContext {
        …
    }
    else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
}

Ответ 3

Полный пример с использованием Swift:

//
//  AppDelegate.swift
//  Photos-MediaFramework-swift
//
//  Created by Phurg on 11/11/16.
//
//  Displays URLs for all photos in Photos Library
//
//  @see http://stackoverflow.com/info/30144547/programmatic-access-to-the-photos-library-on-mac-os-x-photokit-photos-framewo
//

import Cocoa
import MediaLibrary

// For KVO: https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID12
private var mediaLibraryLoaded = 1
private var rootMediaGroupLoaded = 2
private var mediaObjectsLoaded = 3

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!
    var mediaLibrary : MLMediaLibrary!
    var allPhotosAlbum : MLMediaGroup!


    func applicationDidFinishLaunching(_ aNotification: Notification) {

        NSLog("applicationDidFinishLaunching:");

        let options:[String:Any] = [
            MLMediaLoadSourceTypesKey: MLMediaSourceType.image.rawValue, // Can't be Swift enum
            MLMediaLoadIncludeSourcesKey: [MLMediaSourcePhotosIdentifier], // Array
        ]

        self.mediaLibrary = MLMediaLibrary(options:options)
        NSLog("applicationDidFinishLaunching: mediaLibrary=%@", self.mediaLibrary);

        self.mediaLibrary.addObserver(self, forKeyPath:"mediaSources", options:[], context:&mediaLibraryLoaded)
        NSLog("applicationDidFinishLaunching: added mediaSources observer");

        // Force load
        self.mediaLibrary.mediaSources?[MLMediaSourcePhotosIdentifier]

        NSLog("applicationDidFinishLaunching: done");

    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        NSLog("observeValue: keyPath=%@", keyPath!)
        let mediaSource:MLMediaSource = self.mediaLibrary.mediaSources![MLMediaSourcePhotosIdentifier]!

        if (context == &mediaLibraryLoaded) {
            NSLog("observeValue: mediaLibraryLoaded")
            mediaSource.addObserver(self, forKeyPath:"rootMediaGroup", options:[], context:&rootMediaGroupLoaded)
            // Force load
            mediaSource.rootMediaGroup

        } else if (context == &rootMediaGroupLoaded) {
            NSLog("observeValue: rootMediaGroupLoaded")
            let albums:MLMediaGroup = mediaSource.mediaGroup(forIdentifier:"TopLevelAlbums")!
            for album in albums.childGroups! {
                let albumIdentifier:String = album.attributes["identifier"] as! String
                if (albumIdentifier == "allPhotosAlbum") {
                    self.allPhotosAlbum = album
                    album.addObserver(self, forKeyPath:"mediaObjects", options:[], context:&mediaObjectsLoaded)
                    // Force load
                    album.mediaObjects
                }
            }

        } else if (context == &mediaObjectsLoaded) {
            NSLog("observeValue: mediaObjectsLoaded")
            let mediaObjects:[MLMediaObject] = self.allPhotosAlbum.mediaObjects!
            for mediaObject in mediaObjects {
                let url:URL? = mediaObject.url
                // URL does not extend NSObject, so can't be passed to NSLog; use string interpolation
                NSLog("%@", "\(url)")
            }
        }
    }

}