In a loop I'm hitting like 1000 GET requests. Since there are too many requests and the gateway could not handle it, it fails with the error code 502. Whats the best way to solve this. My code as bellow.
//In the array there are about 10000 ids
for id in Array {
Network.shared.getData(accountId: id ?? "", successBlock: {(result) in
//Save results to coredata
}) {(errorCode:Int, error:String) in
print(errorCode, error)
}
}
My Singleton Network later that ill be calling in the above method as bellow.
class Network: NSObject {
//These blocks catches success & failures
typealias SuccessBlock = (Any) -> Void
typealias FailureBlock = (_ errorCode: Int, _ error: String) -> Void
private static var networkCalls: Network?
private override init() {}
public static var shared: Network {
if networkCalls == nil {
networkCalls = Network()
}
return networkCalls!
}
private lazy var configurationManager: URLSession = {
let configuration = URLSessionConfiguration.default
configuration.allowsExpensiveNetworkAccess = true
configuration.allowsConstrainedNetworkAccess = true
configuration.allowsCellularAccess = true
configuration.timeoutIntervalForRequest = 60
configuration.timeoutIntervalForResource = 60
configuration.httpMaximumConnectionsPerHost = 2
let manager = URLSession(configuration: configuration)
return manager
}()
Inside the above class I use a generic method to call HTTP request (Here Im not showing the full method with all the parameters, but you can get an idea what it looks like)
private func performWebServiceRequest(with url: URL, contentType: CONTENT_TYPE? = nil, requestOptions: [String: String]?,successBlock: #escaping SuccessBlock, failureBlock: #escaping FailureBlock) {
let request = NSMutableURLRequest(url: url)
request.httpMethod = requestType // As in "POST", "GET", "PUT" or "DELETE"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let task = configurationManager.dataTask(with: request as URLRequest) { (data, response, error) in
guard let data: Data = data, let response: URLResponse = response, error == nil else
{return}
do {
let result = try JSONDecoder().decode(type, from: data)
successBlock(result)
} catch {
failureBlock(0,errorMessage)
}
}
task.resume()
}
You could use DispatchSemaphore to limit the number of parallel running tasks, like this:
var countedSemaphore = DispatchSemaphore(value:500) // Allow 500 parallel network calls
var queue = DispatchQueue(label: "runner", qos: .background, attributes: .concurrent)
//In the array there are about 10000 ids
for id in Array {
queue.async { //(1)
countedSemaphore.wait() // (2)
Network.shared.getData(accountId: id ?? "", successBlock: {(result) in
countedSemaphore.signal() // (3a)
//Save results to coredata
}) {(errorCode:Int, error:String) in
countedSemaphore.signal() // (3b)
print(errorCode, error)
}
}
}
The example uses a custom concurrent queue (1) to create tasks that will start the network fetching.
In the task, we first wait (2) for the semaphore. This call will simply decrement the semaphore's counter, as long as the counter is larger than zero. If the counter is zero, the wait call blocks.
After wait returns, we call getData.
In the success (3a) and error (3b) handlers, we call signal on the semaphore, to indicate that we've finished the network call. signal will increment the counter or wake up any waiting call.
This construct ensures that at utmost 500 getData calls are running in parallel
Related
Please refer to the code snippet below (certain parts not relevant to question are omitted)
In WebService1, dataTask is an instance variable/property whereas in WebService2, dataTask is a local variable inside the function callWebService.
final class WebService1 {
let urlSession = URLSession(configuration: .default)
// 1. data task is a private property of PNWebService here
private var dataTask: URLSessionDataTask?
func callWebService(completion: () -> ()) {
var urlRequest = URLRequest(url: url)
dataTask = urlSession.dataTask(with: urlRequest) {
// task complete
completion()
}
dataTask?.resume()
}
}
final class WebService2 {
let urlSession = URLSession(configuration: .default)
func callWebService(completion: () -> ()) {
var urlRequest = URLRequest(url: url)
// 2. data task is a local variable here
var dataTask = urlSession.dataTask(with: url) {
// task complete
completion()
}
dataTask.resume()
}
}
Clients an call these two services in the usual way:
let ws1 = WebService1()
ws1.callWebService() {
print("1. complete")
}
let ws2 = WebService2()
ws2.callWebService() {
print("2. complete")
}
Q1) Who owns a strong reference to dataTask in WebService2 so that it is not deallocated before the completion handler is called?
Q2) From a client perspective what is the difference at runtime between WebService1 & WebService2?
Are you asking which pattern is correct? Neither. The URLSession owns the data task and manages its memory as soon as you resume it for the first time, so there is no need for you to keep any reference to it, unless you plan to do something else with that reference such as configuring the task further or cancelling the operation later. Generally it is sufficient and quite usual to say
urlSession.dataTask(with:url) { data, resp, err in
// whatever
}.resume()
I am attempting to take a string from JSON data and set it to a variable. My problem is that the variable shows as empty. I am using JSONDecoder to retrieve the JSON data and setting the string to a variable outside of the function. I then want to use that variable inside of another function
When I print the variable it still shows up as blank even after the function has loaded. Within the function the string appears correctly.
Code:
var filmTitle = ""
override func viewDidLoad() {
super.viewDidLoad()
loadFilms()
print(self.filmTitle) //Prints as an empty string
}
func loadFilms() {
let id = filmId
let apiKey = "97a0d64910120cbeae9df9cb675ad235"
let url = URL(string: "https://api.themoviedb.org/3/movie/\(id)?api_key=\(apiKey)&language=en-US")
let request = URLRequest(
url: url! as URL,
cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData,
timeoutInterval: 10 )
let session = URLSession (
configuration: URLSessionConfiguration.default,
delegate: nil,
delegateQueue: OperationQueue.main
)
let task = session.dataTask(with: request, completionHandler: { (dataOrNil, response, error) in
if let data = dataOrNil {
do { let details = try! JSONDecoder().decode(Details.self, from: data)
self.filmTitle = details.title
print(self.filmTitle) //string prints correctly
}
}
})
task.resume()
}
What am I missing to correctly set the string to the variable?
Loading data from the internet is an asynchronous method. The print statement is being called before loadFilms() has completed.
Use a callback to get the data after it has completed.
func loadFilms(completion: #escaping (Details?, Error?) -> Void) {
//...
let task = session.dataTask(with: request, completionHandler: { (dataOrNil, response, error) in
if let data = dataOrNil {
do { let details = try JSONDecoder().decode(Details.self, from: data)
completion(details, nil)
} catch {
completion(nil, error)
}
})
}
At the call site:
override func viewDidLoad() {
loadFilms { details, error in
if error { //* Handle Error */ }
self.filmTitle = details.title
print(filmTitle)
}
}
Web request are asynchronous and from the CP's perspective, take a long time to complete. When you call this:
override func viewDidLoad() {
super.viewDidLoad()
loadFilms()
print(self.filmTitle) // loadFilms() hasn't finished so `filmTitle` is empty
}
It's better to set a property observer on filmTitle:
var filmTitle: String? = nil {
didSet {
print(filmTitle)
Dispatch.main.async {
// update your GUI
}
}
}
The solution to this problem was to reload the collection view that the array was being sent to within the decoder function after the data was set to the array.
I'm attempting to create a simple package tracking app that uses the USPS API to fetch data. The callRestService method successfully fetches the data and its completion handler serviceCallback (which sets the unparsedXml property) works. However, the method that I call callRestService from does not wait for it and its completion handler to complete before moving on, resulting in my print(unparsedXml) statement returning nil.
As shown below, I tried to use a DispatchGroup object and DispatchQueue to make the function wait for callRestService's completion but it continues regardless. How can I make the function wait for the call to complete?
var unparsedXml:String?
public func getTrackingInfo(_ trackingNumber: String) -> TrackingInfo {
let group = DispatchGroup()
group.enter()
DispatchQueue.global(qos: DispatchQoS.default.qosClass).async {
self.callRestService(requestUrl: self.getRequest(trackingNumber))
group.leave()
}
group.wait()
print(unparsedXml)
return TrackingInfo()
}
private func getRequest(_ trackingNumber: String) -> String {
let APIUsername = "Intentionally Omitted"
let trackingXmlLink = "http://production.shippingapis.com/ShippingAPI.dll?API=TrackV2&XML=%3CTrackFieldRequest%20USERID=%22" + APIUsername + "%22%3E%20%3CRevision%3E1%3C/Revision%3E%20%3CClientIp%3E127.0.0.1%3C/ClientIp%3E%20%3C/SourceId%3E%20%3CTrackID%20ID=%22" + trackingNumber + "%22%3E%20%3CDestinationZipCode%3E66666%3C/DestinationZipCode%3E%20%3CMailingDate%3E2010-01-01%3C/MailingDate%3E%20%3C/TrackID%3E%20%3C/TrackFieldRequest%3E"
return trackingXmlLink
}
public func callRestService(requestUrl:String) ->Void
{
var request = URLRequest(url: URL(string: requestUrl)!)
request.httpMethod = "GET"
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: serviceCallback)
task.resume()
}
private func serviceCallback(data:Data? , response:URLResponse? , error:Error? ) -> Void
{
unparsedXml = String(data: data!, encoding: .utf8)
//print(unparsedXml) Works correctly when uncommented
}
Your problem is that callRestService will dispatch an asynchronous network operation, so your group.leave will be called immediately, firing your group.notify.
You could put the group.leave in a completion handler, but you should avoid blocking code. I would suggest you structure your getTrackingInfo as asynchronous function that takes a completion handler:
public func getTrackingInfo(_ trackingNumber: String, completion:(TrackingInfo?,Error?) -> Void) {
self.callRestService(requestUrl: self.getRequest(trackingNumber)) { (data, response, error) in
guard error == nil, let returnData = data else {
completion(nil,error)
return
}
completion(TrackingInfo(returnData),nil)
}
}
private func getRequest(_ trackingNumber: String) -> String {
let APIUsername = "Intentionally Omitted"
let trackingXmlLink = "http://production.shippingapis.com/ShippingAPI.dll?API=TrackV2&XML=%3CTrackFieldRequest%20USERID=%22" + APIUsername + "%22%3E%20%3CRevision%3E1%3C/Revision%3E%20%3CClientIp%3E127.0.0.1%3C/ClientIp%3E%20%3CSourceId%3EFaiz%20Surani%3C/SourceId%3E%20%3CTrackID%20ID=%22" + trackingNumber + "%22%3E%20%3CDestinationZipCode%3E66666%3C/DestinationZipCode%3E%20%3CMailingDate%3E2010-01-01%3C/MailingDate%3E%20%3C/TrackID%3E%20%3C/TrackFieldRequest%3E"
return trackingXmlLink
}
public func callRestService(requestUrl:String, completion:(Data? , URLResponse? , Error? ) -> Void) ->Void
{
var request = URLRequest(url: URL(string: requestUrl)!)
request.httpMethod = "GET"
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: completion)
task.resume()
}
Wait for the completion of the operation in the notify
Simulate a network request:
public func networkTask(label: String, cost: UInt32, complete: #escaping ()->()) {
NSLog("Start network Task task%#",label)
DispatchQueue.global().async {
sleep(cost)
NSLog("End networkTask task%#",label)
DispatchQueue.main.async {
complete()
}
}
}
let group = DispatchGroup()
group.enter()
networkTask(label: "2", cost: 4) {
group.leave()
}
group.enter()
networkTask(label: "1", cost: 3) {
group.leave()
}
group.notify(queue: .main) {
print("task complete!")
// ......
}
you can try this example.
In general, it is necessary to implement a class for the network. This is a class that will take a URL, and to give data. All this is done in order not to score an extra logic controllers. I encountered such a problem that when you first create a View, the data do not come. That's Network Class:
private static var dataTask: NSURLSessionDataTask?
private static var dataJSON: NSData?
private static var sessionConfig: NSURLSessionConfiguration = {
var configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.allowsCellularAccess = false
configuration.HTTPMaximumConnectionsPerHost = 2
configuration.HTTPAdditionalHeaders = ["Accept": "application/json"]
configuration.timeoutIntervalForRequest = 30.0
configuration.timeoutIntervalForResource = 60.0
return configuration
}()
static func getListObjectsBy(url: String?) -> NSData? {
let session = NSURLSession(configuration: sessionConfig)
log.debug("DataTask start")
dataTask = session.dataTaskWithURL(NSURL(string: url!)!) { (data, response, error) in
log.debug("if error = error")
if let error = error {
print(error.localizedDescription)
} else if let httpResponse = response as? NSHTTPURLResponse {
log.debug("if httpResponse")
if httpResponse.statusCode == 200 {
dataJSON = data
} else {
print("Bad request")
}
}
}
dataTask?.resume()
log.debug("DataTask Resume")
return dataJSON
}
Method viewDidLoad in my main controller:
let response = Network.getListObjectsBy("http://lb.rmc.su/api-dev/v2/wc/5")
print(String(response))
My log say me, that data return nil. Notes, i'm switch between controllers with help SWRevealViewController. When reloading the main view controller, the data is returned. What me do?
enter image description here
You seem to be misunderstanding that this is an asynchronous call.
static func getListObjectsBy(url: String?) -> NSData? {
let session = NSURLSession(configuration: sessionConfig)
log.debug("DataTask start")
dataTask = session.dataTaskWithURL(NSURL(string: url!)!) { (data, response, error) in
// Everything in this block is happening on a separate thread.
log.debug("if error = error")
if let error = error {
print(error.localizedDescription)
} else if let httpResponse = response as? NSHTTPURLResponse {
log.debug("if httpResponse")
if httpResponse.statusCode == 200 {
// this won't happen until the data comes back from the remote call.
dataJSON = data
} else {
print("Bad request")
}
}
}
// This code here does not wait for the response from the remote.
// The call to the remote is sent then this code
// is immediately executed WITHOUT WAITING
dataTask?.resume()
log.debug("DataTask Resume")
// dataJSON will be nil until the remote answers.
return dataJSON
}
When you do this:
let response = Network.getListObjectsBy("http://lb.rmc.su/api-dev/v2/wc/5")
print(String(response))
The remote has not answered yet so you will get nil.
Your next question might be "what do I do about this?". The answer isn't clear without knowing everything else that you are doing.
Threads
Multiple threads of execution is like two programs running at the same time. Think of 2 people working on two different tasks at the same time. In order to keep the interface very responsive, iOS uses one thread of execution for updating the screen. If a process has to run that take a long time, we would not want the screen to wait until that is done. Let's say you have to fetch data from some remote system and that remote system is slow, your device would sit there frozen until the response came back. To avoid this, activities like calls to remote systems are done in another thread. The request is sent to the operating system itself and the operating system is told to call back when the operation is done.
This is what is happening here.
Sets up the request to send to the operating system.
dataTask = session.dataTaskWithURL(NSURL(string: url!)!)
Tells the operating system to start doing the work.
dataTask?.resume()
This block is the callback AKA the closure. iOS will run this code when the remote call is done.
dataTask = session.dataTaskWithURL(NSURL(string: url!)!) {
// Closure starts here
// Gets called when the remote has sent a response.
(data, response, error) in
// Everything in this block is happening on a separate thread.
log.debug("if error = error")
etc
}
This means you must wait until the response has come back before printing your output. You can use a closure in your function to do this.
public typealias CompletionHandler = (data: NSData?, error: NSError?) -> Void
static func getListObjectsBy(url: String?, completion: CompletionHandler) {
let session = NSURLSession(configuration: sessionConfig)
log.debug("DataTask start")
dataTask = session.dataTaskWithURL(NSURL(string: url!)!) {
(data, response, error) in
// Everything in this block is happening on a separate thread.
log.debug("if error = error")
if let error = error {
print(error.localizedDescription)
} else if let httpResponse = response as? NSHTTPURLResponse {
log.debug("if httpResponse")
if httpResponse.statusCode == 200 {
// this won't happen until the data comes back from the remote call.
} else {
print("Bad request")
}
}
// Call your closure
completion(data, error)
}
// This code here does not wait for the response from the remote.
// The call to the remote is sent then this code
// is immediately executed WITHOUT WAITING
dataTask?.resume()
log.debug("DataTask Resume")
}
In your calling code you would do this:
Network.getListObjectsBy("http://lb.rmc.su/api-dev/v2/wc/5") {
(data, error) in
if let data == data {
print(data)
}
}
I've started learning ios development a while back and I've reached the part where I'm trying to send and receive data from a server.
I've ran into an issue where for example if I wanted to sign a user in or sign him up using an Asynchronous connection. Code example on a datamanager class using swiftyjson :
class func signUp() -> NSString {
var url: NSURL = NSURL(string: "http://localhost/Test/signup.php")!
var request:NSMutableURLRequest = NSMutableURLRequest(URL:url)
var bodyData = "username=Datforis&name=firas&password=123123"
request.HTTPMethod = "POST"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
var status = "error"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue())
{
(response, data, error) in
println(response)
if(error != nil){
//handleerror
}
else if data == nil {
println("Server could not be reached please check your internet connection")
status = "connectionerror"
}
else {
println("i'm here")
let json = JSON(data : data)
var msgint : Int = json["status"].int!
status = String(msgint)
println(status)
}
}
println(status + " is current" )
return status //returns status if sign up was successful or not
}
the code will always return "error" (default value) because the return statement is being executed before the status string is being modified.
If I repeat the same code but with a synchronous connection it works fine, but everyone I ask tells me to steer clear of Synchronous connections because they freeze the UI while they execute
Is there a way to handle this without having to use a Synchronous connection? Or is it the only way? And is there a better way to handle a sign up/sign in request or any request in general?
Thanks
I would advice you to take a look at this article about Completion handlers.
Completion handlers are used instead of return statements.
The Completion handler will be called when your statement is completed instead of when it reaches the end of the function.
The link I mentioned before has the following example
func hardProcessingWithString(input: String, completion: (result: String) -> Void) {
…
completion(“we finished!”)
}
This could be used in mathematical calculations which take a long time, or in your case, URL requests. To retrieve the data you could use
hardProcessingWithString(“commands”) {
(result: String) in
println(“got back: (result)“)
}
thanks to the selected answer I corrected my code
Placed in the Datamanager :
class func signUp(username : String , completion: (status : NSString)->Void) {
var url: NSURL = NSURL(string: "http://localhost/WhosIn/signup.php")!
var request:NSMutableURLRequest = NSMutableURLRequest(URL:url)
var bodyData = "username=Datforis&name=firas&password=123"
request.HTTPMethod = "POST"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
var status = "error"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue())
{
(response, data, error) in
println(response)
if(error != nil){
completion(status: status)
}
else if data == nil {
println("Server could not be reached please check your internet connection")
status = "connectionerror"
completion(status: status)
}
else {
println("i'm here")
let json = JSON(data : data)
var msgint : Int = json["status"].int!
status = String(msgint)
println(status)
completion(status: status)
}
}
}
Placed in the View Controller when attempting to run the function
DataManager.signUp("asdasd", completion: {
(status) in
println(status)
})