2015-08-03 3 views
8

Итак, у меня есть объект PFFile из Parse, и я пытаюсь создать функцию, которая извлекает представление UIImage этого PFFile и возвращает его. Что-то вроде:iOS - Swift - функция, возвращающая асинхронно полученное значение

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 
} 

Однако проблема здесь очевидна. Я получаю нуль каждый раз, потому что функция getDataInBackroundWithBlock является асинхронной. Есть ли способ подождать, пока UIImage не будет восстановлен до возвращения переменной изображения? Я не знаю, является ли использование синхронного getData() эффективным способом в этом случае.

+1

Можно ждать ('NSCondition можно использовать), но это плохая идея, если ваш код вызова находится в основном потоке. Обычной стратегией было бы сделать что-то с изображением внутри обработчика завершения. –

+0

Если вы подождете, это не будет асинхронно. – EmilioPelaez

+0

Дубликат [Возвращаемое значение из обработчика завершения - Swift] (http://stackoverflow.com/questions/31608302/return-value-from-completion-handler-swift/31608684#31608684) –

ответ

11

Да, это возможно. Его называют closure, или, чаще всего, callback. A 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 
} 
+0

Я знаю, что такое закрытие;) Я думал о чем-то более сильном потоке, но я не знаю много о них, поэтому я не знаю, насколько это актуально в этом случае. Использование закрытия здесь просто кажется немного свернутым, потому что все, что я хочу сделать, это вернуть изображение для будущего использования, но я думаю, что это способ сделать это. –

+0

@itstrueimryan Вы * можете * поместить изображение в словарь для будущего использования, но я чувствую, что это, вероятно, будет лучшим способом сделать это. Если вы когда-либо хотели изменить, как и когда вы используете изображение, используя закрытие, вы должны будете изменить его код. – Jojodmo

+3

@itstrueimryan - Да, этот шаблон закрытия закрытия лучше всего. Технически вы должны блокировать поток до тех пор, пока сетевой запрос не будет выполнен (например, семафоры, условия и т. Д.), Но это ужасный шаблон (если вам нужен список причин, почему его плохая идея, сообщите нам об этом). Если вы ищете другие шаблоны, они включают асинхронные подклассы NSOperation с зависимостями (немного больше кода, но полезны в очень сложных сценариях), шаблоны делегат-протокола (более громоздкие, IMHO) или фьючерсы/обещания (нестандартные , сторонний шаблон для написания асинхронного кода). – Rob

3

То, что вы хотите, именно то, что делает обещание/будущее шаблон дизайна. В 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.

Это решает проблему возврата «nil» и также является одним из лучших методов обработки асинхронных процессов.

1

Я не рекомендую этот

Я написал небольшую библиотеку для Swift 2.0, который может конвертировать между синхронными и асинхронными методами, которые были бы то, что вы просите. Вы можете найти here. Обязательно прочитайте об отказе от ответственности, которая объясняет, в которой сценарии это нормально использовать его (скорее всего, не в вашем случае)

Вы бы иметь возможность использовать его как это:

func imageFromFile(file: PFFile) throws -> UIImage? { 
    let syncGet = toSync(file.getDataInBackgroundWithBlock) 
    let data = try syncGet() 
    return data.map(UIImage.init) 
} 
Смежные вопросы