Trying to save an array of objects

This could become the first question in a row, so some explanation up front. I want to save and load data contained in an array of objects, in a non-document ASOC-application. Reading the Archives and Serializations Programming Guide and searching for relevant topics on this forum has so far resulted in more confusion than wisdom.

What I’m trying to do is save an array of objects to a file “ and retrieve them “ in the simplest possible way. I have the project from chapter 7 in Shane’s book, ‘A Class Of Your Own’. So the data is in an array of objects of the class Ad, implemented in Ad.applescript.

Now I want to go further than writing tabbed text for use outside the app. It’s the array of Ad objects itself that I want to encode and write to disk, so that I can load it into the same app again.

Shane describes encoding in chapter 22, ‘Doing it With Documents’. But in this case, without documents, I obviously cannot use the on dataOfType_error_(…) handler. So I tried the following as a handler in AppDelegate:

    on saveData_(sender)
        set dataToSave to current application's NSMutableData's |data|()
        set theEncoder to current application's NSKeyedArchiver's alloc()'s initForWritingWithMutableData_(dataToSave)
        tell theEncoder
            encodeObject_forKey_((current application's Ad)'s adDescription, "adDescription")
            encodeObject_forKey_((current application's Ad)'s adColumns, "adColumns")
            encodeObject_forKey_((current application's Ad)'s adDepth, "adDepth")
        end tell
        set newFile to (choose file name with prompt "Save data" default name "Saved data")
        set fileRef to (open for access newFile with write permission)
        set eof fileRef to 0
        write dataToSave to fileRef
        close access fileRef
    end saveData

Now Xcode tells me:

Obviously something goes wrong accessing the properties of the object. How should I proceed?

Another thing: can somebody explain why my posted code snippets loose indentation all the time? I use the ‘applescript’ tags. Is that the wrong way to do it?

Model: iMac
AppleScript: Xcode 4.5
Browser: Safari 536.25
Operating System: Mac OS X (10.8)

To help you help me, I’ve been thinking further… The Ad class is a descendant of NSObject, not of NSDocument. So it does not adopt the NSCoding protocol, which is what’s needed. Please correct me if I’m wrong.

So to restate the question: how do I make the Ad class adopt NSCoding?

You’re right.
Only property list compliant objects can be archived directly.
Your class must implement the NSCoding protocol and archive/unarchive the necessary properties inside the class
with the intWithCoder_()/decodeWithCoder_() methods.

Thank you Stefan! And here’s the question from the newbie who got a bit too far ahead of himself: how do I do that? The only examples I can find are in ObjC, and there’s still too many holes in my knowledge to translate them…

You’d want something like this in the Ad class:

    on encodeWithCoder_(coder)
        coder's encodeObject_forKey_(adDescription, "adDescription")
        coder's encodeObject_forKey_(adColumns, "adColumns")
        coder's encodeObject_forKey_(adDepth, "adDepth")
    end encodeWithCoder_
    on initWithCoder_(coder)
        continue init()
        setAdDescription_(coder's decodeObjectForKey_("adDescription"))
        setAdColumns_(coder's decodeObjectForKey_("adColumns"))
        setAdDepth_(coder's decodeObjectForKey_("adDepth"))
        return me 
    end initWithCoder_

Then in the app delegate:

    on saveAsObjects_(sender)
        set allAds to theArrayController's arrangedObjects()
        set newFile to POSIX path of (choose file name with prompt "Save the objects" default name "Exported objects.plist")
        current application's NSKeyedArchiver's archiveRootObject_toFile_(allAds, newFile)
     end saveAsObjects_
    on importAsObjects_(sender)
        set theFile to POSIX path of (choose file with prompt "Choose exported file:")
        set my theData to current application's NSKeyedUnarchiver's unarchiveObjectWithFile_(theFile)
        end importAsObjects_
end script

I’m always a bit leary of overwriting init methods because there is no AS equivalent to self = [super init].

Ok Shane, again you’re the first to make it really clear: encodeWithCoder_/initWithCoder in the class, and then call NSKeyed(Un)Archiver in the AppDelegate. This wasn’t at all obvious from my limited viewpoint but it gave me some more insight in how ApplesciptObjC en Cocoa work… Thanks!

BUT now the strangest thing happens!
I try to use this code in the project that goes with the book, the 4.x-version, 5 Ad Table final. I insert the methods where they belong, and add two new buttons to the UI.

Now of course I need to connect these to the relevant handlers in Ad_TableAppDelegate in the Objects table. And I can’t!

Ad Table App Delegate does not seem to accept anything; the name next to the blue cube doesn’t turn blue when I touch it, neither using right-mouse-and-drag, nor opening the button’s HUD-window and drag the action from there. Furthermore, when I look at the Ad_TableAppDelegate’s HUD panel, I see that outlets and actions are not connected.

But nevertheless the project builds and runs…

Same thing happens when I write a different new handler in the delegate, different button… nothing helps. I made sure all files are saved. Tried restarting Xcode and even rebooting my system. In other projects there’s no such problem.

What could be wrong here?

Are you editing the file in Xcode or an external app?

Xcode only, version 4.5.

I’m not sure what the problem is. I did the same thing to test my code before I posted, so it works. Email me your project.

Are you sure you didn’t save it in an old version of AppleScript Editor at some stage?

The problem is that the app delegate file in your project has been saved with CR line endings, instead of LF. Xcode can only deal with LF line endings in .applescript files (although as you see, they compile and run OK).

I’m absolutely sure I didn’t save it in any other version of Xcode or any other editor. All I did was open the project file as it came with the book. I even unzipped the original file again and used a fresh copy.

You must have used an older version, because 4.5 (that I’m using now) came after I downloaded the book. But other projects I tried so far don’t show this behaviour.

In the meantime I started setting up the entire project again. Also because it’s a good exercise to retype and think through every line once more. From what you told me so far, I’m almost sure the problem won’t reproduce but at least I’ll end up with a working project. I’ll let you know as soon as I’m finished!

I just checked, and it was indeed saved that way, but only in the Final version. Now I’m baffled :frowning:

FWIW, this is another good reason to use Open with External Editor on 10.7+.

Your post came in exactly at the moment I finished setting up the project from scratch. And as expected: now I can add the buttons to save and import the data as objects without problems. And everything works!

I learned a lot, not only from having the app crash on my typo’s a lot :wink:

So thank you for being a great and helpful teacher again!

Couldn’t stop thinking about why all this happened. I don’t know about previous versions, but in Xcode 4.5’s Preferences’s Text Editing there’s a setting for Default line endings. Perhaps that got messed up when you save your final version?

(I know it should be Preferences’ without that extra s, but that’s what happens to your English when you speak ASOC all the time :wink:

I run some post-processing scripts to do things like clean up the comments at the top, and I may have done it inadvertently when doing spot checks. Or it could have happened back in the day when AppleScript Editor would do that if it were used as an external editor. I don’t think I can blame Xcode for this one.

:lol: I know the feeling…