on writePlist(thePlist, theDictionary)
set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:thePlist
set theDefaults's theKey to theDictionary
end writePlist
The next time I run the script the dictionary is read with:
on readPlist(thePlist)
set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:thePlist
set theDictionary to theDefaults's theKey
return theDictionary
end readPlist
The issue I’ve encountered is that the dictionary returned by the readPlist handler is no longer mutable. I say that because I get an unrecognized selector error from the setObject method. I fixed this by rewriting the readPlist handler as shown below.
on readPlist(thePlist)
set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:thePlist
set theDictionary to theDefaults's theKey
return (theDictionary's mutableCopy())
end readPlist
My question is a simple one–why doesn’t the first-above readPlist handler return a mutable dictionary. The script with the second-above readPlist handler works fine and I guess I should accept that as good, but I couldn’t help but wonder. Thanks for the help.
In his book, Shane has a whole chapter dedicated to “Persisting Preferences” and this chapter appears to discuss doing exactly what I am doing. The dictionary saved to the plist file is used in my script only and has nothing to do with any other apps. I probably should have been clearer on this point.
BTW, the actual script I am working to optimize will be found in the last post of the following thread. In my testing, I have determined that NSMutableDictionary’s dictionaryWithContentsOfFile is appreciably slower than NSUserDefaults at this particular task.
I did a little addition research, and the documentation for NSUserDefaults contains the following paragraph, which appears to confirm the above:
Also, Shane’s book contains the following example, which seems to confirm that mutableCopy is the correct method to use to make the dictionary mutable after retrieval by NSUserDefaults:
use framework "Foundation"
on testIt()
set anNSArray to current application's NSArray's arrayWithArray:{1, 2, 3}
set newArray to anNSArray's mutableCopy()
newArray's addObject:4
return newArray as list
end testIt
testIt()
It looks like you’ve answered your own question, but to go a little further, you can use NSPropertyListSerialization when reading property lists (not via defaults) to control mutability.
For example:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
set theData to current application's NSData's dataWithContentsOfFile:thePath
set {theDict, theError} to current application's NSPropertyListSerialization's propertyListWithData:theData options:(current application's NSPropertyListMutableContainersAndLeaves) format:(missing value) |error|:(reference)
Thanks Shane. I placed the your code in my script and it worked fine. I’m not familiar with NSData or NSPropertyListSerialization, and I’ll work to learn those.
Just by fumbling around a bit, I got the following to work to write the plist file. Other than error correction, which I’ll add, does this approach look OK? I use this script a lot and want to make sure. Thanks again.
on writePlist(thePath, theDictionary) -- theDictionary is a mutable dictionary and thePath is POSIX path
set {theData, theError} to current application's NSPropertyListSerialization's dataWithPropertyList:theDictionary format:(current application's NSPropertyListXMLFormat_v1_0) options:0 |error|:(reference)
set theResult to theData's writeToFile:thePath atomically:true
end writePlist
All of my questions have been answered and I thought I would add a postscript.
I ran timing tests with three approaches that can be used to write a mutable dictionary to a property list file and to read it afterwards. The test dictionary contained 300 keys (which is a worse-case scenario for me) and the tested options were:
NSMutableDictionary
NSUserDefaults
NSData (NSPropertyListSerialization)
The write times were measured with a timing script and the read times with Script Geek. The end result was that all three options were reasonably quick with write times of 12 milliseconds or less and read times of 8 milliseconds or less. The read times, which were of greatest interest to me, generally varied by no more than 2 milliseconds.
I state in an earlier post that NSMutableDictionary was significantly slower than NSUserDefaults. That is not correct and was the result of an error in my testing.
Just wanted to say that on some versions of SDKs and Applescripts
I’ve had issues trying to save NSURL’s in a dict or Using Serialization and NSArchiver.
I had to get the path (NSString) then store that as a value in the dictionary.
And on the reverse side, recreate the NSURL’s from the “saved” NSString.
I believe it may have been in OS10.11 that this wasn’t an issue.
So if you ever have other issues when trying to save.
Remember the basic NSObjects that it can encode.
Things like enums, NSPoint, NSRect etc need to be converted to NSValue
And then stored in the Dictionary as a value for Key.
Then also when un archiving the reverse must be done.
Some of the Classes have convince methods for creating the NSValue and also for i it the Class with a NSValue. (Rather than found thru NSData).
The bridging of NSURL and files/aliases was introduced in 10.11 (as was bridging between NSDate and AS dates).
Points and rects are probably better stored as strings. The functions to use are: NSStringFromRect(), NSRectFromString(), NSStringFromPoint() and NSPointFromString(). The result is a lot more human-readable.