I'm trying to make a simple messaging app using an API. Right now, I have a thread that checks a request each second and see if the number of messages have changed, but this causes so much trouble, the RAM is constantly going up and the API becomes unresponsive because of the large number of requests. At the moment my code looks like this :
var request = URLRequest(url: URL(string: "URL")!)
let session = URLSession.shared
public func thread()
{
DispatchQueue.global(qos: .background).async {
while(true)
{
self.request.httpMethod = "GET"
self.session.dataTask(with: self.request) {data, response, err in
let json = try! JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any]
let data = json?["data"] as? [[String: Any]]
if((data?.count)! > self.nbMessages)
{
self.messages.removeAll()
for message in data! {
let text = message["message_body"] as? String
let creator = message["creator_id"] as? Int
self.messages.append([text!, String(creator!)])
}
DispatchQueue.main.async {
self.nbMessages = (data?.count)!
self.TableView.reloadData()
let scrollPoint = CGPoint(x: 0, y: self.TableView.contentSize.height - self.TableView.frame.size.height)
self.TableView.setContentOffset(scrollPoint, animated: false)
}
}
}.resume()
usleep(2000)
}
}
}
This works fine, I can send messages and see messages sent to me (with a decent delay), but my logic with the request at every 2 second is way off and I acknowledge it. I'm still learning Swift so I'd really appreciate some advises on this matter, thanks!
In comments you provide elaboration, saying that you are implementing a messenger. For that purpose simple HTTP requests are not appropriate approach. Instead, you want to introduce so-called socket connection. I dare quote myself from another relevant thread:
It's called socket-connections, at a glance it looks like a channel, that hosts on a server side, and any clients (devices) can join this channel (two or more, whatever you want). If device send a message to the server, it should broadcast the message toward all other participants (it can broadcast the message even to the sender itself, but with some meta-information so we can distiguish our own messages and ignore them).
Thereby first of all you need server with socket connection established, then you can implement any of already existing solutions (e.g. https://github.com/daltoniam/Starscream for iOS). Also you may want to take a look at AWS https://aws.amazon.com, as it has the socket connection service out of the box for the server side and required SDK for both Android and iOS platforms.
Related
What is difference between URLSession vs DispatchQueue.global().async + Data(contentsOf: ) in terms of download images from image urls?
func loadImageWithUrlSession() {
guard let url = URL(string: IMAGE_URL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let data = data else { return }
let image = UIImage(data: data)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.urlSessionImageView.image = image
}
}.resume()
}
func loadImageWithGCD() {
DispatchQueue.global(qos: .background).async {
guard
let url = URL(string: self.IMAGE_URL),
let data = try? Data(contentsOf: url) else {
return
}
let image = UIImage(data: data)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.gcdImageView.image = image
}
}
}
I know that URLSession can cancel or suspend task.
But if I use Rx instead, I can do the same thing as above as well.
I had an experiment that and it was depending on which QoS I'm using.
By the way, .userInitiated QoS was way faster than URLSession.
Which one are you guys use for something like downloading task with a background thread and why?
Is there any kind-teacher can help me specifically?
URLSession offers far greater configuration control, diagnostics of failures, cancelation, background sessions, ability to download directly to persistent storage to minimize peak memory usages, etc. URLSession and Data(contentsOf:) just are not comparable on feature set.
The synchronous Data(contentsOf:) unnecessarily blocks GCD worker threads and is also susceptible to misuse. It also is quite limiting and you will easily find yourself regretting the decision in the future (e.g. you later add some authentication process; you want to customize the cache behaviors, you want to parse and act upon status codes in the responses, you need cancelation capabilities because you are retrieving images for collection or table views, etc.).
It’s illuminating to look at the documentation for one of the init with a URL methods for Data, where it warns us:
Important
Don't use this synchronous initializer to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Instead, for non-file URLs, consider using the dataTask(with:completionHandler:) method of the URLSession class. See Fetching Website Data into Memory for an example.
Yes, dispatching this to a background thread addresses many of the above concerns, but Apple didn’t just suggest “just dispatch this to some background queue,” but rather explicitly advised to use URLSession instead. While your use of GCD global queue avoids some of issues that Apple warns us of above, it also imposes many unnecessarily limitations. If you use Data(contentsOf:), this is a decision that you’ll likely regret/refactor in the future. You might as well use URLSession now.
Regarding Data(contentsOf:) being appreciably faster when using .userInitiated, vs .default or URLSession approach, usually the network latency and transmission time dwarfs any queue priority related factors, so I find that claim hard to believe. In fact, I just tested download of 50 images via GCD (using both .default and .userInitiated) and the speed was not appreciably different than URLSession approach.
I'm using http request with JSON serialization in collectionViews, but the API interval is limited.
"We do enforce a small amount of rate limiting. Our current limits are 40 requests every 10 seconds and are limited by IP address, not API key. You can think of this is being burstable to 40 in a single second, or as an average of 4 requests/second. The timer will reset 10 seconds from your first request within the current 10 second "bucket". This means that if you trigger the limit you will have to wait up to 9 seconds before the timer resets but depending where you are within the 10 second window, it could be the very next second.
You can use the X-RateLimit headers that get returned with every request to keep track of your current limits. If you exceed the limit, you will receive a 429 HTTP status with a Retry-After header. As soon your cool down period expires, you are free to continue making requests."
Source: https://developers.themoviedb.org/3/getting-started/request-rate-limiting
I will like to know if it is possible to set a global variable on application(_:didFinishLaunchingWithOptions:) that will automatically increase time between all http requests that are made, I need to avoid to trigger the 10 seconds waiting timmer.
private func requestJSON(for dataItem: Movie) -> UIImage? {
let query = dataItem.title!.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
let requestURL = URL(string: "https://api.themoviedb.org/3/search/movie?api_key=25149a6f75e14fb0672911327a13939a&language=en-US&query=\(query!)&page=1&include_adult=false&year=\(dataItem.primary_release_year!)")!
if let data = try? Data(contentsOf: requestURL) {
if let JSON = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] {
if let x = JSON?["results"] as? [Dictionary<String, Any>], let y = x.first {
if let pp = y["poster_path"] as? String {
dataItem.poster = try? Data(contentsOf: URL(string: defaultBaseURL + pp)!)
print("Downloading... \(dataItem.title!)'s poster.")
}
}
}
}
return dataItem.poster?.first != nil ? UIImage(data: dataItem.poster!) : UIImage(named: "default-movie")
}
I will like to avoid to use lots of tasks as much as I can where possible, anyway, any other solution will also be very appreciated.
No, there is no global variable, NSURLSessionConfiguration property or anything built into iOS that implements this - you'll have to do this yourself.
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'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.
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
}
}