iOS 14 StoreKit - SKCloudServiceController's requestusertoken not being called properly - ios

I am trying to play songs on iOS 14 using Apple Music API.
I have the developer's token, and I have asked for the permission for accessing the user's apple music.
However, when I call requestusertoken api, its closure never gets called, so obviously I don't receive anything from the request - not even an error. It's driving me crazy.
Here is my code. What am I doing wrong?
func getUserToken() -> String {
var userToken = String()
let lock = DispatchSemaphore(value: 0)
SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { (receivedToken, error) in
guard error == nil else { return }
if let token = receivedToken {
userToken = token
lock.signal()
}
}
lock.wait()
return userToken }

I've tried the code and there were two major problems.
First, DispatchSemaphore makes the return line execute too early. Second, original developer token doesn't work due to latest iOS 14.3 issue.
So, I first erased DispatchSemaphore.
func getUserToken() {
var userToken = String()
SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { (receivedToken, error) in
guard error == nil else { return }
if let token = receivedToken {
userToken = token
print(userToken)
}
}
}
Then tweaked developer token following this repository.
Now, it's printing user token properly. I hope this helped.

I think we've all got stuck on the same tutorial. To fix, I put it on a different thread as the lock was holding up the main thread hence preventing the completion handler.
DispatchQueue.global(qos: .background).async {
print(AppleMusicAPI().fetchStorefrontID())
}
If it's the same tutorial, this will put the fetchStorefrontID() and the getUserToken() methods (which is called by the former) on a background thread and allow the completion handlers and the lock.signal() to occur.
If it's not, then this shall suffice for an answer:
DispatchQueue.global(qos: .background).async {
getUserToken()
}

Did you remove Bearer from your developerToken?

Okay so I know what's going on -- the DispatchSemaphore is being locked right after it's being created -- so it's never executing that code. Once I made the Semaphore value 1 instead of 0 it started to execute -- but then I had issues with the rest of the code because of the sequencing of events.
It looks like perhaps you're working with the same tutorial I was working through on Apple Music SDK integration -- if that's the case, I basically tweaked the code to :
download the user token to a local variable, and then the other methods begin to reference it in their requests.
Remove the Semaphore lock in the getuserToken() method only
The rest started working again, without having to change any other DispatchSemaphore values.
By no means am I an expert in how DispatchSemaphore works and best practices with apple music user tokens, but wanted to at least let you know why you were running into the same wall as me -- with no code being executed at all.

Related

GCD-based login timeout sometimes fires at next login attempt

I had some trouble formulating the Question title, please make an edit if you have a better alternative.
I have a Login procedure to which I add a timeout:
private func startTimeout() {
self.timeoutActive = true
DispatchQueue.main.asyncAfter(deadline: .now() + 20) {
[weak self] in
guard let weakSelf = self else {
return
}
if weakSelf.timeoutActive && !weakSelf.loggedIn {
weakSelf.onError("Login timed out")
}
}
}
I have designed the Login procedure so that if we for any reason need to log in again (for example after logging out, or after the login process determined that there missing or wrong credentials), we end up in the same instance of the class that performs the Login.
Now, as far as I see, we can never prevent a scheduled block from executing, only prevent it by using some flags, which is what I have done with the timeoutActive flag. This works like a charm.
However, I run into a problem if the second Login is timed exactly so that the Previous dispatch block executes after the new Login process is started (When the new login procedure is initiated, the timeoutActive flag is once again set to true). The new Login receives a Timeout that is not correct.
I have been thinking about different ways to solve it, and tried a few, but couldn't get any of them to work.
I had an idea to use performSelectorAfterDelay instead of GCD, which is cancellable, but not available in Swift (3).
I also played with the thought of having some unique block ID with a list of blocked block IDs - but it just seems overkill.
I also had an idea about comparing the current dispatch time (.now()) in the block with the original deadline (.now() + 20) and see if it matches, but I don't know how exact this deadline is and it feels unstable.
The only idea I'm left with is making some kind of Task like object around the Login procedure itself, with the timeout included and create a new instance of that task for different Logins. Seems like a bit of work and I'd prefer if I found an easier way.
Has anyone encountered this type of situation before and have a solution?
So this is what I made from Dan's comment on the Question:
private var timeoutBlock: DispatchWorkItem? = nil
private func startTimeout() {
self.timeoutBlock = DispatchWorkItem(block: { [weak self] in
guard let weakSelf = self else {
return
}
if !weakSelf.loggedIn {
weakSelf.onError("Login timed out")
}
})
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 20, execute: self.timeoutBlock!)
}
And using self.timeoutBlock?.cancel() when leaving the ViewController or completing the process. This works as expected!

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.

Uploading Files onto Firebase is skipping lines of code?

I am relatively new to swift and Firebase but I am definitely encountering a weird problem. What seems to be happening after messing around in the debugger is that the following function seems to be exhibiting weird behavior such as skipping the line storageRef.put()
So whats been happening is this, this function is triggered when the user clicks on a save button. As I observe in the debugger, storageRef is called but the if else statements are never invoked. Then, after my function returns the object which wasn't properly initalized, it then returns into the if else statement with the proper values... By then it is too late as the value returned and uploaded to the database is already incorrect..
func toAnyObject() -> [String : Any] {
beforeImageUrl = ""
let storageRef = FIRStorage.storage().reference().child("myImage.png")
let uploadData = UIImagePNGRepresentation(beforeImage!)
storageRef.put(uploadData!, metadata: nil) { (metadata, error) in
if (error != nil) {
print(error)
} else {
self.beforeImageUrl = (metadata?.downloadURL()?.absoluteString)!
print("upload complete: \(metadata?.downloadURL())")
}
}
let firebaseJobObject : [String: Any] = ["jobType" : jobType! as Any,
"jobDescription" : jobDescription! as Any,
"beforeImageUrl" : beforeImageUrl! as Any,]
return firebaseJobObject
}
Consider a change in your approach here. The button target-action is typical of a solution that requires an immediate response.
However, when you involve other processes (via networks) like the - (FIRStorageUploadTask *)putData:(NSData *)uploadData method above, then you must use some form of delegation to perform the delayed action whenever the server side method returns a value.
Keep in mind that when you are trying to use the above method, that it is not meant for use with large files. You should use - (FIRStorageUploadTask *)putFile:(NSURL *)fileURL method.
I'd suggest that you rework the solution to ensure that the follow-up action only happens when the put succeeds or fails. Keep in mind that network traffic means that the upload could take some time. If you want to validate that the put is completing with a success or failure, just add a breakpoint at the appropriate location inside the completion block and run the method on a device with/and without network access (to test both code flows).

NSOperation operationDidFinish with errors, add operation to queue again

I'm subclassing a GroupOperation and would like to run it again if it fails and the amount of attempts is less than 5. I also have a delayOperation to delay it by 3 seconds. I can't get this to work however, this is what I have so far:
self.conversation is an Object.
produceConversationOperation() returns a backgroundOperation
override func operationDidFinish(operation: NSOperation, withErrors errors: [NSError]) {
if let _ = errors.first {
if let _ = operation as? BackgroundOperation {
context.performBlockAndWait({ () -> Void in
self.conversation.retryCountValue++
if self.conversation.retryCountValue < 5 {
let postConversationOperation = self.produceConversationOperation()
let delayOperation = DelayOperation(interval: 3)
postConversationOperation.addDependency(delayOperation)
self.produceOperation(delayOperation)
self.produceOperation(postConversationOperation)
}
else {
self.conversation.retryCountValue = 0
}
self.saveContext()
})
}
}
}
For some reason the operation isn't running again after the first failure. I have a feeling the issue is with the self.produceOperation method but I have no idea what.
There have been a few solutions to similar questions but I haven't found anything that has helped.
Thanks
Bit late to answer this, but you might want to check out using RetryOperation from http://github.com/danthorpe/Operations which supports many of these issues:
max count of retries
customisable delay (with a wait strategy if you need random/exponential/etc)
it accepts a GeneratorType of the operation
it accepts a "retry handler" trailing closure which allows the consumer to "tweak" the next instance of the operation which will be retried.
To answer this question a bit more concretely though, and without knowing for sure what produceOperation() is doing, I would hazard a guess that it is not actually creating a fresh new instance, but in some way is returning the original operation.

Cannot signal semaphore with Firebase observeEventType - what gives?

I am using Firebase to read/write data to a remote server.
My objective is to write a routine that also takes a callback function. Heres what the routine should accomplish:
Write some data to Firebase (this works fine)
Observe for changes on above written data
If change occurs call callback with success
If a timeout is triggered, call callback with failure
Im unable to figure out why steps 2-4 are failing for me. Below is a snippet of the code
Here's whats going on in the code:
It sets up a GCD semaphore
It writes some data to the root firebase URL - specifically it adds a user called joe. This works fine. I can see the data written from Vulcan
it sets up a reference to Joe's node and watches for events on childAdded for Joe. Here is the API reference
Upon successfully getting an event it signals the semaphore, which should call the callback with message "Success"
Instead I see the "timeout" message. Strangely though the observer does print out the newly added key:value pair, which is doubly confusing!
Whats going on here? Am I using semaphores incorrectly? Or am i using Firebase's API incorrectly?
typealias completionfoo = (gid:String) -> Void
func setStateReady(somestr: String, callfoo: completionfoo) {
let semaphore = dispatch_semaphore_create(0)
var myRootRef = Firebase(url:"https://xxxxxx.firebaseio.com/")
var joe = ["state" : "READY", "msg" : somestr]
var usersRef = myRootRef.childByAppendingPath("users")
var users = ["joe": joe]
usersRef.setValue(users)
var joeref = Firebase(url: "https://xxxxxx.firebaseio.com/users/joe/")
// Read data and react to changes
joeref.observeEventType(.ChildAdded, withBlock: {
snapshot in
print(snapshot.key)
print(snapshot.value)
dispatch_semaphore_signal(semaphore)
})
let timeout = dispatch_time(DISPATCH_TIME_NOW, Int64(30 * Double(NSEC_PER_SEC)))
if dispatch_semaphore_wait(semaphore, timeout) != 0 {
joeref.removeAllObservers()
callfoo(gid: "timeout")
} else {
joeref.removeAllObservers()
callfoo(gid: "success")
}
}
override func viewDidLoad() {
super.viewDidLoad()
setStateReady("hi i am joe") { (gid) -> Void in
print(gid)
}

Resources