0

I have a number of derived classes whose common base-class conforms to NSCoding. I want to be able to easily encode an NSArray holding instances of the various deriving classes.

@interface Base : NSObject <NSCoding>
@end

@interface DerivedSingleton : Base
+(instancetype) sharedInstance;
@end

@interface DerivedNonSingleton : Base
@end

The deriving singleton should have only one instance in the running system. It doesn't actually have any state to encode in the coder. It's instance is created with the +(void) initialize class method.

DerivedSingleton *sharedInstance;

@implementation DerivedSingleton
+(void) initialize
{
  sharedInstance = [DerivedSingleton new];
}

+(instancetype) sharedInstance
{
  return sharedInstance;
}
@end

So, if I now make an array holding instances of the classes, and encode it:

NSArray *const array = @[
  [DerivedSingleton sharedInstance], 
  [DerivedNonSingleton new], 
  [DerivedNonSingleton new]];

NSData *const arrayData = [NSKeyedArchiver archivedDataWithRootObject: array];

When I later decode it, I require references to the shared singleton to decode as references to the shared singleton…

NSArray *const decodedArray = [NSKeyedUnarchiver unarchiveObjectWithData: arrayData];
[decodedArray objectAtIndex: 0] == [DerivedSingleton sharedInstance];

I note that the NSCoding protocol wants me to implement initWithCoder: for the singleton, but during decode, I want this class to provide the shared instance, not a newly alloced object.

Benjohn
  • 13,228
  • 9
  • 65
  • 127
  • Totally unsure why there are votes to close this question? Could you please provide a comment if you're asking for that. Many thanks. – Benjohn Jul 02 '14 at 14:25

2 Answers2

1

Something like this should work

- (instancetype)initWithCoder:(NSCoder *)decoder
{
    self = [super init];

    return [self.class shared];
}
tomahh
  • 13,441
  • 3
  • 49
  • 70
  • :-) I'd just found `replacementObjectForCoder:` and `awakeAfterUsingCoder:`, and had implemented a Placeholder object … your solution seems simpler :-) – Benjohn Jul 02 '14 at 13:41
  • Is it worth throwing in a `self = [super init];` prior to the return, there, or is self.class already set up? – Benjohn Jul 02 '14 at 13:43
  • @Benjohn I'd say add the `self = [super init]`, but I'd bet `self.class` is already set up anyway. – tomahh Jul 02 '14 at 14:01
  • Thanks Tom – I'll be able to let you know if it works in a little while :-) – Benjohn Jul 02 '14 at 14:17
0

I would do something similar with the singleton:

.h

#import <Foundation/Foundation.h>

#pragma mark - Interface

@interface SerializableSingleton : NSObject <NSCoding>

#pragma mark - Class methods

#pragma mark - Shared instance

+ (instancetype)sharedInstance;

@end

.m

#import "SerializableSingleton.h"

#pragma mark - Reference

static id _serializableSingletonSharedInstance = nil;

#pragma mark - Implementation

@implementation SerializableSingleton

#pragma mark - Class methods

#pragma mark - Shared instance

+ (instancetype)sharedInstance {

    @synchronized([self class]) {
        if (_serializableSingletonSharedInstance == nil) {
            _serializableSingletonSharedInstance = [[[self class] alloc] init];
        }
    }

    return _serializableSingletonSharedInstance;
}

#pragma mark - Instance methods

#pragma mark - <NSCoding>

- (id)initWithCoder:(NSCoder *)aDecoder {
    @synchronized([self class]) {
        if (_serializableSingletonSharedInstance == nil) {
            if (self = [super init]) {
                // decode the desired data...
            }
            _serializableSingletonSharedInstance = self;
        }
    }
    return _serializableSingletonSharedInstance;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    // encode the desired data
}

@end
holex
  • 23,961
  • 7
  • 62
  • 76
  • Hi @holex, thanks, but I don't think that answers the question? The issue is that the shared instance will be in an array with other objects that are not singletons, but derive from the same base class. The whole array needs to support `NSCoding`. It's not the shared instance that I specifically want to store. When I decode an array store like this, any shared singleton references must decode as references to the shared singleton instance. – Benjohn Jul 02 '14 at 14:17
  • I've added rather a lot to the questions – sorry if it was not clear before. – Benjohn Jul 02 '14 at 14:41
  • @Benjohn, oh I see. that is true, you need to update the singleton references after unarchiving the items of array in that situation, but you can put such a code into the `-initWithCoder:` method before your return the `self`. – holex Jul 02 '14 at 14:43
  • @Benjohn, no problems, I've updated my answer slightly. – holex Jul 02 '14 at 14:50