【问题标题】:Adding observer for KVO without pointers using Swift使用 Swift 为没有指针的 KVO 添加观察者
【发布时间】:2014-08-02 06:15:00
【问题描述】:

在 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 即将消失;使用全局引用作为您的上下文是现在的方法。

【问题讨论】:

  • 您是在问如何创建 CMutableVoidPointer?
  • 我已经编辑了我的帖子以更准确。

标签: pointers swift key-value-observing


【解决方案1】:

现在官方有一种技术recommended in the documentation,就是创建一个私有可变变量,并使用它的地址作为上下文。

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

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • UnsafePointer&lt;()&gt;() 是一个 null 指针。使用空指针作为上下文不会为您提供特别可靠的唯一值。 (我意识到现在这已经过时了——我的回答也是如此——只是为了完整起见,应该提到这一点。)
    【解决方案3】:

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

    我一直在寻找更改为基于块的 KVO 的答案,因为我收到了一个 swiftlint 警告,我需要将很多不同的答案拼凑在一起才能找到正确的解决方案。 Swiftlint 警告:

    基于块的 KVO 违规:在使用 Swift 3.2 或更高版本时,更喜欢带有键路径的新的基于块的 KVO API。 (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]

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 问题是关于上下文 API,顺便说一下,它支持子类化,而基于块的不支持。
      【解决方案5】:

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

      【讨论】:

      • 你能解释一下这个完整的例子吗?
      猜你喜欢
      • 2011-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-22
      • 2012-01-16
      • 1970-01-01
      相关资源
      最近更新 更多