Managing File Access in Xojo with NSFileCoordinator

In the realm of cross-platform development, handling file access consistently across different operating systems can be a challenging task. For Xojo developers working on macOS or iOS, NSFileCoordinatorMBS and NSFilePresenterMBS from the Monkeybread Software’s Xojo plugins offer a powerful way to manage file access while avoiding conflicts with iCloud. In this blog post, we’ll delve into a practical example of using these classes in a Xojo project.

Introduction to NSFileCoordinatorMBS and NSFilePresenterMBS

Before we dive into the code, let’s briefly understand what NSFileCoordinatorMBS and NSFilePresenterMBS do:

  • NSFileCoordinatorMBS: This class helps in coordinating access to files across different processes. It ensures that multiple processes can read from or write to a file without interfering with each other, thus avoiding file access conflicts.
  • NSFilePresenterMBS: This class works in tandem with NSFileCoordinatorMBS and represents a file or directory that presents its state to the file coordinator. It allows your app to be notified of changes to the file or directory it is coordinating. We won’t really use this class today.

Example Scenario

Let’s walk through a simple Xojo application example where a user selects a file, and the application reads its contents. We will use NSFileCoordinatorMBS and NSFilePresenterMBS to manage file access on macOS or iOS while skipping these steps on Windows and Linux.

Sample Code Explained

Here’s a breakdown of the code provided:

Class MainWindow Inherits DesktopWindow
    Control OpenButton Inherits DesktopButton
        ControlInstance OpenButton Inherits DesktopButton
        EventHandler Sub Pressed()
            var ofd as new OpenFileDialog
            
            var f as FolderItem = ofd.ShowModal(Self)
            
            if f = nil then return
            
            #if TargetMacOS or TargetIOS then
                fp = new NSFilePresenterMBS
                fc = new NSFileCoordinatorMBS(fp)
                
                AddHandler fc.coordinateReadingItemAtURL, AddressOf coordinateReadingItemAtURL
                
                // don't ask other apps to save change
                var error as NSErrorMBS
                var flags as integer = NSFileCoordinatorMBS.NSFileCoordinatorReadingWithoutChanges
                fc.coordinateReadingItemAtURL(f, flags, error)
                
                if error <> nil then
                    MessageBox error.localizedDescription
                end if
            #else
                // Windows and Linux skip this
                ReadTheFile file
            #endif
        End EventHandler
    End Control
    Sub ReadTheFile(file as FolderItem)
        var b as BinaryStream = BinaryStream.Open(file)
        
        var Data as string = b.Read(b.Length)
        
        MessageBox "Read file with "+data.Bytes.ToString+" bytes"
    End Sub
    Sub coordinateReadingItemAtURL(N as NSFileCoordinatorMBS, url as string, file as folderitem, tag as Variant)
        // we got access
        ReadTheFile file
    End Sub
    Property fc As NSFileCoordinatorMBS
    Property fp As NSFilePresenterMBS
End Class

1. Setting Up the Open File Dialog

The OpenButton control’s Pressed event handler is triggered when the user clicks the button:

var ofd as new OpenFileDialog
var f as FolderItem = ofd.ShowModal(Self)

Here, we present the user with an OpenFileDialog to select a file. If the user cancels the dialog, f will be nil, and the method exits.

2. Conditional Code for macOS/iOS

For macOS and iOS, we initialize NSFilePresenterMBS and NSFileCoordinatorMBS. We connect the coordinateReadingItemAtURL event with out local method and ask the system to coordinate reading and notify us with an error if this fails or later call the callback method.

#if TargetMacOS or TargetIOS then
    fp = new NSFilePresenterMBS
    fc = new NSFileCoordinatorMBS(fp)
    
    AddHandler fc.coordinateReadingItemAtURL, AddressOf coordinateReadingItemAtURL
    
    var error as NSErrorMBS
    var flags as integer = NSFileCoordinatorMBS.NSFileCoordinatorReadingWithoutChanges
    fc.coordinateReadingItemAtURL(f, flags, error)
    
    if error <> nil then
        MessageBox error.localizedDescription
    end if
#else
    ReadTheFile f
#endif

You could subclass NSFileCoordinatorMBS instead of using addHandler if you prefer that of course. But the advance of using delegate, is that you don’t need to subclass each time you call it and just connect the event in code to som method.
While the sample code uses properties to store fp and fc, this is not really necessary. The coordinateReadingItemAtURL method retains the fc object reference and passes it to the delegate method. Once done, this reference gets released automatically.

3. Reading the File

If there are no errors during coordination, the coordinateReadingItemAtURL method is called, which then proceeds to read the file:

Sub ReadTheFile(file as FolderItem)
    var b as BinaryStream = BinaryStream.Open(file)
    var Data as string = b.Read(b.Length)
    MessageBox "Read file with "+data.Bytes.ToString+" bytes"
End Sub

This method reads the contents of the file and displays a message box with the number of bytes read. We call this directly for Windows and Linux since we don’t coordinate there.

4. Handling File Access Coordination Completion

The coordinateReadingItemAtURL method confirms that access to the file has been coordinated and then calls ReadTheFile to perform the actual file reading.

Conclusion

Using NSFileCoordinatorMBS and NSFilePresenterMBS ensures that your Xojo application handles file access gracefully, particularly on macOS and iOS where multiple processes might interact with the same files. This approach helps in avoiding file access conflicts and ensures a smooth user experience. On Windows and Linux, the code simply reads the file without the added complexity of file coordination.

By incorporating these classes, Xojo developers can create robust cross-platform applications that handle file operations consistently and effectively.

1 Like


Thanks Christian. But should be

 // Windows and Linux skip this
 ReadTheFile f

?

1 Like

Yes, of course. Thanks

AddHandler fc.coordinateReadingItemAtURL

Does this or a different process apply when wanting to write to an existing file?

Use coordinateWritingItemAtURL function.

I have a problem with cross platform.
The handler methods include these classes.
But I cannot exclude the method from compiling in Windows.

My tester reports this issue on Windows, even though the DLL is present

and I cannot work out how to stop the app trying to use the Mac classes this way.
@Christian_Schmitz

First, looks like you didn’t include the DLLs in the libs folder with the install.
Maybe you didn’t copied them?
Or the installer builder didn’t include them?

See also

Decouple two features for smaller application sizes

If you have a property with one of the NS*MBS classes, you could make it Variant and use local variables with the real type within #if lines.

Yes, it looks like that based on the message.
But that DLL at least is definitely present.

If you have a property with one of the NS*MBS classes

It’s not a property, its the added handler method which takes one of these as a parameter.
Not sure if that will work as a variant parameter.

Recompiled today, issue has gone. Weird