swift, alamofire cancel previous request - swift

I have an NetworkRequest class, where all my alamofire requests made:
class NetworkRequest {
static let request = NetworkRequest()
var currentRequest: Alamofire.Request?
let dataManager = DataManager()
let networkManager = NetworkReachabilityManager()
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
func downloadData<T: Film>(slug: String, provider: String, section: String, dynamic: String, anyClass: T, completion: ([T]?) -> Void ) {
var token: String = ""
if LOGGED_IN == true {
token = "\(NSUserDefaults.standardUserDefaults().valueForKey(TOKEN)!)"
}
let headers = [
"Access": "application/json",
"Authorization": "Bearer \(token)"
]
let dataUrl = "\(BASE_URL)\(slug)\(provider)\(section)\(dynamic)"
print(headers)
print(dataUrl)
if networkManager!.isReachable {
currentRequest?.cancel()
dispatch_async(dispatch_get_global_queue(priority, 0)) {
if let url = NSURL(string: dataUrl) {
let request = Alamofire.request(.GET, url, headers: headers)
request.validate().responseJSON { response in
switch response.result {
case .Success:
if let data = response.result.value as! [String: AnyObject]! {
let receivedData = self.dataManager.parseDataToFilms(data, someClass: anyClass)
completion(receivedData)
}
case .Failure(let error):
print("Alamofire error: \(error)")
if error.code == 1001 {
self.goToNoConnectionVC()
}
print("canceled")
}
}
}
}
} else {
goToNoConnectionVC()
}
}
}
And I need to cancel previous downloadData request, when the new one starts, tried to cancel using currentRequest?.cancel(), but it doesn't help.
Already tried to cancelOperations using NSOperationsBlock, but it doesn't cancels current operation.
I block UI now, so that user can't send another request. But this is not correct, causes some errors later...
Pls, help

Now on Alamofire 4 the Alamofire.Manager.sharedInstance.session is not available you should use this solution:
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in
dataTasks.forEach { $0.cancel() }
uploadTasks.forEach { $0.cancel() }
downloadTasks.forEach { $0.cancel() }
}
and if you want to cancel (suspend, resume) a particular request you can check the request url in your .forEach block like this:
dataTasks.forEach {
if ($0.originalRequest?.url?.absoluteString == url) {
$0.cancel()
}
}

Found needed solution:
func stopAllSessions() {
Alamofire.Manager.sharedInstance.session.getAllTasksWithCompletionHandler { tasks in
tasks.forEach { $0.cancel() }
}
}
Update for Alamofire 5
func stopAllSessions() {
AF.session.getTasksWithCompletionHandler { (sessionDataTask, uploadData, downloadData) in
sessionDataTask.forEach { $0.cancel() }
uploadData.forEach { $0.cancel() }
downloadData.forEach { $0.cancel() }
}
}

If you want to cancel the request, you need to trace the requests made and try to cancel it. You can store it in an array and cancel every previous request stored.
In your code you create a request:
let request = Alamofire.request(.GET, url, headers: headers)
but you try to cancel the currentRequest?.cancel() that is never valued.

Swift 5
To cancel all requests use
Alamofire.Session.default.session.getTasksWithCompletionHandler({ dataTasks, uploadTasks, downloadTasks in
dataTasks.forEach { $0.cancel() }
uploadTasks.forEach { $0.cancel() }
downloadTasks.forEach { $0.cancel() }
})
To cancel a request with a particular url use
Alamofire.Session.default.session.getTasksWithCompletionHandler({ dataTasks, uploadTasks, downloadTasks in
dataTasks.forEach {
if ($0.originalRequest?.url?.absoluteString == "www.google.com") {
$0.cancel()
}
}
uploadTasks.forEach {
if ($0.originalRequest?.url?.absoluteString == "www.google.com") {
$0.cancel()
}
}
downloadTasks.forEach {
if ($0.originalRequest?.url?.absoluteString == "www.google.com") {
$0.cancel()
}
}
})

Related

Call request by request Alamofire swift

I wonder how I could make the request executed one by one. It's about image processing. And the server cannot process more than one image. The problem is that I need to send 10 pictures at a time, and if I send everything at once, I have a problem with the timeout, and now I'm interested in how to send the next request when response from the previous one arrives?
func clearingImage() {
if clearingImageArray.count < 0 || indexOfClearImage >= clearingImageArray.count
{
guard let imageName = ImageFilenameUtilities.getNameOfImageFromPath(filePath: clearingImageArray[indexOfClearImage]) else
{
indexOfClearImage+=1
clearingImage()
return
}
guard let clearType = ImageFilenameUtilities.getClearTypeFromFilename(filename: imageName) else
{
indexOfClearImage+=1
clearingImage()
return
}
guard let image = gallery?.getImageForPath(path: clearingImageArray[indexOfClearImage]) else
{
indexOfClearImage+=1
clearingImage()
return
}
sendImageToServer(image: image, imageName: imageName)
}
}
func sendImageToServer(image:UIImage,imageName:String)
{
let url = "example.com"
let headers : HTTPHeaders = [
"Connection": "Keep-Alive"
// "Content-Type": "application/x-www-form-urlencoded"
]
let manager = Alamofire.SessionManager.default
manager.session.configuration.timeoutIntervalForRequest = 12000
manager.session.configuration.timeoutIntervalForResource = 12000
manager.upload(multipartFormData: { multipleData in
for (key, value) in parameter {
multipleData.append(value.data(using: String.Encoding.utf8)!, withName: key)
}
multipleData.append(
imageData, withName: "image", fileName: imageName, mimeType: "image/jpg")}, to: url, method: .post, headers: headers){
(result) in
switch result {
case .success(let upload, _, _):
upload.responseJSON { response in
self.indexOfClearImage += 1
if let image = UIImage(data: response.data!) {
//save image to galery
}
else
{
if let error = response.result.error {
if error._code == NSURLErrorTimedOut {
self.indexOfClearImage -= 1
}
}
}
}
case .failure(let encodingError):
//send next image to server
self.indexOfClearImage += 1
}
}
self.clearingImage()
}
Just send the 2nd request on the 1st completion block, when you get the response, and so on.
func firstRequest() {
AF.request("url/here").response { response in
self.secondRequest()
}
}
func secondRequest() {
AF.request("url/here").response { response in
// call 3rd request and so on...
}
}

Refreshing bearer token and using AccessTokenPlugin

For many of my endpoints, I require a bearer token to be passed with the request to authenticate the user. Because of that, I also need to refresh the user's token if it expires.
I found this question to start me along, but I'm still unsure about some things and how to implement them.
As the answer to the question above suggests, I've created an extension for MoyaProvider:
extension MoyaProvider {
convenience init(handleRefreshToken: Bool) {
if handleRefreshToken {
self.init(requestClosure: MoyaProvider.endpointResolver())
} else {
self.init()
}
}
static func endpointResolver() -> MoyaProvider<Target>.RequestClosure {
return { (endpoint, closure) in
//Getting the original request
let request = try! endpoint.urlRequest()
//assume you have saved the existing token somewhere
if (#tokenIsNotExpired#) {
// Token is valid, so just resume the original request
closure(.success(request))
return
}
//Do a request to refresh the authtoken based on refreshToken
authenticationProvider.request(.refreshToken(params)) { result in
switch result {
case .success(let response):
let token = response.mapJSON()["token"]
let newRefreshToken = response.mapJSON()["refreshToken"]
//overwrite your old token with the new token
//overwrite your old refreshToken with the new refresh token
closure(.success(request)) // This line will "resume" the actual request, and then you can use AccessTokenPlugin to set the Authentication header
case .failure(let error):
closure(.failure(error)) //something went terrible wrong! Request will not be performed
}
}
}
}
I've also created an extension for TargetType, which looks like this:
import Moya
public protocol TargetTypeExtension: TargetType, AccessTokenAuthorizable {}
public extension TargetTypeExtension {
var baseURL: URL { return URL(string: Constants.apiUrl)! }
var headers: [String: String]? { return nil }
var method: Moya.Method { return .get }
var authorizationType: AuthorizationType { return .bearer }
var sampleData: Data { return Data() }
}
Here is one such implementation of it:
import Moya
public enum Posts {
case likePost(postId: Int)
case getComments(postId: Int)
}
extension Posts: TargetTypeExtension {
public var path: String {
switch self {
case .likePost(_): return "posts/like"
case .getComments(_): return "posts/comments"
}
}
public var authorizationType: AuthorizationType {
switch self {
case .likePost(_): return .bearer
case .getComments(_): return .none
}
}
public var method: Moya.Method {
switch self {
case .likePost, .getComments:
return .post
}
}
public var task: Task {
switch self {
case let .likePost(postId):
return .requestParameters(parameters: ["postId": postId], encoding: JSONEncoding.default)
case let .getComments(postId):
return .requestParameters(parameters: ["postId": postId], encoding: JSONEncoding.default)
}
}
}
As you can see, my likePost request requires a bearer token, while my getComments request does not.
So I have a few questions:
How would I modify my MoyaProvider extension to utilize Moya's AccessTokenPlugin, found here?
How would I modify my MoyaProvider extension to NOT require a bearer token if the AuthorizationType is .none for a specific request?
Thanks!
I did with custom moya provider and RxSwift if you are using RxSwift you can take reference, it will not directly fit into yor code!!
extension Reactive where Base: MyMoyaProvider {
func request(_ token: Base.Target, callbackQueue: DispatchQueue? = nil) -> Single<Response> {
return Single.create { [weak base] single in
var pandingCancelable:Cancellable?
/// reschedule original request
func reschedulCurrentRequest() {
base?.saveRequest({
pandingCancelable = base?.request(token, callbackQueue: callbackQueue, progress: nil) { result in
switch result {
case.success(let response):
single(.success(response))
case .failure(let error):
single(.error(error))
}
}
})
}
func refreshAccessToken(refreshToken:String) -> Cancellable? {
base?.isRefreshing = true /// start retrieveing refresh token
return base?.request(.retriveAccessToken(refreshToken: refreshToken), completion: { _ in
base?.isRefreshing = false /// end retrieveing refresh token
base?.executeAllSavedRequests()
})
}
if (base?.isRefreshing ?? false) {
reschedulCurrentRequest()
return Disposables.create { pandingCancelable?.cancel() }
}
let cancellableToken = base?.request(token, callbackQueue: callbackQueue, progress: nil) { result in
switch result {
case let .success(response):
if !(base?.isRefreshing ?? false), !token.isOAuth2TokenRefreshType, response.statusCode == TokenPlugin.CODE_UNAUTHORIZED, let refreshToken = token.getRefreshToken {
reschedulCurrentRequest()
_ = refreshAccessToken(refreshToken: refreshToken)
} else if (base?.isRefreshing ?? false) {
reschedulCurrentRequest()
} else {
single(.success(response))
}
case let .failure(error):
single(.error(error))
}
}
return Disposables.create {
cancellableToken?.cancel()
}
}
}
}
CustomProvider:
class MyMoyaProvider: MoyaProvider<MyServices> {
var isRefreshing = false
private var pandingRequests: [DispatchWorkItem] = []
func saveRequest(_ block: #escaping () -> Void) {
// Save request to DispatchWorkItem array
pandingRequests.append( DispatchWorkItem {
block()
})
}
func executeAllSavedRequests() {
pandingRequests.forEach({ DispatchQueue.global().async(execute: $0); })
pandingRequests.removeAll()
}
}
extension TargetType {
var isOAuth2TokenRefreshType: Bool { return false }
var getRefreshToken:String? { return nil }
}

Alamofire Promise make generic request method

I am trying to implement a generic method which is used to get data from API call.
Here is My code.
class Http {
class func asyncGetRequest()->Promise<List> {
return Promise {
resolve in
Alamofire.request(GET_PROFFESSIONAL).responseData().done { response in
guard let blog = try? JSONDecoder().decode(List.self, from: response.data) else {
let error = MyError(message: "Error: Couldn't decode")
resolve.reject(error)
return
}
resolve.fulfill(blog as List)
}.ensure {
}.catch { error in
resolve.reject(error)
}
}
}
}
I would like to make a generic method for all request. In my code, I made promise type List
and JSONDecoder().decode(List.self), Here I would like to make List type dynamic. How can we do that? I would like to reuse asyncGetRequest method for all the API calls. I want to pass request URL (GET_PROFFESSIONAL) and List class type as dynamic.
You could use Generics and write something like this:
class Http {
class func asyncGetRequest<ListType: Decodable>() -> Promise<ListType> {
return Promise { resolve in
Alamofire.request(GET_PROFFESSIONAL).responseData().done { response in
guard let blog = try? JSONDecoder().decode(ListType.self, from: response.data) else {
let error = MyError(message: "Error: Couldn't decode")
resolve.reject(error)
return
}
resolve.fulfill(blog as ListType)
}.ensure {
}.catch { error in
resolve.reject(error)
}
}
}
}
Alamofire (5.2.2)
PromiseKit (6.13.1)
struct Network {
static func get<Mode: Codable>(url: String) -> Promise<Mode> {
return Promise { seal in
AF.request(url, method: .get, encoding: URLEncoding.default, headers: nil)
.response { response in
if let error = response.error {
seal.reject(error)
return
}
guard let data = response.data else { return }
do {
let items = try JSONDecoder().decode(Mode.self, from: data)
seal.resolve(items, nil)
} catch {
seal.reject(error)
}
}
}
}
}

How to get all page url of a paginated page with RxSwift

I am starting my first RxSwift project.
I want all the pagination url of this page : http://mangafox.me/directory/
So far, I can get the next page url.
Here is the code used so far:
func getNextPageUrl(currentStringUrl: String) -> Observable<String> {
return Observable.create { observer -> Disposable in
let request = Alamofire.request(currentStringUrl)
.validate()
.responseString { response in
if response.result.isSuccess {
if let doc = HTML(html: response.result.value!, encoding: .utf8) {
if let nextPage = doc.css("a > span.next").first?.parent {
observer.onNext("\(currentStringUrl)\(nextPage["href"]!)")
}
}
observer.onCompleted()
}else{
observer.onError(response.result.error!)
}
}
return Disposables.create {
request.cancel()
}
}
}
Now I want to make a string list of other next pagination link. How can I make that?
I have found myself so here my method :
func getAllMangaPage() -> Observable<String>{
return Observable.create { observer in
var urls :[String] = []
let subs = self.repeatEvery(second: 0.2)
.subscribe { event in
if urls.isEmpty {
urls.append(self.popularMangaInitialUrl())
}else{
self.getNextPageUrl(currentStringUrl: urls.last!)
.subscribe(
onNext: { url in
var nextUrl = url as String
if !nextUrl.contains("http://") {
let completeUrl = "\(self.popularMangaInitialUrl())\(nextUrl)"
if !urls.contains(completeUrl){
urls.append("\(self.popularMangaInitialUrl())\(nextUrl)")
observer.onNext(urls.last!)
}
}else{
if !urls.contains(nextUrl){
urls.append(nextUrl)
observer.onNext(urls.last!)
}
}
},
onCompleted: {
},
onDisposed: {
}
)
}
}
return Disposables.create {
print("Disposed")
subs.dispose()
}
}
}

Alamofire number of requests one after another

I have a number of requests witch I would like to call one after another without having nested spaghetti code.
I tried it already with a serial dispatch queue
let queue = dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL)
Alamofire.request(Router.Countries).responseString { (response:Response<String, NSError>) in
print(1)
}
Alamofire.request(Router.Countries).responseString { (response:Response<String, NSError>) in
print(2)
}
Alamofire.request(Router.Countries).responseString { (response:Response<String, NSError>) in
print(3)
}
But unfortunately that does not work. The output of this can be 1,3,2 or 3,1,2 or any other combination.
What would be the best approach to get the output 1,2,3 so one after the other.
Ok I ended up writing my own implementation.
I created a class RequestChain wich takes Alamofire.Request as parameter
class RequestChain {
typealias CompletionHandler = (success:Bool, errorResult:ErrorResult?) -> Void
struct ErrorResult {
let request:Request?
let error:ErrorType?
}
private var requests:[Request] = []
init(requests:[Request]) {
self.requests = requests
}
func start(completionHandler:CompletionHandler) {
if let request = requests.first {
request.response(completionHandler: { (_, _, _, error) in
if error != nil {
completionHandler(success: false, errorResult: ErrorResult(request: request, error: error))
return
}
self.requests.removeFirst()
self.start(completionHandler)
})
request.resume()
}else {
completionHandler(success: true, errorResult: nil)
return
}
}
}
And I use it like this
let r1 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("1")
}
let r2 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("2")
}
let r3 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("3")
}
let chain = RequestChain(requests: [r1,r2,r3])
chain.start { (success, errorResult) in
if success {
print("all have been success")
}else {
print("failed with error \(errorResult?.error) for request \(errorResult?.request)")
}
}
Importent is that you are telling the Manager to not execute the request immediately
let manager = Manager.sharedInstance
manager.startRequestsImmediately = false
Hope it will help someone else
I'm using Artman's Signals to notify my app once a result is returned, after which a queue elsewhere can call it's next request:
Alamofire.request( httpMethod, url, parameters: params ).responseJSON
{
( response: Response< AnyObject, NSError > ) in
self._signals.dispatchSignalFor( Key: url, data: response.result )
}
More details here.
One solution is to call your second request in the first one's callback :
Alamofire.request(Router.Countries).responseString { (response:Response<String, NSError>) in
print(1)
Alamofire.request(Router.Countries).responseString { (response:Response<String, NSError>) in
print(2)
Alamofire.request(Router.Countries).responseString { (response:Response<String, NSError>) in
print(3)
}
}
}