Alamofire ignoring closure that sets/handles data - ios

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

Related

API call function with completion handler crashes when accessed from different VC

Can someone fix my function code because I have created a API call function which will get the imageURL for the specific object in my class and display the results in the second view controller. I have created custom completion handler so that the code from second VC is only executed when dowloading of the imageURL is completed.
However, when I am testing this function in the second view controller to print me data that it has arrived I am getting a crash on the print statement line.
Here is the code for my API call function located in Model class file:
func parseImageData(finished: () -> Void) {
let urlPath = _exerciseURL
let url = URL(string: urlPath!)
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("Error while parsing JSON")
}
else {
do {
if let data = data,
let fetchedImageData = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String:Any],
let images = fetchedImageData["results"] as? [[String: Any]] {
for eachImage in images {
let imageUrl = eachImage["image"] as! String
self._imageUrl = URL(string: imageUrl)
}
print(self._imageUrl)
}
}
catch {
print("Error while parsing data.")
}
}
}
task.resume()
finished()
}
And here in the second view controller I am just testing if I can access the code block:
override func viewDidLoad() {
super.viewDidLoad()
exercise.parseImageData() {
print("Arrived Here?") // I am getting crash on this line moving to debug navigator.
}
}
If the crash says something about force unwrapping nil then it's probably because let task = URLSession.shared.dataTask(with: url!) is unwrapping url which is a nil optional variable here.
But your completion handler is called in the wrong place anyway, try putting your finished() callback into the do statement instead. Because finished was executed the moment you called exercise.parseImageData()
if error != nil {
print("Error while parsing JSON")
}
else {
do {
if let data = data,
let fetchedImageData = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String:Any],
let images = fetchedImageData["results"] as? [[String: Any]] {
for eachImage in images {
let imageUrl = eachImage["image"] as! String
self._imageUrl = URL(string: imageUrl)
}
print(self._imageUrl)
finished()
}
}
catch {
print("Error while parsing data.")
}
}

Avoid re-loading UITableView data

I have a problem where a table shows a long list of drills that I want to remain at their current scroll position when user navigates away and then returns.
In my view controller, I have code that loads the data for a UITableView whenever the view appears by calling getDrillList(optimize: true) which stores the data in a property called drillListArray.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
getDrillList(optimize: true)
}
Here is the code that loads the data
private func getDrillList(optimize: Bool = false)
{
// MAKE API CALL, THE ARRAY IS POPULATED IN THE COMPLETE HANDLER
let appDelegate = UIApplication.shared.delegate as! AppDelegate
SharedNetworkConnection.apiGetDrillList(apiToken: appDelegate.apiToken, limit: (optimize ? 13 : 0), completionHandler: { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
// 403 on no token
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
SharedNetworkConnection.apiLoginWithStoredCredentials(completionHandler: { data, response, error in
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let json = try? JSONSerialization.jsonObject(with: data!, options: [])
if let dictionary = json as? [String: Any] {
if let apiToken = dictionary["token"] as? (String) {
appDelegate.apiToken = apiToken
self.getDrillList()
}
}
})
return
}
self.drillListParser = DrillListParser(jsonString: String(data: data, encoding: .utf8)!)
self.drillListArray = (self.drillListParser?.getDrillListArray())!
DispatchQueue.main.async {
self.drillTableView.reloadData()
}
if optimize {
self.getDrillList()
}
})
}
Two questions
First, if user is shown a segue to another view controller and then returns via Back, how can I check if the data is already loaded to avoid loading it a second time? Is it safe to check if the array is empty?
Second, are there any reprocussions I should be aware of with this approach?
Add getDrillList(optimize: true) in viewDidLoad() and it will calls once in lifecycle or you can put a check that checks if already loaded a data or not via a boolean flag.

iOS PickerView empty after read Json

I'm making an app in iOS and everything is going fairly well but for one bug that I can't fix. When the user starts the app for the first time the app request a json from my server. When the json is read, I show the result in a picker view. The problem is that the pickerview always shows empty until the user touches the screen. I've tried quite a few things but nothing works. In theory it is empty because the json hasn't been read, but this is not the case because in the console I can see that the json is ready.
Here are the relevant pieces of code:
override func viewDidLoad() {
super.viewDidLoad()
warning.isHidden = true
self.codeInput.delegate = self;
DispatchQueue.main.async {
self.readJson()
self.picker.reloadAllComponents()
}
}
And the part where I read the json
func readJson(){
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(url: requestURL)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest as URLRequest, completionHandler: {
(data, response, error) -> Void in
let httpResponse = response as! HTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
print("Everyone is fine, file downloaded successfully.")
do{
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String:AnyObject]
if let events = json["events"] as? [[String: AnyObject]] {
for event in events {
//here I read the json and I save the data in my custom array
}
self.picker.reloadAllComponents()
}
print(self.eventsArray)
}
}catch {
print("Error with Json: \(error)")
}
}
else{
print(statusCode)
}
})
picker.reloadAllComponents()
task.resume()
}
You need to do a couple of things:
You need to move the call to reload the picker view to inside the completion handler for your data task. That closure gets called once the data has been loaded.
However, the completion methods of URLSession tasks get executed on a background thread. Thus you'll need to wrap your call in a GCD call to the main thread. Add this code as the very last line in your completion closure, right before the closing brace:
DispatchQueue.main.async{
picker.reloadAllComponents()
}
(That's Swift 3 syntax.)
EDIT:
The code would look like this:
func readJson(){
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(url: requestURL)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest as URLRequest, completionHandler: {
(data, response, error) -> Void in
let httpResponse = response as! HTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
print("Everyone is fine, file downloaded successfully.")
do{
let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String:AnyObject]
if let events = json["events"] as? [[String: AnyObject]] {
for event in events {
//here I read the json and I save the data in my custom array
}
//Delete this call to reloadAllComponents()
//self.picker.reloadAllComponents()
}
print(self.eventsArray)
}
//------------------------------------
//This is where the new code goes
DispatchQueue.main.async{
picker.reloadAllComponents()
}
//------------------------------------
}catch {
print("Error with Json: \(error)")
}
}
else{
print(statusCode)
}
})
//Delete this call to reloadAllComponents()
//picker.reloadAllComponents()
task.resume()
}

Swift get value from api possible threading issue

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.

Swift - get results from completion handler

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

Resources