darkginger
darkginger

Reputation: 690

Passing variable outside of a function for use in Swift 3

I'm new to Swift, and I want to 1) run a function that extracts a value from a JSON array (this part works) and 2) pass that variable into another function which will play that URL in my audio player.

My issue: I can't access that string stored in a variable outside the first function. Luckily, there's a bunch of questions on this (example), and they say to establish a global variable outside the function and update it. I have tried this like so:

var audio = ""

override func viewDidLoad() {
    super.viewDidLoad()

        let url = URL(string: "http://www.example.json")
            URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
                guard let data = data, error == nil else { return }

                let json: Any?
                do{
                    json = try JSONSerialization.jsonObject(with: data, options: [])
                }
                catch{
                    return
                }
                guard let data_list = json as? [[String:Any]] else {
                    return
                }
                // here's the important part
                if let foo = data_list.first(where: {$0["episode"] as? String == "Special Episode Name"}) {
                    // do something with foo

                    self.audio = (foo["audio"] as? String)!

                } else {
                    // item could not be found
                }

            }).resume()

    print(audio) // no errors but doesn't return anything
  1. I have confirmed the JSON extraction is working -- if I move that print(audio) inside the function, it returns the value. I just can't use it elsewhere.

  2. I originally tried it without the self. but returned an error.

Is there a better way to store this string in a variable so I can use it in another function?

EDIT: Trying new approach based on Oleg's first answer. This makes sense to me based on how I understand didSet to work, but it's still causing a thread error with the play button elsewhere.

  var audiotest = ""{
        didSet{
            // use audio, start player
            if let audioUrl = URL(string: audiotest) {

                let documentsDirectoryURL =  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

                let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)

                //let url = Bundle.main.url(forResource: destinationUrl, withExtension: "mp3")!

                do {
                    audioPlayer = try AVAudioPlayer(contentsOf: destinationUrl)

                } catch let error {
                    print(error.localizedDescription)
                }
            } // end player
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

            let url = URL(string: "http://www.example.com/example.json")
                URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
                    guard let data = data, error == nil else { return }

                    let json: Any?
                    do{
                        json = try JSONSerialization.jsonObject(with: data, options: [])
                    }
                    catch{
                        return
                    }

                    guard let data_list = json as? [[String:Any]] else {
                        return
                    }

                    if let foo = data_list.first(where: {$0["episode"] as? String == "Houston Preview"}) {
                        // do something with foo

                        self.audiotest = (foo["audio"] as? String)!


                    } else {
                        // item could not be found
                    }

                    print(self.audiotest)


                }).resume()

Upvotes: 0

Views: 479

Answers (2)

Oleg Sherman
Oleg Sherman

Reputation: 2802

The request for the data is asynchronous so the code that is inside the completionHandler block happens some time later (depending on the server or the timeout) , that’s why if you try to print outside the completionHandler actually the print func happens before you get the data.

There are couple of solution: 1. Add property observer to your audio property and start playing when it is set:

var audio = “”{
     didSet{
        // use audio, start player 
    }

}

2. Wrapping the request with a method that one of its parameters is a completion closure:

// the request
func fetchAudio(completion:(String)->()){
       // make request and call completion with the string inside the completionHandler block i.e. completion(audio)
}

// Usage
fetchAudio{ audioString in
     // dispatch  to main queue and use audioString
}

Upvotes: 1

Priya
Priya

Reputation: 749

Try this code. No need to take global variable if it is not being used in multiple function. you can return fetched URL in completion handler.

    func getAudioUrl(completionHandler:@escaping ((_ url:String?) -> Void)) {

        let url = URL(string: "http://www.example.json")
        URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
            guard let data = data, error == nil else { return }

            let json: Any?
            do{
                json = try JSONSerialization.jsonObject(with: data, options: [])
            }
            catch{
                return
            }
            guard let data_list = json as? [[String:Any]] else {
                return
            }
            // here's the important part
            if let foo = data_list.first(where: {$0["episode"] as? String == "Special Episode Name"}) {
                // do something with foo

                let audio = (foo["audio"] as? String)!
                completionHandler(audio)

            } else {
                // item could not be found
                completionHandler(nil)
            }

        }).resume()
    }

    func useAudioURL() {

        self.getAudioUrl { (url) in

            if let strUrl = url {

                // perform your dependant operation
                print(strUrl)
            }else {

                //url is nil
            }
        }
    }

Upvotes: 0

Related Questions