Я совершенно беспомощен прямо сейчас, я пытаюсь отправить электронное письмо для загрузки данных в приложении iOS Swift 4. Я нашел библиотеку Obj-C Mailcore, которая работает в обычном коде, например, в обратном вызове нажатия кнопки. Но если я пытаюсь использовать тот же код в моей функции выборки AppDelegate, он не возвращается из завершенияHandler.
Операция отправки Mailcore является асинхронной, поэтому я использовал оболочку семафора вокруг нее, но обработчик завершения никогда не вызывается.
Редактировать:
Видимо не работает код в кнопке с Семафором. Только если я удалю
let _ = semaphore.wait(timeout: DispatchTime.distantFuture)
часть.
Так есть ли другой способ дождаться завершения асинхронного завершения блока?
Да, посмотрите мой ответ :-)
Мой код:
let smtpSession = MCOSMTPSession()
[...]
let sendOperation = smtpSession.sendOperation(with: rfc822Data!)
[...]
let semaphore = DispatchSemaphore(value: 0)
if (sendOperation != nil) {
print("Starting sendOperation...")
sendOperation?.start { (error) -> Void in
if (error != nil) {
sendingError = true
print("Error sending email: \(error)")
} else {
print("Successfully sent email!")
}
sendingDone = true
semaphore.signal()
}
let _ = semaphore.wait(timeout: DispatchTime.distantFuture)
} else {
print("Cant init sendOperation")
sendingError = true
}
@Warren Burton Я не уверен, но я просто скопировал и вставил его из кода кнопки, где он работал, поэтому меня это не беспокоило. Я проверю это, но не произойдет ли сбой, если я вызову start () для объекта nil?
sendOperation выглядит как Optional из вашего кода. Вы можете видеть это из sendOperation?, используя необязательную цепочку.
@ Уоррен Бертон. Теперь я понимаю, что вы имеете в виду. Я проверю, равно ли оно нулю.
@WarrenBurton Это не ноль, я добавил проверку и запуск выполняется.





Завершение либо происходит в том же потоке, либо запускает что-то в этом потоке.
Обычно нужно не ждать, а вместо этого делать все, что вам нужно, после завершения самого завершения.
Если вы должны сделать это с семафором, переместите вызов start в другой поток.
Итак, я нашел решение:
SyncManager.swift
import Foundation
/// A generic Cocoa-Style completion handler
typealias CompletionHandler = (Error?) -> Void
/// Provides syncronous access to results returned by
/// asynchronous processes with completion handlers
class SyncMaker {
var result: Error? = nil
/// Generates a synchronous-compliant completion handler
func completion() -> CompletionHandler{
return {
(error: Error?) in
// Store result, return control
self.result = error
CFRunLoopStop(CFRunLoopGetCurrent())
}
}
// Perform task (that must use custom completion handler) and wait
func run(_ task: @escaping () -> Void) -> Error? {
task()
CFRunLoopRun()
return result
}
}
AppDelegate.swift (частично)
[...]
let syncMaker = SyncMaker()
let result = syncMaker.run {
sendOperation?.start(
syncMaker.completion())
}
if (result != nil) {
print("Error sending email: \(result)")
} else {
sendingError = false
print("Successfully sent email!")
}
[...]
Вы уверены, что
sendOperationне равен нулю при вызовеstart? Нет ничего особенного в объекте, который принимаетUIApplicationDelegate.