"I have various functions that are GUIs that are called by other functions (or even other GUIs) and I need to be able to pass data back to whatever called the data gathering GUI in the first place."
If your GUI was created with GUIDE or with uicontrol(), you can get outputs from the GUI but not in app designer. It's nice when GUIs are self-contained or only rely on built-in functions so that only the GUI file is needed to run the GUI in matlab. But if you have other functions or scripts that rely on a particular function, that function should be accessible outside of a GUI file. So, one solution would be to move the commonly used local function from the GUI to an external, independent function file. Afterall, GUI's are meant to be used by humans (or well trained non-human primates), not by other functions. So redesigning the flow of your code is probably the best solution.
In response to your listed options,
1) Forget about using the app structure outside of the app. There's no reason to save a bunch of graphics handles just to extract some embedded data. Besides, saving graphics handles can cause very large files and often times when you load the handle structure, it opens an additional instance of the GUI figure which is a nuisance.
2) Using assignin() or any of its friends causes more problems that what you started with. As you've correctly identified, this would be a cumbersome solution. Here's more background on why this is bad [link].
3a) "I could simply save the data and load it later"; If you can't move the local GUI functions to an external, independent function file, this may be a viable solution but it comes with more problems. The GUI can detect when it's been activated by a function rather than manually by a user. If it was activated by a function it could gather the needed outputs in a neat, tidy format and save it to a mat file. However, ...
3b) "but how do I trigger the calling function to load the data when it's available?" ...this is where things get complicated and ugly. I can imagine solutions for this problem but they all look like crumbling scaffolding around a demolished building in my imagination. For example, just before you call the GUI you could create an invisible graphics object. Just after calling the GUI you could add a while-loop that continually runs as long as the invisible graphics object exists (ishghandle). From within the GUI, when it's finished it searches for and deletes the inivisble graphics object which triggers the end of the while loop. Then the code can load the mat file into the function. This is a bad idea for so many reasons. What is the name of the file? Where does it exist? What if there's an error in the GUI and the while-loop runs for infinity? Why does a function need to run a GUI in the first place?
4) I don't see a big difference between this idea and idea #2.