Professional Web Applications Themes

[Cocoa] NSKeyedUnarchiver and "root" object - Mac Programming

Hi, I tried to subclass NSKeyedUnarchiver because I want to add some instance variables, e.g. a "doent version". There is no chance to extend the class by overriding "unarchiveObjectWithData" because this method is a class method (there are no instance variables at this point). So I tried it with "initForReadingWithData". I wrote a subclass of NSKeyedUnarchiver, e.g. named MyUnarchiver, and added a method e.g. "initForReadingWithData:myExtensions:..." where I can add some parameters (e.g. doent version) that are then stored as instance variables in my MyArchiver object. Then I replaced my previous decoding code: myObject = [[NSKeyedUnarchiver unarchiveObjectWithData:data] retain]; with this new ...

  1. #1

    Default [Cocoa] NSKeyedUnarchiver and "root" object

    Hi,

    I tried to subclass NSKeyedUnarchiver because I want to add some instance
    variables, e.g. a "doent version".
    There is no chance to extend the class by overriding
    "unarchiveObjectWithData" because this method is a class method (there are
    no instance variables at this point). So I tried it with
    "initForReadingWithData". I wrote a subclass of NSKeyedUnarchiver, e.g.
    named MyUnarchiver, and added a method e.g.
    "initForReadingWithData:myExtensions:..." where I can add some parameters
    (e.g. doent version) that are then stored as instance variables in my
    MyArchiver object.

    Then I replaced my previous decoding code:

    myObject = [[NSKeyedUnarchiver unarchiveObjectWithData:data] retain];

    with this new version:

    MyUnarchiver *unarchiver = [[MyUnarchiver alloc]
    initForReadingWithData:data myExtensions:...];
    myObject = [[unarchiver decodeObject] retain];
    [unarchiver release];

    I did this a while ago with a normal NSUnarchiver (not keyed) and there this
    works. But not this time with the keyed archiver. Every time I got back a
    <null> object. After debugging a while, I found out that the NSKeyedArchiver
    (that generates the archive) encodes the root object as keyed object with a
    key named "root"! So I have to write this:

    MyUnarchiver *unarchiver = [[MyUnarchiver alloc]
    initForReadingWithData:data myExtensions:...];
    myObject = [[unarchiver decodeObjectForKey:"root"] retain];
    [unarchiver release];

    This works like a charm. :-)
    BUT: Is it somewhere doented, that the root object of keyed archiver data
    has a key named "root"?!? Can I be sure Apple does not change it sometime? I
    searched the doentation and could not find an answer to this. I had to
    write a subclass of NSKeyedArchiver and override "encodeObject:ForKey:" and
    print out the key names to find out that the root key is named "root" ...
    not a very obvious way. ;-) So now I want to get sure that this is specified
    somewhere to be also true for future Cocoa versions.

    Or is there some better way to add some parameters (e.g. doent version)
    to NSKeyedUnarchiver?
    The objects that implement the NSCoding protocol only gets references to the
    NSCoder subclass (normally the used NSKeyedUnarchiver or own subclass of
    it), so there is no way to pass some "user data", but in my solution they
    simply can ask the NSCoder for the parameters. Is this bad design? What's a
    better way?

    Thanks,
    Mani

    Manfred Guest

  2. #2

    Default Re: [Cocoa] NSKeyedUnarchiver and "root" object

    On Wed, 31 Dec 2003, Manfred Lippert wrote:
     

    Why don't you just do something that you can be sure will work? Instead of
    archiving a root object, archive the 'root' object with a key that is
    known to you, and decode it using that same key. Do what you actually mean
    to do.
     

    It is rare to need to save the doent version, from what I understand.
    With keyed archiving, it's normally easy to make things compatible. You
    can check for the existance of keys while decoding, and modify your code
    appropriately without relying on exlicit version checks. If you really
    need to know a version, you shouldn't need a doent version, but a class
    version. NSCoder has provisions for storing and fetching your class's
    versions. Each class should be able to correctly decode itself based on
    that without referring to a global version number.

    Barring that, you should be able to accomplish what you need by having
    your main controller object archive the doent's version in separate
    keys alongside the main object. Have it decode these numbers first, then
    make the version available to classes as they decode themselves. But this
    really shouldn't be necessary.
    Mike Guest

  3. #3

    Default Re: [Cocoa] NSKeyedUnarchiver and "root" object

    > Why don't you just do something that you can be sure will work? Instead of 

    The problem is that this code is for backwards compatibility of already
    existing archives. The object was encoded with [NSKeyedArchiver
    archivedDataWithRootObject:object].
    Now I want to decode it with a subclass of NSKeyedUnarchiver.
     

    Yes, that's true. But not in my case. ;-) The problem was the _poor_
    performance of keyed archives, so I switched to the old serial archives. The
    speed increase was huge: encoding of my archive (with around 2000 objects)
    took 9 seconds before (with keyed archiving) and now it takes 0,3 seconds.
    This is a factor of 30. And maybe this factor gets even bigger with more
    objects. I think it has to do with some necessary graph checkings when using
    a keyed archiver.

    By the way, the root object is an NSMutableDictionary.

    The main problem is that I have to be able to read already existing (keyed)
    archives. So I wrote _two_ subclasses: One subclass of NSKeyedUnarchiver and
    one subclass of NSUnarchiver. I already know the doent version if my
    archive and I put this info into the appropriate unarchiver. So the objects
    that decode themself could just ask the NSCoder for the doent version and
    to the right thing.

    So my code looks something like this:

    if (version >= VERSION_WITH_SERIAL_CODING) {
    MyUnarchiver *unarchiver =
    [[MyUnarchiver alloc]
    initForReadingWithData:data
    version:version];
    object = [[unarchiver decodeObject] retain];
    [unarchiver release];
    } else { // backwards compatibility:
    MyKeyedUnarchiver *unarchiver =
    [[MyKeyedUnarchiver alloc]
    initForReadingWithData:data
    version:version];
    object = [[unarchiver decodeObjectForKey:"root"] retain];
    [unarchiver release];
    }

    The objects decode themselfes this way:

    - (SomeClass *)initWithCoder:(MyUnarchiver *)decoder {
    if ((self = [super init]) != nil) {
    if ([decoder version] >= VERSION_WITH_SERIAL_CODING) {
    // decode serialized (new) ...
    } else {
    // decode keyed (backwards compatibility) ...
    }
    }
    return self;
    }

    So my code is backwards compatible to existing keyed archives, and they are
    converted to (much faster) serialized archives smoothly without loosing
    existing data.
    The only thing I don't like on my code is this undoented "root" key
    thing. Hopefully this will not change in Cocoa before every customer
    switched his data to the new format. ;-)

    For the curious: it's the cache data of iVolume.

    Thanks,
    Mani
    --
    iVolume, LittleSecrets, SharingMenu:
    <http://www.mani.de/>

    Manfred Guest

  4. #4

    Default Re: [Cocoa] NSKeyedUnarchiver and "root" object

    In article <BC19D208.20514%de>,
    Manfred Lippert <de> wrote:
     
    >
    > Yes, that's true. But not in my case. ;-) The problem was the _poor_
    > performance of keyed archives, so I switched to the old serial archives. The
    > speed increase was huge: encoding of my archive (with around 2000 objects)
    > took 9 seconds before (with keyed archiving) and now it takes 0,3 seconds.
    > This is a factor of 30. And maybe this factor gets even bigger with more
    > objects. I think it has to do with some necessary graph checkings when using
    > a keyed archiver.
    >
    > By the way, the root object is an NSMutableDictionary.[/ref]

    Ah, I understand. I've run into this problem as well. It's not an
    inherent problem to the idea of keyed archiving, just to the particular
    implementation of NSKeyedArchiver. In particular, NSKeyedArchiver is
    realyl, really slow at archiving arrays. It tries to coalesce identical
    arrays in the archive, which means that it has to check every array you
    encode against every other array you encode.

    My solution to this problem was to write a custom keyed
    archiver/unarchiver pair using a completely different format that
    implemented the same methods as NSKeyedArchiver/Unarchiver. I saw
    similar performance improvements; about 15 times the speed, and about
    1/7th the file size in the result. This way I get all of the niceness of
    keyed archiving without the massive speed hit. My code is slightly less
    robust than Apple's, and it fails to catch certain error conditions, but
    those could both be fixed if necessary. If you or anyone else is
    interested, I'd consider making the code available.
     
    [snip] 

    Actually, you should do if(![decoder allowsKeyedCoding]) here. That way
    you don't need to do any ugly hacks to store the version, and you get
    the same effect. Your code shouldn't care about archive versions, it
    should only care about whether it can use keyed coding or not.
    Michael Guest

  5. #5

    Default Re: [Cocoa] NSKeyedUnarchiver and "root" object

    Michael Ash <com> wrote:
     

    That would be great. Also, don't forget to mention what error conditions
    it fails to catch.

    Per
    Per Guest

  6. #6

    Default Re: [Cocoa] NSKeyedUnarchiver and "root" object

    In article <BC19D208.20514%de>,
    Manfred Lippert <de> wrote:
     

    Nothing you say indicates you should be subclassing. It sounds like
    you're trying to version archives (improperly, too) instead of trying to
    version objects in archives.
     

    Now I'm confused. Does the old version serialize objects with an
    NSArchiver or an NSKeyedArchiver? If you're simply moving from a
    standard archive format to a keyed format, use -allowsKeyedCoding. If
    both "versions" have been done with a keyed archive, use a
    -containsValueForKey: call to determine what it does and doesn't
    contain. Again, no subclassing necessary.
     

    It sound like your biggest mistake is using a file archive
    (reading/updating the whole thing for even minor changes) when you
    should probably be using a database. This is where I put in a word for
    STEnterprise:

    http://www.subsume.com/cgi-bin/go.pl?k=STEnterprise

    I would think you could run multiple threads for the ysis of files
    and archive the results as you go instead of doing things serially.
    Doc Guest

  7. #7

    Default Re: [Cocoa] NSKeyedUnarchiver and "root" object

    In article <1g6xken.w6zmhz4krfeoN%com>,
    com (Per Bull Holmen) wrote:
     
    >
    > That would be great. Also, don't forget to mention what error conditions
    > it fails to catch.[/ref]

    As requested, you can download MAKeyedArchiver at this address:

    http://www.mikeash.com/software/MAKeyedArchiver/

    The distribution includes MAKeyedArchiver, Unarchiver, and a few support
    classes. You need to provide one error-reporting function and link
    against libz and libcrypto. At that point, it's as simple as using
    MAKeyedArchiver and Unarchiver instead of the NS versions.

    The errors it fails to catch are due to its lack of type information. If
    you store an int and then try to read it back as a float, or an object,
    the unknown will happen. NSCoder is kind enough to throw exceptions in
    these cases. If you aren't relying on those exceptions being thrown (and
    as far as I know they aren't doented, so you shouldn't) then
    everything should work fine. MAKeyedArchiver properly handles circular
    references and conditional encoding. There's some ugly code for
    compatibility with Jaguar, where Apple used some private methods on
    NSCoder to encode certain Cocoa objects. If you don't need Jaguar
    compatibility you can get rid of that, but otherwise the code should
    work as-is on both platforms. Details are in the readme.

    It's also a bit loose on the error handling. It uses an MD5 sum to make
    sure the file wasn't corrupted, but it's probably possible to construct
    a file with a good MD5 sum that would cause the unarchiver to crash.

    I hope you find it useful, and please let me know if you have any
    questions or comments.
    Michael Guest

  8. #8

    Default Re: [Cocoa] NSKeyedUnarchiver and "root" object

    Michael Ash <com> wrote:
     
    > >
    > > That would be great. Also, don't forget to mention what error conditions
    > > it fails to catch.[/ref]
    >
    > As requested, you can download MAKeyedArchiver at this address:
    >
    > http://www.mikeash.com/software/MAKeyedArchiver/[/ref]

    Thank you very much. I will probably need it later...

    :)

    Per
    Per Guest

Similar Threads

  1. Can't locate object method "newFromJpeg" via package "GD::Image"
    By francescomoi@usa.com in forum PERL Modules
    Replies: 3
    Last Post: December 20th, 11:39 AM
  2. Can't locate object method "blocking" via package "IO::Handle"
    By kemton@kemton.com in forum PERL Modules
    Replies: 1
    Last Post: June 20th, 02:54 PM
  3. Replies: 2
    Last Post: April 15th, 01:41 PM
  4. Can't not locate object method "isadmin" via package "Noc1"
    By Perldiscuss - Perl Newsgroups And Mailing Lists in forum PERL Beginners
    Replies: 1
    Last Post: November 13th, 03:34 PM

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139