Getting HTTP data in background not working - swift

Arbitrary loads are = YES and background fetch is checked in capabilities. What am I doing wrong exactly? Are there online courses that explain these concepts? This is inside the app delegate file. I want to retrieve data from a URL when the app goes into the background.
func applicationDidEnterBackground(_ application: UIApplication) {
if let url = URL(string:"https://finviz.com/screener.ashx?v=111&f=exch_nasd,sh_curvol_o500,sh_price_u5&o=-change") {
//url successful
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if error != nil {
//failed
print("FAILED")
} else {
//successful
print("SUCCESSFUL")
}
})
print("TASK DID NOT RUN")
task.resume()
} else {
//url failed
print("URL FAILED")
}
}

Use beginBackgroundTask(expirationHandler:) before running your code. Take a look here. The system stops the execution of your app when entering background and you should use this function in order to obtain more time for running. Also remove "TASK DID NOT RUN". It will always be printed
This is your full code:
var identifier: UIBackgroundTaskIdentifier = 0
func applicationDidEnterBackground(_ application: UIApplication) {
self.identifier = application.beginBackgroundTask {
application.endBackgroundTask(self.identifier)
}
if let url = URL(string:"https://finviz.com/screener.ashx?v=111&f=exch_nasd,sh_curvol_o500,sh_price_u5&o=-change") {
//url successful
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if error != nil {
//failed
print("FAILED")
} else {
//successful
print("SUCCESSFUL")
}
})
task.resume()
} else {
//url failed
print("URL FAILED")
}
}

Related

How to detect if there is no more post to fetch in CollectionView?

I am making new app with Xcode using Swift and i am fetching posts from my WordPress website , all works fine but there is one problem, when i scroll down to the very last post of category then the indicator is just running and nothing happens, i want when there is no more post then Progress bar should stop running and i want to toast a message that there is no more post , how is that possible ? this is my code to fetch posts
func fetchPostData(completionHandler: #escaping ([Postimage]) -> Void ) {
let url = URL(string: "https://www.sikhnama.com/wp-json/wp/v2/posts/?categories=4&page=\(page)\(sortBy)")!
print(url)
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {return}
do {
let postsData = try JSONDecoder().decode([Postimage].self, from: data)
completionHandler(postsData)
DispatchQueue.main.async {
if(self.newsData.isEmpty == false){
print("collection view empty")
self.collectionView.reloadData()
SVProgressHUD.dismiss()
}
else{
if(self.collectionView == nil){
print("collection view nill")
self.fetchPostData { (posts) in
self.newsData = posts }
}
}
}
}
catch {
let error = error
print(String(describing: error))
}
}
task.resume()
}
can you please help ?

getting output from URLSession in XCUITest

I'm trying to grab a value from a URL inside a XCUI Test. However it's not outputting so I'm not sure if there's anything I'm supposed to be doing aside from I've already tried in the code below:
import XCTest
class ExampleUITests: XCTestCase {
func testExample() {
print("We are in XCUITest textExample right now")
let urlString = "https://api.ipify.org/"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data
else {
print("error found in data")
return
}
print("*******")
let outputStr = String(data: data, encoding: String.Encoding.utf8) as String!
print (outputStr)
}.resume()
}
}
The problem is, response from server is returned after test is finished. You should add waiting for network response in the test.
At the start of the test add:
let expectation = expectationWithDescription("")
At the end of the test add:
waitForExpectationsWithTimeout(5.0) { (error) in
if error != nil {
XCTFail(error.localizedDescription)
}
}
And in the network completion block add:
expectation.fulfill()
You can get more info about waining for async task in the test for example here How can I get XCTest to wait for async calls in setUp before tests are run?.

Completion handler only gets called if it wasn't called before

My application receives location updates, and when it does (if they are relevant), I call an API asynchronously using a completion handler. When the application opens, the completion handler responds only if there was no request that finished before (two requests come in at the same time usually). When I debug, after the first 2-3 requests (which come in at the same time) where everything works, when the location update passes as relevant, the whole completion handling part of code gets skipped.
This is how I call the completion handler:
if conditions {
let lat = Float(loc.lat)
let long = Float(loc.long)
// calls function using completion handler in order to add new location
BusStations.allBusStations(lat: lat, long: long) { (busStations, error) in
if let error = error {
// got an error in getting the data
print(error)
return
}
guard let busStations = busStations else {
print("error getting all: result is nil")
return
}
if !busStations.stops.isEmpty || self.locations.isEmpty {
// do stuff
}
}
}
This is how I make the API call:
static func allBusStations (lat: Float, long: Float, completionHandler: #escaping (BusStations?, Error?) -> Void) {
let endpoint = BusStations.endpointForBusStations(lat: lat, long: long)
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not construct URL")
completionHandler(nil, error)
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
completionHandler(nil, error)
return
}
guard error == nil else {
completionHandler(nil, error)
return
}
let decoder = JSONDecoder()
do {
let stations = try decoder.decode(BusStations.self, from: responseData)
completionHandler(stations, nil)
} catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
}
task.resume()
}
What am I doing wrong? Any help would be appreciated.
I would try to dispatch the completion handler to global or main queue to see if it is deferred by system to execute on a queue of lower levels.

Swift 4: How to asynchronously use URLSessionDataTask but have the requests be in a timed queue?

Basically I have some JSON data that I wish to retrieve from a bunch of URL's (all from the same host), however I can only request this data roughly every 2 seconds at minimum and only one at a time or I'll be "time banned" from the server. As you'll see below; while URLSession is very quick it also gets me time banned almost instantly when I have around 700 urls to get through.
How would I go about creating a queue in URLSession (if its functionality supports it) and while having it work asynchronously to my main thread; have it work serially on its own thread and only attempt each item in the queue after 2 seconds have past since it finished the previous request?
for url in urls {
get(url: url)
}
func get(url: URL) {
let session = URLSession.shared
let task = session.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error {
DispatchQueue.main.async {
print(error.localizedDescription)
}
return
}
let data = data!
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
DispatchQueue.main.async {
print("Server Error")
}
return
}
if response.mimeType == "application/json" {
do {
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
if json["success"] as! Bool == true {
if let count = json["total_count"] as? Int {
DispatchQueue.main.async {
self.itemsCount.append(count)
}
}
}
} catch {
print(error.localizedDescription)
}
}
})
task.resume()
}
Recursion solves this best
import Foundation
import PlaygroundSupport
// Let asynchronous code run
PlaygroundPage.current.needsIndefiniteExecution = true
func fetch(urls: [URL]) {
guard urls.count > 0 else {
print("Queue finished")
return
}
var pendingURLs = urls
let currentUrl = pendingURLs.removeFirst()
print("\(pendingURLs.count)")
let session = URLSession.shared
let task = session.dataTask(with: currentUrl, completionHandler: { (data, response, error) in
print("task completed")
if let _ = error {
print("error received")
DispatchQueue.main.async {
fetch(urls: pendingURLs)
}
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
print("server error received")
DispatchQueue.main.async {
fetch(urls: pendingURLs)
}
return
}
if response.mimeType == "application/json" {
print("json data parsed")
DispatchQueue.main.async {
fetch(urls: pendingURLs)
}
}else {
print("unknown data")
DispatchQueue.main.async {
fetch(urls: pendingURLs)
}
}
})
//start execution after two seconds
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in
print("resume called")
task.resume()
}
}
var urls = [URL]()
for _ in 0..<100 {
if let url = URL(string: "https://google.com") {
urls.append(url)
}
}
fetch(urls:urls)
The easiest way is to perform recursive call:
Imagine you have array with your urls.
In place where you initially perform for loop with, replace it with single call get(url:).
self.get(urls[0])
Then add this line at the and of response closure right after self.itemsCount.append(count):
self.urls.removeFirst()
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (_) in
self.get(url: urls[0])
}
Make DispatchQueue to run your code on threads. You don't need to do this work on Main Thread. So,
// make serial queue
let queue = DispatchQueue(label: "getData")
// for delay
func wait(seconds: Double, completion: #escaping () -> Void) {
queue.asyncAfter(deadline: .now() + seconds) { completion() }
}
// usage
for url in urls {
wait(seconds: 2.0) {
self.get(url: url) { (itemCount) in
// update UI related to itemCount
}
}
}
By the way, Your get(url: url) function is not that great.
func get(url: URL, completionHandler: #escaping ([Int]) -> Void) {
let session = URLSession.shared
let task = session.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error {
print(error.localizedDescription)
/* Don't need to use main thread
DispatchQueue.main.async {
print(error.localizedDescription)
}
*/
return
}
let data = data!
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
print("Server Error")
/* Don't need to use main thread
DispatchQueue.main.async {
print("Server Error")
}
*/
return
}
if response.mimeType == "application/json" {
do {
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
if json["success"] as! Bool == true {
if let count = json["total_count"] as? Int {
self.itemsCount.append(count)
// append all data that you need and pass it to completion closure
DispatchQueue.main.async {
completionHandler(self.itemsCount)
}
}
}
} catch {
print(error.localizedDescription)
}
}
})
task.resume()
}
I would recommend you to learn concept of GCD(for thread) and escaping closure(for completion handler).
GCD: https://www.raywenderlich.com/148513/grand-central-dispatch-tutorial-swift-3-part-1
Escaping Closure: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID546

Syncronize async functions

I try to create my first project with swift 3.
I try to get data from my API. This works pretty good if I manually start the function. I need to synchronize the async request.
I need to trigger my function 3 times and wait for the others to complete.
makeGetCall(URLstring: "api1")
wait to complete
makeGetCall(URLstring: "api2")
wait to complete
makeGetCall(URLstring: "api3")
Set this to background and trigger every 5 seconds.
func makeGetCall(URLstring: String, update: Bool) {
let completeURL = "http://myapi/" + URLstring
// Set up the URL request
guard let url = URL(string: completeURL) 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 as Any)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as XML
if URLstring == "devicelist.cgi" {
self.readDevice(XMLData: responseData)
}
if URLstring == "statelist.cgi" {
self.readDeviceData(XMLData: responseData, update: update)
}
if URLstring == "functionlist.cgi" {
self.readGewerke(XMLData: responseData)
}
}
task.resume()
}
Can somebody help please.
Hagen
This is what I tried with completion handler:
override func viewDidLoad() {
super.viewDidLoad()
makeGetCall(input: "statelist.cgi") {
(result: Bool) in
print("finished statelist")
}
makeGetCall(input: "devicelist.cgi") {
(result: Bool) in
print("finished devicelist")
}
makeGetCall(input: "functionlist.cgi") {
(result: Bool) in
print("finished functionlist")
}
}
func makeGetCall(input: String, completion: #escaping (_ result: Bool) -> Void) {
let completeURL = "http://192.168.0.25/addons/xmlapi/" + input
// Set up the URL request
guard let url = URL(string: completeURL) 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 as Any)
return
}
// make sure we got data
guard data != nil else {
print("Error: did not receive data")
return
}
completion(true)
}
task.resume()
}
If I put the 3 calls together it worked as it should.
I thing it should also work with GCD but most of examples for swift 2.
makeGetCall(input: "devicelist.cgi") {
(result: Bool) in
print("finished devicelist")
self.makeGetCall(input: "functionlist.cgi") {
(result: Bool) in
print("finished functionlist")
self.makeGetCall(input: "statelist.cgi") {
(result: Bool) in
print("finished statelist")
}
}
}
Maybe now somebody can help.
Thanks Hagen
Use a DispatchGroup to get notified when something happens.
override func viewDidLoad() {
super.viewDidLoad()
let stateListGroup = DispatchGroup()
stateListGroup.enter()
makeGetCall(input: "statelist.cgi") {
(result: Bool) in
print("finished statelist")
stateListGroup.leave()
}
let deviceListGroup = DispatchGroup()
deviceListGroup.enter()
// the notify closure is called when the (stateList-) groups enter and leave counts are balanced.
stateListGroup.notify(queue: DispatchQueue.main) {
self.makeGetCall(input: "devicelist.cgi") {
(result: Bool) in
print("finished devicelist")
deviceListGroup.leave()
}
}
let functionListGroup = DispatchGroup()
functionListGroup.enter()
deviceListGroup.notify(queue: DispatchQueue.main) {
self.makeGetCall(input: "functionList") {
(result: Bool) in
print("finished functionlist")
functionListGroup.leave()
}
}
functionListGroup.notify(queue: DispatchQueue.main) {
print("update ui here")
}
}
Prints:
statelist.cgi
finished statelist
devicelist.cgi
finished devicelist
functionList
finished functionlist
update ui here
Also keep in mind that the completion handler of session.dataTask() is called on a background queue, so I recommend to dispatch completion(true) on the main queue to avoid unexpected behaviour:
DispatchQueue.main.async {
completion(true)
}