completionhandler in for in loop - ios

I am running a completionhandler inside a for in loop, so yeah an async operation inside a loop...
Thats why I included DispatchGroups():
for fileName in fileNames {
group.enter()
let url = URL(fileURLWithPath: "\(self.documentsUrl.path)/\(fileName)")
let ref = storage.reference().child("pathTo/\(fileName)")
let _ = ref.putFile(from: url, metadata: nil) { metadata, error in
print("completed")
if let error = error {
print("error")
} else {
print("success")
}
self.removeFile()
group.leave()
}
}
group.notify(queue: .main, execute: {
print("finished")
})
Well, the filepath´s exists, but I dont get any prints in the console, but I need to get notified after each async operation is finished. Could anybody help me with this?

I am ASSUMING you're uploading to Firebase.
Couple of things to try:
Change let _ = ref.putFile(from: url... to let uploadObj = ref.putFile(from: url...
Then, as the last line of your for loop do this: uploadObj.resume().
If that doesn't work, then my best guess is that Firebase's framework can't handle multiple simultaneous uploads. The solution to that is to wait for each file to finish uploading, then upload the next one.
You can also try your code, but only have it upload 1 file. If that works, then the problem is most likely the concurrent upload issue. Try it with 2, and then keep going if it doesn't fail. If it does eventually fail, then the simultaneity is definitely the issue.
If you insist on attempting simultaneous uploads, then go to Google's documentation on this, and implement their examples to monitor uploads. Then you'll be able to see exactly what's going on with each upload.
Here's an update:
This post's answer also suggests uploading one at a time.

Related

Data contentsOf yields nil, but the URL is valid

I am using this code
let url = URL(string: "http://image.tmdb.org/t/p/w185" + movie.poster_path!) // https://www.themoviedb.org/talk/568e3711c3a36858fc002384
print(url!)
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
self?.movieImage.image = UIImage(data: data!)
}
}
from this stack overflow post. I have a URL with an image on it, I would like to use that URL to bring the image into my app and have it show up in a
#IBOutlet weak var movieImage: UIImageView!
but for some reason, I am getting an error saying that data is nil. Why would data be nil if the URL is valid? Is this an issue with the contentsOf function or am I doing something wrong here?
If you try changing your URL declaration to be: let url = URL(string: "http://image.tmdb.org/t/p/w185//nBNZadXqJSdt05SHLqgT0HuC5Gm.jpg") it works as expected. So perhaps you are not assembling the URL correctly?
I would print whatever URL you're creating and try visiting the website to see if it is actually correct
I’d suggest not using try? (which discards any meaningful error data) and instead use try wrapped in a do-catch block, and in the catch block, examine what the error is. Right now, you’re flying blind.
Or, better, use URLSession.shared.dataTask(with:) and look at the error in the completion handler.
You asked:
... but why is this such a bad thing [to use Data(contentsOf:)] if it is the background thread?
Yes, by dispatching this to a global queue you’ve mitigated the “don’t block the main thread” problem. But Data(contentsOf:) doesn’t provide much diagnostic information about why it failed. Also, it ties up one of the very limited number of worker threads that GCD draws upon. If you exhaust the worker thread pool, then GCD won’t be able to do anything else until it’s freed up. Using URLSession offers the chance to do more meaningful diagnostics and avoids blocking GCD worker threads.
So, I would suggest removing all of those ! forced unwrapped operators and not using Data(contentsOf:). Thus, I might suggest something like:
guard
let path = movie.poster_path,
let baseURL = URL(string: "http://image.tmdb.org/t/p/w185")
else {
print("problem getting path/URL")
return
}
let url = baseURL.appendingPathComponent(path)
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard
let data = data,
let response = response as? HTTPURLResponse,
error == nil
else {
print("network error:", error ?? "Unknown error")
return
}
guard 200..<300 ~= response.statusCode else {
print("invalid status code, expected 2xx, received", response.statusCode)
}
guard let image = UIImage(data: data) else {
print("Not valid image")
return
}
DispatchQueue.main.async {
self?.movieImage.image = image
}
}.resume()
Then, by displaying the error, if any, we’ll see what the problem was. FWIW, the above network request identifies three types of errors, which might be helpful for diagnostic purposes:
Basic network errors
HTTP errors
Content errors (not an image)

How to know if the file I'm uploading to firebase is done uploading?

I'm using firebase with swift.
Below is my code to upload an image from an image picker, and to store the download url of the image.
the point of my code is to store the download url after uploading the image. So I'm trying to find a way to wait for the upload process to finish in order to proceed.
_ = imageRef.putData(data, metadata: nil, completion: {(metadata,error) in
guard let metadata = metadata
else{
print(error)
return
}
})
imageRef.downloadURL { (URL, error) -> Void in
if (error != nil) {
// Handle any errors
} else {
// Get the download URL for 'images/stars.jpg'
let UrlString = URL?.absoluteString}
That's what the completion handler is for. You're already passing a completion handler to putData, which will be invoked when the upload is finished. You should check the error object to make sure it completed successfully.
This is documented, along with a code sample. You can see in the sample that the download URL is fetched from inside the completion handler, only if there is no error.

Download multiple video and audio

I have made one demo in which i have to do download multiple video and audio so i found one library from github. But i do not know how to save donwloading progress when i quite app(means when downloading done 50% and i want to save 50% video data in Document directory but when i open app downloading start from initial)
Code which i had used in my demo.
self.progressView.setProgress(0, animated: false)
self.progressLabel.text = "0.0 %"
self.finalUrlLabel.text = ""
let request = URLRequest.init(url: URL.init(string: "http://techslides.com/demos/samples/sample.mp4")!)
let downloadKey = SDDownloadManager.shared.dowloadFile(withRequest: request,
inDirectory: directoryName,
withName: nil,
onProgress: { [weak self] (progress) in
let percentage = String(format: "%.1f %", (progress * 100))
self?.progressView.setProgress(Float(progress), animated: true)
self?.progressLabel.text = "\(percentage) %"
print("percentage",percentage)
}) { [weak self] (error, url) in
if let error = error {
print("Error is \(error as NSError)")
} else {
if let url = url {
print("Downloaded file's url is \(url.path)")
self?.finalUrlLabel.text = url.path
}
}
}
print("The key is \(downloadKey!)")
let dasd = SDDownloadManager.shared.ongoingDownloads
print("dasd",dasd.count)
If you know any other solution please help me via your best experience in downloading progress.
TIA
Edit:
I have used this Link for download For downloading video using url session. But don't know how to resume download when application terminated and open again.
first thing SDDownloadManager says Resumable Downloads not implemented yet in the framework. Its mentioned there as below
Future Enhancements
I'm planning to integrate the following features in upcoming releases :
Background Downloads.
Resumable Downloads.
so you cant achieve this using this. Instead you can use NSUrlSession to
achieve this.

Quick Look Preview Extension iOS preparePreviewOfFile(at:completionHandler:)

I'm trying to write a simple Quick Look Preview Extension for my UIDocument-based iOS app.
The problem is that in my implementation of preparePreviewOfFile(at:completionHandler:) my attempt to open the UIDocument based on the URL I'm being handed is failing. I instantiate my document with the file URL and call open(completionHandler:) but I'm not getting any data, and I'm seeing a console message that the file coordinator has crashed.
All of this works fine in my actual app; it's just the Quick Look Preview Extension implementation that's having trouble. Is there something special I have to do to open a UIDocument from inside a Quick Look Preview Extension? Apple doesn't provide any sample code; in WWDC 2017 video 229 they just gloss over the whole thing.
EDIT: Curiouser and curiouser. I created a simplified testbed app that displays a Quick Look preview with UIDocumentInteractionController, along with my custom Quick Look Preview Extension. On the Simulator, the preview works! On the device, it doesn't. It looks like, when I tell my document to open, its load(fromContents:ofType) is never even called; instead, we are getting a pair of error messages like this:
The connection to service named com.apple.FileCoordination was invalidated.
A process invoked one of the -[NSFileCoordinator coordinate...] methods but filecoordinationd crashed. Returning an error.
I was able to work around the issue by not calling open on my UIDocument. Instead, I call read directly, on a background thread, like this:
func preparePreviewOfFile(at url: URL, completionHandler handler: #escaping (Error?) -> Void) {
DispatchQueue.global(qos: .background).async {
let doc = MyDocument(fileURL: url)
do {
try doc.read(from: url)
DispatchQueue.main.async {
// update interface here!
}
handler(nil)
} catch {
handler(error)
}
}
}
I have no idea if that's even legal. You'd think that just reading the document straight in, without the use of a file coordinator, would be Bad. But it does seem to work!
I found yet another workaround, using NSFileCoordinator and calling load manually to get the UIDocument to process the data:
let fc = NSFileCoordinator()
let intent = NSFileAccessIntent.readingIntent(with: url)
fc.coordinate(with: [intent], queue: .main) { err in
do {
let data = try Data(contentsOf: intent.url)
let doc = MyDocument(fileURL: url)
try doc.load(fromContents: data, ofType: nil)
self.lab.text = doc.string
handler(nil)
} catch {
handler(error)
}
}
Again, whether that's legal, I have no idea, but I feel better about it than calling read directly, because at least I'm passing through a file coordinator.

Strange behavior of Dispatch Group

I have experiencing very strange crash from iOS App. The function below is an implementation of some protocol so I cannot change its declaration to use some success/failure callback. It has input parameters and expects AVAsset at the output. My problem is during writing asset I get strange crash during leaving dispatch group (dg variable). I marked line of the crash with comment. This crash is not always happens. Just from time to time. This is the function:
func writeAsset(to url: URL, metadataArray: [AVTimedMetadataGroup]) -> AVAsset {
let writer = try! AVAssetWriter(url: url, fileType: AVFileTypeQuickTimeMovie)
writer.movieTimeScale = track.timeScale
// setup writer, inputs and metadata adaptor and so on ...
if writer.startWriting() {
writer.startSession(atSourceTime: kCMTimeZero)
}
let writeQueue = DispatchQueue(label: "HH.Write.Track.Queue")
let dg = DispatchGroup()
var i = 0
dg.enter() // Entering to the group
writerMetadataIn.requestMediaDataWhenReady(on: writeQueue) {
while writerMetadataIn.isReadyForMoreMediaData {
//let group = ..fetch next group to write
if i < metadataArray.count {
let group = metadataArray[i]
if writerMetadataAdaptor.append(group) {
}
i += 1
} else {
writerMetadataIn.markAsFinished()
writer.finishWriting {
dg.leave() // CRASH IN THIS LINE
}
break
}
}
}
dg.wait()
let writtenAsset = AVAsset(url: url)
return writtenAsset
}
Can somebody have idea what is the cause of this crash? I have only this information from crash report in xCode.
I suspect your issue is that since you are entering the dispatch group once, and then (sometimes) leaving it more than once inside the loop, that you do not have balanced calls. ie. you are calling leave more times than you have called enter.
Found solution for the problem. It was not related to DispatchGroup but with AVAssetWriter and input array of AVTimedMetadataGroup elements. Each of this elements has time range. If start times for two of them is identical then writter during appending this groups is going to be in error state and behavior is very unpredictible. I don't know why error was in this line during leaving group but solution for me was to detect groups with the same start times and skip them.

Resources