Best design pattern for un-archiving instances of shared resources?

  • Hi all,

    Please forgive the cryptic subject. I'm in search of design advice and
    obviously don't even know how to succinctly describe the problem I'm
    facing. I know it's generally bad form to ask a question without code,
    but since this is a design issue not an implementation issue, I
    thought I'd start with description first (I'll be happy to send code
    if needed). Let me explain what I'm trying to do:

    I'm writing a data acquisition program for a physiology lab. One of
    the classes in the application is a singleton instance of a Controller
    for the A-D converter used to digitize signals from an analog
    amplifier. The A-D converter has several channels, each of which are
    represented (in my program) by an instance of a Channel object.
    Because the A-D hardware can be reconfigured, the controller class
    queries the hardware at program startup and indentifies the channels
    that are available given the hardware configuration. The instance
    diagram (after initialization) then looks like

    Controller <-->*Channel

    with the the available channels accessible as an NSSet* property of
    the controller. Each channel is identified by a channel number (a
    property of Channel) that corresponds to the hardware channel number
    on the A-D converter.

    Each channel object also has several user-configurable properties
    (e.g. desired sampling rate, a user-visible description, etc.). The
    Controller instance stores these properties by archiving (using
    NSKeyedArchiver) the set of channels. The set of Channels can be
    unarchived later to restore the user-set properties.

    So, now the design problem(s) I've run into:

    The Controller creates instances of Channels at startup but then wants
    to replace these instances with the unarchived instances that contain
    the users' settings. I can't skip the creation of the Channel
    instances at startup because there may be channels that are now
    available that weren't before (because of hardware configuration
    changes in the A-D converter) and to simply replace the Controller's
    Channels with the unarchived set would overwrite these new channels
    and/or add Channel instances that correspond to channels that are no
    longer physically present.

    Finally, other objects in the program (such as the objects that
    coordinate stimulus presentation and data recording) archive
    references to the Channel instances when they save their settings.
    When unarchived, obviously these objects want references to the
    Channel instances being managed by the Controller, not the orphaned
    instance that gets unarchived.

    I see two possible solutions:
    1. store the user-set properties in a separate object and archive that
    rather than the Channel objects directly. This solution seems to
    violate the "do it once" principle since this archived object would
    have to keep information to identify the Channel instance it goes
    with, duplicating a lot of information with the Channel object.

    2. The solution I've chosen is to override Channel's initWithCoder: to
    return the Channel instance that corresponds to the same channel
    number, if it exists. This way, unarchived instances of Channel
    automatically turn into the correct Channel instance (the one being
    managed by the Controller) if possible. initWithCoder then decodes the
    user-configurable properties of the Channel and sets the corresponding
    properties in the Channel instance. In this way, user-configured
    properties get set on the appropriate Channel instances when the
    Controller unarchives its settings.

    The problem I see with solution #2 is that multiple Channel instances
    may be unarchived during the lifetime of the program (e.g. by the
    stimulus coordinating objects described above) and I don't want these
    newly unarchived instances to overwrite the user-configured
    properties. Therefore, I've added a flag to Channel that indicates
    whether the user-configurable properties have been initialized yet or
    not. The pseudo-code for initWithCoder then looks something like this

    - (id)initWithCoder:(NSKeyedUnarchiver*)decoder {

        if(self = [super init]) { //super-class doesn't implement initWithCoder
            int channelNumber = [decoder decodeIntForKey:@"channelNumber"];
            id existingChannel;
            if(existingChannel = [[Controller sharedInstance]
    channelWithChannelNumber:channelNumber]) { //channel already exists
                [self release];
                self = existingChannel;
            }

            if(![self isInitialized]) {
                ...decode and set user-configured properties
                [self setInitialized:YES];
            }
        }

        return self;
    }

    with an init method that looks like

    - (id)init {
        if(self = [super init]) {
            ...set default values for user-configured properties
            [self setInitialized:NO];
        }
    }

    This works, but what to do in subclasses of Channel? We now need to
    subclass Channel to provide specialized processing for different
    channel types (and to add extra user-configurable properties
    corresponding to these channel types). We could use a similar solution
    in the subclass, with a subclass-specific isSubclassInitialized flag
    which gets set to YES after unarchiving the additional user-confurable
    properties. This solution has an odd smell to me. It seems to require
    subclasses to know about and deal with a very implemenation-specific
    detail of the superclass.

    So, question for the gurus:
    Is there a better pattern for solving the problems I've outlined
    above, particularly one that doesn't require a similar hack for every
    subclass?

    If you've made it this far, Thank You! I look forward to any
    suggestions you have,

    Barry
  • Am 19.10.2007 um 02:06 schrieb Barry Wark:
    > So, question for the gurus:
    > Is there a better pattern for solving the problems I've outlined
    > above, particularly one that doesn't require a similar hack for every
    > subclass?

      Did you check whether NSUnarchiver and NSArchiver have any useful
    methods? The NSArchiverCallback informal protocol for NSObject sounds
    like it could solve your problem or alleviate it a bit. It seems to
    allow providing a different object to archive in place of the current
    object.

      NSUnarchiver also has -replaceObject:withObject:, which looks like
    it's the opposite.

      I hope that hasn't been mentioned yet, I haven't yet managed to go
    up-thread on this one.

    Cheers,
    -- M. Uli Kusterer
    http://www.zathras.de
  • By going with the strategy described by David, I was able to make the
    change stick, even if the decoding was done with an unarchiver that
    didn't have the delegate set. It seems that there are two approaches
    possible here... the more global approach that David described and the
    local approach that you describe. Since this was a refactor of some
    existing code, I went with the global option.

    One last question for the gurus: it appears that awakeAfterUsingCoder
    should follow the memory management rules of an init method, namely
    returning a retained object (as opposed to an autoreleased object as
    you might expect from the standard idiom of methods that do not
    allocate the object). Can someone confirm this?

    Thanks for all the help!

    Barry

    On 10/20/07, Uli Kusterer <witness.of.teachtext...> wrote:
    > Am 19.10.2007 um 02:06 schrieb Barry Wark:
    >> So, question for the gurus:
    >> Is there a better pattern for solving the problems I've outlined
    >> above, particularly one that doesn't require a similar hack for every
    >> subclass?
    >
    > Did you check whether NSUnarchiver and NSArchiver have any useful
    > methods? The NSArchiverCallback informal protocol for NSObject sounds
    > like it could solve your problem or alleviate it a bit. It seems to
    > allow providing a different object to archive in place of the current
    > object.
    >
    > NSUnarchiver also has -replaceObject:withObject:, which looks like
    > it's the opposite.
    >
    > I hope that hasn't been mentioned yet, I haven't yet managed to go
    > up-thread on this one.
    >
    > Cheers,
    > -- M. Uli Kusterer
    > http://www.zathras.de
    >
    >
    >
    >
  • On 21-Oct-07, at 1:25 AM, Barry Wark wrote:
    >
    > One last question for the gurus: it appears that awakeAfterUsingCoder
    > should follow the memory management rules of an init method, namely
    > returning a retained object (as opposed to an autoreleased object as
    > you might expect from the standard idiom of methods that do not
    > allocate the object). Can someone confirm this?
    >

    The way I understand it is -awakeAfterUsingCoder: is called implicitly
    after -initWithCoder:, so self already has a retain count of one; if
    you return a different object, it must also have a retain count of
    one.  Of course if you simply return self, as the default
    implementation does, then you needn't perform an extra retain...

    dave
  • That's my understanding as well (after several weird cases of
    substituted objects being deallocated unexpectedly). If
    awakeAfterUsingCoder: releases self and returns an existing object, it
    must increment the replacement object's retain count by one before
    returning it.

    Barry

    On 10/21/07, David Spooner <dave...> wrote:
    >
    > On 21-Oct-07, at 1:25 AM, Barry Wark wrote:
    >>
    >> One last question for the gurus: it appears that awakeAfterUsingCoder
    >> should follow the memory management rules of an init method, namely
    >> returning a retained object (as opposed to an autoreleased object as
    >> you might expect from the standard idiom of methods that do not
    >> allocate the object). Can someone confirm this?
    >>
    >
    > The way I understand it is -awakeAfterUsingCoder: is called implicitly
    > after -initWithCoder:, so self already has a retain count of one; if
    > you return a different object, it must also have a retain count of
    > one.  Of course if you simply return self, as the default
    > implementation does, then you needn't perform an extra retain...
    >
    > dave
    >
    >
previous month october 2007 next month
MTWTFSS
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        
Go to today