2015-12-01 3 views
2

В моем приложении подкласса NSDocument критически важная аппаратная часть - пользователи действительно не хотят закрывать документ случайно! Итак, я внедрил canCloseDocumentWithDelegate…, чтобы показать NSAlert и спросить перед закрытием.Как реализовать метод NSDocument -canCloseDocumentWithDelegate: shouldCloseSelector: contextInfo: в Swift?

Теперь я пытаюсь реализовать эту же вещь в приложении, написанном в Swift.

Поскольку ответ поступает асинхронно, результат «должен закрыть» передается обратному вызову делегата, а не просто возвращается. В документации к -canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo:, он говорит:

shouldCloseSelector метод обратного вызова должна иметь следующую подпись:

- (void)document:(NSDocument *)doc shouldClose:(BOOL)shouldClose contextInfo:(void *)contextInfo

Так что, как есть 3 аргументов различных типов, я не могу использовать простые методы performSelector:withObject: - вам нужно использовать NSInvocation. Обратите внимание, что делегат имеет тип id, а подпись выше не отображается в каком-либо формальном протоколе - вы не можете просто вызвать метод в обычном режиме. (См. Это mailing list post, например, как это должно быть сделано)

Теперь проблема заключается в том, что NSInvocation не разрешено в Swift! См. Swift blog “What Happened to NSMethodSignature”:

Привлечение каркасов Cocoa к Swift дало нам уникальную возможность взглянуть на наши API-интерфейсы с новой перспективой. Мы нашли классы, которые нам не нравились цели Swift, чаще всего из-за приоритета, который мы придаем безопасности. Например, некоторые классы, связанные с вызовом динамического метода, не отображаются в Swift, а именно NSInvocation и NSMethodSignature.

Это звучит неплохо, но падает, когда простой API NSDocument требует еще NSInvocation! Реальное решение этой проблемы было бы для Apple, чтобы представить новый API canCloseDocument…, используя обратный вызов блока. Но пока это не произойдет, какое лучшее решение?

ответ

2

Вы можете решить эту проблему с некоторыми функциями низкого времени выполнения уровня:

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 в свойствах, чтобы вы могли получить к ним доступ позже.

+0

Я могу подтвердить это! Вот мой примерный код, теперь использующий эту чистую реализацию Swift: https://github.com/DouglasHeriot/canCloseDocumentWithDelegate/commit/d664efdccabd4c049b60a99f70a0dc3bb866299e – DouglasHeriot

2

Итак, моим текущим решением является использование Objective-C для выполнения NSInvocation. Подкласс NSDocument написан в Swift и вызывает категорию Objective-C для выполнения этой части работы.

С NSInvocation не существует в Swift, я действительно не вижу другого пути.

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

+0

Я надеюсь, что кто-то может придумать лучшее решение, но я сомневаюсь в этом. Я подал ошибку с Apple rdar: // 23714588. До этого ответа, когда Googling «canCloseDocumentWithDelegate» «Swift», было всего около 20 результатов, и ни один из них не имеет значения. Так что, надеюсь, это поможет кому-то застрять в будущем. – DouglasHeriot

-1

Вот Свифт решение этого вопроса, который я получил от компании Apple для разработчиков технической поддержки:

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() 
    } 
} 
+0

Не могли бы вы объяснить, как это поможет решить проблему? –

+0

Теперь вы можете видеть, что это используется в ветке моего repo-приложения 'apple-dts-solution'. https://github.com/DouglasHeriot/canCloseDocumentWithDelegate/blob/apple-dts-solution/canCloseDocumentWithDelegate/Document.swift – DouglasHeriot

+0

Я не уверен, насколько мне это нравится - чувствует себя грязно, чтобы заменить делегата собой, а затем просто сделайте свой собственное дело. Но, похоже, это работает, и это меньше кода. Я думаю, что я мог бы продолжать использовать решение @cjcaufield 'class_getMethodImplementation'. – DouglasHeriot