pointers swift key-value-observing

pointers - Agregando observador para KVO sin punteros usando Swift



key-value-observing (5)

Actualización para Swift 4

El contexto no es necesario para la función de observador basada en bloques y la sintaxis #keyPath () existente se reemplaza con la ruta de acceso inteligente para lograr una seguridad de tipo rápido.

class EventOvserverDemo { var statusObserver:NSKeyValueObservation? var objectToObserve:UIView? func registerAddObserver() -> Void { statusObserver = objectToObserve?.observe(/UIView.tag, options: [.new, .old], changeHandler: {[weak self] (player, change) in if let tag = change.newValue { // observed changed value and do the task here on change. } }) } func unregisterObserver() -> Void { if let sObserver = statusObserver { sObserver.invalidate() statusObserver = nil } } }

En Objective-C, normalmente usaría algo como esto:

static NSString *kViewTransformChanged = @"view transform changed"; // or static const void *kViewTransformChanged = &kViewTransformChanged; [clearContentView addObserver:self forKeyPath:@"transform" options:NSKeyValueObservingOptionNew context:&kViewTransformChanged];

Tengo dos métodos sobrecargados para elegir para agregar un observador para KVO con la única diferencia que es el argumento de contexto:

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

Como Swift no usa punteros, no estoy seguro de cómo eliminar la referencia de un puntero para usar el primer método.

Si creo mi propia constante KVOContext para usarla con el segundo método, termino preguntando por esto:

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

EDIT: ¿Cuál es la diferencia entre CMutableVoidPointer y KVOContext? ¿Puede alguien darme un ejemplo de cómo usar ambos y cuándo usaría uno sobre el otro?

EDITAR # 2: Un desarrollador en Apple acaba de publicar esto en los foros: KVOContext se va; El uso de una referencia global como su contexto es el camino a seguir en este momento.


Swift 4: observación del cambio de tamaño del contenido en la ventana emergente UITableViewController para corregir el tamaño incorrecto

Había estado buscando una respuesta para cambiar a un KVO basado en bloque porque recibía una advertencia rápida y me tomó unir varias respuestas diferentes para llegar a la solución correcta. Advertencia de Swiftlint:

Block Based KVO Violation: Prefer the new block based KVO API with keypaths when using Swift 3.2 or later. (block_based_kvo).

Mi caso de uso fue presentar un controlador emergente conectado a un botón en una barra de navegación en un controlador de vista y luego cambiar el tamaño de la ventana emergente una vez que se muestra; de lo contrario, sería demasiado grande y no se ajustaría al contenido de la ventana emergente. El popover en sí mismo era un UITableViewController que contenía celdas estáticas, y se mostraba a través de un segmento de Storyboard con estilo popover.

Para configurar el observador basado en bloques, necesita el siguiente código dentro de su UITableViewController emergente:

// class level variable to store the statusObserver private var statusObserver: NSKeyValueObservation? // Create the observer inside viewWillAppear override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) statusObserver = tableView.observe(/UITableView.contentSize, changeHandler: { [ weak self ] (theTableView, _) in self?.popoverPresentationController?.presentedViewController.preferredContentSize = theTableView.contentSize }) } // Don''t forget to remove the observer when the popover is dismissed. override func viewDidDisappear(_ animated: Bool) { if let observer = statusObserver { observer.invalidate() statusObserver = nil } super.viewDidDisappear(animated) }

No necesitaba el valor anterior cuando se activó el observador, por lo que options: [.new, .old] las options: [.new, .old] al crear el observador.


Ahora hay una técnica recomendada oficialmente en la documentación , que consiste en crear una variable mutable privada y usar su dirección como contexto.

(Actualizado para Swift 3 el 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) } }


Ahora que KVOContext se ha ido en Xcode 6 beta 3, puede hacer lo siguiente. Defina un global (es decir, no una propiedad de clase) así:

let myContext = UnsafePointer<()>()

Añadir un observador:

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

En el observador:

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


Ejemplo completo usando Swift:

// // AppDelegate.swift // Photos-MediaFramework-swift // // Created by Phurg on 11/11/16. // // Displays URLs for all photos in Photos Library // // @see http://.com/questions/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)") } } } }