8

So I have a PFFile object from Parse, and I'm trying to create a function that retrieves the UIImage representation of that PFFile and returns it. Something like:

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
}

However, the problem here is obvious. I'm going to get nil every time because the getDataInBackroundWithBlock function is asynchronous. Is there any way to wait until the UIImage has been retrieved before the image variable is returned? I don't know if using the synchronous getData() is an efficient way to go in this case.

Ryan Bobrowski
  • 2,641
  • 3
  • 31
  • 52
  • 1
    It's possible to wait (`NSCondition can be used) but it's a bad idea if your calling code is on the main thread. The usual strategy would be to do something with the image inside the completion handler. – Phillip Mills Aug 03 '15 at 18:48
  • If you wait, then it wouldn't be asynchronous. – EmilioPelaez Aug 03 '15 at 18:49
  • Duplicate of [Return value from completion handler - Swift](http://stackoverflow.com/questions/31608302/return-value-from-completion-handler-swift/31608684#31608684) – Hamza Ansari Aug 04 '15 at 13:28

3 Answers3

15

Yes, it is possible to do this. Its called a closure, or more commonly a callback. A callback is essentially a function that you can use as an argument in another functions. The syntax of the argument is

functionName: (arg0, arg1, arg2, ...) -> ReturnType

ReturnType is usually Void. In your case, you could use

result: (image: UIImage?) -> Void

The syntax of calling a function with one callback in it is

function(arg0, arg1, arg2, ...){(callbackArguments) -> CallbackReturnType in
    //code
}

And the syntax of calling a function with several callbacks is (indented to make it easier to read)

function(
    arg0, 
    arg1,
    arg2,
    {(cb1Args) -> CB1Return in /*code*/},
    {(cb2Args) -> CB2Return in /*code*/},
    {(cb3Args) -> CB3Return in /*code*/}
)

If your callback function escapes the main function (the callback is called after the main function returns), you must add @escaping in front of the callback's argument type

You're going to want to use a single callback that will be called after the function returns and that contains UIImage? as the result.

So, your code could look something like this

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)
        }
    }
}

And to call it, you could simply use

imageFromFile(myPFFile){(image: UIImage?) -> Void in
    //use the image that was just retrieved
}
Jojodmo
  • 23,357
  • 13
  • 65
  • 107
  • I know what a closure is ;) I was thinking something more along the lines of a thread blocker but I don't know much about them so I don't know if that is relevant in this case. Using a closure here just seems to convolute things a little bit since all I want to do is return the image for future use but I guess this is the way to do it. – Ryan Bobrowski Aug 03 '15 at 19:08
  • @itstrueimryan You *could* put the image in a dictionary for future use, but I feel like this would probably be the best way to do it. If you ever wanted to change how or when you use the image, using a closure would make it so you have to change less code – Jojodmo Aug 03 '15 at 19:10
  • 3
    @itstrueimryan - Yes, this completion closure pattern is best. Technically, you _could_ block the thread until the network request is done (e.g., using semaphores, conditions, etc.), but that's a horrible pattern (if you need a list of reasons why its a bad idea, let us know). If you're looking for other patterns, they include asynchronous `NSOperation` subclasses with dependencies (a little more code, but useful in very complicated scenarios), delegate-protocol patterns (more cumbersome, IMHO) or futures/promises (non-standard, third-party pattern for writing asynchronous code). – Rob Aug 03 '15 at 19:32
  • Now it's important making result: @escaping (image: UIImage?) -> Void because by default the completion closure is a non escaping one. – abanet Jul 06 '17 at 08:08
3

What you want is exactly what the promise/future design pattern does. There are many implementations in Swift. I will use as an example the excellent BrightFutures library. (https://github.com/Thomvis/BrightFutures)

Here the code:

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
}

Explanation: what you basically do is creating a "promise" that there will be a result in the "future". And you are returning this future-promise immediately, before the async method completes.

The caller of this method will handle it like this:

func doSomethingWithTheImage() {
    let future = imageFromFile(file: pffile)

    future.onSuccess { image in
        // do something with UIImage: image
    }

    future.onFailure { error in
        // handle NSError: error
    }
}

In the onSuccess handler you are doing all the stuff with the successfully downloaded image. If there is an error you handle it in the onFailure handler.

This solves the problem of returning "nil" and is also one of the best practices to handle asynchronous processes.

Darko
  • 9,655
  • 9
  • 36
  • 48
1

I do not recommend this

I have written a small library for Swift 2.0 which can convert between synchronous and asynchronous methods, which would be what you're asking for. It can be found here. Be sure to read the disclaimer which explains in which scenarios it's okay to use it (most likely not in your case)

You'd be able to use it like this:

func imageFromFile(file: PFFile) throws -> UIImage? {
    let syncGet = toSync(file.getDataInBackgroundWithBlock)
    let data = try syncGet()
    return data.map(UIImage.init)
}
Kametrixom
  • 14,673
  • 7
  • 45
  • 62