iOS - Swift - функция, которая возвращает асинхронно полученное значение
Итак, у меня есть объект PFFile из Parse, и я пытаюсь создать функцию, которая получает UIImage-представление этого PFFile и возвращает его. Что-то вроде:
func imageFromFile(file: PFFile) -> UIImage? {
var image: UIImage?
file.getDataInBackgroundWithBlock() { (data: NSData?, error: NSError?) -> Void in
if error != nil {
image = UIImage(data: data!)
}
}
return image
}
Однако проблема здесь очевидна. Я собираюсь получить nil каждый раз, потому что функция getDataInBackroundWithBlock является асинхронной. Есть ли способ подождать, пока UIImage не будет получен, прежде чем возвращается переменная изображения? Я не знаю, является ли использование синхронного getData() эффективным способом в этом случае.
3 ответа
Да, это возможно сделать. Это называется closure
или чаще callback
, callback
по сути, это функция, которую вы можете использовать в качестве аргумента в других функциях. Синтаксис аргумента
functionName: (arg0, arg1, arg2, ...) -> ReturnType
ReturnType
обычно Void
, В вашем случае вы могли бы использовать
result: (image: UIImage?) -> Void
Синтаксис вызова функции с одним обратным вызовом
function(arg0, arg1, arg2, ...){(callbackArguments) -> CallbackReturnType in
//code
}
И синтаксис вызова функции с несколькими обратными вызовами (с отступом, чтобы было легче читать)
function(
arg0,
arg1,
arg2,
{(cb1Args) -> CB1Return in /*code*/},
{(cb2Args) -> CB2Return in /*code*/},
{(cb3Args) -> CB3Return in /*code*/}
)
Если функция экранирует функцию (вызывается после ее возврата), вы должны добавить @escaping перед типом аргумента
Вы захотите использовать один обратный вызов, который будет вызван после возврата функции и который содержит UIImage?
в результате.
Итак, ваш код может выглядеть примерно так
func imageFromFile(file: PFFile, result: @escaping (image: UIImage?) -> Void){
var image: UIImage?
file.getDataInBackgroundWithBlock() { (data: NSData?, error: NSError?) -> Void in
//this should be 'error == nil' instead of 'error != nil'. We want
//to make sure that there is no error (error == nil) before creating
//the image
if error == nil {
image = UIImage(data: data!)
result(image: image)
}
else{
//callback nil so the app does not pause infinitely if
//the error != nil
result(image: nil)
}
}
}
И чтобы назвать это, вы могли бы просто использовать
imageFromFile(myPFFile){(image: UIImage?) -> Void in
//use the image that was just retrieved
}
То, что вы хотите, это именно то, что делает модель обещания / будущего дизайна. В Swift есть много реализаций. Я буду использовать в качестве примера отличную библиотеку BrightFutures. ( https://github.com/Thomvis/BrightFutures)
Вот код:
func imageFromFile(file: PFFile) -> Future<UIImage> {
let promise = Promise<UIImage>()
file.getDataInBackgroundWithBlock() { (data: NSData?, error: NSError?) -> Void in
if error != nil {
image = UIImage(data: data!)
// As soon as the method completes this will be called
// and triggers the future.onSuccess in the caller.
promise.success(image)
} else {
// This would trigger future.onFailure in the caller
promise.failure(error)
}
}
return promise.future
}
Пояснение: в основном вы создаете "обещание", что в "будущем" будет результат. И вы возвращаете это обещание на будущее немедленно, до завершения асинхронного метода.
Вызывающая сторона этого метода будет обрабатывать это так:
func doSomethingWithTheImage() {
let future = imageFromFile(file: pffile)
future.onSuccess { image in
// do something with UIImage: image
}
future.onFailure { error in
// handle NSError: error
}
}
В обработчике onSuccess вы все делаете с успешно загруженным образом. Если есть ошибка, вы обрабатываете ее в обработчике onFailure.
Это решает проблему возврата "ноль", а также является одним из лучших методов обработки асинхронных процессов.
Я не рекомендую это
Я написал небольшую библиотеку для Swift 2.0, которая может конвертировать между синхронными и асинхронными методами, что и будет тем, о чем вы просите. Это можно найти здесь. Обязательно прочитайте заявление об отказе от ответственности, в котором объясняется, в каких сценариях его можно использовать (скорее всего, не в вашем случае).
Вы сможете использовать это так:
func imageFromFile(file: PFFile) throws -> UIImage? {
let syncGet = toSync(file.getDataInBackgroundWithBlock)
let data = try syncGet()
return data.map(UIImage.init)
}