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)
}
Другие вопросы по тегам