How to call custom method right before sessionTask resume in Alamofire - swift

I need to record some data/info when Alamofire called resume to start the request.
(I used Swift in my project)
Is there anyway without do method_swizzling?
the timeline will be like this:
Call a request (put in request queue or execute right away) -> [custom method] -> SessionTask.resume()
I know Moya did something similar called WillSend. But I would like to know how to do it without using Moya.
Thank you.

If all you need to do is inspect various request elements you can use Alamofire 5's EventMonitor protocol, which is called during various lifetime events as Alamofire makes requests. There's a built in ClosureEventMonitor which allows you to set closures for those events. For example:
let monitor = ClosureEventMonitor()
monitor.requestDidCompleteTaskWithError = { (request, task, error) in
debugPrint(request)
}
let session = Session(eventMonitors: [monitor])
Please see our documentation for ClosureEventMonitor and the EventMonitor protocol itself.

Related

Unable to return value via Platform Channel when using Swift Callback Handlers

I am working with Flutter and Swift. I'm trying to authorize a credit card using the AuthrizeNet SDK, which unfortunately, does not natively support Flutter. To get around this, I created a Platform Channel in my iOS AppDelegate, which I successfully managed receive data in from Flutter. My question now is how can I return that data from inside the callback handler of the authorizing function (from the AuthorizeNet SDK)? When trying to call the Flutter result function, the Swift compiler throws this error: Escaping closure captures non-escaping parameter 'result'. Here's my code:
handler!.getTokenWithRequest(request, successHandler: { (inResponse:AcceptSDKTokenResponse) -> () in
let paymentResponseModel: PaymentResponseModel = PaymentResponseModel.init(
token: inResponse.getOpaqueData().getDataValue(),
resultCode: inResponse.getMessages().getResultCode(),
tokenResultDescription: inResponse.getOpaqueData().getDataDescriptor(),
messageCode: inResponse.getMessages().getMessages()[0].getCode(),
messageText: inResponse.getMessages().getMessages()[0].getText()
)
result(String(data: try! JSONEncoder().encode(paymentResponseModel), encoding: String.Encoding.utf8))
}) { (inError:AcceptSDKErrorResponse) in
let paymentResponseModel: PaymentModelErrorResponse = PaymentModelErrorResponse.init(
code: inError.getMessages().getResultCode(),
message: inError.getMessages().getMessages()[0].getCode(),
details: inError.getMessages().getMessages()[0].getText()
)
result(String(data: try! JSONEncoder().encode(paymentResponseModel), encoding: String.Encoding.utf8))
}
I've tried different way of calling the result function outside of the getTokenWithRequest function, but I couldn't get it to work. I'm fairly new to Swift programming, so I apologize if this question is vague or has a simple solution. Thank you!
The problem is that the successHandler will be called in the future (as a callback), but result must be called immediately and you must return from the method of your code snippet immediately. So, how do you get the success/error response back? You have to make a call from native to Dart when the the token response becomes available. Then, at the Dart end, you'll have to hang about until that response call is made when you can update your app state, and the UI.
It becomes a bi-directional method channel like this:
---token request--->
<---result----------
<---token response--
----result--------->
Neither of the results contains much information, but could be used to convey errors. For example, if the first result comes back false, something went wrong and the Dart end shouldn't expect the token response - it won't happen.

Swift Alamofire RequestInterceptor not called if using SessionDelegate

I am trying to refresh my authentication credential for my request using Alamofire's RequestInterceptor.
I noticed I am unable to enter the validate() portion of the interceptor as my breakpoints show me I am entering my SessionDelegate handlers instead. More precisely the urlSession(_:dataTask:didReceive:completionHandler:) and urlSession(_:task:didCompleteWithError:) methods of my SessionDelegate class seems to swallow the logic for the RequestInterceptor. If I remove these two handlers from my SessionDelegate I am able to enter the validate() code which then triggers the refresh method on my Authenticator class.
Is there a way to force the RequestInteceptor to intercept my request and refresh my credential if needed before it enters my SessionDelegate handlers? Removing the handlers from my SessionDelegate is not an option as I need to perform specific business logic in there.
Code:
self.session = Session(delegate: mySessionDelegate, interceptor: myRequestInterceptor)
self.session.request(myURL)
.validate({ _, response, data -> DataRequest.ValidationResult in
let statusCode = response.statusCode
//this code is not entered if I have my SessionDelegate handler
if 200 ... 299 ~= statusCode { return .success(Void()) }
return .failure(AFError.responseValidationFailed(reason: .unacceptableStatusCode(code: statusCode)))
})
.resume()
If you're writing a custom SessionDelegate subclass (which isn't recommended; there's almost always a better way to accomplish what you need), it's your responsibility to call super in your overridden methods, as they contain important events. Otherwise you'll break important Alamofire events, as you've found.

RxSwift - Fetch undetermined resources via HTTP

I have undetermined resources that need to be fetched from a server. I tried to accomplish this by using the repeatElement() and concat() operators like this:
repeatElement(0, CurrentThreadScheduler.instance).map({ _ -> Observable<[Task]> in
// API.getTasks() uses Alamofire to request data
return API.getTasks(loggedUser, after: loggedUser.taskPullTime)
}).concat().takeWhile({ (tasks) -> Bool in
return tasks.count > 0
})
Unfortunately, repeatElement will just emit an item without waiting for the old one to be handled. I think the reason is that Alamorfire executes in a private serial queue.
However, I cannot figure out how to solve this problem.
I used the strategy inspired from here in my Android project. Everything works fine because Retrofit init HTTP request in a synchronous manner.

Restangular - how to cancel/implement my own request

I found a few examples of using fullRequestInterceptor and httpConfig.timeout to allow canceling requests in restangular.
example 1 | example 2
this is how I'm adding the interceptor:
app.run(function (Restangular, $q) {
Restangular.addFullRequestInterceptor(function (element, operation, what, url, headers, params, httpConfig) {
I managed to abort the request by putting a resolved promise in timeout (results in an error being logged and the request goes out but is canceled), which is not what I want.
What I'm trying to do - I want to make the AJAX request myself with my own requests and pass the result back to whatever component that used Restangular. Is this possible?
I've been looking a restangular way to solve it, but I should have been looking for an angular way :)
Overriding dependency at runtime in AngularJS
Looks like you can extend $http before it ever gets to Restangular. I haven't tried it yet, but it looks like it would fit my needs 100%.
I'm using requestInterceptor a lot, but only to change parameters and headers of my request.
Basically addFullRequestInterceptor is helping you making change on your request before sending it. So why not changing the url you want to call ?
There is the httpConfig object that you can modify and return, and if it's close to the config of $http (and I bet it is) you can change the url and even method, and so change the original request to another one, entirely knew.
After that you don't need timeout only returning an httpConfig customise to your need.
RestangularConfigurer.addFullRequestInterceptor(function (element, operation, route, url, headers, params, httpConfig) {
httpConfig.url = "http://google.com";
httpConfig.method = "GET";
httpConfig.params = "";
return {
httpConfig: httpConfig
};
});
It will be pass on and your service or controller won't know that something change, that's the principle of interceptor, it allow you to change stuff and returning to be use by the next process a bit like a middleware. And so it will be transparent to the one making the call but the call will be made to what you want.

Retry an NSURLSessionDataTask request after re-authorizing (AKA NSInvocation equivalent in Swift)

TL;DR version: What is the nearest equivalent to NSInvocation - or similar way to package up a method call, including arguments - in Swift?
Background: I have a ServerController object which is used across my app to make requests to an API. The magic happens in this method:
private func makeRequestWithPath(
path: String,
method: String = "GET",
body: NSData? = nil,
successBlock: (AnyObject?) -> Void) {
// ...
self.urlSession.dataTaskWithRequest(request,
completionHandler: { (data, resp, error) -> Void in
if let httpResp = resp as? NSHTTPURLResponse {
switch httpResp.statusCode {
case 200:
successBlock(/* pass process response in here */)
case 401:
self.postNotificationOnMainThread(kAppTokenRefreshRequiredNotification)
// ...
}.resume()
Periodically API requests fail with a 401, which gets broadcast in the app as a notification, as many parties are interested in the fact the request is going to take a bit longer. Another API request is used to refresh an app token and then other API requests start working again.
This is the part I need help with: I then need to repeat the original API request following re-authorization.
Previously in Objective-C I might have used an NSInvocation object to package up the original method call, but this is not available in Swift and I'm therefore thinking there must be a more Swift-like approach. However I've not yet managed to get it figured out.
The best approach I've come up with so far is to:
put the arguments, along with a reference to the ServerController into a dictionary and pass them into the notification as the object: parameter
in the notification observer (after completing the re-authorization), pull the arguments out of the dictionary and manually call the same method again to repeat the request
This seems like a sub-optimal solution and there must be better way.