A question in a recent thread (here) was how one might save an array of strings to a file, and there are three alternatives that I am aware of.
The approach I have always used is NSArray’s writeToFile method:
use framework "Foundation"
use scripting additions
set theFile to POSIX path of (path to desktop) & "Test File.txt"
set theArray to current application's NSArray's arrayWithArray:{"a", "b", "c"}
theArray's writeToFile:theFile atomically:true -- write file
set theArray to current application's NSArray's arrayWithContentsOfFile:theFile -- read file
The following is a suggestion by Fredrik71; it uses the NSKeyedArchiver class. For consistency, I modified the script to use file rather than URL.
use framework "Foundation"
use scripting additions
set theFile to POSIX path of (path to desktop) & "saveData.dat"
set theArray to current application's NSArray's arrayWithArray:{"a", "b", "c"}
set theData to current application's NSKeyedArchiver's archivedDataWithRootObject:theArray
theData's writeToFile:theFile atomically:true -- write file
set theData to current application's NSData's dataWithContentsOfFile:theFile
set theArray to current application's NSKeyedUnarchiver's unarchiveObjectWithData:theData -- read file
The third approach is loosely derived from posts by Shane and uses the NSPropertyListSerialization class:
use framework "Foundation"
use scripting additions
set theFile to POSIX path of (path to desktop) & "Test File.plist"
set theArray to current application's NSArray's arrayWithArray:{"a", "b", "c"}
set {theData, theError} to current application's NSPropertyListSerialization's dataWithPropertyList:theArray format:(current application's NSPropertyListBinaryFormat_v1_0) options:0 |error|:(reference)
set theResult to theData's writeToFile:theFile atomically:true -- write to file
set theData to current application's NSData's dataWithContentsOfFile:theFile
set theArray to current application's NSPropertyListSerialization's propertyListWithData:theData options:0 format:(missing value) |error|:(missing value) -- read file
I wondered if there are any compelling reasons to use one approach over the other? A few miscellaneous thoughts:
NSArray’s writeToFile method is deprecated, and Shane has commented that “direct saving of arrays is discouraged these days”.
The NSKeyedArchiver class will save additional types and, apparently, will save multiple arrays (or other objects) identified by keys.
The archivedDataWithRootObject method is deprecated, although there is a simple alternative.
All three approaches took about 1.7 millisecond to run.
Approach 1 and 3 do exactly the same, they serialize the array to Property List. The writeToFile method is deprecated because it doesn’t provide any error handling unlike NSPropertyListSerialization.
archivedDataWithRootObject in approach 2 is deprecated, too. The more secure replacement is archivedDataWithRootObject:requiringSecureCoding:error:. But NSCoding for a simple string array is pretty overkill.
A lightweight fourth approach is serializing to JSON (with NSJSONSerialization).
use framework "Foundation"
use scripting additions
set theFile to POSIX path of (path to desktop) & "Test File.json"
try
set {theData, theError} to current application's NSJSONSerialization's dataWithJSONObject:{"a", "b", "c", "d"} options:0 |error|:(reference)
if theError is not missing value then error (theError's localizedDescription()) as text
set theResult to theData's writeToFile:theFile atomically:true -- write to file
set theData to current application's NSData's dataWithContentsOfFile:theFile
set {theArray, readError} to current application's NSJSONSerialization's JSONObjectWithData:theData options:0 |error|:(reference) -- read file
if readError is not missing value then error (readError's localizedDescription()) as text
set theArray to theArray as list
on error e
display dialog e
end try
It avoids the overhead of the Property List XML.
Side-note: It’s not necessary to create a Foundation array from an AppleScript array. The framework can infer it. And please handle all errors
The timing results with the same array are shown below. These results include the time it took to read a text file and make it into an array, which timed separately took 2.3 milliseconds.
The reason for the different outcome of NSArray and NSPropertyListSerialization is that NSArray writes always plain XML while you explicitly specified the binary format in the NSPropertyListSerialization approach.
You should get the same file size and (almost the same) speed with the format NSPropertyListXMLFormat_v1_0
Of course plain XML is the most expensive format with regard to file size because of the header and the XML tags
Thanks Stefan. I edited script 3 to use the XML property list format. I retested and added approach 5 to my file-size and timing-results above. The results were as expected.
FWIW, I looked at the documentation for the NSJSONSerialization class, and the same approach can be used for a dictionary. The following is a simple example for demonstration purposes only:
use framework "Foundation"
use scripting additions
set theFile to POSIX path of (path to desktop) & "Test File.json"
set listOne to {"a", "b"} -- can also be an array
set listTwo to {"c", "d"} -- can also be an array
set theRecord to {keyOne:listOne, keyTwo:listTwo} -- can also be a dictionary
-- write file
set theData to current application's NSJSONSerialization's dataWithJSONObject:theRecord options:0 |error|:(missing value)
theData's writeToFile:theFile atomically:true
-- read file
set theData to current application's NSData's dataWithContentsOfFile:theFile
set theDictionary to current application's NSJSONSerialization's JSONObjectWithData:theData options:0 |error|:(missing value)
-- test
((theDictionary's valueForKey:"keyTwo")'s objectAtIndex:0) as text --> "c"