Can't access ivar in notification chain

I’m writing an app that burns to optical disks and there’s a strange problem when returning from the burn progress panel. Any attempt to read one of the AppDelegate ivars after a disc burn causes a crash. The chain of events is:


User presses burn button > DRSetupPanel is presented > user presses OK > DRBurnProgressPanel is displayed and a track is burnt to disc > AppDelegate receives a notification of burn finishing.


It's at this point, during the handling of receiving a finshed notification, that I suddenly can't access an array that is held by the AppDelegate. I can read it's count but any attempt to directly access an element causes a crash as though it had been deallocated. I can access elements just fine before the progress panel is presented.

I call the disk recording setup sheet like this:


    let setup_panel = DRBurnSetupPanel()
    let passed = UnsafeMutableRawPointer(Unmanaged.passUnretained(self.disk_buckets[current_burn_disk]).toOpaque())
    setup_panel.beginSetupSheet(for: self.window, modalDelegate: self, didEnd: #selector(self.burnSetupPanelDidEnd(_:return_code:context_info:)), contextInfo: passed)


disk_buckets is an array of classes each containing a reference to a DRTrack to be burnt - I pass an element so that I can deal with the DRTrack reference later.


After the setup panel is dismissed, the following method is then called:


    func burnSetupPanelDidEnd(_ panel: DRBurnSetupPanel, return_code: Int, context_info: UnsafeMutableRawPointer) {
    //…. some code ….
    var track:DRTrack = self.disk_buckets[self.current_burn_disk].vDRTrack!
    NotificationCenter.default.addObserver(self, selector: #selector(self.progressDidEnd), name: NSNotification.Name(rawValue: BTVDiscBurnDidEndNotification), object: nil)
    self.objc_panel.presentDiscProgressPanel(self.window, burner: panel.burnObject(), layout: track!)
    }


objc_panel is a obj-c singleton that initialises and presents a disc burning progress panel (I did it this way as doing it directly from Swift is buggy - the progress panel is displayed as a tiny window instead of the default OS one - bug report has been sent to Apple). When the burn finishes the objc_panel posts a "BTVDiscBurnDidEndNotification" notification to let the AppDelegate know it’s finished which in turn calls:


func progressDidEnd(_ note: Notification?) {
     print (“DEBUG count: \(self.disk_buckets.count)")
     print (“DEBUG first element: \(self.disk_buckets[0])")
    //…. more code ….
}


All goes well until the end of ths chain when any attempt to access the first element (or any, for that matter), i.e. the print statement on line 03 causes a crash:


    2016-10-04 14:01:34.654 DiskSpan[4025:220213] *** -[DiskSpan.BackupDisk retain]: message sent to deallocated instance 0x6000cafc0f40


(BackupDisk being the class that populates the array)


The thing is that the first print line outputs a correct count of the array i.e. 1.


So how come I can't access anything in the array during a notification post? It should be noted that this worked under Swift 2 and has only become a problem since Swift 3!

Replies

Do you ever use the "contex_info" parameter for anything? The second code fragment re-indexes into the array instead of using the array element that you passed as context info.


My guess is that the problem arises from a difference in the semantics of Swift value types, vs. what would have been reference type if you'd be writing this code in Obj-C. Specifically, the copy-on-write behavior of Swift arrays might be masking an issue where you use the "unsafe" address of something. Or it may be that your unsafe address calculation is resulting in an overrelease of the first array element (or, at least, of a reference type that's embedded in what looks like a struct element in the array).


It looks like you don't really need a (meaningful) context info parameter at all, since the context you really need is expressed self.current_burn_disk.


The other thing to be careful of is what thread each of your completion handlers is being called on. If any are being called on a background thread, you might instead be running into a thread safety issue with your mutable disk_buckets array.


Ultimately, the easiest solution might be to migrate this functionality back to Obj-C, and avoid any adventure in Swift unsafe-adventure-land.

I think you're right, Quincey. I started thinking about ARC (which I've become lazy in considering), and sure enough - after putting a deint {} method that prints a debug line - at least one element is released just after the call to obj-c. As a hack I created a dummy array and enumerated through the buckets array, appending each element to the dummy one thus retaining them all. Everything suddenly worked fine. I'll re-write to lose the context and see if that also works. At least I've learned something! Thanks.