Swift 3, URLSession dataTask completionHandler not called - nsurlsession

I am writing a library, So not using UIKit, Even in my iOS app same code works, but when i execute in command line in doesn't . In PlayGround also it seems working.
For some reason callback is not getting triggered, so print statements are not executing.
internal class func post(request: URLRequest, responseCallback: #escaping (Bool, AnyObject?) -> ()) {
execTask(request: request, taskCallback: { (status, resp) -> Void in
responseCallback(status, resp)
})
}
internal class func clientURLRequest(url: URL, path: String, method: RequestMethod.RawValue, params: Dictionary<String, Any>? = nil) -> URLRequest {
var request = URLRequest(url: url)
request.httpMethod = method
do {
let jsonData = try JSONSerialization.data(withJSONObject: (params! as [String : Any]), options: .prettyPrinted)
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
} catch let error as NSError {
print(error)
}
return request
}
private class func execTask(request: URLRequest, taskCallback: #escaping (Bool,
AnyObject?) -> ()) {
let session = URLSession(configuration: URLSessionConfiguration.default)
print("THIS LINE IS PRINTED")
let task = session.dataTask(with: request, completionHandler: {(data, response, error) -> Void in
if let data = data {
print("THIS ONE IS NOT PRINTED")
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
taskCallback(true, json as AnyObject?)
} else {
taskCallback(false, json as AnyObject?)
}
}
})
task.resume()
}
Edits -: I am writing a library, So not using UIKit, Even in my iOS app same code works, but when i execute in command line in doesn't . In PlayGround also it seems working.

I made a simple App from scratch. (Xcode 8 beta 6 / swift 3)
In controller I pasted Your code. (plus url creation..)
I see all in debugger:
THIS ONE IS PRINTED
THIS ONE IS PRINTED, TOO
I AM BACK
so it seems workin.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let URLString = "https://apple.com"
let url = URL(string: URLString)
let request = URLRequest(url: url!)
ViewController.execTask(request: request) { (ok, obj) in
print("I AM BACK")
}
}
private class func execTask(request: URLRequest, taskCallback: #escaping (Bool,
AnyObject?) -> ()) {
let session = URLSession(configuration: URLSessionConfiguration.default)
print("THIS LINE IS PRINTED")
let task = session.dataTask(with: request, completionHandler: {(data, response, error) -> Void in
if let data = data {
print("THIS ONE IS PRINTED, TOO")
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
taskCallback(true, json as AnyObject?)
} else {
taskCallback(false, json as AnyObject?)
}
}
})
task.resume()
}
}

I know its late for the answer but in case you have not figure out the issue or getting issue at other places, lets try this.
You need to save session variable outside method scope (make it a instance variable). Since you defined it locally in function scope. Its get deallocated before completion handler can be called, remember completion handler can't retain your session object and after execution of run loop, garbage collector will dealloc your session object. We need to retain such objects whenever we want call back from delegates or from completion handler..
self.session = URLSession(configuration: URLSessionConfiguration.default)

Did the changes suggested here, It works now.
Using NSURLSession from a Swift command line program
var sema = DispatchSemaphore( value: 0 )
private func execTask(request: URLRequest, taskCallback: #escaping (Bool,
AnyObject?) -> ()) {
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil )
session.dataTask(with: request) {(data, response, error) -> Void in
if let data = data {
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
taskCallback(true, json as AnyObject?)
} else {
taskCallback(false, json as AnyObject?)
}
}
}.resume()
sema.wait()
}

let dataTask = session.dataTask(with: request, completionHandler: {data, response,error -> Void in
print("Request : \(response)")
let res = response as! HTTPURLResponse
print("Status Code : \(res.statusCode)")
let strResponse = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Response String :\(strResponse)")
})
dataTask.resume()

Swift 3.0
Just copy below code into your view controller.
#IBAction func btnNewApplicationPressed (_ sender: UIButton) {
callWebService()
}
func callWebService() {
// Show MBProgressHUD Here
var config :URLSessionConfiguration!
var urlSession :URLSession!
config = URLSessionConfiguration.default
urlSession = URLSession(configuration: config)
// MARK:- HeaderField
let HTTPHeaderField_ContentType = "Content-Type"
// MARK:- ContentType
let ContentType_ApplicationJson = "application/json"
//MARK: HTTPMethod
let HTTPMethod_Get = "GET"
let callURL = URL.init(string: "https://itunes.apple.com/in/rss/newapplications/limit=10/json")
var request = URLRequest.init(url: callURL!)
request.timeoutInterval = 60.0 // TimeoutInterval in Second
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
request.addValue(ContentType_ApplicationJson, forHTTPHeaderField: HTTPHeaderField_ContentType)
request.httpMethod = HTTPMethod_Get
let dataTask = urlSession.dataTask(with: request) { (data,response,error) in
if error != nil{
return
}
do {
let resultJson = try JSONSerialization.jsonObject(with: data!, options: []) as? [String:AnyObject]
print("Result",resultJson!)
} catch {
print("Error -> \(error)")
}
}
dataTask.resume()
}

Sometimes, for me, the solution when completionHandler were not called in these cases was because the flag "Allow Arbitrary loads" on Info.plist was defined as NO.
Allow Arbitrary loads flag defined as YES

Related

What does [weak self] do and what is this code structure means?

I have couple of questions about the structure of the following code.I assume progressBlock and completionhandlers are callback functions passed to downloadWithDownloadType function. Is my assumption correct? And What does [weak Self] before function parameters do? In what situation do you need that?
func downloadContent(key: String, pinOnCompletion: Bool) {
let manager = AWSUserFileManager.defaultUserFileManager()
let content = manager.contentWithKey(self.prefix + key)
content.downloadWithDownloadType(
.IfNewerExists,
pinOnCompletion: pinOnCompletion,
progressBlock: {[weak self](content: AWSContent?, progress: NSProgress?) -> Void in
guard self != nil else { return }
/* Show progress in UI. */
},
completionHandler: {[weak self](content: AWSContent?, data: NSData?, error: NSError?) -> Void in
guard self != nil else { return }
if let error = error {
// Handle Error
return
}
if let fileData = data {
let rawData = NSString(data: fileData, encoding:NSUTF8StringEncoding) as! String
// Do something
}
//Download Complete
})
}

Swift: Verifying is valid url in OS X playground

I'm trying to verify/validate url but when I do it always opens safari. Any of you know how can accomplish this without open safari. Here is my code:
func validateUrl (urlString: String?) -> Bool {
let url:NSURL = NSURL(string: urlString!)!
if NSWorkspace.sharedWorkspace().openURL(url) {
return true
}
return false
}
print (validateUrl("http://google.com"))
I'll really appreciate your help.
There's two things to check: if the URL itself is valid, and if the server responds without error.
In my example I'm using a HEAD request, it avoids downloading the whole page and takes almost no bandwidth.
func verifyURL(urlPath: String, completion: (isValid: Bool)->()) {
if let url = NSURL(string: urlPath) {
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "HEAD"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { (_, response, error) in
if let httpResponse = response as? NSHTTPURLResponse where error == nil && httpResponse.statusCode == 200 {
completion(isValid: true)
} else {
completion(isValid: false)
}
}
task.resume()
} else {
completion(isValid: false)
}
}
Usage:
verifyURL("http://google.com") { (isValid) in
print(isValid)
}
For use in a Playground, don't forget to enable the asynchronous mode in order to be able to use NSURLSession:
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
You rather need to do following check:
func validateUrl (urlString: String?) -> Bool {
let url: NSURL? = NSURL(string: urlString!)
if url != nil {
return true
}
return false
}
print (validateUrl("http://google.com"))
print (validateUrl("http:/ /google.com"))

This application is modifying the autolayout engine from a background thread, which can lead to engine corruption

I am getting this error This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes.This will cause an exception in a future release. I don't know what is causing this error. Can anybody help me.
func getUserDataFromTwitterWithUser(user : PFUser)
{
//NRLoader.showLoader()
let strTwURL = "https://api.twitter.com/1.1/users/show.json? screen_name="+PFTwitterUtils.twitter()!.screenName! + "&access_token="+PFTwitterUtils.twitter()!.authToken!
let twURL = NSURL (string: strTwURL)
let request = NSMutableURLRequest(URL: twURL!, cachePolicy: NSURLRequestCachePolicy.UseProtocolCachePolicy, timeoutInterval: 2.0) as NSMutableURLRequest
PFTwitterUtils.twitter()?.signRequest(request)
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if error == nil {
var jsonOptional = Dictionary<String, AnyObject>()
do {
jsonOptional = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers ) as! Dictionary<String, AnyObject>
// use jsonData
} catch {
// report error
}
var userName = ""
if let screenName = jsonOptional["screen_name"] as? String{
userName = screenName
}
else if let name = jsonOptional["name"] as? String{
userName = name
}
var profilePicUrl = ""
if let picUrl = jsonOptional["profile_image_url"] as? String{
profilePicUrl = picUrl
}
AppUser.currentUser()?.username = userName
AppUser.currentUser()?.profileAwsURL = profilePicUrl
//NRLoader.hideLoader()
//if ParseUtils.isLoggedInUserIsAnonymous() {
let signUpVC:SignMeUpViewController = self.storyboard!.instantiateViewControllerWithIdentifier("SignMeUpViewController") as! SignMeUpViewController
signUpVC.isFromLogin = true
self.navigationController!.pushViewController(signUpVC, animated: true)
//} else {
// self.pushToSubmitDreamViewController()
//}
}
else {
//NRLoader.hideLoader()
NRToast.showToastWithMessage(error!.description)
}
}).resume()
}
The dataTaskWithRequest call runs in the background and then calls your completion handler from the same thread. Anything that updates the UI should run on the main thread, so all of your current handler code should be within a dispatch_async back onto the main queue:
dispatch_async(dispatch_get_main_queue()) {
// Do stuff to UI
}
Swift 3:
DispatchQueue.main.async() {
// Do stuff to UI
}
Therefore, ideally all the code you currently have within if error == nil should be off in another function, say called handleRequest, so your current code becomes:
session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if error == nil {
dispatch_async(dispatch_get_main_queue(), {
self.handleRequest(...)I
})
}
Swift 3
session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if error == nil {
DispatchQueue.main.async {
self.handleRequest(...)I
}
}
Should try Symbolic Breakpoint to detect the issue:-
Then put your UI Update code in main thread
DispatchQueue.main.async {}
You'd better change UI only in the main thread
swift3,
let liveInfoUrl = URL(string: "http://192.168.1.66/api/cloud/app/liveInfo/7777")
let task = URLSession.shared.dataTask(with: liveInfoUrl! as URL) {data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async {
print(String(data: data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) ?? "aaaa")
//do some ui work
}
}
if the above suggestions still give you no joy then the sure-est way is to redesign your functions so that getting what you need with
URLSession.shared.dataTask
then hands over so a variable declared outside that function, then a separate UIControl ( button, swipe etc ) displays it to a label or textview or whatever.
After all that is what the error message is telling you. they're separate concerns

cannot convert the expressions type UIImage to type void swift

Im having an issue, I am trying to create a method which accepts a PFObject as a parameter. The PFObject in this case is the facebook picture URL. The method takes the URL and basically converts it into an image. I can get it to work if i just use this block of code without trying to make it into a method, however I would like to create a method out of this so that I dont have to keep repeating myself. When i try to return the users image i keep getting the error cannot convert the expressions type UIImage to type void swift
Here is the code
func downloadFBUserImage(object: PFObject?) -> UIImage? {
var userProfilePhotoURLString = object?.valueForKey("pictureURL") as String?
if userProfilePhotoURLString != nil {
var pictureURL: NSURL = NSURL(string: userProfilePhotoURLString!)!
var urlRequest: NSURLRequest = NSURLRequest(URL: pictureURL)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: { (NSURLResponse response, NSData data, NSError error) -> Void in
if error == nil && data != nil {
var userProfilePic: UIImage? = UIImage(data: data)
return userProfilePic
}
})
return nil
}
The error is reporting that the completionHandler of the sendAsynchronousRequest is defined to pass you the response, data, and error objects, but that it expects that completionHandler, itself, to not return any values. But you're trying to return a value from within that completionHandler closure.
Bottom line, you cannot simply return the UIImage from your function, because you are performing asynchronous method (i.e. the data is returned later even though you return from the function immediately). So, employ asynchronous pattern:
func downloadFBUserImage(object: PFObject?, completionHandler: (UIImage?, NSError?) -> Void) {
if let userProfilePhotoURLString = object?.valueForKey("pictureURL") as? String {
let pictureURL = NSURL(string: userProfilePhotoURLString)!
let urlRequest = NSURLRequest(URL: pictureURL)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (response, data, error) -> Void in
if data != nil {
var userProfilePic = UIImage(data: data)
completionHandler(userProfilePic, nil)
} else {
completionHandler(nil, error)
}
}
}
}
And you'd call it using the same completion handler pattern that sendAsynchronousRequest does:
downloadFBUserImage(object) { image, error in
if image == nil {
println(error)
} else {
// use the image here
}
}
// but don't try to use asynchronously retrieved image here
You cannot return to the completion block like that. The completion block does not have a return parameter. This is why you are getting an error.
For updating the image after download, you can pass in a block along with your downloadFBUserImage function like below.
I used dispatch_async because UI updates have to be done on the main thread.
func downloadFBUserImage(object: PFObject?, completion completionBlock:(UIImage) -> ()) -> (){
var userProfilePhotoURLString = object?.valueForKey("pictureURL") as String?
if userProfilePhotoURLString != nil {
var pictureURL: NSURL = NSURL(string: userProfilePhotoURLString!)!
var urlRequest: NSURLRequest = NSURLRequest(URL: pictureURL)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: { (NSURLResponse response, NSData data, NSError error) -> Void in
if error == nil && data != nil {
if let userProfilePic = UIImage(data: data) {
completionBlock(userProfilePic)
}
}
})
}
}
It can be called like this
func do() {
downloadFBUserImage(pfObject, completion: { (image) -> () in
//updateImage
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// UI updates
}
})
}

Get data from HTTP Request - Swift

Today I'm trying to get some data from a webpage.
It keeps crashing saying "Exc Bad Access", any suggestion ?
class CodeViewController: NSObject {
#IBOutlet var output_box : NSTextField
#IBOutlet var url_textField : NSTextField
var output : NSString = "Unable to load data"
func downloadHTML (path: String) -> String {
var url = NSURL(string: "\(path)")
var request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
self.output = NSString(data: data, encoding: NSUTF8StringEncoding)
//EXC_BAD_ACCESS when trying to set self.output new value
}
return output
}
#IBAction func retrive(sender: AnyObject!) {
println(downloadHTML(url_textField.stringValue))
}
}
I suspect that "url_textField.stringValue" is nil at the point that you pass it in. Trying it with a fixed string works. But note also that the function will return before the async request completes, and so your code will always print "Unable to load". If you add a line that assigns the output to your output_box in the completion handler, you will see the the text box update once the request completes...
The code I tried, which works, is...
class CodeViewController: NSObject {
var output : NSString = "Unable to load data"
func downloadHTML (path: String) -> String {
var url = NSURL(string: "\(path)")
var request = NSURLRequest(URL: url)
let completionBlock: (NSURLResponse!, NSData!, NSError!) -> Void = {response, data, error in
self.output = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Asynch completed \(self.output)")
}
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: completionBlock)
return output
}
}
var c: CodeViewController? = nil
c = CodeViewController()
c!.downloadHTML("http://www.google.com")
println("Immediate \(c!.output)")
and its output is:
Immediate Unable to load data
Asynch completed <!doctype html><html ...

Resources