Search code examples
swiftmacoswhile-loopwait

Swift - How to wait for something without making the app hanging


I have a code like this:

    print("Migration Execution: Successfully uninstalled MCAfee")
    migrationInfoPicture.image = NSImage(named: "Unroll")
    migrationInfoText.stringValue = NSLocalizedString("Unrolling from old server... Please wait!", comment: "Unrolling")
    while(!readFile(path:logfilePath)!.contains("result: 2 OK")) {
        searchLogForError(scriptPath: scriptOnePath)
    }
    print("Migration Execution: Successfully unrolled from old server")
    migrationInfoText.stringValue = NSLocalizedString("Setting up MDM profile... Please wait!", comment: "Setting up MDM")
    while(!readFile(path:logfilePath)!.contains("result: 3 OK")) {
        searchLogForError(scriptPath: scriptOnePath)
    }

It actually works in the background, reading from the file works and logging works but since the GUI will be hanging executing a while loop with a quickly completed task, the image and the text changes will not be visible.

Code for searchForLogError is:

func searchLogForError(scriptPath:String) {
    if((readFile(path:logfilePath)!.filter { $0.contains("ERROR") }).contains("ERROR")) {
        print("Migration abborted")
        migrationInfoPicture.image = NSImage(named: "FatalError")
        migrationInfoText.stringValue = NSLocalizedString("An error occured: \n", comment: "Error occurence") + readFile(path:logfilePath)!.filter { $0.contains("ERROR") }[0]
        migrationWarningText.stringValue = NSLocalizedString("In order to get further help, please contact: [email protected]", comment: "Error support information")
        self.view.window?.level = .normal
        btnExitApplicationOutlet.isHidden = false
        getScriptProcess(path:scriptPath).terminate()
        return
    }
}

How can I achieve a visible change of NSImage and NSLocalizedString while constantly looking for log file change without a hanging GUI (or even with a hanging GUI, but with enough time to change the visible elements between the while-loops)?


Solution

  • Polling file system resources is a horrible practice. Don't do that. There are dedicated APIs to observe file system resources for example DispatchSourceFileSystemObject

    Create a property

    var fileSystemObject : DispatchSourceFileSystemObject?
    

    and two methods to start and stop the observer. In the closure of setEventHandler insert the code to read the file

    func startObserver(at url: URL)
    {
        if fileSystemObject != nil { return }
    
        let fileDescriptor : CInt = open(url.path, O_EVTONLY);
        if fileDescriptor < 0  {
            print("Could not open file descriptor"))
            return
        }
        fileSystemObject = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fileDescriptor, eventMask: [.write, .rename], queue: .global())
    
        if fileSystemObject == nil {
            close(fileDescriptor)
            print"Could not create Dispatch Source"))
            return
        }
        fileSystemObject!.setEventHandler {
            if self.fileSystemObject!.mask.contains(.write) {
               // the file has been modified, do something
    
            }
        }
        fileSystemObject!.setCancelHandler {
            close(fileDescriptor)
        }
        fileSystemObject!.resume()
    }
    
    func stopObserver()
    {
       fileSystemObject?.cancel()
       fileSystemObject = nil
    }