Storing Persistent Values in Preferences

thanks, Shane. That’s very helpful. I now have a complete working model. :+1:

FYI, here are some prose and cons of using properties versus using defaults:

Properties
Pros:
Near automatic; simple to use
Store all non-ASObjC classes

Cons:
Can’t be used with code-signed apps
Fail (silently) in several circumstances: locked scripts, scripts on locked volumes, scripts with top-level ASObjC values, scripts that get interrupted
Can’t store Cocoa values
Results in changed modification date and size of scripts

Neither pro nor con:
Stores per-script

Defaults
Pros:
Can be used with code-signed scripts
Reliable – work on locked volumes, with ASObjC values, and unaffected by script interruptions
Values are transferrable
Some visibility
Can be shared

Cons:
Requires ASObjC
More complex to use, especially with non-bridged AS classes

Neither pro nor con:
Stores per-user

Coming back to this code…

There’s a potential issue where the id changes, such as during a save-as, or where you change it in the Resources panel. Unfortunately the system caches the initial value, and there seems to be no way of flushing it. So my id can potentially return an out-of-date result in an editor. (It’s a non-issue in applets, just when editing.)

The solution is not to rely on the id if it doesn’t match that of current application, but instead to extract it directly from the Info.plist file.

Here’s revised code to cover that situation:

use framework "Foundation"
use scripting additions
global theDefaults

if my id = missing value then error "This code works only in an applet or script bundle."
if current application's id = my id then -- must be running as applet
	set theDefaults to current application's NSUserDefaults's standardUserDefaults()
else -- being run by editor or other host app
	set myPath to POSIX path of (path to me)
	set theData to current application's NSData's dataWithContentsOfFile:(myPath & "Contents/Info.plist")
	set infoDict to current application's NSPropertyListSerialization's propertyListWithData:theData options:0 |format|:(missing value) |error|:(missing value)
	set myID to infoDict's objectForKey:"CFBundleIdentifier"
	set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:myID
end if

The extra code is only ever run when editing, so it has no effect on performance.

I’m having a bit of difficulty just getting the initial code in posting #8 to run.

For some reason the system is triggering the error message, “This code works only in an applet or script bundle.”

I assume the note about it having to be in an applet or script bundle is because there wouldn’t otherwise be a plist to contain the id. Is this correct?

In the plist for the app containing the noted code, I see:
CFBundleIdentifier
com.apple.automator.Setting User Defaults

It seems like there is an identifier, though I’m not sure if this was what gets reflected in the id variable.

This is essentially due to a short-coming in Automator.

Thank you. Sounds like Automator is really not up to some very basic tasks.

AppleScript is really just a bit player in the Automator world. It’s meant to be a way of building Automator actions, not complete workflows. As such, I think you need to temper your expectations. Different tools suit different tasks.

Yes, I’m getting that impression about Automator as a coding platform.

I worked in the aerospace industry for years using Ada, strict data-typing and very good reference and style manuals. So; I suppose that I still have certain (possibly too high) expectations of coding platforms. The Automator “environment” definitely seems like an afterthought by Apple. Like you, I get the impression it was more likely geared to stringing together the pre-coded building blocks they ship with it, than doing serious line-by-line coding. Script Debugger definitely seems closer to what I would expect. So; I’m trying to convince “she who must be obeyed” to allow me to purchase Script Debugger. :>)

This is very useful, but Script Editor won’t allow me to save … I get
The document ... could not be autosaved. C and Objective-C pointers cannot be saved in scripts.

Any solutions? Thanks

You need to recompile the script first.

1 Like

Hello! I am a student trying to use this code in an existing applet to replace my usage of AppleScript properties (in preparation for Big Sur arm64’s codesigning requirement), but I’m finding two things:

When running it as an applet (or, less importantly, in Script Editor), I’m finding two issues:

1. This code somehow removes the applet’s ability to recognize its own version number (CFBundleShortVersionString) that is located in the bundle’s Info.plist. Right before executing this snippet of code, using the version command in AppleScript returns the correct value, but afterwards, it instead returns the string “vers.”

Since I need the version number for relevant services later on, this behavior breaks my applet. If I try modifying your code to set version back to what’s stored in Info.plist, Script Editor says I don’t have permission to set the version myself, which doesn’t surprise me.

As a workaround, I set a variable of my own to the AppleScript version and then execute your code after it. Then I use that regular variable throughout the applet in all places where I used to use version. But I would feel more comfortable if the issue could be rectified, as this seems a little strange.

2. Most importantly, this code seems to prevent the applet from using the load script file or run script command, where it instead terminates with error “Can’t make current application into type file.” number -1700 from current application to file. Why would it do something like that?

I also tried using PrefsStorageLib, but encountered similar behavior regarding versioning, as well as the error -45 saving issue in Script Editor (which has been granted Full Disk Access in System Preferences on Catalina). (I do prefer the method outlined in this thread, which seems simpler, both in syntax as well as letting me avoid bundling a script library for deployment; although I would love to know what the benefits/difference is in purpose between this code vs. PrefsStorageLib).

Would you be able to help me? I really need to replace my use of traditional AppleScript properties in this applet with the much more modern and compatible defaults system, but at the same time my applet can’t function without its version number and its ability to load other scripts that I’ve bundled with it. Thanks so much!

You will have to be more specific. What code, exactly?

Sorry! The code posted earlier in the thread.

I put this in an applet:

use framework "Foundation"
use scripting additions
global theDefaults

set theVers to my version
display dialog theVers

if my id = missing value then error "This code works only in an applet or script bundle."
if current application's id = my id then -- must be running as applet
	set theDefaults to current application's NSUserDefaults's standardUserDefaults()
else -- being run by editor or other host app
	set myPath to POSIX path of (path to me)
	set theData to current application's NSData's dataWithContentsOfFile:(myPath & "Contents/Info.plist")
	set infoDict to current application's NSPropertyListSerialization's propertyListWithData:theData options:0 format:(missing value) |error|:(missing value)
	set myID to infoDict's objectForKey:"CFBundleIdentifier"
	set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:myID
end if

set theVers to my version
display dialog theVers

changed the version using File -> Bundle & Export Settings…, saved, and ran the app. I got the version number both times. Please try this code.

This is an issue that happens when you use ASObjC code in a script — file references don’t always work. The solution is to change:

load script file pathToScript

with:

load script (pathToScript as «class furl»)

You can also avoid this issue by using PrefsStorageLib.

Ah, thank you so much! Using my version instead of just version works beautifully. I was testing this the same way and had no idea that was the proper syntax.

I have two versions of my applet, one using this thread’s ASObjC method, and one using PrefsStorageLib. (Three if you count the stable original version using properties.)

My only issue with PrefsStorageLib is that it becomes very difficult to save and debug my code, because I constantly get the error -45 “could not be saved” message. Then I have to restart Script Editor and lose the changes. Is there anything I can do to mitigate or stop this? Am I doing something wrong? Script Editor has been granted Full Disk Access.

With the other version using ASObjC, I will try using «class furl», thanks! I’m also getting Finder permissions issues. The script needs to access files in the Documents folder, which usually prompts the system to request access. But with the ASObjC code in it, somehow it never prompts the system dialog, and just errors as if the user clicked “Don’t Allow.”

This seems related to the file references issue you are describing. How do I use «class furl» with a file or folder path that has an alias, like (((path to documents folder as text) & folderName) as alias) as string?

Thanks again so much!

You keep referring to Script Editor. Do you mean that, or Script Debugger?

It should make no difference. I suspect something else is failing.

Use ((path to documents folder as text) & folderName) as «class furl»

Yes, I’m getting the error -45 in Script Editor when I try to save.

(It was my first time writing AppleScripts, and sadly I didn’t find out about Script Debugger until much later in the development process. It seems amazing and much more useful but I’m not comfortable transitioning in the middle of a project.)

If you’re getting the error when auto-saving, it’s probably because a global contains an ASObjC value, and re-compiling will fix it. Otherwise I have no idea.

Recompiling doesn’t do it, but I just tried Script Debugger and haven’t encountered the error -45 yet, so it looks like PrefsStorageLib is viable for me now. Is PrefsStorageLib Universal? (And if not, could I just recompile it as a .scptd in Big Sur to make it so?)

FYI, just tried Script Debugger because you mentioned it, and I’m totally sold on it, and not least because the error went away. Wow. Thanks for your help.

Universality only relates to executable binaries, not scripts.

1 Like