I'm performing an HTTP request using iOS's NSURLSession. My code looks like this:
let session = NSURLSession.sharedSession()
let url = NSURL(string:"www.example.com")
guard let url = url else {
return
}
let request = NSURLRequest(URL: url)
var task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
//do stuff with the data
}
task.resume()
(sorry if my code isn't 100% correct I just typed it real quickly inside my browser, you get the idea)
When Wi-Fi Assist is turned off it works fine, but when Wi-Fi Assist is turned on the app crashes.
I found this but the discussion never got an answer.
Apart from the fact that I want to fix the problem I am very curious WHY this is happening.
Related
I am using this code
let url = URL(string: "http://image.tmdb.org/t/p/w185" + movie.poster_path!) // https://www.themoviedb.org/talk/568e3711c3a36858fc002384
print(url!)
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
self?.movieImage.image = UIImage(data: data!)
}
}
from this stack overflow post. I have a URL with an image on it, I would like to use that URL to bring the image into my app and have it show up in a
#IBOutlet weak var movieImage: UIImageView!
but for some reason, I am getting an error saying that data is nil. Why would data be nil if the URL is valid? Is this an issue with the contentsOf function or am I doing something wrong here?
If you try changing your URL declaration to be: let url = URL(string: "http://image.tmdb.org/t/p/w185//nBNZadXqJSdt05SHLqgT0HuC5Gm.jpg") it works as expected. So perhaps you are not assembling the URL correctly?
I would print whatever URL you're creating and try visiting the website to see if it is actually correct
I’d suggest not using try? (which discards any meaningful error data) and instead use try wrapped in a do-catch block, and in the catch block, examine what the error is. Right now, you’re flying blind.
Or, better, use URLSession.shared.dataTask(with:) and look at the error in the completion handler.
You asked:
... but why is this such a bad thing [to use Data(contentsOf:)] if it is the background thread?
Yes, by dispatching this to a global queue you’ve mitigated the “don’t block the main thread” problem. But Data(contentsOf:) doesn’t provide much diagnostic information about why it failed. Also, it ties up one of the very limited number of worker threads that GCD draws upon. If you exhaust the worker thread pool, then GCD won’t be able to do anything else until it’s freed up. Using URLSession offers the chance to do more meaningful diagnostics and avoids blocking GCD worker threads.
So, I would suggest removing all of those ! forced unwrapped operators and not using Data(contentsOf:). Thus, I might suggest something like:
guard
let path = movie.poster_path,
let baseURL = URL(string: "http://image.tmdb.org/t/p/w185")
else {
print("problem getting path/URL")
return
}
let url = baseURL.appendingPathComponent(path)
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard
let data = data,
let response = response as? HTTPURLResponse,
error == nil
else {
print("network error:", error ?? "Unknown error")
return
}
guard 200..<300 ~= response.statusCode else {
print("invalid status code, expected 2xx, received", response.statusCode)
}
guard let image = UIImage(data: data) else {
print("Not valid image")
return
}
DispatchQueue.main.async {
self?.movieImage.image = image
}
}.resume()
Then, by displaying the error, if any, we’ll see what the problem was. FWIW, the above network request identifies three types of errors, which might be helpful for diagnostic purposes:
Basic network errors
HTTP errors
Content errors (not an image)
Good evening,
I have a table view that gets populated with data from my local MySQL database. I'm using MAMP along with PHPmyadmin.
It works perfectly fine on the simulator, as you can see:
Working Database
However, when I try to run it on my iPhone device, it doesn't load anything:
Not working Database
What could be the cause for that? I don't really get the difference between the simulator and the device.
The code looks like this:
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON(data!)
}
It prints "Data Downloaded" when on simulator and "Failed to download data" on the device.
I'm using Xcode 9 and Swift 4.
Thanks a lot for your help
I want to know how you guys handle errors when using a URLRequest in your app. How do you go about notifying your users that an error has occurred? Do you even notify your users at all? Do you try and reload the URLRequest again? Do you tell your users to close the current screen and open it again with an alert box? I have no clue.
Once there's an error, your app stops. So what do you do when this happens and you have a network issue, bad Json data?
What do you do when you get a "Bad Network Connection (The server is down)" or the URLSession comes back with an error and the internet connection is fine?
Please look at the code below and help me figure out what needs to be done when an error occurs.
let url = URL(string:"http://example/jsonFile.php")
var request = URLRequest(url:url!)
request.httpMethod = "POST"
let postingString = "id=\(id)"
request.httpBody = postingString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest){(data, response, error) -> Void in
if error != nil {
print("error \(error)")
// *****
// What do you do here? Do you tell your users anything?
// *****
return
}
// Check for Error
if let urlContent = data {
do{
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: .allowFragments) as! [String: AnyObject]
print("jsonResult \(jsonResult)")
}
catch{
print("JSON serialization failed")
// *****
// What do you do here? Do you tell your users anything?
// *****
}
}
}
task.resume()
It is often a bad idea to hide the errors and do nothing (see Error Hiding Anti-Pattern). Unfortunately, handling errors is not easy and can be tedious some times. Pretty code examples quickly become "uglier" when exceptions and errors have to be handled. It takes time to learn to do it well.
In the case of network connections, you would want to wrap your requests into asynchronous methods that take a completion callback as parameter. This callback will often receive a successful result or an error, sometimes wrapped in an enum to remove ambiguity (see Result as an example).
Once you know a network request has failed, you can present that information to the user in a clean, informational but non-obstructive way. To find out what other applications do in this scenario, turn on Airplane Mode in your phone and poke around.
Here are some examples:
Apple Music
Facebook Messenger
I have a Login View Controller, and an Other View Controller. What I'd like to do is: when the user hits login, it sends their credentials to the remote server. The remote server returns a response indicating whether the credentials were good or not, and if they were good, the app redirects to the Other View Controller.
The code below crashes at the call to .performSegueWithIdentifier.
The crash gives an error code of EXC_BAD_ACCESS(code=1, address=0xbbadbeef)
Question: what is the swifty way of doing this?
var request = NSMutableURLRequest(URL: NSURL(string: "http://url.to/my/login/handler")!)
var session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
//user initialized earlier
bodyData = "email=\(user.username)&password=\(user.password)"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
// check that log in was successful by looking in 'response' arg
// if login was successful
self.performSegueWithIdentifier("SegueToOtherController", sender: self)
}
task.resume()
}
If it's crashing, you should share the details the crash in order to identify why. Likely problems include that it didn't find a segue of that identifier as the "storyboard id" from the current view controller to the next scene. But it's impossible to say without details on the precise error.
Having said that, there is another problem here: The completion block may not run on the main thread, but all UI updates must happen on the main thread. So make sure to dispatch that back to the main queue, e.g.
let request = NSMutableURLRequest(URL: NSURL(string: "http://url.to/my/login/handler")!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
//user initialized earlier
bodyData = "email=\(user.username)&password=\(user.password)"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
let task = session.dataTaskWithRequest(request) {data, response, error in
// check that log in was successful by looking in 'response' arg
// if login was successful
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("SegueToOtherController", sender: self)
}
}
task.resume()
Note, I also changed all of those var references to let (as a general rule, use let wherever possible). Also, I haven't tackled it here, but you really should be percent escaping the username and password properties. If, for example, the password included any reserved characters like + or &, this would fail. There are lots of ways of doing that, e.g. something like the method discussed here: https://stackoverflow.com/a/26317562/1271826 or https://stackoverflow.com/a/25154803/1271826.
Adhoc deployed app on a device, the app runs fine with wifi, but does not work with 3g connection. Any idea what i'm missing?
Din't get much help from google.
And also the app installed does not show in the Use Mobile Data For : list
Code :
//To handle time out issue with 3g
configuration.timeoutIntervalForResource = 60
// Mark using Alamofire to do the downloads
self.alamofireManager = Alamofire.Manager(configuration:configuration)
self.alamofireManager!.request(.GET, jsonUrl).responseJSON(){
(_, _, JSON, _) in
println("printing json :\(JSON)")
if JSON != nil {
let imageInfos = (JSON!.valueForKey("image") as [NSDictionary]).map {
ImageInfo(id: $0["id"] as String, url: $0["url"] as String)
}
self.tableData.addObjectsFromArray(imageInfos)
}
Error log:
I get a
(Error Domain = NSURLErrorDomain Code = -1004 )
no matter how much i increase the timeoutInterval. Tried both with Resource and Request
Forgot to mention that the server was a Raspberry Pi, and i could not access the server outside the LAN so that was causing the problem. Thanks for the help. timeout was very helpful in handling server faults.
there wan't be any issue with app working on wifi or 3G. the only problem with request timeout. the issue related to internet bandwidth.
Please use following code for change request time out may be help to you.
var alamofireManager : Alamofire.Manager?
func getCallToServer(){
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.timeoutIntervalForResource = 10 // seconds
self.alamofireManager = Alamofire.Manager(configuration: configuration)
self.alamofireManager!.request(.GET, "http://example.com/")
.response { (request, response, data, error) in
}
}