I find that I often have to maintain a singleton primarily to hold an array that is sometimes persisted between user sessions, that should have only one class of items, and that sometimes needs to be ordered. I got tired of writing the same boilerplate over and over so came up with this. I'd welcome critiques (particularly re: thread safety) and suggestions for how to expand this to cover more situations. Some notes:This only takes care of object properties, not fundamental types.This uses NSUserDefaults for inter-session persistence. This is meant to remove boilerplate and make smart default assumptions for users.Here's the generic object from which custom objects to be put in a singleton class should be subclassed:CKGeneralizedItem.h#import <Foundation/Foundation.h>#import CKGeneralSingletonArray.h@interface CKGeneralizedItem : NSObject <CKArchivingItem, NSCoding>+ (NSArray *)objectPropertiesToArchive;+ (NSArray *)boolPropertiesToArchive;+ (NSArray *)floatPropertiesToArchive;+ (NSArray *)intPropertiesToArchive;@endCKGeneralizedItem.m#import CKGeneralizedItem.h@implementation CKGeneralizedItem#pragma mark Encoding- (void) encodeWithCoder:(NSCoder *)aCoder {    for (NSString *keyName in [[self class] objectPropertiesToArchive]) {        [aCoder encodeObject:[self valueForKey:keyName] forKey:keyName];    }    return;}- (id)initWithCoder:(NSCoder *)coder;{    self = [super init];    if (self != nil)    {        for (NSString *keyName in [[self class] objectPropertiesToArchive]){            [self setValue:[coder decodeObjectForKey:keyName] forKey:keyName];        }    }    return self;}#pragma mark Archival properties+ (NSArray *)objectPropertiesToArchive {    return @[];}+ (NSArray *)boolPropertiesToArchive {    return @[];}+ (NSArray *)floatPropertiesToArchive {    return @[];}+ (NSArray *)intPropertiesToArchive {    return @[];}#pragma mark Inspection- (NSString *) description {    NSString *descriptionString = @;    for (NSString *keyString in [[self class] objectPropertiesToArchive]) {        descriptionString = [descriptionString stringByAppendingString:[NSString stringWithFormat:@%@: %@\n, keyString, [self valueForKey:keyString]]];    }    return descriptionString;}@endHere's the singleton array:CKGeneralSingletonArray.h#import <Foundation/Foundation.h>@protocol CKArchivingItem <NSObject>#pragma mark State persistence- (void) retrieveItemsFromArchive;- (void) archiveItems;@end@interface CKGeneralSingletonArray : NSObject@property (strong, atomic) NSString *arrayObjectClassString;@property (strong, atomic) NSComparator sortWithThisComparator;@property NSBinarySearchingOptions binarySearchOption;+ (instancetype) sharedManager;// array membership- (void) insertItem:(id<NSCoding, CKArchivingItem>) item;- (void) clearAllItems;- (void) printItems;// saving/uploading data- (void) uploadItems;// archiving items- (void) archiveItems;- (void) retrieveItemsFromArchive;- (void) setArchiveName: (NSString *)archiveName;@endCKGeneralSingletonArray.m#import CKGeneralSingletonArray.h#import CKGeneralizedItem.h#define kCKGeneralizedItemsDefaultTheadName com.yourAppHere.Bundle#define kCKGeneralizedItemsDefaultArchivingName @yourAppKeyedArchiving#define kCKGeneralizedItemsDefaultNSUserDefaultsSuite @com.DefaultsThisApp.AppBundle@interface CKGeneralSingletonArray ()@property (strong, nonatomic) NSMutableArray <id<NSCoding, CKArchivingItem>> *items;@property (strong, nonatomic) NSString *archiveString;@property (strong, nonatomic) NSString *threadingQueueString;@property (strong, nonatomic) NSString *userDefaultsString;@property dispatch_queue_t itemsAccessQueue;@end@implementation CKGeneralSingletonArray@synthesize items, itemsAccessQueue, archiveString, threadingQueueString, arrayObjectClassString, sortWithThisComparator, binarySearchOption;#pragma mark Instantiation+ (instancetype) sharedManager {    static dispatch_once_t once;    static id sharedInstance;    dispatch_once(&once, ^{        sharedInstance = [[self alloc] init];    });    return sharedInstance;}- (id) init {    self = [super init];    if (self) {        self.items = [NSMutableArray new];        self.binarySearchOption = NSBinarySearchingFirstEqual;    }    return self;}#pragma mark Public-facing array manipulation- (void) insertItem:(id<NSCoding, CKArchivingItem>) item {    if (arrayObjectClassString && ![item isKindOfClass:NSClassFromString(arrayObjectClassString)]) {        [NSException raise:@YOU MADE A MISTAKE format:@you tried to insert a %@ but I can only accept %@, [item class], arrayObjectClassString];    } else {        if (!sortWithThisComparator) {            [self.items addObject:item];        } else {            NSInteger newItemIndex = [self.items indexOfObject:item inSortedRange:NSMakeRange(0, [self.items count]) options:NSBinarySearchingFirstEqual usingComparator:sortWithThisComparator];            [self.items insertObject:item atIndex:newItemIndex];        }       }}- (void) clearAllItems {    [self.items removeAllObjects];}- (void) printItems {    NSLog(@here are printed items %@, self.items);}#pragma mark Thread-safe multiple-point thread-safe access- (void) setItems:(NSMutableArray<id<NSCoding,CKArchivingItem>> *)itemsVal {    if (!threadingQueueString) {        itemsAccessQueue = dispatch_queue_create(kCKGeneralizedItemsDefaultTheadName, DISPATCH_QUEUE_CONCURRENT);    }    dispatch_barrier_async(self.itemsAccessQueue, ^{        items = itemsVal;    });}- (NSMutableArray <id<NSCoding, CKArchivingItem>> *) getItems {    if (!threadingQueueString) {        itemsAccessQueue = dispatch_queue_create(kCKGeneralizedItemsDefaultTheadName, DISPATCH_QUEUE_CONCURRENT);    }    __block NSMutableArray <id<NSCoding, CKArchivingItem>> *copyItems;    dispatch_sync(self.itemsAccessQueue, ^{        copyItems = [items mutableCopy];    });    return copyItems;}- (void) setThreadingQueueString:(NSString *)threadingQueueStringVal {    static dispatch_once_t once;    dispatch_once(&once, ^{        threadingQueueString = threadingQueueStringVal;    });}#pragma mark Between-session persistence in user defaults// this can also be modified to store the NSData in a file- (void) retrieveItemsFromArchive {    if (!archiveString) {        [self setArchiveName:kCKGeneralizedItemsDefaultArchivingName];    }    NSString *defaultsString = self.userDefaultsString;    if (!defaultsString) defaultsString = kCKGeneralizedItemsDefaultNSUserDefaultsSuite;    NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:defaultsString];    NSArray *retrievedItems = [NSKeyedUnarchiver unarchiveObjectWithData: [defaults objectForKey:archiveString]];    if (retrievedItems) {        self.items = [retrievedItems mutableCopy];    }}- (void) archiveItems {    if (!archiveString) {        [self setArchiveName:kCKGeneralizedItemsDefaultArchivingName];    }    NSString *defaultsString = self.userDefaultsString;    if (!defaultsString) defaultsString = kCKGeneralizedItemsDefaultNSUserDefaultsSuite;    NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:defaultsString];    [defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:self.items] forKey:archiveString];    [defaults synchronize];    NSLog(@here is what got archived %@, [NSKeyedUnarchiver unarchiveObjectWithData: [defaults objectForKey:archiveString]]);}- (void) setArchiveName: (NSString *)archiveStringVal {    static dispatch_once_t once;    dispatch_once(&once, ^{        archiveString = archiveStringVal;    });}#pragma mark Uploading to remote API - (void) uploadItems {    // this should be entirely defined by the sub-classer    [NSException raise:@uploadItems has not been properly defined format:@This class method must be overridden.];}@endHere's a sample of what you could do:[[CKGeneralSingletonArray sharedManager] retrieveItemsFromArchive];[[CKGenericSingletonSubclass sharedManager] printItems];[[CKGeneralSingletonArray sharedManager] setArrayObjectClassString:@NSString];CKGeneralizedItemSubclass *subclass = [[CKGeneralizedItemSubclass alloc] init];subclass.firstName = @Doofus;subclass.lastName = @Potter;subclass.age = @(11);CKGeneralizedItemSubclass *subclass2 = [[CKGeneralizedItemSubclass alloc] init];subclass2.firstName = @Draco;subclass2.lastName = @Malfoy;subclass.birthday = [[NSDate date] dateByAddingTimeInterval:-1*24*60*60*50];subclass2.age = @(11);[[CKGenericSingletonSubclass sharedManager] insertItem:subclass]; // this will throw an exception because the array singleton has been set to only accept NSStrings[[CKGenericSingletonSubclass sharedManager] insertItem:subclass2];[[CKGenericSingletonSubclass sharedManager] printItems];
Generic singleton array with archiving and insertion functions
objective c;singleton