Reputation: 690
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
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.
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
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
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