taskWillPerformHTTPRedirection never called in Alamofire 5 - ios

Updating Alamofire to 5.0.4. As the title says taskWillPerformHTTPRedirection is never called.
In Alamofire 4.x we could do something like:
let sessionDelegate = request.session.delegate as! Alamofire.SessionDelegate
sessionDelegate.taskWillPerformHTTPRedirection = { session, task, response, request in
if let url = task.currentRequest?.url {
// look at redirected url & act accordingly
}
}
}
A request's session/delegate has been overhauled in Alamofire 5 and is no longer directly accessible from the request. More specifically, taskWillPerformHTTPRedirection is a closure callback on ClosureEventMonitor. As a sanity check, I tested using some of the other closure callbacks.. and they worked.
// ClosureEventMonitor
let monitor = ClosureEventMonitor()
monitor.requestDidCreateTask = { request, task in
// Event fires
}
let monitor2 = ClosureEventMonitor()
monitor2.taskWillPerformHTTPRedirection = { sess, task, resp, req in
// Event Never fires
}
monitor2.requestDidFinish = { request in
// Event Fires
}
// Set up Session
var session: Session? = Session(startRequestsImmediately: false, eventMonitors: [monitor, monitor2])
let url = URL(string: "https://google.com")!
let urlRequest = URLRequest(url: url)
let trequest = session?.request(urlRequest)
For reference this code is being fired from my AppDelegate func application(_ application: UIApplication, continue userActivity: NSUserActivity for handling deep/universal links.
I'm not exactly sure what I'm missing here. Any help is greatly appreciated. Thank you for your time.

There are three things here:
First, session?.request(urlRequest) will never actually make a request, since you never call resume() (or attach a response handler).
Second, using a one off Session like that is not recommended. As soon as the Session goes out of scope all requests will be cancelled.
Third, EventMonitors cannot interact with the request pipeline, they're only observational. Instead, use Alamofire 5's new RedirectHandler protocol or Redirector type to handle redirects. There is more in our documentation. A simple implementation that customizes the action performed would be:
let redirector = Redirector(behavior: .modify { task, request, response in
// Customize behavior.
})
session?.request(urlRequest).redirect(using: redirector)

Related

Session completion handler not being called for ASWebAuthenticationSession

I am trying to use ASWebAuthenticationSession web view. After the authentication is complete, the session completion handler is not being called. Hence, the web view doesn't dismiss.
guard let authURL = URL(string: "https://github.com/login/oauth/authorize?client_id=<client_id>/")
else { return }
let scheme = "octonotes"
session = ASWebAuthenticationSession.init(url: authURL, callbackURLScheme: scheme, completionHandler: { callbackURL, error in
// Handle the callback.
print(callbackURL!)
print(error!)
})
session?.presentationContextProvider = self
session?.start()
I have set the callback url scheme in info.plist. The same is updated in Targets -> info -> URL Types
It looks like:
URL Types
After running the above code, ASWebAuthenticationSession web view is presented, which provides user with sign in page. Once the authentication is complete, web view does not dismiss unlike WKWebView.
There is cancel option on top left of the web view, it calls the completion handler with error.
Is there a way to dismiss webview after the authentication session is complete?
It looks like you're missing the redirect URI in your authURL - so GitHub doesn't know where to redirect the successful auth to.
Try this:
let scheme = "octonotes"
guard let authURL = URL(string: "https://github.com/login/oauth/authorize?client_id=<client_id>&redirect_uri=\(scheme)://authcallback")
Bear in mind you may be missing some other parameters too, take a look here for the other ones you can provide https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#redirect-urls

Dispatch Group not allowing Alamofire request to execute

I'm using DispatchGroup to wait until a callback for one of my functions executes before continuing. Within that function, I'm calling Alamo fire get request. My issue occurs when I introduce the DispatchGroup, the AlamoFire closure never gets executed.
Sample
let group = DispatchGroup()
group.enter()
Networking.getInfo(userID: userID) { info in
group.leave()
}
group.wait()
Networking class:
static func getInfo(userID: Int, completion: #escaping(_ info: String) -> Void) {
// Program reaches here
Alamofire.request("https://someurl.com").responseJSON { response in
// Program does NOT get here
if let json = response.result.value {
completion("Successful request")
} else {
completion("Some Error")
}
}
}
When I don't use the DispatchGroup, it works fine. When I DO use the DispatchGroup, The getInfo function starts, but the closure of the Alamofire request never gets executed.
Not sure I'm right, but I suspect that the Alamofire response is being queued on the same queue (main) that the group has suspended (wait()). Because the queue is suspended, the completion closure never executes.
Manually writing asynchronous code like this can be quite tricky. My suggestion would be to use any one of the asynchronous libraries out there which can help with this. My personal favourite being PromiseKit which also has specific extensions to support Alamofire. Projects like this can take a lot of the headache out of asynchronous code. They may take some time to get your head around their paradigms, but it's worth doing.
I faced the same problem. In that case I used URLSession request to get ride of it. This API enables your app to perform background downloads when your app isn’t running or, in iOS, while your app is suspended.
https://developer.apple.com/documentation/foundation/urlsession
let request = try URLRequest(url: url, method: .get, headers: headers)
Alamofire.request(request) { response in
...
...
}
I changed it like this :-
let request = try URLRequest(url: url, method: .get, headers: headers)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
...
...
}
task.resume()
And then it worked fine.

Waiting for Asynchronous function call to complete

I know this question has been asked before, but all solutions do not work for me.
I have a function with sends parameters to an API, and returns the data as a list, I have a UITableView set up to use that list, but it runs before the list is assigned to a variable.
code:
var functionResult = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//gradesscrollView.contentSize.height = 3000
fetchItems{ (str) in
var returnedItems = [String]()
let result = self.convertoArray(itemstoPass: str!)
for i in result{
functionResult.append(i)
}
}
self.tableofItems.delegate = self
self.tableofItems.dataSource = self //Data source is set up to use functionResult, however functionResult is empty before fetchItem runs.
}
I would appreciate it if it is not immediately voted as a duplicate, here is what I have tried.
Dispatch groups
Semaphore timing
running variables
including self.tableofItems.delegate = self & self.tableofItems.dataSource = self in the fetchItems{ (str) in part.
EDIT:
Fetch items has been requested,
func fetchItems(completionHandler: #escaping (String?) -> ()) -> () {
let headers = [
"Content-Type": "application/x-www-form-urlencoded"
]
//Switch to keychain
let username = UserDefaults.standard.object(forKey: "username") as! String?
let password = UserDefaults.standard.object(forKey: "password") as! String?
let usernametoSend = username!
let passwordtoSend = password!
print(usernametoSend)
print(passwordtoSend)
let parameters: Parameters = [
"username": usernametoSend,
"password": passwordtoSend
]
Alamofire.request("https://www.mywebsite.com/API/getItems", method: .post, parameters: parameters, headers: headers)
.responseString { response in
completionHandler(String(response.result.value!))
You can't - and shouldn't - wait until an async call to complete. You need to study async programming until you understand it.
An async function accepts a job to do, and returns immediately, before the job is done.
in Swift you usually write an async function to take a completion handler, which is a block of code that you want to be run one the async task is complete.
I have a project called Async_demo (link) on Github that illustrates this. It implements a DownloadManager class that handles async downloads.
The key part is the function downloadFileAtURL(), which should more properly be named downloadDataAtURL, since it returns in-memory data rather than a file.
I created that function to take a completion handler as a parameter:
/**
This function demonstrates handling an async task.
- Parameter url The url to download
- Parameter completion: A completion handler to execute once the download is finished
*/
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}
}
It uses an NSURLSession to download a block of data from the specified URL. The data request call I use takes a completion handler that gets executed on a background thread. In the completion handler that I pass to the data task, I invoke the completion handler that's passed in to the downloadFileAtURL() function, but on the main thread.
The code you posted is kind of confusing. It isn't clear which part is the async function, what the flow is, or what data is needed to display your table view.
If you rewrite your function that does async work to take a completion block then you could call tableView.reloadData() in your completion block. (Make sure that call is performed on the main thread.)
EDIT:
As others have said, you need to edit your question to show the code for your fetchItems() function.
I'm guessing that that function is the one that does the Async work, and that the block after it is a completion handler that gets performed asynchronously. If so, you should probably refactor your code like this:
var functionResult = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//I moved these lines above the call to fetchItems to make it clear
//that they run before fetchItems' completion closure is executed
self.tableofItems.delegate = self
self.tableofItems.dataSource = self //Data source is set up to use functionResult, however functionResult is empty before fetchItem runs.
print("Step 1")
fetchItems{ (str) in
var returnedItems = [String]()
let result = self.convertoArray(itemstoPass: str!)
for i in result{
functionResult.append(i)
}
print("Step 3")
DispatchQueue.main.async() {
tableview.reloadData() //Do this from the main thread, inside the closure of `fetchItems()`
}
}
print("Step 2")
}
You should load the data of the list in the method of loadView, so that it will be loaded early before UITableView reads.
Sometimes, viewDidLoad performs a little bit slowly. Generally, people will initialize the data of list or sth in the method of loadView to make sure the data is completed before view is created.

Launch function at the end of another swift

I need to launch the funcion "caricaImmagine" after the function "caricaLavori". In the code is clear that caricaImmagine need that caricaLavori finish to append item to an array.
I've tried this but I don't know how to implement it.
Here's the "newMethod" (called in viewWillAppear):
func newMethod(){
print("login: \(login)")
let userFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "UserEntity")
do {
fetchUsers = try moc.fetch(userFetch) as! [User]
} catch {
fatalError("Failed to fetch person: \(error)")
}
do{
let users = try moc.fetch(userFetch)
if(users.count > 0){
print("utente connesso")
tableview.delegate = self
tableview.dataSource = self
let d_email = fetchUsers.first!.email
caricaLavori(datore_email: d_email!)
for i in 0...lavori.count{
caricaImmagine(id_lavoro: lavori[i].id)
}
}else{
DispatchQueue.main.async {
self.performSegue(withIdentifier: "area_utente_segue", sender: self)
}
}
}catch {}
}
If you want the other 2 functions I'll edit this post (both of them loads data from php script, both do an http request)
thank you guys
You say "both of them loads data from php script, both do an http request". That is a key piece of information.
On iOS you want to do network access asynchronously. That means that a function that submits a network requests submits that request and then returns immediately, before the request has even been sent to the remote server.
The solution is to write your methods to take a completion handler. The method invokes the completion handler once the task is complete.
Take a look at my answer in the thread below for code that includes a working example of using completion handlers:
Storing values in completionHandlers - Swift
Note that that post (And the sample app it links to) was written in Swift 2, and will need to be updated for Swift 3.

Every 11th execution of NSURLSessionTask takes much longer than others

I'm having a strange behavior in my Swift app, that I currently don't understand.
I have subclassed NSOperation to create different operations that can call Rest-WebServices via NSURLSession / NSURLSessionTask. This works fine in general.
In my case I have to execute many of these operations successively. Let's say I create a "chain" of 30 NSOperations with setting dependencies to execute them one by one.
Now I could reproduce the behavior, that every 11th (?!) execution of such an operation, takes much longer than the others. It seems as if the execution "sleeps" for nearly 10 seconds before it goes on. I can rule out, that the concrete web service call is the issue. Because if I change the order of execution, it is still the 11th operation that "hangs".
Currently I am creating a new instance of NSURLSession (defaultConfiguration) during the execution of every operation. Yesterday I tried to create a static instance of NSURLSession and create the instances of NSURLSessionTask during execution only. And now the "hanger" is gone! Unfortunately I could not do it this way, because the NSURLSessionDelegate has to be different for some operations, but this delegate must be passed during initialization.
Did anyone experience a similar behavior?
First I thought my code is too complex to post. But after Ketans comment, I will give it a try. I have trimmed it down to the most important parts. I hope this helps to show my problem. If you need more detail, please let me know.
class AbstractWebServiceOperation: NSOperation {
// VARIANT 2: Create a static NSURLSession only once --> The "sleep" DOES NOT occur!
static let SESSION = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
init(servicePath:String, httpMethod:String) {
// This is an 'abstract' class, that will be subclassed for concrete webService calls that differ in servicePath for URL, HTTP Method and parameters
}
// Override start() function of NSOperation to do webService call. NSOperations vars (ready, executing, finished) are overridden too, to get NSOperation "waiting" for the webService result. But I don't think it is relevant for the issue. So I did leave it out.
override func start() {
super.start()
// [...]
if let request = createRequest()
{
let task = createTask(request)
task.resume()
}
// [...]
}
// Creates the concrete NSURLRequest by using the service path and HTTP method defined by the concrete subclass.
private func createRequest()-> NSMutableURLRequest? {
// [...]
let webServiceURL = "https://\(self.servicePath)"
let url = NSURL(string: webServiceURL)
let request = NSMutableURLRequest(URL: url!)
request.timeoutInterval = 60
request.HTTPMethod = self.httpMethod
request.addValue("application/json;charset=UTF-8", forHTTPHeaderField: "Content-Type")
request.addValue("application/json;charset=UTF-8", forHTTPHeaderField: "Accept")
return request;
}
// Creates the concrete NSURLSessionTask for the given NSURLRequest (using a completionHandler defined by getCompletionHandler())
func createTask(request:NSURLRequest) -> NSURLSessionTask
{
// VARIANT 1: Create a new NSURLSession every time a AbstractWebServiceOperation is executed --> The "sleep" occurs!
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: nil, delegateQueue: nil)
return session.dataTaskWithRequest(request, completionHandler:getCompletionHandler())
// VARIANT 2: Create a static NSURLSession only once --> The "sleep" DOES NOT occur!
return AbstractWebServiceOperation.SESSION.dataTaskWithRequest(request, completionHandler:getCompletionHandler())
}
// Returns the completion handler for the NSURLSessionTask (may be overriden in subclass)
func getCompletionHandler() -> (NSData?, NSURLResponse?, NSError?) -> Void
{
return completionHandler
}
// Default completion handler
lazy var completionHandler:(NSData?, NSURLResponse?, NSError?) -> Void = {(data : NSData?, response : NSURLResponse?, error : NSError?) in
// default completion handling
}
}
Awww... I simply forgot to call session.finishTasksAndInvalidate() to invalidate the session after my webService call is done.
That solves my problem!

Resources