Search code examples
swiftxcodensfilemanagernsopenpanel

Open File Dialog crashes in Swift


I would like to use the open file dialogs from NSFilemanager but my code crashes sometimes and sometimes works and I do not know why. Sometimes it works 100%, sometimes the window is empty, sometimes the background behind the dialog ist shown in the window. When a craash occurs, "signal:SIGABRT" is shown in Xcode.

func openfiledlg (title: String, message: String) -> String
{
    var myFiledialog: NSOpenPanel = NSOpenPanel()

    myFiledialog.prompt = "Öffnen"
    myFiledialog.worksWhenModal = true
    myFiledialog.allowsMultipleSelection = false
    myFiledialog.canChooseDirectories = false
    myFiledialog.resolvesAliases = true
    myFiledialog.title = title
    myFiledialog.message = message
    myFiledialog.runModal()
    var chosenfile = myFiledialog.URL
    if (chosenfile != nil)
    {
        var TheFile = chosenfile.absoluteString!
        return (TheFile)
    }
    else
    {
        return ("")
    }
}

What have I done wrong? Why does it crash?

The App does not run on the main thread. I always open a new thread, which runs my program. The Main-Thread only handels the Screen Updates from SpriteKit, which I use for my programs.

I just built up a new Cocoa-Based App and let the function run in the main-Thread and there it works. When I start a Thread in the Cocoa-App it crashes like in the SpriteKit Environment.

I need to start a new thread in the Sprite-Kit Environment because the updates will not be done if I start my main program directly from the AppDelegate. The main Program runs until the whole SpriteKit quits, so I have no chance, to do my work in the main thread.

The crash occurs in the line with the runModal() and then in "NSSavePanel._spAuxiliaryStorage":

0x7fff84dfec20:  movq   -0x10c18197(%rip), %rsi   ; "_refreshDelegateOptions"
0x7fff84dfec27:  movq   %rbx, %rdi
0x7fff84dfec2a:  callq  *%r15
0x7fff84dfec2d:  movq   -0x10c17ed4(%rip), %rsi   ; "_loadPreviousModeAndLayout"
0x7fff84dfec34:  movq   %rbx, %rdi
0x7fff84dfec37:  callq  *%r15
0x7fff84dfec3a:  movq   -0x10b57079(%rip), %r12   ; NSSavePanel._spAuxiliaryStorage <--- Thread 7: signal SIGABRT
0x7fff84dfec41:  movq   (%rbx,%r12), %rax
0x7fff84dfec45:  movq   -0x10b5716c(%rip), %rcx   ; NSSavePanelAuxiliary._clientSetADirectory
0x7fff84dfec4c:  movb   (%rax,%rcx), %al
0x7fff84dfec4f:  shrb   $0x2, %al
0x7fff84dfec52:  andb   $0x1, %al
0x7fff84dfec54:  xorb   $0x1, %al
0x7fff84dfec56:  movzbl %al, %ecx
0x7fff84dfec59:  movq   -0x10c18310(%rip), %rsi   ; "_configureForDirectory:forceDefault:"
0x7fff84dfec60:  movq   %rbx, %rdi
0x7fff84dfec63:  xorl   %edx, %edx
0x7fff84dfec65:  callq  *%r15
0x7fff84dfec68:  movq   -0x10c2d767(%rip), %rsi   ; "drain"
0x7fff84dfec6f:  movq   %r14, %rdi
0x7fff84dfec72:  callq  *%r15
0x7fff84dfec75:  movq   (%rbx,%r12), %rsi

In the Terminal Window is shown:

CocoaTest(54483,0x106f78000) malloc: *** error for object 0x60800017efc0: Heap corruption detected, free list canary is damaged

Any idea how to solve this problem without doing it inside the main thread?


Solution

  • The restrictions of the User Interface (UI) Calls, which are not Thread-Save, can be solved, when you use the following code, which executes a block of commands in the main thread asynchronously:

    dispatch_async(dispatch_get_main_queue())
    {
        // This commands are executed ansychronously
    }
    

    So you have to write your own functions for every built-in-function, which is not thread-save like this (example with the open file dialog):

    func not (b: Bool) -> Bool
    {
        return (!b)
    }
    
    func suspendprocess (t: Double)
    {
        var secs: Int = Int(abs(t))
        var nanosecs: Int = Int(frac(abs(t)) * 1000000000)
        var time = timespec(tv_sec: secs, tv_nsec: nanosecs)
        let result = nanosleep(&time, nil)
    }
    
    func openfiledialog (windowTitle: String, message: String, filetypelist: String) -> String
    {
        var path: String = ""
        var finished: Bool = false
    
        suspendprocess (0.02) // Wait 20 ms., enough time to do screen updates regarding to the background job, which calls this function
        dispatch_async(dispatch_get_main_queue())
        {
            var myFiledialog: NSOpenPanel = NSOpenPanel()
            var fileTypeArray: [String] = filetypelist.componentsSeparatedByString(",")
    
            myFiledialog.prompt = "Open"
            myFiledialog.worksWhenModal = true
            myFiledialog.allowsMultipleSelection = false
            myFiledialog.canChooseDirectories = false
            myFiledialog.resolvesAliases = true
            myFiledialog.title = windowTitle
            myFiledialog.message = message
            myFiledialog.allowedFileTypes = fileTypeArray
    
            let void = myFiledialog.runModal()
    
            var chosenfile = myFiledialog.URL // Pathname of the file
    
            if (chosenfile != nil)
            {
                path = chosenfile!.absoluteString!
            }
            finished = true
        }
    
        while not(finished)
        {
            suspendprocess (0.001) // Wait 1 ms., loop until main thread finished
        }
    
        return (path)
    }
    

    Please note, that the block is called asynchronously, that means you have to check, if the block has been processed and is finished or not. So I add a boolean variable "finsihed" which shows, when the block reaches its end. Without this you do not get the pathname but only an empty string.

    If you are interested, I will post my savefiledialog-function, too. Please leave a comment if so.