Uploading Files onto Firebase is skipping lines of code? - ios

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).

Related

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

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.

Make multiple asynchronous requests but wait for only one

I have a question concerning asynchronous requests. I want to request data from different sources on the web. Each source might have the data I want but I do not know that beforehand. Because I only want that information once, I don't care about the other sources as soon as one source has given me the data I need. How would I go about doing that?
I thought about doing it with a didSet and only setting it once, something like this:
var dogPicture : DogPicture? = nil {
didSet {
// Do something with the picture
}
}
func findPictureOfDog(_ sources) -> DogPicture? {
for source in sources {
let task = URL.Session.shared.dataTask(with: source) { (data, response, error) in
// error handling ...
if data.isWhatIWanted() && dogPicture == nil {
dogPicture = data.getPicture()
}
}
task.resume()
}
}
sources = ["yahoo.com", "google.com", "pinterest.com"]
findPictureOfDog(sources)
However it would be very helpful, if I could just wait until findPictureOfDog() is finished, because depending on if I find something or not, I have to ask the user for more input.
I don't know how I could do it in the above way, because if I don't find anything the didSet will never be called, but I should ask the user for a picture then.
A plus: isWhatIWanted() is rather expensive, so If there was a way to abort the execution of the handler once I found a DogPicture would be great.
I hope I made myself clear and hope someone can help me out with this!
Best regards and thank you for your time
A couple of things:
First, we’re dealing with asynchronous processes, so you shouldn’t return the DogPicture, but rather use completion handler pattern. E.g. rather than:
func findPictureOfDog(_ sources: [String]) -> DogPicture? {
...
return dogPicture
}
You instead would probably do something like:
func findPictureOfDog(_ sources: [String], completion: #escaping (Result<DogPicture, Error>) -> Void) {
...
completion(.success(dogPicture))
}
And you’d call it like:
findPictureOfDog(sources: [String]) { result in
switch result {
case .success(let dogPicture): ...
case .failure(let error): ...
}
}
// but don’t try to access the DogPicture or Error here
While the above was addressing the “you can’t just return value from asynchronous process”, the related observations is that you don’t want to rely on a property as the trigger to signal when the process is done. All of the “when first process finishes” logic should be in the findPictureOfDog routine, and call the completion handler when it’s done.
I would advise against using properties and their observers for this process, because it begs questions about how one synchronizes access to ensure thread-safety, etc. Completion handlers are unambiguous and avoid these secondary issues.
You mention that isWhatIWanted is computationally expensive. That has two implications:
If it is computationally expensive, then you likely don’t want to call that synchronously inside the dataTask(with:completionHandler:) completion handler, because that is a serial queue. Whenever dealing with serial queues (whether main queue, network session serial queue, or any custom serial queue), you often want to get in and out as quickly as possible (so the queue is free to continue processing other tasks).
E.g. Let’s imagine that the Google request came in first, but, unbeknownst to you at this point, it doesn’t contain what you wanted, and the isWhatIWanted is now slowly checking the result. And let’s imagine that in this intervening time, the Yahoo request that came in. If you call isWhatIWanted synchronously, the result of the Yahoo request won’t be able to start checking its result until the Google request has failed because you’re doing synchronous calls on this serial queue.
I would suggest that you probably want to start checking results as they came in, not waiting for the others. To do this, you want a rendition of isWhatIWanted the runs asynchronously with respect to the network serial queue.
Is the isWhatIWanted a cancelable process? Ideally it would be, so if the Yahoo image succeeded, it could cancel the now-unnecessary Pinterest isWhatIWanted. Canceling the network requests is easy enough, but more than likely, what we really want to cancel is this expensive isWhatIWanted process. But we can’t comment on that without seeing what you’re doing there.
But, let’s imagine that you’re performing the object classification via VNCoreMLRequest objects. You might therefore cancel any pending requests as soon as you find your first match.
In your example, you list three sources. How many sources might there be? When dealing with problems like this, you often want to constrain the degree of concurrency. E.g. let’s say that in the production environment, you’d be querying a hundred different sources, you’d probably want to ensure that no more than, say, a half dozen running at any given time, because of the memory and CPU constraints.
All of this having been said, all of these considerations (asynchronous, cancelable, constrained concurrency) seem to be begging for an Operation based solution.
So, in answer to your main question, the idea would be to write a routine that iterates through the sources, and calling the main completion handler upon the first success and make sure you prevent any subsequent/concurrent requests from calling the completion handler, too:
You could save a local reference to the completion handler.
As soon as you successfully find a suitable image, you can:
call that saved completion handler;
nil your saved reference (so in case you have other requests that have completed at roughly the same time, that they can’t call the completion handler again, eliminating any race conditions); and
cancel any pending operations so that any requests that have not finished will stop (or have not even started yet, prevent them from starting at all).
Note, you’ll want to synchronize the the above logic, so you don’t have any races in this process of calling and resetting the completion handler.
Make sure to have a completion handler that you call after all the requests are done processing, in case you didn’t end up finding any dogs at all.
Thus, that might look like:
func findPictureOfDog(_ sources: [String], completion: #escaping DogPictureCompletion) {
var firstCompletion: DogPictureCompletion? = completion
let synchronizationQueue: DispatchQueue = .main // note, we could have used any *serial* queue for this, but main queue is convenient
let completionOperation = BlockOperation {
synchronizationQueue.async {
// if firstCompletion not nil by the time we get here, that means none of them matched
firstCompletion?(.failure(DogPictureError.noneFound))
}
print("done")
}
for source in sources {
let url = URL(string: source)!
let operation = DogPictureOperation(url: url) { result in
if case .success(_) = result {
synchronizationQueue.async {
firstCompletion?(result)
firstCompletion = nil
Queues.shared.cancelAllOperations()
}
}
}
completionOperation.addDependency(operation)
Queues.shared.processingQueue.addOperation(operation)
}
OperationQueue.main.addOperation(completionOperation)
}
So what might that DogPictureOperation might look like? I might create an asynchronous custom Operation subclass (I just subclass a general purpose AsynchronousOperation subclass, like the one here) that will initiate network request and then run an inference on the resulting image upon completion. And if canceled, it would cancel the network request and/or any pending inferences (pursuant to point 3, above).
If you care about only one task use a completion handler, call completion(nil) if no picture was found.
var dogPicture : DogPicture?
func findPictureOfDog(_ sources, completion: #escaping (DogPicture?) -> Void) {
for source in sources {
let task = URL.Session.shared.dataTask(with: source) { (data, response, error) in
// error handling ...
if data.isWhatIWanted() && dogPicture == nil {
let picture = data.getPicture()
completion(picture)
}
}
task.resume()
}
}
sources = ["yahoo.com", "google.com", "pinterest.com"]
findPictureOfDog(sources) { [weak self] picture in
if let picture = picture {
self?.dogPicture = picture
print("picture set")
} else {
print("No picture found")
}
}
You can use DispatchGroup to run a check when all of your requests have returned:
func findPictureOfDog(_ sources: [String]) -> DogPicture? {
let group = DispatchGroup()
for source in sources {
group.enter()
let task = URLSession.shared.dataTask(with: source) { (data, response, error) in
// error handling ...
if data.isWhatIWanted() && dogPicture == nil {
dogPicture = data.getPicture()
}
group.leave()
}
task.resume()
}
group.notify(DispatchQueue.main) {
if dogPicture == nil {
// all requests came back but none had a result.
}
}
}

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!

Call a file-download function from multiple View Controllers; how to return results TO those VC's?

My app will need to download files from my website from several different places in the app, so it seems to make sense to write the function to accomplish the download once, put it in its own class, and call that function from each ViewController. So far, so good, things work. The download is happening, and the downloaded file will print correctly.
The problem comes when the download function goes to send a "success" or "failed" message back to the ViewController that called it, so that the VC can then react accordingly -- update the display, close out the download dialog, whatever. How to make that happen is where I'm stuck.
What I have:
Each of ViewControllerTwo and ViewControllerThree (which are identical for now, other than requesting different files from my server) calls the download function thus:
Downloader.load(url: urlForFileA!, to: localDestinationFileA, callingViewControllerNumber: 2)
The code for the downloader function (which is currently synchronous, but will eventually become asynchronous) looks like this (in its own Downloader class):
class func load(url: URL, to localUrl: URL, callingViewControllerNumber: Int) {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Got a file, might be a 404 page...
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Success downloading: \(statusCode)")
if statusCode == 404 {
// ERROR -- FILE NOT FOUND ON SERVER
returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .fileNotFound, errorMessage: "File not Found, 404 error")
}
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: localUrl)
// SUCCESS!
returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .success, errorMessage: "")
} catch (let writeError) {
returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .movingError, errorMessage: "\(writeError)")
}
} else {
returnToCaller(sourceIdent: callingViewControllerNumber, successStatus: .downloadFailed, errorMessage: "Grave Unexplained Failure")
}
}
task.resume()
}
This part works.
The returnToCaller function is an admittedly ugly (okay, very, very ugly) way to send something back to the calling ViewController:
class func returnToCaller(sourceIdent : Int, successStatus : downloadSuccessStatusEnum, errorMessage : String) {
switch sourceIdent {
case 2:
ViewControllerTwo().returnFromDownload(successStatus: successStatus, errorMessage: errorMessage)
case 3:
ViewControllerThree().returnFromDownload(successStatus: successStatus, errorMessage: errorMessage)
default:
fatalError("Unknown sourceIdent of \(sourceIdent) passed to func returnToCaller")
}
}
The problem is that when that returnFromDownload function in the original ViewController is called, it isn't aware of anything in the VC that's loaded -- I go to change the background color of a label, and get a runtime error that the label is nil. The label exists, but this call into the ViewController code is happening in isolation from the running, instantiated VC itself. (Probably the wrong vocabulary there -- apologies.) The code runs and can print but errors out when interacting with anything in the View itself.
The more I work with this, the less confident I am that I'm on the right track here, and my limited experience with Swift isn't enough to see what needs to be happening so that the download function can do all its work "over here" and then return a success/failure message to the calling VC so that the VC can then work with it.
This question seems to be asking something similar; the one answer there doesn't address my (nor, I think, his) root question of how to get code within the presented VC running again with the results of what happened outside the VC (manager approval in his case, download in mine).
Not asking for my code to be rewritten (unless it's a quick fix), but needing to be pointed in the right direction. Many thanks!
What you want can be accomplished pretty easily with a closure.
The first step is to add another parameter to your load method and remove your callingViewController param:
class func load(url: URL, to localUrl: URL, completion: (downloadSuccessStatusEnum, String) -> Void)
This will allow you to call completion instead of returnToCaller when your method completes like so:
DispatchQueue.main.async {
completion(.success, "Insert your error message here")
}
Lastly, to call this method you simply need to call the method like so in your VCs:
Downloader.load(url: nameOfYourURL, to: destinationName) { statusEnum, errorString in
// Insert your code you want after the request completes here
}

NSMetadataQuery’s update notification interferes with (run loop?)

I emailed an Apple engineer last week about a problem with my NSMetadataQuery.
Here’s the email:
Hi,
I'm writing a document-based app or iOS and my method for renaming (moving the document to a new location) seems to conflict with the running NSMetadataQuery.
The query updates a couple of time after the move method is called, the first time it has the old URL of the item that just moved, and the next it has the new URL. However, because of my updating method (below) if a URL has been removed since the update, my model removes the deleted URL and vice versa for if it finds a URL which doesn't exist yet.
I think my problem is one of two issue, either the NSMetadataQuery's update method is insufficient and doesn't check an item's URL for the 'correct' attributes before deleting it (although looking over documentation I can't see anything that would suggest I'm missing something) or my renaming method isn't doing something it should.
I have tried disabling updates at the start of the renaming method and reenabling once all completion blocks are finished but it doesn't make any difference.
My NSMetadataQuery's update method:
func metadataQueryDidUpdate(notification: NSNotification) {
ubiquitousItemsQuery?.disableUpdates()
var ubiquitousItemURLs = [NSURL]()
if ubiquitousItemsQuery != nil && UbiquityManager.sharedInstance.ubiquityIsAvailable {
for var i = 0; i < ubiquitousItemsQuery?.resultCount; i++ {
if let result = ubiquitousItemsQuery?.resultAtIndex(i) as? NSMetadataItem {
if let itemURLValue = result.valueForAttribute(NSMetadataItemURLKey) as? NSURL {
ubiquitousItemURLs.append(itemURLValue)
}
}
}
// Remove deleted items
//
for (index, fileRepresentation) in enumerate(fileRepresentations) {
if fileRepresentation.fileURL != nil && !contains(ubiquitousItemURLs, fileRepresentation.fileURL!) {
removeFileRepresentations([fileRepresentation], fromDisk: false)
}
}
// Load documents
//
for (index, fileURL) in enumerate(ubiquitousItemURLs) {
loadDocumentAtFileURL(fileURL, completionHandler: nil)
}
ubiquitousItemsQuery?.enableUpdates()
}
}
And my renaming method:
func renameFileRepresentation(fileRepresentation: FileRepresentation, toNewNameWithoutExtension newName: String) {
if fileRepresentation.name == newName || fileRepresentation.fileURL == nil || newName.isEmpty {
return
}
let newNameWithExtension = newName.stringByAppendingPathExtension(NotableDocumentExtension)!
// Update file representation
//
fileRepresentation.nameWithExtension = newNameWithExtension
if let indexPath = self.indexPathForFileRepresentation(fileRepresentation) {
self.reloadFileRepresentationsAtIndexPaths([indexPath])
}
UbiquityManager.automaticDocumentsDirectoryURLWithCompletionHandler { (documentsDirectoryURL) -> Void in
let sourceURL = fileRepresentation.fileURL!
let destinationURL = documentsDirectoryURL.URLByAppendingPathComponent(newNameWithExtension)
// Update file representation
//
fileRepresentation.fileURL = destinationURL
if let indexPath = self.indexPathForFileRepresentation(fileRepresentation) {
self.reloadFileRepresentationsAtIndexPaths([indexPath])
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
let coordinator = NSFileCoordinator(filePresenter: nil)
var coordinatorError: NSError?
coordinator.coordinateWritingItemAtURL(sourceURL, options: .ForMoving, writingItemAtURL: destinationURL, options: .ForReplacing, error: &coordinatorError, byAccessor: { (newSourceURL, newDestinationURL) -> Void in
var moveError: NSError?
let moveSuccess = NSFileManager().moveItemAtURL(newSourceURL, toURL: newDestinationURL, error: &moveError)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
assert(moveError == nil || moveSuccess, "Error renaming (moving) document from \(newSourceURL) to \(newDestinationURL).\nSuccess? \(moveSuccess).\nError message: \(moveError).")
if let query = self.ubiquitousItemsQuery {
query.enableUpdates()
}
if moveError != nil || moveSuccess {
// TODO: Implement resetting file rep
}
})
})
})
}
}
I had a reply almost instantly but since then there’s been no reply.
Here’s the reply
One of the big things that jumps out at me is your usage of disableUpdates() and enableUpdates(). You’re executing them both on the same turn of the run loop, but NSMetadataQuery delivers results asynchronously. Since this code executes within your update notification, it is executing synchronously with respect to the query. So from the query’s point-of-view, it’s going to begin delivering updates by posting the notification. Posting a notification is a synchronous process, so while it’s posting the notification, updates will be disabled and the re-enabled. Thus, by the time the query is done posting the notification, it’s back in the exact same state it was in when it started delivering results. It sounds like that’s not the behavior you’re wanting.
Here’s where I need help
I took this to assume that NSMetadataQuery has some kind of cache which it adds results to while updates are disabled and when enabled, those (perhaps many) cache results are looped through and each are sent via the updates notification.
Anyway, I had a look at run loops on iOS and although I understand them as much as I can on my own, I don’t understand how the reply is helpful, i.e how to actually fix the problem - or what’s even causing the problem.
If anyone has any good idea I’d love your help!
Thanks.
Update
Here’s my log of when functions start and end:
start metadataQueryDidUpdate:
end metadataQueryDidUpdate:
start metadataQueryDidUpdate:
end metadataQueryDidUpdate:
start renameFileRepresentation:toNewNameWithoutExtension
start metadataQueryDidUpdate:
end metadataQueryDidUpdate:
end renameFileRepresentation:toNewNameWithoutExtension
start metadataQueryDidUpdate:
end metadataQueryDidUpdate:
start metadataQueryDidUpdate:
end metadataQueryDidUpdate:
start metadataQueryDidUpdate:
end metadataQueryDidUpdate:
I was having the same problem. NSMetaDataQuery updates tell you if there is a change, but does not tell you what that change was. If the change is a rename, there is no way to identify the previous name, so I can find the old entry in my tableView. Very frustrating.
But, you can get the information by using NSFileCoordinator and NSFilePresenter.
Use the NSFilePresenter method presentedSubitemAtURL(oldURL: NSURL, didMoveToURL newURL: NSURL)
As you noted, the query changed notification is called once with the old URL, and once with the new URL. The method above is called between those two notifications.

Resources