【问题标题】:Can I receive a callback whenever an NSPasteboard is written to?每当写入 NSPasteboard 时,我可以收到回调吗?
【发布时间】:2011-02-17 18:55:10
【问题描述】:

我读过 Apple 的 Pasteboard Programming Guide,但它没有回答我的特定问题。

我正在尝试编写一个 Cocoa 应用程序(适用于 OS X,而不是 iOS),它将跟踪写入 general pasteboard 的所有内容(因此,每当任何应用程序复制和粘贴时,而不是拖-and-drops,它也使用 NSPasteboard)。我可以(几乎)通过不断地在后台线程上轮询通用粘贴板并检查changeCount来实现这一点。当然,这样做会让我觉得内心很肮脏。

我的问题是,有没有办法让粘贴板服务器在任何时候对通用粘贴板进行更改时通过某种回调通知我?我在 NSPasteboard 类参考中找不到任何内容,但我希望它潜伏在其他地方。

我可以想象实现此目的的另一种方法是,如果有一种方法可以将通用粘贴板实现替换为 NSPasteboard 的子类,我可以定义自己以发出回调。也许这样的事情是可能的?

如果公共的、App Store 合法的 API 可以做到这一点,我会非常喜欢,但如果需要使用私有 API,我也会接受。

谢谢!

【问题讨论】:

  • 不是一个答案,但如果您在监视粘贴板,请注意以下事项:有一个非正式协议可以在粘贴板上标记瞬态和应用程序生成的数据:nspasteboard.org

标签: cocoa macos nspasteboard


【解决方案1】:

不幸的是,唯一可用的方法是轮询(嘘!)。没有通知,也没有什么可观察到更改的粘贴板内容。查看 Apple 的 ClipboardViewer sample code 以了解他们如何处理检查剪贴板。添加一个(希望不是过分热心的)计时器来继续检查差异,你就有了一个基本的(如果笨拙的)解决方案,应该是应用商店友好的。

bugreporter.apple.com 提交增强请求以请求通知或其他一些回调。不幸的是,它最早要等到下一个主要操作系统版本才会对您有所帮助,但现在它正在轮询,直到我们都要求他们给我们更好的东西。

【讨论】:

  • 我害怕那个。谢谢! :)
  • 自 2.5 年前有什么变化?
  • 自 3 年前以来有什么变化吗?
  • 到目前为止我还没有看到任何内容,但考虑到最新版本(10.10 Yosemite 和 iOS 8),粘贴板可能会保持简单,因为现在有更现代、更多内容 - Apple 正在吹捧在应用程序(和设备)之间传递信息的感知机制。突然间,对于那些对管理临时模糊存储空间感兴趣的应用来说,粘贴板似乎不再是一个目标...... ;-)
  • @Supertecnoboff 看起来是这样,可悲的是。没有允许回调的新 API。
【解决方案2】:

曾经有一个邮件列表上的帖子描述了反对通知 API 的决定。虽然我现在找不到。最重要的是,可能有太多的应用程序会注册该 api,即使他们真的不需要。如果您随后复制某些内容,整个系统会疯狂地浏览新的剪贴板内容,从而为计算机创建大量工作。所以我认为他们不会很快改变这种行为。整个 NSPasteboard API 也是围绕使用 changeCount 在内部构建的。因此,即使是您自定义的 NSPasteboard 子类也必须继续轮询。

如果你真的想检查粘贴板是否改变了,只要持续观察 changeCount 半秒。比较整数真的很快,所以这里真的没有性能问题。

【讨论】:

  • 假设甚至需要“瞬时”。我想知道是否真的有必要记录每一个变化(因此需要像孩子一样反复询问令人讨厌的资源浪费问题,那就是每隔该死的一秒问一句,“我们还在那里吗???!!!”)。 ;-) 当用户调用您的服务时(否则您将成为一个浪费资源的爱管闲事的应用程序),是查看的好时机。如果您必须捕获后台更新(也许是日记),也许每隔几秒钟就可以了。如果用户在几秒钟内复制了两次,第一次可能是他们后来纠正的错误......
  • 老实说,向 NSPasteboard 询问 changeCount 并比较两个整数有什么浪费?我同意这种行为在九十年代初和之前是浪费的,但现在不是。
  • 现代笔记本电脑的功耗对您来说并不重要?如今,让您的应用进入睡眠状态至关重要,除了一个旨在记录放置在粘贴板上的每一件事情的应用,无论多么短暂(包括意外和立即更正的副本) ),没有充分的理由经常轮询。当用户真正需要信息时进行轮询(例如当您的应用被调用/前台时),除非您能够证明非常非常好的理由为什么您的应用需要耗尽笔记本电脑的电池。
  • 每半秒轮询一次并不会完全耗尽电池电量,特别是如果它只是检查更改计数。我刚刚创建了一个测试项目,它显示出零能源影响。唯一有用的场景是用于构建剪贴板历史记录的应用程序。
  • 在一篇相关的文章中(这使我看到了这篇文章),我提到了关于“唯一原因”你可能需要如此频繁地投票的相同的事情。但是重复计时器会导致应用程序被周期性地唤醒以进行工作,绝不允许它长时间“打盹”。这就是常识所暗示的,但您似乎已经证明并非如此。你介意在某个地方分享这个项目吗? (我保证,完全是友好的专业兴趣,而不是单方面的技巧。:-))
【解决方案3】:

根据 Joshua 提供的答案,我想出了类似的实现,但很快,这里是其要点的链接:PasteboardWatcher.swift

代码 sn-p 来自同一:

class PasteboardWatcher : NSObject {

    // assigning a pasteboard object
    private let pasteboard = NSPasteboard.generalPasteboard()

    // to keep track of count of objects currently copied
    // also helps in determining if a new object is copied
    private var changeCount : Int

    // used to perform polling to identify if url with desired kind is copied
    private var timer: NSTimer?

    // the delegate which will be notified when desired link is copied
    weak var delegate: PasteboardWatcherDelegate?

    // the kinds of files for which if url is copied the delegate is notified
    private let fileKinds : [String]

    /// initializer which should be used to initialize object of this class
    /// - Parameter fileKinds: an array containing the desired file kinds
    init(fileKinds: [String]) {
        // assigning current pasteboard changeCount so that it can be compared later to identify changes
        changeCount = pasteboard.changeCount

        // assigning passed desired file kinds to respective instance variable
        self.fileKinds = fileKinds

        super.init()
    }
    /// starts polling to identify if url with desired kind is copied
    /// - Note: uses an NSTimer for polling
    func startPolling () {
        // setup and start of timer
        timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: Selector("checkForChangesInPasteboard"), userInfo: nil, repeats: true)
    }

    /// method invoked continuously by timer
    /// - Note: To keep this method as private I referred this answer at stackoverflow - [Swift - NSTimer does not invoke a private func as selector](http://stackoverflow.com/a/30947182/217586)
    @objc private func checkForChangesInPasteboard() {
        // check if there is any new item copied
        // also check if kind of copied item is string
        if let copiedString = pasteboard.stringForType(NSPasteboardTypeString) where pasteboard.changeCount != changeCount {

            // obtain url from copied link if its path extension is one of the desired extensions
            if let fileUrl = NSURL(string: copiedString) where self.fileKinds.contains(fileUrl.pathExtension!){

                // invoke appropriate method on delegate
                self.delegate?.newlyCopiedUrlObtained(copiedUrl: fileUrl)
            }

            // assign new change count to instance variable for later comparison
            changeCount = pasteboard.changeCount
        }
    }
}

注意: 在共享代码中,我试图确定用户是否复制了 文件 url 与否,提供的代码可以很容易地修改为其他一般 目的。

【讨论】:

  • 我今天刚看到这个并投了赞成票。一个不错的,简单的解决方案。建议:要么在初始化时需要一个委托(因此它不是可选的),要么在委托上实现didSet,以在给出委托时创建/启动计时器,如果委托被带走则停止/销毁。您还应该使委托变弱以避免保留周期。这样,如果委托人离开,您就可以避免资源消耗。 (您当前的使用可能无法实现,但请考虑“重用”)。
【解决方案4】:

没有必要轮询。粘贴板通常只会在当前视图处于非活动状态或没有焦点时更改。粘贴板有一个计数器,当内容更改时会递增。当窗口重新获得焦点(windowDidBecomeKey)时,检查 changeCount 是否已更改,然后进行相应处理。

这不会捕获所有更改,但让您的应用程序在粘贴板激活时做出响应。

在 Swift 中...

var pasteboardChangeCount = NSPasteboard.general().changeCount
func windowDidBecomeKey(_ notification: Notification)
{   Swift.print("windowDidBecomeKey")
    if  pasteboardChangeCount != NSPasteboard.general().changeCount
    {   viewController.checkPasteboard()
        pasteboardChangeCount  = NSPasteboard.general().changeCount
    }
}

【讨论】:

  • 这是处理查找粘贴板的好方法。应用程序文本搜索工具栏/面板/等(您在键入 Cmd-F 时看到的内容)应该跟踪全局查找粘贴板。
【解决方案5】:

对于那些需要 Swift 5 中一些非常简化的版本的人,它就可以工作(基于@Devarshi 代码):

    func WatchPasteboard(copied: @escaping (_ copiedString:String) -> Void) {
        let pasteboard = NSPasteboard.general
        var changeCount = NSPasteboard.general.changeCount
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            if let copiedString = pasteboard.string(forType: .string) {
                if pasteboard.changeCount != changeCount {        
                    copied(copiedString)                
                    changeCount = pasteboard.changeCount
                }
            }
        }
    }

使用方法如下:

WatchPasteboard {
    print("copy detected : \($0)")
}

它会像下面这样打印出来..

watched : pasteboard1
watched : pasteboard2

【讨论】:

    【解决方案6】:

    对于更严格的情况,我有一个解决方案:检测您在 NSPasteboard 中的内容何时被其他内容替换。

    如果您创建一个符合NSPasteboardWriting 的类并将其与实际内容一起传递给-writeObjects:NSPasteboard 将保留此对象,直到其内容被替换。如果没有对该对象的其他强引用,则将其释放。

    这个对象的释放是新NSPasteboard得到新内容的时刻。

    【讨论】:

    • 这不适用于系统中缓存/唯一的内容。想想NSString 常量、NSIndexPath 等。它们会在应用程序的整个生命周期中保留。还有许多其他情况,由于“原因”,某些东西在预期寿命之外被保留在其他地方。请不要做任何取决于何时释放 else 的事情。仅当它在已释放实例的 dealloc/deinit 本身内时。
    • 我说的是自定义类,而不是系统类。无论如何,你怎么可能为系统类覆盖-dealloc?有有效的用例将对象的生命周期与回调绑定。
    • 我的意思是你不能依赖解除分配作为其他事情应该发生的迹象。出于多种原因,这是一个坏主意。
    • 您可以依赖解除分配作为对象正在结束其生命周期的标志。这样,您可以将对象的生命周期与数据库中的条目或外部资源生命周期等联系起来。Objective-C 的内存管理是确定性的,可以通过这种方式可靠地使用。
    猜你喜欢
    • 2021-08-06
    • 2013-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-10
    • 2021-11-28
    相关资源
    最近更新 更多