Swift 3: not working with completion handler in iOS - ios

I have created one function using completion handler in NSObject class for consumption of web services. However I am not getting a way to call that function with handler return.
func getUser(url:String, completionHandler: #escaping (NSDictionary?, NSError?) -> ()) {
let config = URLSessionConfiguration.default // Session Configuration
let session = URLSession(configuration: config) // Load configuration into Session
let url = URL(string: url)!
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
if error != nil {
completionHandler(nil, error as NSError?)
} else {
do {
if let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [NSDictionary: Any] {
completionHandler(json as NSDictionary?,nil)
}
} catch {
print("error in JSONSerialization")
}
}
})
task.resume()
}

You should make sure that your completionHandler is called in every cases: for example, when the JSONSerialization throws, you catch and print the error, but you're not calling your completionHandler. The same if the JSON result is nil
ADDING
You can handle it in this way:
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [NSDictionary: Any]
completionHandler(json,nil)
} catch(let error) {
print(error)
completionHandler(nil, error)
}

Related

Parse image from web json

I have a json file that looks something like this:
{
"adTitle": "My Title",
"adURL": "https://mylink.com/",
"adImageURL": "http://mywebsite/bannerx#3x.png"
}
I get the JSON value from website: http://mywebsite.com/file.json
The problem is that the ad somehow doesn't load the adImageURL, so when I press the UIImageView, but when I press the area that then UIImageView should be, it open my adURL. This is the code I use for JSON:
var imageURL:String = "http://mywebsite/bannerx#3x.png"
var adURL:String = "https://mylink.com/"
func loadAdvertisement() {
// Set up the URL request
let todoEndpoint: String = "http://mywebsite.com/file.json"
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
// print("error calling GET on /todos/1")
// print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
do {
guard (try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: AnyObject]) != nil else {
print("error trying to convert data to JSON")
return
}
let json = try JSONSerialization.jsonObject(with: responseData, options:.allowFragments) as! [String:AnyObject]
if (json != nil) {
self.imageURL = (json["adImageURL"] as? String)!
self.adURL = (json["adURL"] as? String)!
print(self.imageURL)
print(self.adURL)
DispatchQueue.main.async { () -> Void in
self.loadAdImage(self.imageURL)
}
}
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
// let jsonURL = URL(string: "http://mywebsite.com/file.json")
// self.getDataFromUrl(jsonURL!, completion: (data:Data?, response:URLResponse?, error:Error?)) -> Void
}
func loadAdImage(_ url:String) {
getDataFromUrl(URL(string: url)!) { (data, response, error) in
DispatchQueue.main.async { () -> Void in
guard let data = data, error == nil else { return }
print(response?.suggestedFilename ?? "")
print("Download Finished")
self.advertImageView.image = UIImage(data: data)
}
}
}
func getDataFromUrl(_ url:URL, completion: #escaping ((_ data: Data?, _ response: URLResponse?, _ error: NSError? ) -> Void)) {
URLSession.shared.dataTask(with: url) { (data:Data?, response:URLResponse?, error:Error?) in
completion(data, response, error as NSError?)
}.resume()
}
In the event LOG, is prints out both of the print("error trying to convert data to JSON") commands. I have used this code before in my project, and it worked just fine, but I have no idea why it wont work anymore.
Add the message to catch and check what actually error you are getting like this way:
do {
let json = try JSONSerialization.jsonObject(with: responseData, options:.allowFragments) as! [String:AnyObject]
} catch let message {
print("error trying to convert data to JSON" + "\(message)")
return
}

fatal error: unexpectedly found nil while unwrapping an Optional value json

When I get data from server it will display fatal error
below my code
URLSession.shared.dataTask(with: myRequest, completionHandler: { (data:Data?, response:URLResponse?, error:Error?) -> Void in
DispatchQueue.main.async {
if error != nil {
}
do {
if let json = try JSONSerialization.jsonObject(with: (data)!, options: .mutableContainers) as? NSMutableDictionary {
}
}
}
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
do
{
let dict = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
Dispatch.main.async {
// refresh ui like tableview[tableView.roloadData()] or collectionview
}
}catch{
}
}
task.resume()
You a have few errors in your code.
You are checking error for nil and deserializing json outside this check.
You are force unwrapping (!) data without checking it for nil.
You use do, but I don't see catch. Please don't use do/catch unless you really need it.
Also, I suggest you using mainThread when you already deserialized object.
URLSession.shared.dataTask(with: myRequest) { (data, _, error) -> Void in
guard let data = data else { return }
let dict = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) {
...
Dispatch.main.async {
// refresh ui
}
}
}

How to make a synchronous GET request

I have a method for GET request in my code:
func makeHTTPGetRequest(path: String, parameters: [String: AnyObject], completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionTask {
let parameterString = parameters.stringFromHttpParameters()
let requestURL = NSURL(string:"\(path)?\(parameterString)")!
let request = NSMutableURLRequest(URL: requestURL)
request.HTTPMethod = "GET"
request.setValue("Bearer " + userInfoDefaults.stringForKey("accessToken")!, forHTTPHeaderField: "Authorization")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler:completionHandler)
task.resume()
return task
}
That is called by an another method that populates a picker view on a specific scene:
func getAffiliateds() -> [String]? {
var affiliateds:[String] = []
makeHTTPGetRequest(baseURL + "affiliateds", parameters: [:], completionHandler: { (data, response, error) in
do {
affiliateds = try NSJSONSerialization.JSONObjectWithData(data!, options:[]) as! [String]
print (affiliateds)
}
catch { print("Error: \(error)") }
})
return affiliateds
}
I need to get all affiliateds from my webservice and then list it on the picker view. But when I debugged the code I noticed that affiliateds are first returned as a null array and then it is returned with the correct information. I need to return the array from getAffiliateds only when it has already received the data from the webservice. How can I make this?
You can't. Your getAffiliateds() cannot return a value dependent on the asynchronous code that it will run. That is the nature of asynchronous code. Instead, perform a callback of some sort in the completion handler when it is called:
makeHTTPGetRequest(baseURL + "affiliateds", parameters: [:], completionHandler: { (data, response, error) in
do {
affiliateds = try NSJSONSerialization.JSONObjectWithData(data!, options:[]) as! [String]
print (affiliateds)
// DO SOMETHING HERE
}
}
A frequent strategy is for the caller to provide another completion handler which this completion handler will call.
You have a routine:
func getAffiliateds() -> [String]? {
var affiliateds:[String] = []
makeHTTPGetRequest(baseURL + "affiliateds", parameters: [:], completionHandler: { (data, response, error) in
do {
affiliateds = try NSJSONSerialization.JSONObjectWithData(data!, options:[]) as! [String]
print (affiliateds)
}
catch { print("Error: \(error)") }
})
return affiliateds
}
And you presumably have some code that does something like:
func populatePicklist() {
let affiliateds = getAffiliateds()
// populate picklist here
}
You should change this to:
func getAffiliatedsWithCompletionHandler(completionHandler: ([String]?) -> ()) {
makeHTTPGetRequest(baseURL + "affiliateds", parameters: [:]) { data, response, error in
do {
let affiliateds = try NSJSONSerialization.JSONObjectWithData(data!, options:[]) as? [String] // two notes here: first, define local var here, not up above; second, use `as?` to gracefully handle problems where result was not `[String]`
print (affiliateds)
completionHandler(affiliateds)
}
catch {
print("Error: \(error)")
completionHandler(nil)
}
}
}
func populatePicklist() {
getAffiliatedsWithCompletionHandler { affiliateds in
// populate picklist here
}
// but not here
}

Use Type T as parameter in completion handler

I have written a function for a URL request. This contains a completion handler that returns a dictionary of [String: AnyObject] that is fetched from the URL.
The code for this is:
func getDataAsyncFromURLRequest(url: NSURL, completion: ([String : AnyObject]) -> ()) {
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error != nil {
print("error=\(error)")
return
}
else {
let datastring = NSString(data: data!, encoding: NSUTF8StringEncoding)
if let data = datastring!.dataUsingEncoding(NSUTF8StringEncoding) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! [String : AnyObject]
completion(json)
} catch {
print("json error: \(error)")
}
}
}
}
task.resume()
}
In some cases, however, I will receive an array of [String : AnyObject] and not the dictionary. So instead of making a duplicate function that takes the array of dictionaries as parameter for the completion handler, I though it was possible to do like this
func getDataAsyncFromURLRequest<T>(url: NSURL, completion: (T) -> ()) {
// code here
}
... and then do like this let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! T, but that gives me this error: Cannot invoke 'getDataAsyncFromURLRequest' with an argument list of type '(NSURL, completion: (_) -> ())'
What would be the best way to make the completion handler accept a parameter with a type decided at runtime, if possible at all?
It's very easy why don't you use AnyObject
func getDataAsyncFromURLRequest(url: NSURL, completion: (AnyObject) -> ()) {
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error != nil {
print("error=\(error)")
return
}
else {
let datastring = NSString(data: data!, encoding: NSUTF8StringEncoding)
if let data = datastring!.dataUsingEncoding(NSUTF8StringEncoding) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
completion(json)
} catch {
print("json error: \(error)")
}
}
}
}
task.resume()
}
And result of JSONObjectWithData can be [AnyObject] (Array) or [String:AnyObject] and tree of those items.
So after got result, you can also check type of result in completion block
Like this
if result is [String:AnyObject]
...
else if result is [AnyObject]
...
else
//throw error : because then it is not JSON

Swift 2.0 do block not executing in dataTaskWithURL

I grabbed a version of this code from this site (thanks to Sergey A. Novitsky). However, the 'do' block is never executed so the "json" variable is always nil. What's going on? I'm using Xcode 7.0 Beta.
func getJson(url:NSURL) -> NSDictionary! {
var json:NSDictionary!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) {
(data:NSData?, response:NSURLResponse?, error:NSError?) in
do {
json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments) as? NSDictionary
} catch let caught as NSError {
print(caught)
} catch {
// Something else happened.
let error: NSError = NSError(domain: "<Your domain>", code: 1, userInfo: nil)
print(error)
}
}
task.resume()
return json
}
As mentioned in the other answers you need a completion block like this
func getJson(url:NSURL, completion: (json:NSDictionary?, error:NSError?)->()) {
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) {
(data:NSData?, response:NSURLResponse?, error:NSError?) in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments) as? NSDictionary
completion(json: json, error:nil)
} catch let caught as NSError {
completion(json: nil, error:caught)
} catch {
// Something else happened.
let error: NSError = NSError(domain: "<Your domain>", code: 1, userInfo: nil)
completion(json: nil, error:error)
}
}
task.resume()
}
call the function with
getJson(NSURL(string:"http://myserver.com")!) { (json, error) -> () in
if error != nil {
print(error!)
} else {
print(json!)
// do something with the json dictionary
}
}
That's not how asynchronous functions work. json is nil when it's returned because it won't actually be set until the asynchronous completion block for dataTaskWithURL is called. To get the value out of getJson, pass in a completion block of your own and call it inside the task's, passing back the parsed JSON to your calling site.

Resources