Can someone explain dispatch_async() for me in this scenario - thread-safety

I had the following method in a separate class:
class API: NSObject {
var data = NSData()
var delegate: APIProtocol?
func getItems(callback: (Array<Image>) -> ()) {
let urlPath: NSString = "http://localhost:3000/files"
let url = NSURL(string: urlPath)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Accept")
let config = NSURLSessionConfiguration.defaultSessionConfiguration() as NSURLSessionConfiguration
let session = NSURLSession(configuration: config) as NSURLSession
var dataTask = NSURLSessionDataTask()
dataTask = session.dataTaskWithRequest(request) { (data, response, error) in
if (error == nil) {
println("API at URL \(url)")
let responseArray = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil) as NSArray
var images = Image[]()
for item: AnyObject in responseArray {
var location = Image(dict: item as NSDictionary)
images.append(location)
}
var img = images[0] as Image
callback(images)
//self.delegate?.didReceiveResponse(responseArray)
}
}
dataTask.resume()
}
}
I couldn't get my tableView to reload when calling self.tableView.reloadData() inside the callback() until I added the dispatch_async() around it.
My questions are:
1) Why wouldn't it work without it and is it the proper thing for me to do now that it's refreshing the tableView correctly?
2) Is there another way to get it working without having to add the dispatch on the main queue?
api.getItems() { (theArray) in
dispatch_async(dispatch_get_main_queue(), {
self.images = theArray
self.tableView.reloadData()
if (viaPullToRefresh) {
self.refreshControl.endRefreshing()
}
})
}

When creating a NSURLSession you can specify the delegate/completion queue. If you don't specify a queue
the session creates a serial operation queue for performing all
delegate method calls and completion handler calls.
So this means that your callbacks are called on a private serial queue. Now, all UI must be updated on the main queue, this is why tableView.reloadData() wasn't working.
To remove the dispatch call to the main_queue create the session like this
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: nil, delegateQueue: NSOperationQueue.mainQueue())

Related

I want to load image Using method of NSURLConnectionDelegate and NSURLConnectionDataDelegate

I want to load images into connectionDidFinishLoading method and the method func connection(_connection: NSURLConnection, didReceive: Data) is not getting called
class ImageDownload: UIImageView,NSURLConnectionDelegate,NSURLConnectionDataDelegate
{
var imageSaved:UIImage!
var imageDownloaded:UIImage!
var connection2:NSURLConnection = NSURLConnection()
var data: NSMutableData = NSMutableData()
var urlstring:String = ""
var fileURL:URL!
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
var pathPlist:String!
func downloadImage()
{
let imgdownload :String = "http://image.tmdb.org/t/p/w500" + urlstring
// let urlnew: NSURL = NSURL(string: imgdownload)!
//print(urlnew,"url")
let url: NSURL = NSURL(string: imgdownload)!
let request1: NSMutableURLRequest = NSMutableURLRequest(url: url as URL)
// let request2: NSMutableURLRequest = NSMutableURLRequest(url: urlnew as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60.0)
connection2 = NSURLConnection(request: request1 as URLRequest, delegate: self, startImmediately: false)!
connection2.start()
}
func setURL(url:String) -> Void
{
print(url,"url")
urlstring = url
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
fileURL = documentsDirectory.appendingPathComponent(url)
print(fileURL,"fileurl")
if FileManager.default.fileExists(atPath: fileURL.path)
{
let image = UIImage(contentsOfFile: fileURL.path)
print("file exists")
self.image = image
}
else
{
downloadImage()
//let imgdownload :String = "http://image.tmdb.org/t/p/w500" + url
// let request = URL(string: imgdownload)
// let myUrl = NSURL(string: imgdownload)
//print("image loaded")
// self.image = self.imageSaved
}
}
func connection(_ connection: NSURLConnection, didReceive response: URLResponse)
{
print("in didReceive response\n")
self.data = NSMutableData()
}
func connection(_connection: NSURLConnection, didReceive: Data)
{
print("in didReceive data\n")
self.data.append(data as Data)
print(data,"image data is")
}
func connection(_ connection: NSURLConnection, didFailWithError error: Error)
{
print("connection error = \(error)")
}
func connectionDidFinishLoading(_ connection: NSURLConnection)
{
}
}*
I'm not familiar with the Swift syntax for NSURLConnection, so there's a nonzero chance I'm missing something subtle with the delegate method names, but off the top of my head, I see a couple of bigger problems:
Why are you using NSURLConnection in Swift? Every version of iOS that supports Swift also has NSURLSession. Just use the shared session, and your behavior will be almost identical to NSURLConnection.
You're using HTTP URLs. In all recent versions of iOS, you have to add special bits to your Info.plist if you want your app to be able to access HTTP URLs. I do not recommend doing that. Just use HTTPS.
You can get free TLS certs for HTTPS from Let's Encrypt. My guess is that as soon as you set that up and switch to an HTTPS URL, your problems will go away even with the existing code. But you should still be using NSURLSession.

Delegate method not called using Singleton

Similar questions have already been asked but they haven't helped. I'm trying to call protocol method declared in Singleton class from a view controller but the called method never invokes.
My protocol and class are:
import Foundation
protocol ServiceDelegate {
func serviceData(data: [String: Any])
}
class Service {
private static let sharedInstance = Service() // singleton object
var delegate: ServiceDelegate?
class func shared() -> Service {
return sharedInstance
}
func login(postData:String) {
let url = URL(string: "MY_URL")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let dataBody = postData.data(using:String.Encoding.ascii, allowLossyConversion: false)
request.httpBody = dataBody
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let dataArray = responseJSON as? [String: Any] {
self.delegate?.serviceData(data: dataArray) // This is getting called
}
}
task.resume()
}
}
My view controller class where protocol is implemented is:
import UIKit
class LoginVC: UIViewController, ServiceDelegate {
var serviceInstance: Service = Service()
override func viewDidLoad() {
super.viewDidLoad()
serviceInstance.delegate = self
}
#IBAction func loginBtn(_ sender: Any) {
let info = "userName=\(String(describing: usernameText.text!))&password=\(String(describing: passwordText.text!))"
Service.shared().login(postData: info)
}
// This is not getting called:
func serviceData(data: [String : Any]) {
print(data)
}
Is the above implementation correct? Can I use private singleton object to successfully implement protocol method?
You are creating two Service instances. You should only be using the one returned by Service.shared(). That’s the actual singleton shared across your app.
In your view controller, replace this line:
serviceInstance.delegate = self
with:
Service.shared().delegate = self
And you should also delete this line (i.e., the unnecessary second instance):
var serviceInstance: Service = Service()

Swift 3, URLSession dataTask completionHandler not called

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

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

XCode 6 UILabel not refreshing text when calling from complition handler

I'm calling to refresh label after refreshQuote() function finishes. When look at the log to see what happened with label.text I can see it has been replaced however the view still looks the same.
Im calling refreshQuote() from viewDidload.
When calling setQuoteLabel directly from viewDidLoad it all works fine
Please help, loosing hope now.
Thanks in advance
func refreshQuote()
{
var url : String = "https://api.parse.com/1/classes/Quotes"
var request : NSMutableURLRequest = NSMutableURLRequest()
request.addValue("------", forHTTPHeaderField: "X-Parse-Application-Id")
request.addValue("------strong text", forHTTPHeaderField: "X-Parse-REST-API-Key")
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
NSLog(error.debugDescription);
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: error) as? NSDictionary
if (jsonResult != nil)
{
var allResults: NSArray = jsonResult["results"] as NSArray
var pickedResult: NSDictionary = allResults[0] as NSDictionary
let q:String = pickedResult["message"] as String!
self.setQuoteLabel(q);
} else
{
// couldn't load JSON, look at error
}
})
}
func setQuoteLabel (quote:String)
{
NSLog(quote)
quoteLabel.text = quote;
quoteLabel.setNeedsDisplay();
NSLog(quoteLabel.text!)
}
All UI components must be updated from the main thread, whereas in your case you are updating most likely from a different thread, because the code is in an asynchronous block.
To force a block of code to be executed in the main thread, you can use dispatch_async as follows:
dispatch_async (dispatch_get_main_queue ()) {
self.setQuoteLabel(q)
}

Resources