使用Swift添加没有指针的KVO观察者

时间:2014-06-12 02:35:58

标签: pointers swift key-value-observing

在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)
编辑:CMutableVoidPointer和KVOContext有什么区别?有人可以给我一个例子,说明如何使用它们以及何时使用它们?

编辑#2:Apple的一个开发者刚刚将这个发布到了论坛:KVOContext正在消失;使用全局引用作为您的上下文是现在的方法。

5 个答案:

答案 0 :(得分:53)

现在有一种技术正式explanation,即创建私有可变变量并使用其地址作为上下文。

(2017-01-09更新为Swift 3)

return

答案 1 :(得分:16)

现在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 :(得分:3)

Swift 4-观察UITableViewController弹出窗口上的contentSize变化以修正不正确的大小

我一直在寻找将答案更改为基于块的KVO的答案,因为我得到了警告,并且花了我很多不同的答案才能得出正确的解决方案。 Swiftlint警告:

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

我的用例是在视图控制器的导航栏中显示一个附加到按钮的弹出控件,然后在其显示后调整其大小-否则它将太大而无法容纳该弹出内容。弹出框本身是一个包含静态单元格的UITableViewController,并通过带有样式弹出框的Storyboard Segue显示。

要设置基于块的观察器,您需要在弹出式UITableViewController内部添加以下代码:

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

触发观察者时,我不需要先前的值,因此在创建观察者时省略了options: [.new, .old]

答案 3 :(得分:1)

Swift 4更新

基于块的观察器功能不需要上下文,并且现有的#keyPath()语法已替换为smart keypath以实现快速类型安全。

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

答案 4 :(得分:-1)

使用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/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)")
            }
        }
    }

}