I have a function that does an api request using google's places api. From the api response data I capture a value and try to set it to a variable. This function is called inside another function. I then try to access that variable but unfortunately the variable doesn't contain the value yet. This appears to be a threading issue but I don't know how to fix it.
update:
I have updated the code based on the responses. Unfortunately I am still not able to access the variable with the value from the api request. I have rewrote the function that does the api request to use a completion handler. The mapView(mapView: GMSMapView!, didTapInfoWindowOfMarker marker: GMSMarker!) is a function from the google maps framework. Would I need to rewrite this as well to use take a completion handler ?
// variable
var website = ""
// code with api request
func getWebsite2(id: String, completion: (result: String) -> Void) {
var url = NSURL(string: "https://maps.googleapis.com/maps/api/place/details/json?placeid=\(id)&key=AIzaSyAWV1BUFv_vcedYroVrY7DWYuIxcHaqrv0")
self.dataTask = defaultSession.dataTaskWithURL(url!) {
data, respnse, error in
let json : AnyObject
do {
json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
var dictionArr = json["result"]
self.website = dictionArr!!["website"] as! String
print(self.website)
}
catch {
print(error)
}
}
self.dataTask?.resume()
}
// second function
func mapView(mapView: GMSMapView!, didTapInfoWindowOfMarker marker: GMSMarker!) {
let storeMarker = marker as! PlaceMarker
self.getWebsite2(storeMarker.id!) {
(result: String) in
print("inside did tap")
print(self.website)
// problem still here
// above two lines of code never run
}
self.performSegueWithIdentifier("toWebView", sender: nil)
}
// I initialize defaultSession and dataTask like this.
let defaultSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
var dataTask: NSURLSessionDataTask?
You are not invoking the completion handler passed into the getWebsite2 function. This (pseudo)code shows how to take the string received from the server and pass it to the closure invoked in didTapInfoWindowOfMarker.
func getWebsite2(id: String, completion: (result: String) -> Void) {
self.dataTask = defaultSession.dataTaskWithURL(url!) {
data, response, error in
// now on background thread
let someStringFromNetwork = data[0]
dispatch_async(dispatch_get_main_queue(),{
completion(someStringFromNetwork)
})
}
}
Firstly do not force unwrapping of the variables and always use do{} catch{} where it is required.
This small code block that show how you should handle try and if let conditions:
do {
let jsonObject = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String:AnyObject]
if let dictionary = jsonObject["result"] as? [String: String] {
self.website = dictionary["website"]
} else {
print("Parse error")
}
} catch {
print("JSON error: \(error)")
}
Secondly defaultSession.dataTaskWithURL is asynchronous request that will set data only when he will finish.
In another worlds you try to print value when request is not finished.
For solving of youre problem you should use Completion Handlers.
Related
I am using Alamofire to perform a network request to the dummy data source https://jsonplaceholder.typicode.com/posts and render it in my application.
I have a file called NetworkingClient.swift that abstracts most of this logic out and allows is to be reused.
public class NetworkingClient {
typealias WebServiceResponse = ([[String: Any]]?, Error?) -> Void
func execute(_ url: URL, completion: #escaping WebServiceResponse) {
Alamofire.request(url).validate().responseJSON { response in
print(response)
if let error = response.error {
completion(nil, error)
} else if let jsonArray = response.result.value as? [[String: Any]] {
completion(jsonArray, nil)
} else if let jsonDict = response.result.value as? [String: Any] {
completion([jsonDict], nil)
}
}
}
}
I call the execute in a set up function I have in my main view controller file:
func setUpView() {
let networkingClient = NetworkingClient()
let posts_endpoint = "https://jsonplaceholder.typicode.com/posts"
let posts_endpoint_url = URL(string: TEST_URL_STRING)
networkingClient.execute(posts_endpoint_url) { (json, error) in
if let error = error {
print([["error": error]])
} else if let json = json {
print(json)
}
}
}
Where I call this inside viewDidLoad() under super.viewDidLoad()
I've set breakpoints inside the response in closure and I wasn't able to trigger any of them, in fact I think it's skipping the entire thing completely and I don't know why.
I am following this youtube video where the video guide does the exact same thing except their request goes through.
What am I missing?
I am using Swift 4, XCode 10, running on iOS 12.1 and my AlamoFire version is 4.7.
It's all about async stuff.your are declaring NetworkingClient object in func called setupView and Alamofire using .background thread to do stuff.so time executing of networkingClient.execute is not clear and after that setUpView deallocate from memory and all it's objects are gone including NetworkingClient.so for preventing this just declare let networkingClient = NetworkingClient() outside of function
I am attempting to take a string from JSON data and set it to a variable. My problem is that the variable shows as empty. I am using JSONDecoder to retrieve the JSON data and setting the string to a variable outside of the function. I then want to use that variable inside of another function
When I print the variable it still shows up as blank even after the function has loaded. Within the function the string appears correctly.
Code:
var filmTitle = ""
override func viewDidLoad() {
super.viewDidLoad()
loadFilms()
print(self.filmTitle) //Prints as an empty string
}
func loadFilms() {
let id = filmId
let apiKey = "97a0d64910120cbeae9df9cb675ad235"
let url = URL(string: "https://api.themoviedb.org/3/movie/\(id)?api_key=\(apiKey)&language=en-US")
let request = URLRequest(
url: url! as URL,
cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData,
timeoutInterval: 10 )
let session = URLSession (
configuration: URLSessionConfiguration.default,
delegate: nil,
delegateQueue: OperationQueue.main
)
let task = session.dataTask(with: request, completionHandler: { (dataOrNil, response, error) in
if let data = dataOrNil {
do { let details = try! JSONDecoder().decode(Details.self, from: data)
self.filmTitle = details.title
print(self.filmTitle) //string prints correctly
}
}
})
task.resume()
}
What am I missing to correctly set the string to the variable?
Loading data from the internet is an asynchronous method. The print statement is being called before loadFilms() has completed.
Use a callback to get the data after it has completed.
func loadFilms(completion: #escaping (Details?, Error?) -> Void) {
//...
let task = session.dataTask(with: request, completionHandler: { (dataOrNil, response, error) in
if let data = dataOrNil {
do { let details = try JSONDecoder().decode(Details.self, from: data)
completion(details, nil)
} catch {
completion(nil, error)
}
})
}
At the call site:
override func viewDidLoad() {
loadFilms { details, error in
if error { //* Handle Error */ }
self.filmTitle = details.title
print(filmTitle)
}
}
Web request are asynchronous and from the CP's perspective, take a long time to complete. When you call this:
override func viewDidLoad() {
super.viewDidLoad()
loadFilms()
print(self.filmTitle) // loadFilms() hasn't finished so `filmTitle` is empty
}
It's better to set a property observer on filmTitle:
var filmTitle: String? = nil {
didSet {
print(filmTitle)
Dispatch.main.async {
// update your GUI
}
}
}
The solution to this problem was to reload the collection view that the array was being sent to within the decoder function after the data was set to the array.
I'm using the Twitter REST API in Swift, and I am trying to get the value of a variable that is assigned inside of a Twitter Request closure, so that I can use that value outside of the closure.
I acquired this code from the Twitter REST API tutorial for Swift, located at: https://dev.twitter.com/twitterkit/ios/access-rest-api
func jsonAvailable() -> Bool {
// Swift
let client = TWTRAPIClient()
let statusesShowEndpoint = "https://api.twitter.com/1.1/statuses/show.json"
let params = ["id": "20"]
var clientError : NSError?
var jsonAvailable: Bool = false
let request = client.urlRequest(withMethod: "GET", url:
statusesShowEndpoint, parameters: params, error: &clientError)
client.sendTwitterRequest(request) { (response, data, connectionError)-> Void in
if connectionError != nil {
print("Error: \(connectionError)")
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: [])
print("json: \(json)")
jsonAvailable = true
} catch let jsonError as NSError {
print("json error: \(jsonError.localizedDescription)")
}
print("Value of jsonAvailable: \(jsonAvailable)")
return jsonAvailable
//always returns false, even if it is changed to true inside of the closure
}
In the last line, jsonAvailable is always false, even when it is changed to true inside of the closure. How can I get the value of jsonAvailable at the end of the function, even as it is modified inside of the sendTwitterRequest closure?
I have tried writing this closure in a separate function and then calling the function to get the value, but because it is a custom closure that requires the client to be called by "sendTwitterRequest" I have found it difficult to pass all these required parameters to fit the API.
Thanks for the help!
Your closure is async. What happens is that you go through all the function body before sendTwitterRequest assigns true to jsonAvailable, resulting in jsonAvailable being false. What you need to do is having a callback instead, providing the json status if you'd like (or the json itself as a nillable object).
EDIT: You could have something like this
func jsonAvailable(callback: ((_ isJsonAvailable: Bool) -> Void)) {
client.sendTwitterRequest(request) { (response, data, connectionError)-> Void in {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: [])
print("json: \(json)")
callback(true)
} catch let jsonError as NSError {
print("json error: \(jsonError.localizedDescription)")
callback(false)
}
}
}
jsonAvailable(callback: { (_ isJsonAvailable: Bool) in
print(isJsonAvailable)
})
I have this method that is inside a class called WebService, inside this method I am getting data from an API:
func GetTableDataOfPhase(phase: String, completion: (result: AnyObject) -> Void)
{
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let requestString = NSString(format:"%#?jobNo=%#", webservice, phase) as String
let url: NSURL! = NSURL(string: requestString)
let task = session.dataTaskWithURL(url, completionHandler: {
data, response, error in
dispatch_async(dispatch_get_main_queue(),
{
do
{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as? [AnyObject]
completion(result: json!)
}
catch
{
print(error)
}
})
})
task.resume()
}
Now I am calling this method from another class like so:
WebService().GetTableDataOfPhase("ORC0005")
{
(result: AnyObject) in
self.data = result as! NSArray
}
This works as expected. Now I am trying to get the results from the completion handler
so I can do this:
WebService().GetTableDataOfPhase("ORC0005")
{
(result: AnyObject) in
self.data = result as! NSArray
}
print(self.data.count)
right now self.data.count is 0, but when I put this print statement inside the curly braces, it is 70, how do I get the results outside the curly braces so I can use self.data.count ?
OK, here is your problem, you're calling dataTaskWithURL(async).
At the time you do:
print(self.data.count)
Your web service call is not finished yet.
When you put this line inside the curly braces, it only runs when the call has a response. That's why it works as expected.
It's a matter of timing, you're tying to evaluate a value that's not there yet.
In your class add
var yourData:NSArray?
And in your method
WebService().GetTableDataOfPhase("ORC0005")
{
(result: AnyObject) in
for res in result
{
self.yourData.append(res)
}
}
dispatch_async(dispatch_get_main_queue(), {
print(self.yourData.count)
}
Trying to pull in some JSON data from an API and then save that to core data.
My current method of doing this is to pull in the JSON data and return that array which ill then iterate and save to core data.
Pull in Data: (Works fine)
func getPlayerDataFromAPI() -> [Dictionary<String,AnyObject>]{
let url: String = "http://api.fantasy.nfl.com/v1/players/stats?"
let request : NSMutableURLRequest = NSMutableURLRequest()
var jsonData = [Dictionary<String,AnyObject>]()
request.HTTPMethod = "GET"
request.URL = NSURL(string: url)
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as? NSDictionary
if (jsonResult != nil) {
if let playerData = jsonResult?["players"] as? [Dictionary<String, AnyObject>]{
jsonData = playerData
print(jsonData.count)
}
} else {
print("No Data")
}
}
catch {
print("Error Occured")
}
}.resume()
return jsonData;
}
And then I wanted to test the returned Dictionary to ensure it was being populated:
func saveData(){
let players = getPlayerDataFromAPI()
print(players.count)
}
I call saveData() in the viewController viewDidLoad method and get an empty dictionary... Moments later, the print statement in the JSON function prints.
0
1427
Is there a reason the getPlayerDataFromAPI() function doesnt finish before the print(count) is being called? Do I have this wrong logically? I always get an empty dictionary returned in this instance and thats no good.
You're trying to synchronously return the results of an asynchronous function. session.dataTaskWithRequest is passed a closure, which doesn't execute until the request completes. So your jsonData = playerData statement doesn't get executed until after your getPlayerDataFromAPI() function has already returned (at which point jsonData is still the empty dictionary you defined at the beginning of the function).
One way to do what you're trying to do is to allow a closure to be passed in to your function; something like this (I haven't tested this code):
func getPlayerDataFromAPI(completion: (data: [String: AnyObject]) -> Void)
Then, at the point you assign jsonData = playerData, you can "return" the data to the caller like this:
completion(data: jsonData)
Calling this function would look something like this:
getPlayerDataFromAPI() { (data) -> Void in
print(data)
}