How to revalidate the session once it is invalidated Alamofire? - ios

The use of invalidateAndCancel() comes up with another problem which is to reinitialize the session again if u need to use it again. After searching on this problem i got the answer of Cnoon's Alamofire Background Service, Global Manager? Global Authorisation Header?. Tried creating the manager but when it is called it never re-initialize the value, to anticipate this problem i did a small cheat i tried giving my session new name each time.
func reset() -> Manager {
let configuration: NSURLSessionConfiguration = {
let identifier = "com.footbits.theartgallery2.background-session\(sessionCount)"
let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(identifier)
return configuration
}()
return Alamofire.Manager(configuration: configuration)
}
func cancelAllRequests() {
manager.session.invalidateAndCancel()
sessionCount += 1
manager = reset()
}
Now i am curious of what is happening in background like the previous session is still there or has been removed by alamofire because if it is still there how many session can i create and what will be the after affect? because i tried:
manager.session.resetWithCompletionHandler {
print("Session reset")
}
it doesnt work.

I know this question is old, but this info will apply to you (the reader) wether or not you are using Alamofire.
You can not revalidate a URLSession, for good reason: The tasks within a session will be told to cancel upon invalidation of the session. However, those tasks may take a while to actually cancel and leave the session. To prove this, cancel a session and retrieve its tasks periodically afterwards. They'll slowly drop towards zero.
What you need to do instead is create a new session with a new identifier. Using the same identifier will cause the old session to be used and crash if new tasks are scheduled to it!
For those seeking to perform background downloads consider using a UUID().uuidString as part of the identifier and storing the id somewhere on the device (Userdefaults come to mind), and generating a new uuid after every call to invalidate your current session. Then create a new session object with a new uuid and reference that instead. Bonus: This new session immediately will have 0 open tasks which proves useful if your logic is based on that.
Example from within my project:
#UserDefaultOptional(defaultValue: nil, "com.com.company.sessionid")
private var sessionID: String?
// MARK: - Init
private var session: URLSession!
private override init() {
super.init()
setupNewSession()
}
private func setupNewSession() {
let id = sessionID ?? UUID().uuidString
sessionID = id
let configuration = URLSessionConfiguration.background(withIdentifier: id)
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
private func invalidateSession() {
session.invalidateAndCancel()
sessionID = nil
setupNewSession()
}

Related

taskWillPerformHTTPRedirection never called in Alamofire 5

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)

Spotify SessionManager continually failing with error "invalid_grant"

What I'm trying to do:
I am implementing the Spotify SDK into my iOS project. I am successfully receiving access tokens for Spotify's API as I am able to do things like search artists, search songs, and view playlists using said API.
The one thing I am struggling to do is play music with the SDK. I have a button that, upon clicking, I want the following flow to happen:
I request Spotify access by doing the following function and using the following Session Manager:
let SpotifyClientID = "###"
let SpotifyRedirectURL = URL(string: "bandmate://")!
lazy var configuration = SPTConfiguration(
clientID: SpotifyClientID,
redirectURL: SpotifyRedirectURL
)
lazy var sessionManager: SPTSessionManager = {
if let tokenSwapURL = URL(string: "https://bandmateallcaps.herokuapp.com/api/token"),
let tokenRefreshURL = URL(string: "https://bandmateallcaps.herokuapp.com/api/refresh_token") {
configuration.tokenSwapURL = tokenSwapURL
configuration.tokenRefreshURL = tokenRefreshURL
configuration.playURI = ""
}
let manager = SPTSessionManager(configuration: configuration, delegate: self)
return manager
}()
func requestSpotifyAccess() {
let requestedScopes: SPTScope = [.appRemoteControl, .userReadPrivate]
self.sessionManager.initiateSession(with: requestedScopes, options: .default)
}
Upon initiation of a SPTSession, I want to connect my remote:
lazy var appRemote: SPTAppRemote = {
let appRemote = SPTAppRemote(configuration: configuration, logLevel: .debug)
appRemote.delegate = self
return appRemote
}()
func sessionManager(manager: SPTSessionManager, didInitiate session: SPTSession) {
self.appRemote.connectionParameters.accessToken = session.accessToken
self.appRemote.connect()
}
Upon app connection, I want to play the ID of a Spotify track that is declared globally:
var pendingSpotifyId: String!
func appRemoteDidEstablishConnection(_ appRemote: SPTAppRemote) {
print("connected")
self.appRemote.playerAPI!.delegate = self
self.appRemote.playerAPI!.subscribe(toPlayerState: { (result, error) in
if let error = error {
debugPrint(error.localizedDescription)
} else if self.pendingSpotifyId != nil {
self.appRemote.playerAPI!.play(self.pendingSpotifyId, callback: { (any, err) in
self.pendingSpotifyId = nil
})
}
})
}
My problem:
This flow is broken up as any time I try to initiate a session, sessionManager(manager: SPTSessionManager, didFailWith error: Error) is always called returning the following error:
Error Domain=com.spotify.sdk.login Code=1 "invalid_grant" UserInfo={NSLocalizedDescription=invalid_grant}
I need the session to initiate successfully so that sessionManager(manager: SPTSessionManager, didInitiate session: SPTSession) can be called and I can connect my remote and, ultimately, play my Spotify track.
What I've tried:
I have ensured a number of things:
Ensured the state of the Spotify app in the background on the user's device is playing (per this ticket: https://github.com/spotify/ios-sdk/issues/31)
Ensured that the correct scopes are in place when receiving an access token. Returned JSON looks something like:
{"access_token":"###","token_type":"Bearer","expires_in":3600,"refresh_token":"###","scope":"app-remote-control user-read-private"}
Things I'm suspicious of:
I am unaware if my token swap via Heroku is being done correctly. This is the only reason I can think of as to why I would be getting this issue. If I am able to use the Spotify API, is this evidence enough that my token swap is being done correctly? (I suspect it is)
Here's what we found out, hope it will help:
The SpotifySDK tutorial doesn't mention that Bundle ID and App Callback URL must precisely match across App Info.plist, source code, Spotify App Dashboard and Heroku Env Vars. The Bundle ID used must match your application Bundle ID.
The App Callback URL must not have empty path, ie: my-callback-scheme://spotify-login-callback
When using both Web Spotify SDK and iOS Framework Spotify SDK in app, take care that only one of them performs auth. Otherwise the App Callback URL will be called twice resulting in error.
The Spotify configuration.playURI may need to be set to empty string rather than nil. Sample app has a note on it.
It's best to have only one instance of object managing Spotify auth in the app. Otherwise ensuring that the correct object is called from the AppDelegate open url method can be tricky.

Am I stuck resolving many ThreadSafeReferences to use Realm with asynchronous tasks like network requests?

I have a set of NSOperations which make network requests. Imagine a case where we have a User object in Realm and I want to make some changes and then send a PATCH request to the server:
let operation = UpdateUserOperation(updatedUser)
Then the operation runs on a different thread so it has to resolve a thread safe reference:
class UpdateUserOperation : Operation {
var userReference : ThreadSafeReference<User>
init(_ user: User) {
userReference = ThreadSafeReference(to: user)
}
func main() {
// We're probably on a different thread so resolve the reference
let user = try! Realm().resolve(userReference)
// That invalidated `userReference` so we need to create a new
// one to use below...
userReference = ThreadSafeReference(to: user)
sendUserPatchRequest(user) { response in
// We might be in _another_ thread again :(
let realm = try! Realm()
let user = realm.resolve(userReference)
try! realm.write {
user.updateFromResponse(response)
}
}
}
}
This feels like a really unclean way to do this – re-fetching the user so many times to do a pretty simple task. It feels especially onerous because we need to re-up the thread-safe reference – they aren't re-usable. In Core Data, we'd be able to choose a single NSManagedObjectContext to do our work in and ensure thread safety by using managedObjectContext.perform { /* ... */ }, but that sort of functionality is unavailable in Realm.
Am I missing anything? Is there a better way to do this, or am I stuck re-fetching the object each time I need to use it?

Alamofire request blocking during application lifecycle

I'm having troubles with Alamofire using Operation and OperationQueue.
I have an OperationQueue named NetworkingQueue and I push some operation (wrapping AlamofireRequest) into it, everything works fine, but during application living, at one moment all Alamofire request are not sent. My queue is getting bigger and bigger and no request go to the end.
I do not have a scheme to reproduce it anytime.
Does anybody have a clue for helping me?
Here is a sample of code
The BackgroundAlamoSession
let configuration = URLSessionConfiguration.background(withIdentifier: "[...].background")
self.networkingSessionManager = Alamofire.SessionManager(configuration: configuration)
AbstractOperation.swift
import UIKit
import XCGLogger
class AbstractOperation:Operation {
private let _LOGGER:XCGLogger = XCGLogger.default
enum State:String {
case Ready = "ready"
case Executing = "executing"
case Finished = "finished"
var keyPath: String {
get{
return "is" + self.rawValue.capitalized
}
}
}
override var isAsynchronous:Bool {
get{
return true
}
}
var state = State.Ready {
willSet {
willChangeValue(forKey: self.state.rawValue)
willChangeValue(forKey: self.state.keyPath)
willChangeValue(forKey: newValue.rawValue)
willChangeValue(forKey: newValue.keyPath)
}
didSet {
didChangeValue(forKey: oldValue.rawValue)
didChangeValue(forKey: oldValue.keyPath)
didChangeValue(forKey: self.state.rawValue)
didChangeValue(forKey: self.state.keyPath)
}
}
override var isExecuting: Bool {
return state == .Executing
}
override var isFinished:Bool {
return state == .Finished
}
}
A concrete Operation implementation
import UIKit
import XCGLogger
import SwiftyJSON
class FetchObject: AbstractOperation {
public let _LOGGER:XCGLogger = XCGLogger.default
private let _objectId:Int
private let _force:Bool
public var object:ObjectModel?
init(_ objectId:Int, force:Bool) {
self._objectId = objectId
self._force = force
}
convenience init(_ objectId:Int) {
self.init(objectId, force:false)
}
override var desc:String {
get{
return "FetchObject(\(self._objectId))"
}
}
public override func start(){
self.state = .Executing
_LOGGER.verbose("Fetch object operation start")
if !self._force {
let objectInCache:objectModel? = Application.main.collections.availableObjectModels[self._objectId]
if let objectInCache = objectInCache {
_LOGGER.verbose("object with id \(self._objectId) founded on cache")
self.object = objectInCache
self._LOGGER.verbose("Fetch object operation end : success")
self.state = .Finished
return
}
}
if !self.isCancelled {
let url = "[...]\(self._objectId)"
_LOGGER.verbose("Requesting object with id \(self._objectId) on server")
Application.main.networkingSessionManager.request(url, method : .get)
.validate()
.responseJSON(
completionHandler: { response in
switch response.result {
case .success:
guard let raw:Any = response.result.value else {
self._LOGGER.error("Error while fetching json programm : Empty response")
self._LOGGER.verbose("Fetch object operation end : error")
self.state = .Finished
return
}
let data:JSON = JSON(raw)
self._LOGGER.verbose("Received object from server \(data["bId"])")
self.object = ObjectModel(objectId:data["oId"].intValue,data:data)
Application.main.collections.availableobjectModels[self.object!.objectId] = self.object
self._LOGGER.verbose("Fetch object operation end : success")
self.state = .Finished
break
case .failure(let error):
self._LOGGER.error("Error while fetching json program \(error)")
self._LOGGER.verbose("Fetch object operation end : error")
self.state = .Finished
break
}
})
} else {
self._LOGGER.verbose("Fetch object operation end : cancel")
self.state = .Finished
}
}
}
The NetworkQueue
class MyQueue {
public static let networkQueue:SaootiQueue = SaootiQueue(name:"NetworkQueue", concurent:true)
}
How I use it in another operation and wait for for result
let getObjectOperation:FetchObject = FetchObject(30)
SaootiQueue.networkQueue.addOperations([getObjectOperation], waitUntilFinished: true)
How I use it the main operation using KVO
let getObjectOperation:FetchObject = FetchObject(30)
operation.addObserver(self, forKeyPath: #keyPath(Operation.isFinished), options: [.new], context: nil)
operation.addObserver(self, forKeyPath: #keyPath(Operation.isCancelled), options: [.new], context: nil)
queue.addOperation(operation)
//[...]
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let operation = object as? FetchObject {
operation.removeObserver(self, forKeyPath: #keyPath(Operation.isFinished))
operation.removeObserver(self, forKeyPath: #keyPath(Operation.isCancelled))
if keyPath == #keyPath(Operation.isFinished) {
//Do something
}
}
A few clarifications:
My application is a radio player and I need, while playing music and the background, to fetch the currently playing program. This is why I need background Session.
In fact I also use the background session for all the networking I do when the app is foreground. Should I avoid that ?
The wait I'm using is from another queue and is never used in the main queue (I know it is a threading antipattern and I take care of it).
In fact it is used when I do two networking operation and the second one depends of the result of the second. I put a wait after the first operation to avoid KVO observing. Should I avoid that ?
Additional edit:
When I say "My queue is getting bigger and bigger and no request go to the end", it means that at one moment during application livecycle, random for the moment (I can not find a way to reproduce it at every time), Alamofire request don't reach the response method.
Because of that the Operation wrapper don't end and the queue is growing.
By the way I'm working on converting Alamofire request into URLRequest for having clues and I founded some problem on using the main queue. I have to sort what is due to the fact that Alamofire use the main queue for reponse method and I'll see if I find a potential deadlock
I'll keep you informed. Thanks
There are minor issues, but this operation implementation looks largely correct. Sure, you should make your state management thread-safe, and there are other stylistic improvements you could make, but I don't think this is critical to your question.
What looks worrisome is addOperations(_:waitUntilFinished:). From which queue are you waiting? If you do that from the main queue, you will deadlock (i.e. it will look like the Alamofire requests never finish). Alamofire uses the main queue for its completion handlers (unless you override the queue parameter of responseJSON), but if you're waiting on the main thread, this can never take place. (As an aside, if you can refactor so you never explicitly "wait" for operations, that not only avoids the deadlock risk, but is a better pattern in general.)
I also notice that you're using Alamofire requests wrapped in operations in conjunction with a background session. Background sessions are antithetical to operations and completion handler closure patterns. Background sessions continue after your app has been jettisoned and you have to rely solely upon the SessionDelegate closures that you set when you first configure your SessionManager when the app starts. When the app restarts, your operations and completion handler closures are long gone.
Bottom line, do you really need background session (i.e. uploads and downloads that continue after your app terminates)? If so, you may want to lose this completion handler and operation based approach. If you don't need this to continue after the app terminates, don't use background sessions. Configuring Alamofire to properly handle background sessions is a non-trivial exercise, so only do so if you absolutely need to. Remember to not conflate background sessions and the simple asynchronous processing that Alamofire (and URLSession) do automatically for you.
You asked:
My application is a radio player and I need, while playing music and the background, to fetch the currently playing program. This is why I need background Session.
You need background sessions if you want downloads to proceed while the app is not running. If your app is running in the background, though, playing music, you probably don't need background sessions. But, if the user chooses to download a particular media asset, you may well want background session so that the download proceeds when the user leaves the app, whether the app is playing music or not.
In fact I also use the background session for all the networking I do when the app is foreground. Should I avoid that ?
It's fine. It's a little slower, IIRC, but it's fine.
The problem isn't that you're using background session, but that you're doing it wrong. The operation-based wrapping of Alamofire doesn't make sense with a background session. For sessions to proceed in the background, you are constrained as to how you use URLSession, namely:
You cannot use data tasks while the app is not running; only upload and download tasks.
You cannot rely upon completion handler closures (because the entire purpose of background sessions is to keep them running when your app terminates and then fire up your app again when they're done; but if the app was terminated, your closures are all gone).
You have to use delegate based API only for background sessions, not completion handlers.
You have to implement the app delegate method to capture the system provided completion handler that you call when you're done processing background session delegate calls. You have to call that when your URLSession tells you that it's done processing all the background delegate methods.
All of this is a significant burden, IMHO. Given that the system is keeping you app alive for background music, you might contemplate using a standard URLSessionConfiguration. If you're going to use background session, you might need to refactor all of this completion handler-based code.
The wait I'm using is from another queue and is never used in the main queue (I know it is a threading antipattern and I take care of it).
Good. There's still serious code smell from ever using "wait", but if you are 100% confident that it's not deadlocking here, you can get away with it. But it's something you really should check (e.g. put some logging statement after the "wait" and make sure you're getting past that line, if you haven't already confirmed this).
In fact it is used when I do two networking operation and the second one depends of the result of the second. I put a wait after the first operation to avoid KVO observing. Should I avoid that ?
Personally, I'd lose that KVO observing and just establish addDependency between the operations. Also, if you get rid of that KVO observing, you can get rid of your double KVO notification process. But I don't think this KVO stuff is the root of the problem, so maybe you defer that.

The NSURLSession Task file download occurs after the program needs to use the file

I have defined an NSURLSession with a download task. I would like to download a file and then use values in the file in my drawing style kit. The session and the drawing style kit do run on the synchronous queues, unfortunately the task runs on an NSOperationQueue which does not run until after the view is created. Therefore, the view does not contain the correct file information. Is there a coding solution or is it a logic issue and does anyone have suggestions for improving the logic. I am a newbie so any suggestions are most welcome.
class SomeView: UIView, NSURLSessionDelegate, NSURLSessionTaskDelegate
{
var session: NSURLSession!
required init(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
// Create the session configuration
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.timeoutIntervalForRequest = 15.0
// Create the session
session = NSURLSession(configuration: configuration,
delegate: self,
delegateQueue: nil)
}
override func drawRect(rect: CGRect)
{
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
dispatch_sync(queue,
{
// Define the data source at www.powerball.com/powerball/winnums-text.txt
let url = NSURL(string: "http://www.powerball.com/powerball/winnums-text.txt")
// Create the download task for the session
let task = self.session.downloadTaskWithURL(url!, completionHandler:
{[weak self] (url: NSURL!,
response: NSURLResponse!,
error: NSError!) in
if error == nil
{
let fileManager = NSFileManager()
//Get the path for the caches folder
var error: NSError?
var cachePath = fileManager.URLForDirectory(
NSSearchPathDirectory.CachesDirectory,
inDomain: NSSearchPathDomainMask.UserDomainMask,
appropriateForURL: url,
create: true,
error: &error)!
// Assign consistent file name
var fileName = "winnums-text"
// Apend the file name to the cache path
var filePath = cachePath.URLByAppendingPathComponent(fileName)
// Move the file to the file path
fileManager.moveItemAtURL(url, toURL: filePath, error: nil)
println(filePath)
}
else
{
println("Connection failed") //add user notification later
}
//Clean up sessions
self!.session.finishTasksAndInvalidate()
}) //Task
task.resume()
}) // Dispatch queue
dispatch_sync(queue,
{
// Only override drawRect: if you perform custom drawing.
SomeappStyleKit.drawMain(frame: self.bounds)
}) // Dispatch queue
}
}
Ok, there are several things ... well ... ill-chosen in my humble opinion I'd say :)
Here is how I would approach that problem.
Your basic idea is:
"Have a custom view, that displays stuff according to some information downloaded from the net"
First, we should see what we need:
You need a custom view
You need some form of asynchronous downloading
You need some controller that coordinates
you need to decide, how the whole thing should behave:
Is the view only to be created when information is downloaded?
Is the view already there in the beginning, and what is it showing meanwhile?
What triggers downloading? Some controller? The view really shouldn't.
Now we do NOT want to mix any of the asynchronous downloading stuff into the view. The view should only be dumb and display what it is given. (I have to admit I don't know what your view will actually display, so there might be cases where this could be considered valid, but most likely not)
Therefore I'd suggest you create a separate class that handles the downloading and a separate class that is your view.
You could create a NSOperation subclass that does all the asynchronous stuff and is enqueued/kicked off by your controller (See Ray Wenderlich's NSOperation Tutorial for example) or you can do the asynchronous stuff inside your controller using GCD, and in the completion handler (in either case) tell the view to redisplay itself.
The view could display a placeholder while the content is not downloaded, and by calling a function from the outside (the controller would call this) could re-render itself when new content arrives. One thing is for sure: DrawRect needs to be as fast as possible!
So you might want to have an if statement in there, and only if the content is fetched (the view could have a Bool or Enum that indicates this state), you call a specialized drawing method of your view, and otherwise just draw the placeholder.
And don't use dispatch_sync anywhere in there... in fact, don't use it anywhere at all. You really want to be asynchronous in most cases (as usual, exceptions apply). In this special case inside drawRect you really MUST not use any GCD functions like this, as drawRect is called on the main thread afaik and you must not do any drawing outside the main thread.
I hope this gets you started. Otherwise I might find the time to come up with some code later this week.
Edit: You could just use the NSURLDownloadTask you have, but in a controller. A separate NSOperation subclass might not be the best option here and unnecessary.

Resources