I am starting UIActivityIndicatorView in a func and want to stop it on success, for some reason it dosent happening:
func imageCaller(url: String , success: #escaping (UIImage) -> Void, failure: #escaping () -> Void) {
self.imageLoaderIndicator.startAnimating()
let handler = AuthenticateHandler()
self.urlSession = URLSession(configuration: URLSessionConfiguration.default, delegate: handler, delegateQueue: OperationQueue.main)
self.imageThumbnailTask = urlSession?.dataTask(with: URL(string:url)!) { data, res, err in
if err != nil {
guard let dataResponse = data,
err == nil else {
print("error from dataResponse:\(err?.localizedDescription ?? "Response Error")")
return
}
if let imageData = data {
if let image = UIImage(data: imageData) {
success(image)
}
}
}
self.imageThumbnailTask?.resume()
}
the code above is the network call.
This one is the method calling:
func imageThumbnailcall() {
self.imageCaller( url: self.isShowingThermal ? self.thermalUrl : self.visualUrl, success: { (image) in
self.imageLoaderIndicator.stopAnimating()
self.backGroundImageView.image = image
if self.isInVC {
self.imageThumbnailcall()
}
}) {
self.imageLoaderIndicator.stopAnimating()
}
}
Also in the storyboard i checked "hide when stopped" and also tried wrapping it with DispatchQueue.main.async {}
You have a lot of branches in your function that doesn't call either the success or the failure blocks and you also need to stop the activity indicator in the failure callback. You should also make sure that you dispatch all UI related activity to the main thread.
func imageCaller(url: String , success: #escaping (UIImage) -> Void, failure: #escaping () -> Void) {
self.imageLoaderIndicator.startAnimating()
let handler = AuthenticateHandler()
self.urlSession = URLSession(configuration: URLSessionConfiguration.default, delegate: handler, delegateQueue: OperationQueue.main)
self.imageThumbnailTask = urlSession?.dataTask(with: URL(string:url)!) { data, res, err in
if err != nil {
guard let dataResponse = data, err == nil else {
print("error from dataResponse:\(err?.localizedDescription ?? "Response Error")")
failure()
return
}
do{
//here dataResponse received from a network request
let jsonResponse = try JSONSerialization.jsonObject(with: dataResponse, options: [])
print("erro after parsing data:\(jsonResponse)") //Response result
failure()
} catch let parsingError {
failure()
print("Error", parsingError)
}
} else if let imageData = data, let image = UIImage(data: imageData) {
success(image)
} else {
failure()
}
}
self.imageThumbnailTask?.resume()
}
And then call stopAnimating from the failure block as well:
func imageThumbnailcall() {
self.imageCaller(url: self.isShowingThermal ? self.thermalUrl : self.visualUrl, success: { image in
DispatchQueue.main.async{
self.imageLoaderIndicator.stopAnimating()
self.backGroundImageView.image = image
}
if self.isInVC {
self.imageThumbnailcall()
}
}, failure: { _ in
DispatchQueue.main.async{
self.imageLoaderIndicator.stopAnimating()
}
})
}
Not sure what is isInVC or how it is set, but you call the same method again based on its value, which might result in an infinite loop of imageThumbnailcall calling itself from the success completion handler.
Try;
func imageThumbnailcall() {
self.imageCaller( url: self.isShowingThermal ? self.thermalUrl : self.visualUrl, success: { (image) in
self.imageLoaderIndicator.stopAnimating()
self.backGroundImageView.image = image
if self.isInVC {
self.imageThumbnailcall()
}
}) {
self.imageLoaderIndicator.stopAnimating()
}
}
Callback of URLSession.dataTask is in background queue so
DispatchQueue.main.async {
self.imageLoaderIndicator.stopAnimating()
}
and make sure to handle both success and failure
Related
I've been using DispatchWorkItem and DispatchQueue to make async requests in my app.
However, I've ran into trouble when trying to abort one of the requests.
I successfully use the workItem.cancel() to change the flag and I then check it where I want to abort. Like this:
for stop in self.userSettingsController.history {
stop.PassingInfo?.removeAll()
if workItem?.isCancelled ?? false {
print("CANCELED")
workItem = nil
break
}
...
However there's one case where I have no loop in which I can keep checking if the cancelled flag changes, so I cannot abort the request using the process above. Here's the code:
let tripQueue = DispatchQueue(label: "tripQueue")
var tripWorkItem: DispatchWorkItem? = nil
tripWorkItem = DispatchWorkItem {
self.soapServices.GetPathsByLineAndDirection(lineCode: self.lineCode!, direction: passingInfo.Direction!) { response in
DispatchQueue.main.async {
self.linePaths = response?.filter({$0.Places.contains(where: {$0.Code == self.singleStopSelected?.Code})})
if realTime {
//Get estimated trip
self.showingRealTime = true
if self.linePaths?.count ?? 0 > 0 {
self.getEstimatedTrip(lineCode: self.lineCode ?? "", direction: passingInfo.Direction ?? 0, stopCode: self.singleStopSelected?.Code ?? "", path: (self.linePaths?.first)!) { updateTripTimes in
//Does not work, as is expected. Just showing what I would like to achieve
if tripWorkItem?.isCancelled ?? false {
tripWorkItem = nil
return
}
if updateTripTimes {
DispatchQueue.main.async {
self.updateTripTimes = true
}
}
}
}
} else {
//Get trip
self.showingRealTime = false
self.getTrip(tripId: passingInfo.Id!)
}
}
}
tripWorkItem = nil
}
self.currentTripWorkItem = tripWorkItem
tripQueue.async(execute: tripWorkItem ?? DispatchWorkItem {})
Is there any way to do this?
Thanks in advance.
p.s: I'm sorry if this is duplicated, but I searched before and I couldn't find the question. I might be using the wrong terms.
Rather than putting your code in a DispatchWorkItem, consider wrapping it in a Operation subclass. You get the same isCancelled Boolean pattern:
class ComputeOperation: Operation {
override func main() {
while ... {
if isCancelled { break }
// do iteration of the calculation
}
// all done
}
}
For your network request, wrap it in an custom AsynchronousOperation subclass (e.g. this implementation), and implement cancel which will cancel the network request. For example:
enum NetworkOperationError: Error {
case unknownError(Data?, URLResponse?)
}
class NetworkOperation: AsynchronousOperation {
var task: URLSessionTask!
init(url: URL, completion: #escaping (Result<Data, Error>) -> Void) {
super.init()
self.task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
let responseData = data,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
error == nil
else {
DispatchQueue.main.async {
completion(.failure(error ?? NetworkOperationError.unknownError(data, response)))
self.finish()
}
return
}
DispatchQueue.main.async {
completion(.success(responseData))
self.finish()
}
}
}
override func main() {
task.resume()
}
override func cancel() {
super.cancel()
task.cancel()
}
}
Don't get lost in the details of the above example. Just note that
It subclassed AsynchronousOperation;
In the completion handler, it calls finish after calling the completion handler; and
The cancel implementation cancels the asynchronous task.
Then you can define your queue:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4 // use whatever you think is reasonable
And add your operations to it:
let operation = NetworkOperation(url: url) { response in
switch response {
case .failure(let error):
// do something with `error`
case .success(let data):
// do something with `data`
}
}
queue.addOperation(operation)
Now, the issue in your GetPathsByLineAndDirection and getEstimatedTrip is that you're not following “cancelable” patterns, namely you don't appear to be returning anything that could be used to cancel the request.
So, let's look at an example. Imagine you had some trivial method like:
func startNetworkRequest(with url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
completion(data, response, error)
}
task.resume()
}
What you'd do is change it to return something that can be canceled, the URLSessionTask in this example:
#discardableResult
func startNetworkRequest(with url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionTask {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
completion(data, response, error)
}
task.resume()
return task
}
Now that’s an asynchronous task that is cancelable (and you can wrap it with the above AsynchronousOperation pattern).
I need to call func fillFields after that func getJsonData is over.
The func getJsonData is a Async Task for get data on server for URLRequest.
func getAPIData() {
let initial = URL(string: "http://10.0.0.2/Blower/app/api/inicial.php")
DispatchQueue.main.async {
_ = URLSession.shared.dataTask(with: initial!) { (dados, requisicao, erro) in
if requisicao != nil {}
if let dados = dados {
do {
let json = try JSONSerialization.jsonObject(with: dados, options: []) as! [String: Any]
/*
*
*/
} catch {
print(erro as Any)
}
}
}.resume()
}
}
How can I know if the function getAPIData is finished?
You can Identify with Completion handler when the task is complete like this.
func getAPIData(complition:#escaping (AnyObject?, Error?) -> Void) {
let initial = URL(string: "http://10.0.0.2/Blower/app/api/inicial.php")
DispatchQueue.main.async {
_ = URLSession.shared.dataTask(with: initial!) { (dados, requisicao, erro) in
if requisicao != nil {}
if let dados = dados {
do {
let json = try JSONSerialization.jsonObject(with: dados, options: []) as! [String: Any]
complition(json as AnyObject, nil) // When Complete task
// Call next function Here
} catch {
print(erro as Any)
complition(nil, erro)
}
} else {
complition(nil, erro)
}
}.resume()
}
}
Call like this
self.getAPIData { (response,error) in
print(response) // Your response is here after complete task
}
I am trying to implement a retry mechanism and i saw that alamofire has one.
I am trying to implement a simple mechanism of retry with number of times for a request , yet something is wrong.
class OAuth2Handler: RequestAdapter, RequestRetrier {
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
return urlRequest
}
var defaultRetryCount = 4
private var requestsAndRetryCounts: [(Request, Int)] = []
private var lock = NSLock()
private func index(request: Request) -> Int? {
return requestsAndRetryCounts.index(where: { $0.0 === request })
}
func addRetryInfo(request: Request, retryCount: Int? = nil) {
lock.lock() ; defer { lock.unlock() }
guard index(request: request) == nil else { print("ERROR addRetryInfo called for already tracked request"); return }
requestsAndRetryCounts.append((request, retryCount ?? defaultRetryCount))
}
func deleteRetryInfo(request: Request) {
lock.lock() ; defer { lock.unlock() }
guard let index = index(request: request) else { print("ERROR deleteRetryInfo called for not tracked request"); return }
requestsAndRetryCounts.remove(at: index)
}
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion){
lock.lock() ; defer { lock.unlock() }
guard let index = index(request: request) else { completion(false, 0); return }
let (request, retryCount) = requestsAndRetryCounts[index]
if retryCount == 0 {
completion(false, 0)
} else {
requestsAndRetryCounts[index] = (request, retryCount - 1)
completion(true, 0.5)
}
}
}
this is the class that i am trying to use this:
let sessionManager = SessionManager()
override init() {
sessionManager.adapter = RequestAdapter.self as? RequestAdapter
sessionManager.retrier = OAuth2Handler()
}
func sendRequest(url: String,meth: HTTPMethod,parameters: [String: AnyObject]?, success: #escaping (String, Data) -> Void, failure: #escaping (Error) -> Void) {
self.asyncSerialWorker.enqueueWork { (done) in
self.sessionManager.request(url, method:meth).responseJSON { (responseObject) -> Void in
if responseObject.result.isSuccess {
print("Generic succsess")
let value = responseObject.result.value
let json = JSON(value!)
guard let result = responseObject.data else {return}
success(self.parser.parseMaiden(json: json), result)
}
if responseObject.result.isFailure {
let error : Error = responseObject.result.error!
print("login failed")
failure(error)
}
done()
}
}
}
if there are any other suggestions i would love to hear them
thanks
sessionManager.adapter = RequestAdapter.self as? RequestAdapter seems very wrong. You should be setting it to an instance of your OAuth2Handler.
So the issue her was to add the request to the retry, so first i did this:
let sessionManager = SessionManager()
var retrier = OAuth2Handler()
override init() {
sessionManager.retrier = retrier
}
and in the call itself i did as follow:
func sendRequest(url: String,meth: HTTPMethod,parameters: [String: AnyObject]?, success: #escaping (String, Data) -> Void, failure: #escaping (Error) -> Void) {
let request = sessionManager.request(url, method: meth, parameters: parameters, encoding: JSONEncoding.default)
retrier.addRetryInfo(request: request)
self.asyncSerialWorker.enqueueWork { (done) in
self.sessionManager.request(url, method:meth).responseJSON { (responseObject) -> Void in
if responseObject.result.isSuccess {
print("Generic succsess")
let value = responseObject.result.value
let json = JSON(value!)
guard let result = responseObject.data else {return}
success(self.parser.parseMaiden(json: json), result)
}
if responseObject.result.isFailure {
let error : Error = responseObject.result.error!
print("login failed")
failure(error)
}
done()
}
}
}
as you can see i have add to the retry a request :
retrier.addRetryInfo(request: request)
maybe i should do a remove in success(will check and update)
I'm experimenting with async calls but I'm a little lost. The print(json) in the viewDidLoad outputs an empty dictionary, but the one within the function prints correctly. This is unsurprising; it gets to that print before the async is completed. I can't figure out how to fix it; I tried putting the return within the completion handler, but I got an error that Unexpected non-void return value in void function. I tried changing the completion handler to expect a return value, but either that's not the right approach or I was doing it wrong.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let json = getJson("https://maps.googleapis.com/maps/api/geocode/json?address=WashingtonDC&sensor=false")
print(json)
}
func getJson(url: String) -> AnyObject {
var json:AnyObject = [:]
let urlPath = NSURL(string: url)
let urlRequest = NSURLRequest(URL: urlPath!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
if error != nil {
print("Error")
} else {
do {
json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
print(json)
} catch {
print("json error")
}
}
})
task.resume()
return json
}
}
You will need to have a completion handler based interface to your async API.
func getJson(url: String, completion : (success: Bool, json: AnyObject? ) ->Void ) -> Void {
var json:AnyObject = [:]
let urlPath = NSURL(string: url)
let urlRequest = NSURLRequest(URL: urlPath!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
if error != nil {
print("Error")
} else {
do {
json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
print(json)
//Call the completion handler here:
completion(success : true, json :json )
} catch {
print("json error")
completion(success : false, json :nil )
}
}
})
task.resume()
}
}
Now you call call this API as follows-
override func viewDidLoad() {
super.viewDidLoad()
getJson("https://maps.googleapis.com/maps/api/geocode/json?address=WashingtonDC&sensor=false") { (success, json) -> Void in
if success {
if let json = json {
print(json)
}
}
}
}
I'm trying to load an image using NSURLSession.
How can I do to "stop" the NSURLSession if the response returns a status code other than 200? I would like to display a network issue pop up.
class ViewController: UIViewController {
override func viewDidLoad()
{
super.viewDidLoad()
loadData()
}
func loadData()
{
self.downloadFullImageFrom("https://www.google.fr/logos/doodles/2015/holidays-2015-day-3-6399865393250304-5649050225344512-ror.jpg") { image in
dispatch_async(dispatch_get_main_queue())
{
//DO SOMETHING WITH images IF DOWNLOAD IS OK
//HOW CAN I KNOW IF dataTaskWithURL FAILED?
}
}
}
func downloadFullImageFrom(url:String, completion: ((image: UIImage?) -> Void)) {
let task = NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!) {(data, response, error) in
if let httpResponse = response as? NSHTTPURLResponse
{
if httpResponse.statusCode == 200
{
completion(image: UIImage(data: data!))
}
else { return }
}
}
task.resume()
}
}
Try using guard to unwrap your optionals and add the where clause to control the expected results, in case of failure just return nil:
func loadData() {
downloadFullImageFrom("https://www.google.fr/logos/doodles/2015/holidays-2015-day-3-6399865393250304-5649050225344512-ror.jpg") { image in
dispatch_async(dispatch_get_main_queue()) {
guard let image = image else {
print("error loading image")
return
}
//DO SOMETHING WITH images IF DOWNLOAD IS OK
print(image.size)
}
}
}
func downloadFullImageFrom(link: String, completion: ((image: UIImage?) -> Void)) {
guard let url = NSURL(string: link) else {
completion(image: nil)
return
}
NSURLSession.sharedSession().dataTaskWithURL(url) {(data, response , error) in
guard
let httpURLResponse = response as? NSHTTPURLResponse where httpURLResponse.statusCode == 200,
let data = data, image = UIImage(data: data) where error == nil else {
// display a network issue pop up
print("======= statusCode =======")
print((response as? NSHTTPURLResponse)?.statusCode)
completion(image: nil)
return
}
completion(image: image)
}.resume()
}
Xcode 8.3.1 • Swift 3.1
func loadData() {
downloadFullImageFrom(link: "https://www.google.fr/logos/doodles/2015/holidays-2015-day-3-6399865393250304-5649050225344512-ror.jpg") { image in
DispatchQueue.main.async() {
guard let image = image else {
print("error loading image")
return
}
//DO SOMETHING WITH images IF DOWNLOAD IS OK
print(image.size)
}
}
}
func downloadFullImageFrom(link: String, completion: #escaping ((UIImage?) -> Void)) {
guard let url = URL(string: link) else {
completion(nil)
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse,
httpURLResponse.statusCode == 200,
let data = data,
let image = UIImage(data: data),
error == nil
else {
// display a network issue pop up
print("======= statusCode =======")
print((response as? HTTPURLResponse)?.statusCode ?? "")
completion(nil)
return
}
completion(image)
}.resume()
}
Replace return with
completion(image: nil)
in your method definition. And check if image is nil inside
dispatch_async