In your app, create a pipe. Start a child process. Have it read the pipe from its parent, your app. When the pipe gets an EOF, it should relaunch your app.
In your app, after the child process is launched, do your app's normal cleanup and termination, but leave the sending end of the pipe open.
When your app exits, the system will automatically close the sending end of the pipe, which your lingering child then detects as an EOF on its reading end, which triggers its action of relaunching your app.
That's the gist of it. You may have to arrange for some signals to be ignored in the child. You may also want a timeout in the child, to abort if an EOF doesn't appear within X seconds (BSD alarm() function, SIGALRM signal). Experiment.
The source for the Sparkle framework has code for a shell script that does approximately what I outlined above, although it polls rather than reading a pipe. Or at least it used to. It may be different now.