Properties not being saved

I have this sample script, copied from elsewhere here in the Script Debugger 8 forum.

use AppleScript version “2.4” – Yosemite (10.10) or later
use scripting additions

property theRunCount : 0

set theRunCount to theRunCount + 1
display dialog “This script has been run " & theRunCount & " time(s).”

I created a new Script Debugger document and pasted that script in. When I click the Run button it shows “This script has been run 1 time(s).” When I click the Run button a second time I am expecting the message to say “This script has been run 2 times(s).” But it doesn’t. No matter how many times I click the Run button it always says “This script has been run 1 time(s).”

But… if I copy the contents of that Script Debugger 8 window, and paste it into a new Script Editor window, the counter does go up by 1 every time I click Run there.

Neither of these scripts have been saved. They are just new Script Debugger and Script Editor documents.

Looking around here in the forum and in the Help for Script Debugger it seems the key is a feature in Script Debugger called “Persistent Properties.” But… first of all, I can’t find that option (supposedly in the Scripts menu according to Script Debugger’s help, but not in my copy of SD 8.0.3), and second of all, the help entry in SD 8 is talking about choosing Script > Persistent Properties when saving a script, and I have not saved these scripts yet at all.

If I do save the scripts (as applications) the one saved from Script Debugger 8 always shows “This script has been run 1 time(s).” The one saved from Script Editor always shows whatever the number was when I saved it (in my case, it’s “This script has been run 3 time(s).” because I’d clicked Run a few times before saving it).

I know that I don’t know enough about Properties but I know enough to know this is not how Properties are supposed to work!

This is with macOS 13.2.1.

If I take those two scripts (saved as applications) to another Mac running 10.13.6, the message in the dialog box increases by 1 every time I double-click either app (that is, the one saved with Script Debugger 8 works, and the one saved with Script Editor works). I don’t have Script Debugger 8 installed on that Mac but I do have SD 6 (which has a Script > Persistent Properties menu item) and in SD 6 the script does increment when run from the editor.

So… I sort of think this has to do with macOS 13.2.1.

If Properties don’t work properly in macOS 13 it’s going to be a big mess for me! Hoping that there’s a way to make things work. THANK YOU…

Properties are not reliably saved with scripts in recent versions of macOS. Shane explains this in the article that can be found here

So properties will not persist in universal applets run under Big Sur. If you need to store values between launches, you will have to do it yourself. This is a big change in long-standing AppleScript applet behavior, one you should consider carefully when updating scripts.

Two alternatives that can be used are text files and plists. Another alternative is Shane’s PrefsStoragLib script library which can be found here

2 Likes

This is the solution I use, and I’ve written a script that makes it seemless to adapt an old script with a lot of properties:

set saveTID to AppleScript's text item delimiters

set myText to GetScriptText(1)
set keysAndValues to {}
set AppleScript's text item delimiters to {" : "}
set x to 0
repeat with thisGraph in myText
   set x to x + 1
   set thisGraph to thisGraph as text
   try
      copy word 1 of thisGraph to paraLead
      if paraLead is "property" then
         set lastPropertyLine to x
         set thisKey to word 2 of thisGraph
         set keyValue to the rest of text items of thisGraph as text
         set the end of keysAndValues to {thisKey, keyValue}
      end if
   end try
end repeat
set AppleScript's text item delimiters to {""}

set defaultValues to {"{"}
set valuesForKeyLines to {}
set savingValueLines to {}
set backupWriteCalls to {}
set keyValueCount to count of keysAndValues
repeat with x from 1 to keyValueCount
   set thisKey to item x of keysAndValues
   set {keyName, keyValue} to thisKey
   if x = keyValueCount then
      set the end of defaultValues to {keyName, ":", "{}"} as text
   else
      set the end of defaultValues to {keyName, ":", "{}, "} as text
   end if
   
   set the end of valuesForKeyLines to "   set my " & keyName & " to value for key \"" & keyName & "\""
   set the end of savingValueLines to "   assign value " & keyName & " to key \"" & keyName & "\""
end repeat

set the end of defaultValues to "}"
set AppleScript's text item delimiters to {" "}
set defaultValues to defaultValues as text
set AppleScript's text item delimiters to {""}

set storagePrepCall to {"prepare storage for (path to me) default values ", defaultValues, return, ¬
   "--prepare storage for domain \"com.edstockly.uniqueID\"  ", return, ¬
   "my RetreiveStoredValues()"} as text

set AppleScript's text item delimiters to {return}

set PersistentVariableHandler to {"on PersistentVariables()", ¬
   storagePrepCall, ¬
   "end PersistentVariables"} as text

set SaveVariablesHandler to {"on StorePersistentValues()", ¬
   savingValueLines, ¬
   "end StorePersistentValues"} as text

set RetreiveVariableHandler to {"on RetreiveStoredValues()", ¬
   valuesForKeyLines, ¬
   "end RetreiveStoredValues"} as text

set persistentVariableCalls to {{""}, ¬
   {"PersistentVariables()"}, ¬
   {"--StorePersistentValues()"}, ¬
   {"--RetreiveStoredValues()"}, ¬
   {""}}

set item lastPropertyLine of myText to {item lastPropertyLine of myText, persistentVariableCalls}

set the end of myText to {¬
   PersistentVariableHandler, "", ¬
   SaveVariablesHandler, "", ¬
   RetreiveVariableHandler, ""}
set myText to myText as text

set myText to FindChange(myText, "√¬√", "¬" & return)
set the clipboard to myText




--displayAlert 
set resultAlertreply to display alert ("Paste in persistent variables") ¬
   message ("The Clipboard contains the entire script with persistant variable handlers" & return & return & "You may paste it into your script.") ¬
   as informational ¬
   buttons {"OK"} ¬
   default button 1 ¬
   giving up after 30
--displayAlert 
return myText as text


on FindChange(textToSearch, textToFind, textToReplace)
   set saveTID to AppleScript's text item delimiters
   set AppleScript's text item delimiters to textToFind
   set textToSearch to every text item of textToSearch
   set AppleScript's text item delimiters to textToReplace
   set textToSearch to textToSearch as string
   set AppleScript's text item delimiters to saveTID
   return textToSearch
end FindChange

on GetScriptText(DocNumber)
   --tell application "TextWrangler" to set myText to text of window 1
   tell application "Script Debugger"
      tell its document DocNumber
         set myText to its source text
      end tell
   end tell
   
   set myText to paragraphs of myText
   set the beginning of myText to ""
   set AppleScript's text item delimiters to {return}
   set myText to myText as text
   set myText to FindChange(myText, "¬" & return, "√¬√")
   set textHeader to {}
   
   if return & "use scripting additions" is not in myText then
      set the end of textHeader to "use scripting additions"
   end if
   if return & "use script \"PrefsStorageLib\"" is not in myText then
      set the end of textHeader to "use script \"PrefsStorageLib\" version \"1.1.0\""
   end if
   set myText to (textHeader as text) & myText
   set myText to paragraphs of myText
   return myText
end GetScriptText

The idea is it will set up storage for all the properties in your script, build the handlers to store and retrieve them, and drop the handler calls at the top of your script.

After that, just make sure you include the
StorePersistentValues()
before your script ends.

(Edit: fixed the broken post)

FWIW, the following is a plist-equivalent of the script included by @christianboyce in post 1. This script works if it is run from within a script editor or as a saved script/app.

use framework "Foundation"
use scripting additions

set savedNumber to readPlist("CurrentNumberKey") -- read plist
if savedNumber is missing value then set savedNumber to 0 -- if plist not found
set newNumber to (savedNumber as integer) + 1
display dialog (newNumber as text)
writePlist("CurrentNumberKey", newNumber) -- write number to plist

on readPlist(theKey)
	set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:"com.peavine.RunningTotal"
	return theDefaults's objectForKey:theKey
end readPlist

on writePlist(theKey, theValue)
	set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:"com.peavine.RunningTotal"
	theDefaults's setObject:theValue forKey:theKey
end writePlist

BTW, the following is from the AppleScript Language Guide:

After you define a property, you can change its value with the copy or set command.

The value set by a property definition is not reset each time the script is run; instead, it persists until the script is recompiled.

So, it would seem that theRunCount should be incremented if the following script is run repeatedly in Script Debugger but it’s not (which may be the point being made by the OP). Perhaps Script Debugger recompiles the script every time it’s run.

property theRunCount : 0
set theRunCount to theRunCount + 1
display dialog "This script has been run " & theRunCount & " time(s)."

See this from Mark…

1 Like

Thanks Ed–that explains everything.

Thanks everyone for the answers. Somehow I missed (or forgot) that properties are not to be used any longer. We have other ways, so I’ll just start using another way. Hopefully I don’t have some old script somewhere using properties, just waiting to break…

Hi peavine, this is really great! I never knew there was such a thing as saving to files in the user preference domain. I just added some code to get the name of the user to make it more versatile:

set thePath to path to home folder as text
set AppleScript’s text item delimiters to “:”
set theUsername to item -2 of (text items of thePath)
set AppleScript’s text item delimiters to “”

set theKey to “CurrentNumberKey”
set savedNumber to readPlist(theKey, theUsername) – read plist

Actually in this example, theKey is actually both the key as well as the preference’s filename. In more complex preference files where there is more than one key, the name of the file will be different.

You can also store AppleScript values in a native AppleScript format, removing the need for any encoding/decoding or conversion to text:

-- Store any persistent values in a record.
set example_values to {|name|:"Mickey Mouse", last_saved_date:current date}

-- Save the record to disk.
set save_path to do shell script "echo ~" & "/Desktop/StoreVariableExample.dat"
my storeVariableInFile(save_path, example_values)

-- Load the record from disk.
set retrieved_values to my loadRecordFromFile(save_path)
return retrieved_values


--- HANDLERS ---
to storeVariableInFile(file_specifier, variable_to_store)
	(*    (file specifier, any) → file specifier
	
	Write variable_to_store, encoded as variable_to_store's class, to a new file at file_specifier, and return file_specifier unchanged. If file_specifier already exists, the operation will not proceed and the handler will throw an error.
	
	The file may be specified by a POSIX path (slash-delimited string), an HFS path (colon-delimited string), a file URL (POSIX file), an AppleScript alias, an AppleScript file reference, a Finder object, or a System Events object.
	
	Parameters:
	file_specifier [string OR file URL OR alias OR file OR "Finder" item OR "System Events" item] : A file object or path to the new file.
	variable_to_store [any] : The variable to store in the file.
	
	Result:
	[string OR file URL OR alias OR file OR "Finder" item OR "System Events" item] : A copy of file_specifier.    *)
	
	set file_reference to getFileReference(file_specifier)
	if fileExists(file_reference) then error "storeVariableInFile: " & getPath(file_specifier) & ": File already exists" number -48
	
	try
		set a_file to open for access file_reference with write permission
		write variable_to_store to file_reference as (class of variable_to_store)
		close access a_file
	on error m number n
		close access a_file
		error m number n
	end try
	
	return file_specifier
	
end storeVariableInFile


to loadRecordFromFile(file_specifier)
	(*    (file specifier) → list
	
	Return the contents of the file specified by file_specifier, decoded as a record.
	
	The file may be specified by a POSIX path (slash-delimited string), an HFS path (colon-delimited string), a file URL (POSIX file), an AppleScript alias, an AppleScript file reference, a Finder object, or a System Events object.
	
	Parameters:
	file_specifier [string OR file URL OR alias OR file OR "Finder" item OR "System Events" item] : A file object or path.
	
	Result:
	[record] : The contents of the file specified by file_specifier, interpreted as a record.    *)
	
	return read getFileReference(file_specifier) as record
	
end loadRecordFromFile


to getPath(file_specifier)
	(*    (file specifier) → string
	
	Return the POSIX path of file_specifier, which may be a POSIX path (slash-delimited string), an HFS path (colon-delimited string), a file URL (POSIX file), an AppleScript alias, an AppleScript file reference, a Finder object, or a System Events object.
	
	Parameters:
	file_specifier [string OR file URL OR alias OR file OR "Finder" item OR "System Events" item] : A file object or path.
	
	Result:
	[string] : The POSIX path of file_specifier.    *)
	
	try
		return POSIX path of (file_specifier as «class furl»)
	on error number -1700
		using terms from application "System Events"
			set POSIX_file to path of file_specifier as «class furl»
		end using terms from
		return POSIX path of POSIX_file
	end try
	
end getPath


to getFileReference(file_specifier)
	(*    (file specifier) → file
	
	Return a reference to the file specified by file_specifier, which may be a POSIX path (slash-delimited string), an HFS path (colon-delimited string), a file URL (POSIX file), an AppleScript alias, an AppleScript file reference, a Finder object, or a System Events object.
	
	Parameters:
	file_specifier [string OR file URL OR alias OR file OR "Finder" item OR "System Events" item] : A file object or path.
	
	Result:
	[file] : A reference to the file specified by file_specifier.    *)
	
	try
		return a reference to file ((file_specifier as «class furl») as string)
	on error number -1700
		using terms from application "System Events"
			set file_path to path of file_specifier
		end using terms from
		return a reference to file file_path
	end try
	
end getFileReference


to fileExists(file_specifier)
	(*    (file specifier) → boolean
	
	Return true if the file specified by file_specifier exists, otherwise return false. The file may be specified by a POSIX path (slash-delimited string), an HFS path (colon-delimited string), a file URL (POSIX file), an AppleScript alias, an AppleScript file reference, a Finder object, or a System Events object.
	
	If file_specifier refers to a broken symbolic link, the handler will return false. If you must specifically test for the existence of a invalid symbolic link, use the symbolicLinkExists handler.
	
	Parameters:
	file_specifier [string OR file URL OR alias OR file OR "Finder" item OR "System Events" item] : A file object or path.
	
	Result:
	[boolean] : True if file_specifier exists.    *)
	
	try
		((file_specifier as «class furl») as alias)
		return true
	on error number -1700
		try
			using terms from application "System Events"
				((path of file_specifier) as alias)
				return true
			end using terms from
		end try
	end try
	
	return false
	
end fileExists