Kevin
Kevin

Reputation: 1233

Return a value inside a function after getting value from firebase storage closure Swift

I have a function which downloads data from firebase:

func downloadData(path: String) -> Data? {
    let data: Data?

    let storage = Storage.storage()
    storage.reference(forURL: path).getData(maxSize: 1024 * 1024) { (result, error) in

    if let error = error {
        print("\(error)")
    } else {
        data = result
        print("finished")
    }

    return data // <-- this is called before closue ends
}

but it is not returning the value from storage closure. I tried using a dispatch group

 func downloadData(path: String) -> Data? {
     ...
     let group = DispatchGroup()
     group.enter()
     storage.reference(forURL: path).getData(maxSize: 1024 * 1024) { (result, error) in 
         ...
         ...
         group.leave()
     }

    group.notify(queue: .main) {
        return data // <-- build time error! the function doesn't return a value
    }
}

so I tried a different approach: instead of group.notify() I used:

group.wait()

but it didn't work also. the whole app was frozen and nothing happened

and I tried another one:

let semaphore = DispatchSemaphore(value: 0)


storage.reference(forURL: path).getData(maxSize: 1024 * 1024) { (result, error) in 
    ...
    ...
    semaphore.signal()
}

semaphore.wait()

return data // <-- nothing happens... it is not being executed

Any ideas on how to figure this out?

UPDATE

I tried using the completion option but it still didn't wait until it is downloded.

this is my outerFunction:

    var soundData: Data?
    var imageData: Data?
    downloadData(path: soundPath) { sound in 
        soundData = sound

        downloadData(path: imageData) { image in
             imageData = image
        }

    }

    doMoreStuff() // <-- called before completeion blocks executed

Upvotes: 1

Views: 183

Answers (3)

Frankenstein
Frankenstein

Reputation: 16341

You should use completion block for data instead of returning the data, here's how:

func downloadData(path: String, completion: @escaping ((Data?) -> Void)?) {
    ...
    let group = DispatchGroup()
    group.enter()
    var data: Data?
    storage.reference(forURL: path).getData(maxSize: 1024 * 1024) { (result, error) in
        ...
            ...
                data = dataReceived
                group.leave()
    }

    group.notify(queue: .main) {
        completion?(data)
    }
}

Upvotes: 1

Cyber Gh
Cyber Gh

Reputation: 300

You can't really do that without some kind of completion closure, because you'd be blocking the thread which calls the function, probably the UI thread.

Another alternative would be to use Apple's new Combine framework and return a Future

import Combine

func downloadData(path: String) -> Future<Data, Error> {
    return Future<Data, Error> { promise in
        let storage = Storage.storage()
        storage.reference(forURL: path).getData(maxSize: 1024 * 1024) { (result, error) in
            if let error = error {
                promise(.failure(error))
            } else {
                promise(.success(result))
            }

        }
    }
}

var bag = Set<AnyCancellable>()

downloadData(path: "test")
    .subscribe(on: DispatchQueue.main)
    .sink(receiveCompletion: { (res) in
        <#code#>
    }) { (data) in
        <#code#>
    }
.store(in: &bag)

Upvotes: 1

emrcftci
emrcftci

Reputation: 3514

Use completion instead of returning ->

func downloadData(path: String, _ completion: @escaping (Data?) -> Void) {
    let data: Data?

    let storage = Storage.storage()
    storage.reference(forURL: path).getData(maxSize: 1024 * 1024) { (result, error) in

        if let error = error {
            print("\(error)")
        } else {
            data = result
            print("finished")
        }
        completion(data)
    }
}

Usage of the downloadData(path:,_)

downloadData(path: "") { data in
    // Your data is in here 
    print("Data is:", data)
}

Upvotes: 1

Related Questions