patternMinor
Generic singleton array with archiving and insertion functions
Viewed 0 times
genericinsertionarraywitharchivingsingletonandfunctions
Problem
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:
Here's the generic object from which custom objects to be put in a singleton class should be subclassed:
CKGeneralizedItem.h
CKGeneralizedItem.m
```
#import "CKGeneralizedItem.h"
@implementation CKGeneralizedItem
#pragma mark Encoding
for (NSString *keyName in [[self class] objectPropertiesToArchive]) {
[aCoder encodeObject:[self valueForKey:keyName] forKey:keyName];
}
return;
}
{
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 *descriptionString = @"";
for (NS
- This only takes care of object properties, not fundamental types.
- This uses
NSUserDefaultsfor 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
#import "CKGeneralSingletonArray.h"
@interface CKGeneralizedItem : NSObject
+ (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 (NS
Solution
As my comment explains, there are some things about your code I definitely don't understand. This answer will focus on the parts that I know need to be changed.
So, starting from the top...
So, a couple of things. First, we should not prefer to use
Instead, we should prefer typed constants. For example:
But importantly, it seems like there's a decent enough chance that these values are used else where in your app. It also seems like you can retrieve these values programmatically based on what the user actually has set for their app bundle here. And we should probably prefer that.
I can see that these are declared as
You don't need to
You're not instantiating a manager, so your singleton method should be called "sharedManager". This should be named something that gives a clue as to what it is. Importantly, even "shared" isn't always the right word here.
So first, "Manager" should be replaced with "Array". This is a singleton array implementation, so any singleton method should tell us we're returning an array.
Second, the word "shared" is typically reserved for cases where one total instance of this class is the only thing that ever possibly makes sense. For example,
If we only ever want one instance created and we want to always force the user through that instance, we need to take the appropriate steps to enforce that (we're not, currently) and name our singleton method
First, I would expect this method to to match the same sort of signature that Apple gives for regular mutable arrays. And you use it within this method:
Second, we should consider whether we want to always maintain the array in sorted order, or only sort it when the array is retrieved. The real question here is how often are you doing which things to the array. If you're inserting more frequently than you are retrieving the full array, than this is the more expensive approach to returning a sorted array.
Two problems here.
First, like above, I don't see a particularly good reason to deviate from Apple's naming scheme. This could & should simply be
Second, despite the efforts we've made for thread safety i
So, starting from the top...
#define kCKGeneralizedItemsDefaultTheadName "com.yourAppHere.Bundle"
#define kCKGeneralizedItemsDefaultArchivingName @"yourAppKeyedArchiving"
#define kCKGeneralizedItemsDefaultNSUserDefaultsSuite @"com.DefaultsThisApp.AppBundle"So, a couple of things. First, we should not prefer to use
#define. The #define macros should be reserved for use cases where basically there is no alternative option. And to be honest, those cases going to be extraordinarily few and far between.Instead, we should prefer typed constants. For example:
NSString * const kCKGeneralizedItemsDefaultThreadName = "com.yourAppHere.Bundle";But importantly, it seems like there's a decent enough chance that these values are used else where in your app. It also seems like you can retrieve these values programmatically based on what the user actually has set for their app bundle here. And we should probably prefer that.
@property (strong, nonatomic) NSString *archiveString;
@property (strong, nonatomic) NSString *threadingQueueString;
@property (strong, nonatomic) NSString *userDefaultsString;I can see that these are declared as
NSString types. You don't have to put String at the end of each of their names. I don't have recommendations for these three properties now--we'll get to that as we work our way down through the code.@synthesize items, itemsAccessQueue, archiveString, threadingQueueString, arrayObjectClassString, sortWithThisComparator, binarySearchOption;You don't need to
@synthesize properties. You haven't needed to for quite some time now. Properties are autosynthesized with an underscore prefix. And that underscore makes for a very strong indication that this is the backing instance variable for a property. We should tend to prefer that.+ (instancetype) sharedManager {
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}You're not instantiating a manager, so your singleton method should be called "sharedManager". This should be named something that gives a clue as to what it is. Importantly, even "shared" isn't always the right word here.
So first, "Manager" should be replaced with "Array". This is a singleton array implementation, so any singleton method should tell us we're returning an array.
Second, the word "shared" is typically reserved for cases where one total instance of this class is the only thing that ever possibly makes sense. For example,
UIApplication's singleton method is called sharedApplication. It doesn't make sense to have two UIApplication instances floating around within a single app. But NSUserDefaults? It opts for standardUserDefaults as its singleton method name.If we only ever want one instance created and we want to always force the user through that instance, we need to take the appropriate steps to enforce that (we're not, currently) and name our singleton method
sharedArray. If, however, we think it makes sense for there to sometimes be multiple instances running around, we should opt for that and name our singleton method defaultArray or standardArray.- (void) insertItem:(id) 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];
}
}
}First, I would expect this method to to match the same sort of signature that Apple gives for regular mutable arrays. And you use it within this method:
addObject:. Second, we should consider whether we want to always maintain the array in sorted order, or only sort it when the array is retrieved. The real question here is how often are you doing which things to the array. If you're inserting more frequently than you are retrieving the full array, than this is the more expensive approach to returning a sorted array.
- (void) clearAllItems {
[self.items removeAllObjects];
}Two problems here.
First, like above, I don't see a particularly good reason to deviate from Apple's naming scheme. This could & should simply be
removeAllObjects, just like we'd expect to see on an NSMutableArray.Second, despite the efforts we've made for thread safety i
Code Snippets
#define kCKGeneralizedItemsDefaultTheadName "com.yourAppHere.Bundle"
#define kCKGeneralizedItemsDefaultArchivingName @"yourAppKeyedArchiving"
#define kCKGeneralizedItemsDefaultNSUserDefaultsSuite @"com.DefaultsThisApp.AppBundle"NSString * const kCKGeneralizedItemsDefaultThreadName = "com.yourAppHere.Bundle";@property (strong, nonatomic) NSString *archiveString;
@property (strong, nonatomic) NSString *threadingQueueString;
@property (strong, nonatomic) NSString *userDefaultsString;@synthesize items, itemsAccessQueue, archiveString, threadingQueueString, arrayObjectClassString, sortWithThisComparator, binarySearchOption;+ (instancetype) sharedManager {
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}Context
StackExchange Code Review Q#118701, answer score: 7
Revisions (0)
No revisions yet.