0

I'm trying to create this function using Swift on Xcode 8. And after I've got the url using the firebase function, I cannot assign the url value to imageURL.

I've tried a workaround - putting the imageURL outside of the function, and assign the url function to self.imageURL and it works perfectly. However, I'm trying to make this a static function, so I cannot use that workaround. Does anyone know how to solve this?

func createImageDownloadURL(path: String) -> URL? {
    // Create a reference to the file you want to download
    let storage = FIRStorage.storage()
    let storageRef = storage.reference()
    let imageRef = storageRef.child(path)
    var imageURL: URL?

    // Fetch the download URL

    imageRef.downloadURL { (url: URL?, error: Error?) in
        if error != nil {
            // Handle any errors
            print(error!)
        } else {
            // Get the download URL for image
            DispatchQueue.main.async {
                // Run UI Updates
                print(imageURL)  // output looks perfectly fine
                imageURL = URL(string: url!.absoluteString) // This is where the problem is
            }
        }
    }
    print(imageURL) // output is "nil"
    return imageURL
}
KENdi
  • 7,576
  • 2
  • 16
  • 31
Henry Ngan
  • 572
  • 1
  • 4
  • 24

1 Answers1

2

This is what you need :)

func createImageDownloadURL(path: String,completionBlock : @escaping (URL?) -> ()) {
    // Create a reference to the file you want to download
    let storage = FIRStorage.storage()
    let storageRef = storage.reference()
    let imageRef = storageRef.child(path)
    var imageURL: URL?

    // Fetch the download URL

    imageRef.downloadURL { (url: URL?, error: Error?) in
        if error != nil {
            // Handle any errors
            print(error!)
        } else {
            // Get the download URL for image
            DispatchQueue.main.async {
                // Run UI Updates
                print(imageURL)  // output looks perfectly fine
                imageURL = URL(string: url!.absoluteString) // This is where the problem is
                completionBlock(imageURL)
            }
        }
    }
}

Whats wrong in your code ?

imageRef.downloadURL { and DispatchQueue.main.async { both gets executed asynchronously. So return imageURL gets executed even before any those blocks finishes executing :)

Solution :

Solution 1:

You can make use of closures :) You can accept a block/closure as one your function parameter and execute it asynchronously when you get the imageURL :)

Solution 2:

You can make use of Protocol/delegate patter and call the delegate methods and pass imageURL to delegate :)

Solution 3:(Not suggested)

If you want to return at any cost (not suggested though) use semaphores and block the execution of thread and once you have the imageURL release the semaphore and execute return statement :)

Sandeep Bhandari
  • 19,999
  • 5
  • 45
  • 78
  • @Henry ngan your imageRef.downloadURL always have a return type void so it never return anything answer by Sandeep is correct use completion blocks.:) – Tushar Sharma Mar 14 '17 at 06:19
  • @tushar-sharma : Thank you :) – Sandeep Bhandari Mar 14 '17 at 06:21
  • omg thanks you guy so much!!! That was quick! I used your code and discovered that I don't need to use `DispatchQueue.main.async` and it works just fine, or should I still use it? And I need to add `@escaping` in front of `(URL?) -> ())` otherwise it would post an error. Make the changes and I will accept your answer :) Thanks a lot guuuuys!!!! – Henry Ngan Mar 14 '17 at 06:31
  • @henry-ngan : Done buddy :) about your question should I still use DispatchQueue.main.async depends on few things 1. Are u updating UI once you receive the imageURL and expect the function passing completion block to update UI only then u might consider changing to main thread else not needed. 2. if imageRef.downloadURL {} if it calls its completion block always in main thread and you want ur completion block to execute on main thread then no if it returns in any other thread then yes you have to :) Please note context switching is always costly so choose dispatch_async carefully :) – Sandeep Bhandari Mar 14 '17 at 06:36
  • I'm still confused. Where exactly is the imageURL being returned and how can I call it in another view? – phast Feb 11 '22 at 19:46
  • when I call createImageDownloadURL() what do I use for path and completionBlock? Also, is path even necessary if it's not being used in the function? – phast Feb 11 '22 at 19:47