0

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() 
5
  • Put print(audio) inside your block Commented Jan 24, 2018 at 5:50
  • Hey @Priya, that would print; however, to clarify, my goal: use this string stored in the var in another function inside ViewDidLoad. Maybe I am missing something, but if possible, I need to be able to print outside the block to make it work (as I want to pass it as a URL in another function that will let the user download the audio from the link). Does that make more sense? Commented Jan 24, 2018 at 5:54
  • dataTask works asynchronously. pass it as a URL in another function inside the completion block. Commented Jan 24, 2018 at 5:56
  • I think you want to use audio after you get response from API. For that you need to use method which takes parameter of completionHandler or Block Commented Jan 24, 2018 at 5:57
  • Yeah, experimenting now. The audio player keeps turning up thread errors when I run it inside the block. Not sure why. Will keep testing. Commented Jan 24, 2018 at 6:04

2 Answers 2

1

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 } 
Sign up to request clarification or add additional context in comments.

3 Comments

Oleg, your solution 1 works for me and I learned something. Now understand why the sequence matters when it seemed like the code was fine to me.
Shoot, I spoke too soon. It works fine with a static value there -- I didn't update it to be dynamic yet, and then I run into a thread issue which is weird. Trying to experiment with DispatchQueue.main.async -- will report back if I figure it out. From documentation, I thought putting my play audio in the didSet would do the trick.
Added what I am working on from your first solution -- now visible in the main thread in case anyone sees this question.
0

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.