Strange behavior of Dispatch Group - ios

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.

Related

IOS app crashes on a line of code if there's no internet connection, how can I prevent this

The code this and it crashes on "try!", but I don't know how to catch the error and it has it be explicit otherwise it won't work.
func downloadPicture2(finished: () -> Void) {
let imageUrlString = self.payments[indexPath.row].picture
let imageUrl = URL(string: imageUrlString!)!
let imageData = try! Data(contentsOf: imageUrl)
cell.profilePicture.image = UIImage(data: imageData)
cell.profilePicture.layer.cornerRadius = cell.profilePicture.frame.size.width / 2
cell.profilePicture.clipsToBounds = true
}
The short answer is don't use try! - Use do/try/catch and recover from the problem in the catch clause.
For example -
func downloadPicture2(finished: () -> Void) {
cell.profilePicture.image = nil
if let imageUrlString = self.payments[indexPath.row].picture,
let imageUrl = URL(string: imageUrlString) {
do {
let imageData = try Data(contentsOf: imageUrl)
cell.profilePicture.image = UIImage(data: imageData)
}
catch {
print("Error fetching image - \(error)")
}
}
cell.profilePicture.layer.cornerRadius = cell.profilePicture.frame.size.width / 2
cell.profilePicture.clipsToBounds = true
}
Now you have code that won't crash if the url is invalid or there is no network, but there are still some serious issues with this code.
Data(contentsOf:) blocks the current thread while it fetches the data. Since you are executing on the main thread this will freeze the user interface and give a poor user experience.
Apple specifically warns not to do this
Important
Don't use this synchronous initializer to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Rather, you should use an asynchronous network operations, such as a dataTask.
This code operates on cell - an external property. Once you move to asynchronous code you will probably be fetching images for multiple cells simultaneously. You should pass the relevant cell to this function to avoid clashes.
The use of the network isn't particularly efficient either; assuming this is part of a table or collection view, cells are reused as the view scrolls. You will repeatedly fetch the same image as this happens. Some sort of local caching would be more efficient.
If it is possible to use external frameworks in your project (i.e. your employer doesn't specifically disallow it) then I strongly suggest you look at a framework like SDWebImage or KingFisher. They will make this task much easier and much more efficient.

completionhandler in for in loop

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.

Using dispatch_async to load images in background

I am trying to update a progress bar as my images load. I've read several answers here and tried formatting my code many different ways. I'm trying to read in the images and update the progress bar. Then, will all the images are loaded call the code to process them. The best result I've got was some code that works most of the time. However, if I'm dealing with a situation where it is pulling in a lot of images, I get weird errors. I think it is going ahead and running the continue code before all the images are fully loaded. When I remove the dispatch_async, the code works fine but the progress bar does not update.
func imageLocXML(il:String) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
let url:NSURL? = NSURL(string: il)
let data:NSData? = NSData(contentsOfURL: url!)
let image = UIImage(data: data!)
pieceImages.append(image!)
self.numImagesLoaded += 1
self.updateProgressBar()
if self.numImagesLoaded == self.numImagesToLoad {
self.continueLoad()
}
}
}
There a number of issues:
This code isn't thread safe, because you have race condition on numImagesLoaded. This could, theoretically, result in continueLoad to be called more than once. You can achieve thread safety by synchronizing numImagesLoaded by dispatching updates to this (and other model objects) back to the main queue.
Like DashAndRest said, you have to dispatch the UI update to the main queue, as well.
When you made this asynchronous, you introduced a network timeout risk when you initiate a lot of requests. You can solve this by refactoring the code to use operation queues instead of dispatch queues and specify maxConcurrentOperationCount.
The images are being added to an array:
Because these tasks run asynchronously, they're not guaranteed to complete in any particular order, and thus the array won't be in order. You should save the images in a dictionary, in which case the order no longer matters.
Just like numImagesLoaded, the pieceImages isn't thread safe.
You are using a lot of forced unwrapping, so if any requests failed, this would crash.
But to address this, we have to step back and look at the routine calling this method. Let's imagine that you have something like:
var pieceImages = [UIImage()]
func loadAllImages() {
for imageUrl in imageURLs {
imageLocXML(imageUrl)
}
}
func imageLocXML(il:String) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
let url:NSURL? = NSURL(string: il)
let data:NSData? = NSData(contentsOfURL: url!)
let image = UIImage(data: data!)
self.pieceImages.append(image!)
self.numImagesLoaded += 1
self.updateProgressBar()
if self.numImagesLoaded == self.numImagesToLoad {
self.continueLoad()
}
}
}
I'm suggesting that you replace that with something like:
var pieceImages = [String: UIImage]()
func loadAllImages() {
let queue = NSOperationQueue()
queue.maxConcurrentOperationCount = 4
let completionOperation = NSBlockOperation {
self.continueLoad()
}
for imageURL in imageURLs {
let operation = NSBlockOperation() {
if let url = NSURL(string: imageURL), let data = NSData(contentsOfURL: url), let image = UIImage(data: data) {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.numImagesLoaded += 1
self.pieceImages[imageURL] = image
self.updateProgressBar()
}
}
}
queue.addOperation(operation)
completionOperation.addDependency(operation)
}
NSOperationQueue.mainQueue().addOperation(completionOperation)
}
Having said that, I think there are deeper issues here:
Should you be loading images in advance like this at all? We would generally advise lazy loading of images, only loading them when needed.
If you're going to load images into a structure like this, you should gracefully handle memory pressure, purging it upon low memory warning. You then need to gracefully handle what to do when you go to retrieve an image and it's been purged due to memory pressure (leading you right back to a just-in-time lazy loading pattern).
We'd generally advise against synchronous network requests (the NSData(contentsOfURL:_)). We'd generally use NSURLSession which is cancellable, offers richer error handling, etc. Admittedly, that complicates the above code even further (probably leading me down the road of asynchronous NSOperation subclass), but you should at least be aware of the limitations of contentsOfURL.
Try DISPATCH_QUEUE_PRIORITY_DEFAULT this for background queue as:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let url:NSURL? = NSURL(string: il)
let data:NSData? = NSData(contentsOfURL: url!)
let image = UIImage(data: data!)
self.pieceImages.append(image!)
self.numImagesLoaded += 1
dispatch_async(dispatch_get_main_queue(), {
//UI must be updated on main thread queue
self.updateProgressBar()
})
if self.numImagesLoaded == self.numImagesToLoad {
self.continueLoad()
}
}
UI must be updated on MAIN thread queue!
You also have to use self for accessing pieceImages.

Realm Threading Confusion

So I'm working on setting up a background queue that does all realm writes on its own thread. I've run into some strange issues I can't figure out.
Issue #1
I'm not sure if this is related (see post: Xcode debug issues with realm) but I do have an apparent mismatch with my lldbg output as to whether a certain field:
messages element
My DataTypes
OTTOSession
class OTTOSession : Object {
dynamic var messages : MessageList?
dynamic var recordingStart : Double = NSDate().timeIntervalSince1970
func addLocationMessage(msg : dmParsedMessage) -> LocationMessage {
let dmsg : dmLocationMessage = msg as! dmLocationMessage
let locMsg = LocationMessage(locMsg: dmsg)
self.messages!.locationMessages.append(locMsg)
return locMsg;
}
}
MessageList
public class MessageList : Object {
dynamic var date : NSDate = NSDate();
dynamic var test : String = "HI";
let locationMessages = List<LocationMessage>()
let ahrsMessages = List<AHRSMessage>()
// let statusMessages = List<StatusMessageRLM>()
let logMessages = List<LogMessage>()
}
Realm Interactions
In my code I create my new OTTOSession in a code block on my realmQueue
internal var realmQueue = dispatch_queue_create("DataRecorder.realmQueue",
DISPATCH_QUEUE_SERIAL)
All realm calls are done on this realmQueue thread
dispatch_async(realmQueue) {
self.session = OTTOSession()
}
I've also tried different variants such as:
dispatch_async(realmQueue) {
self.session = OTTOSession()
// Directly making a message list
self.session!.messages = MessageList()
//Making a separate message list var
self.messages = MessageList()
self.session!.messages = self.messages
}
The reason I've played around with the MessageList is that I cant tell from the debugger whether the .messages variable is set or not
Recording
Once I signal to my processes I want to start recording I then actually make the write calls into Realm (which I'm not 100% sure i'm doing correctly)
dispatch_async(realmQueue){
// Update some of the data
self.session!.recordingStart = NSDate().timeIntervalSince1970
// Then start writing the objects
try! Realm().write {
// I've tried different variants of:
let session = self.session!
try! Realm().add(self.session!)
// Or
try! Realm().add(self.session!)
// or
let session = self.session!
session.messages = MessageList()
session.messages!.ahrsMessages
try! Realm().add(self.session!)
try! self.session!.messages = Realm().create(MessageList)
try! Realm().add(self.session!.messages!)
print ("Done")
}
}
Basically I've tried various combinations of trying to get the objects into realm.
Question: When adding an object with a one-to-one relationship do I have to add both objects to Realm or will just adding the parent object cause the related object to also be added to realm
Adding Data
Where things start to go awry is when I start adding data to my objects.
Inside my OTTOSession Object I have the following function:
func addLocationMessage(msg : dmParsedMessage) -> LocationMessage {
let dmsg : dmLocationMessage = msg as! dmLocationMessage
let locMsg = LocationMessage(locMsg: dmsg)
// THIS LINE IS CAUSING A 'REALM ACCESSED FROM INCORRECT THREAD ERROR
self.messages!.locationMessages.append(locMsg)
return locMsg;
}
I'm getting my access error on this line:
self.messages!.locationMessages.append(locMsg)
Now the function call itself is wrapped in the following block:
dispatch_async(realmQueue) {
try! Realm().write {
self.session?.addLocationMessage(msg)
}
}
So as far as I can tell by looking at the debugger - and by looking at the code - everything should be running inside the right thread.
My queue is SERIAL so things should be happening one after another. The only thing I can't figure out is when I break at this point the debugger does show that messages is nil but I cant trust that because:
Question
So my question is two fold
1) Is my code for adding an object into the RealmDB correct. i.e. do I need to make two separate Realm().add calls for both the OTTOSession and the MessageList or can I get away with a single call
2) Is there anything that pops out to explain why I'm getting a thread violation here - should doing all my realm writing calls on a single thread be enough ?
1) No, you don't need to make two separate calls to Realm.add(). When you add an object to a Realm all related objects are persisted as well.
2) Your thread violation very likely originates from the fact that dispatch queues make no guarantee over the thread on which they are executed on (beside the main queue). So that means your Realm queue is executed on different threads. You will need to make sure to retrieve your session object from a Realm opened on this thread. You might want to use primary keys for that purpose and share those between queues / threads.

Updating a many-to-many relationship in Core Data with Swift

I have the following Core Data model:
And I'm trying to update the many-to-many relationship between Speaker and TalkSlot from a JSON I receive from a REST API call.
I have tried dozens of ways, replacing my many-to-many by 2 one-to-many's, removing from one side or the other, but one way or the other I keep getting EXC_BAD_ACCESS or SIGABRT and I just don't understand the proper way to do it. Here is the last thing I tried:
for speaker in NSArray(array: slot!.speakers!.allObjects){
if let speaker = speaker as? Speaker {
speaker.mutableSetValueForKey("talks").removeObject(slot!)
}
}
slot!.mutableSetValueForKey("speakers").removeAllObjects()
if let speakersArray = talkSlotDict["speakers"] as? NSArray {
for speakerDict in speakersArray {
if let speakerDict = speakerDict as? NSDictionary {
if let linkDict = speakerDict["link"] as? NSDictionary {
if let href = linkDict["href"] as? String {
if let url = NSURL(string: href) {
if let uuid = url.lastPathComponent {
if let speaker = self.getSpeakerWithUuid(uuid) {
speaker.mutableSetValueForKey("talks").addObject(slot!)
slot!.mutableSetValueForKey("speakers").addObject(speaker)
}
}
}
}
}
}
}
}
If it helps, the API I'm using is documented here as I'm trying to cache the schedule of a conference into Core Data in an Apple Watch extension. Note that I managed to store all the rest of the schedule without any issue. But for this relationship, each time I try to update it after storing it the first time, I get an EXC_BAD_ACCESS (or sometimes a SIGABRT), at a random place in my code of course. Any idea what I'm doing wrong?
OK, after reading a few other questions associating Core Data EXC_BAD_ACCESS errors and multi-threading, I noticed that I was doing my caching on a NSURLSession callback. Once I called my caching function on the main thread using the code below, the EXC_BAD_ACCESS errors completely disappeared and now the data seems to be saved and updated properly:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.cacheSlotsForSchedule(schedule, data: data)
})

Resources