如何实现NSDocument方法-canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo:在Swift中?

时间:2015-12-01 23:15:48

标签: objective-c swift cocoa appkit nsdocument

在我的应用程序中,ngMessages子类任务关键型硬件 - 用户真的不想意外关闭文档!所以,我已经实施NSDocument来展示canCloseDocumentWithDelegate…并在结束前询问。

我现在正试图在用Swift编写的应用程序中实现同样的东西。

由于答案是异步的,因此“应该关闭”结果会传递给委托上的回调,而不是简单地返回。在NSAlert的文档中,它说:

  

shouldCloseSelector回调方法应具有以下签名:

     

-canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo:

因此,由于有3个不同类型的参数,我不能使用简单的- (void)document:(NSDocument *)doc shouldClose:(BOOL)shouldClose contextInfo:(void *)contextInfo样式方法 - 您必须使用NSInvocation。请注意,委托的类型为performSelector:withObject:,上面的签名不会出现在任何正式协议中 - 您不能简单地调用该方法。 (请参阅此mailing list post,了解如何执行此操作)

现在问题是,Swift中不允许使用NSInvocation!请参阅Swift博客“What Happened to NSMethodSignature”

  

将Cocoa框架引入Swift给了我们一个独特的机会,以全新的视角审视我们的API。我们发现了一些我们认为不适合Swift目标的课程,最常见的是我们给予安全的优先权。例如,某些与动态方法调用相关的类未在Swift中公开,即idNSInvocation

这听起来像是一件好事,但是当一个简单的NSMethodSignature API需要NSInvocation时,它就会倒下!这整个问题的真正解决方案是Apple使用块回调引入新的NSDocument API。但在此之前,什么是最好的解决方案?

4 个答案:

答案 0 :(得分:4)

您可以使用一些低级运行时函数来解决此问题:

override func canCloseDocumentWithDelegate(delegate: AnyObject, shouldCloseSelector: Selector, contextInfo: UnsafeMutablePointer<Void>) {

    let allowed = true // ...or false. Add your logic here.

    let Class: AnyClass = object_getClass(delegate)
    let method = class_getMethodImplementation(Class, shouldCloseSelector)

    typealias signature = @convention(c) (AnyObject, Selector, AnyObject, Bool, UnsafeMutablePointer<Void>) -> Void
    let function = unsafeBitCast(method, signature.self)

    function(delegate, shouldCloseSelector, self, allowed, contextInfo)
}

如果您需要将此行为移至另一个方法(例如,在工作表获得用户确认后),只需将委托和shouldCloseSelector存储在属性中,以便稍后访问它们。

答案 1 :(得分:2)

因此,我目前的解决方案是继续使用Objective-C来执行NSInvocation。 NSDocument子类是用Swift编写的,并调用Objective-C类来完成这项工作。

由于Swift中不存在NSInvocation,我真的看不到任何其他方式。

- (void)respondToCanClose:(BOOL)shouldClose delegate:(id)delegate selector:(SEL)shouldCloseSelector contextInfo:(void *)contextInfo
{
    NSDocument *doc = self;

    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:shouldCloseSelector]];
    invocation.target = delegate;
    invocation.selector = shouldCloseSelector;
    [invocation setArgument:&doc atIndex:2]; // Note index starts from 2 - 0 & 1 are self & selector
    [invocation setArgument:&shouldClose atIndex:3];
    [invocation setArgument:&contextInfo atIndex:4];

    [invocation invoke];
}

您可以看到我的示例项目: https://github.com/DouglasHeriot/canCloseDocumentWithDelegate

另一个选择是使用Objective-C来回绕objc_msgSend,这在Swift中也是不可用的。 http://www.cocoabuilder.com/archive/cocoa/87293-how-does-canclosedocumentwithdelegate-work.html#87295

答案 2 :(得分:0)

至少从Swift 4.1开始,您可以执行以下操作:

// Application Logic
myDocument.canClose(
    withDelegate: self,
    shouldClose: #selector(MyClass.document(_:_:_:)),
    contextInfo: nil)

...

// Handler
@objc
private func document(_ doc: NSDocument, _ shouldClose: Bool, _ contextInfo: UnsafeMutableRawPointer) {
    ...
}

答案 3 :(得分:-1)

以下是我从Apple Developer Technical Support收到的关于此问题的Swift解决方案:

override func canCloseDocumentWithDelegate(delegate: AnyObject, shouldCloseSelector: Selector, contextInfo: UnsafeMutablePointer<Void>) {
    super.canCloseDocumentWithDelegate(self, shouldCloseSelector: "document:shouldClose:contextInfo:", contextInfo: contextInfo)
}

func document(doc:NSDocument, shouldClose:Bool, contextInfo:UnsafeMutablePointer<Void>) {
    if shouldClose {
        // <Your clean-up code>
        doc.close()
    }
}