c00000fd
c00000fd

Reputation: 22307

How to use named object in macOS to limit app instances?

I need to preface this by saying that I'm coming from years of developing on Windows. This time I'm developing the UI for my macOS app using SwiftUI. The goal is to allow only one instance of the app, depending on where it's started from. For instance, if you copy the app into:

/Users/me/Documents/MyAppCopy/MyApp.app

and to:

/Users/me/Documents/MyApp.app

There should be only two instances of the app allowed, each from those respective locations.

On Windows I would use a named kernel object, say a named event, create it when the app starts and see if it already existed. If so, I will quit the app. Then when the first instance of the app closes, the named event is destroyed by the system automatically.

So I thought to try the same on macOS, but evidently Linux/BSD treats named objects differently.

If I do get the name of the object by calling:

var objName : Bundle.main.bundlePath
IsAnotherInstanceRunning(objName, objName.lengthOfBytes(using: String.Encoding.utf8))

and then using C, remove slashes from it, and use it in a named semaphore:

bool IsAnotherInstanceRunning(const char* pBundlePath, size_t szcbLnPath)
{
    bool bResult = false;

    char* pName = (char*)malloc(szcbLnPath + 1);
    if(pName)
    {
        memcpy(pName, pBundlePath, szcbLnPath);
        pName[szcbLnPath] = 0;
        
        //Remove slashes
        int cFnd = '/';
        char *cp = strchr(pName, cFnd);
        while (cp)
        {
            *cp = '_';
            cp = strchr(cp, cFnd);
        }
        
        //Create if doesn't exist, and return an error if it exists
        sem_t *sem = sem_open(pName, O_CREAT | O_EXCL, 0644, 0);
        if(sem == SEM_FAILED)
        {
            //Failed, see why
            int nErr = errno;
            if(nErr == EEXIST)
            {
                //Already have it
                bResult = true;
            }
        }
        
        free(pName);
    }

    return bResult;
}

Assuming that the path name isn't too long (this is an issue, but it is irrelevant to this question) - the approach above works, but it has one downside.

If I don't close and remove the name semaphore with:

sem_close(sem);
sem_unlink(pName);

It stays in the system if the instance of my app crashes. Which creates an obvious problem for the code above.

So how would you do this on Linux/macOS?

Upvotes: 0

Views: 147

Answers (1)

DarkDust
DarkDust

Reputation: 92384

To prevent/abort the start of an app while another instance is running, you can also use high-level AppKit stuff (we do this in one of our apps and it works reliably).

Use NSWorkspace.runningApplications to get a list of, well, the currently running applications. Check this list, and filter it for the bundleIdentifier of your app. You can then also check the bundleURL to decide whether it's OK to start the current app, which seems to be what you want to do. See also NSRunningApplication.current to get the informations about your current process.

(You can do [otherRunningApplication isEqual:NSRunningApplication.current] to check/filter the current process.)

Do this check in your applicationWillFinishLaunching or applicationDidFinishLaunching method.

Upvotes: 1

Related Questions