2

I have successfully created a NSDictionary subclass, a singleton which is intended to hold a set of clues and answers. I was seeing some odd behaviors which I traced to the fact that subclassing NSDictionary actually leads to NSCFDictionary instead, so the NSDictionary methods I was planning to use don't work unless I write them. I found some useful stuff in two other SO questions: here and here. I see that there might be some workarounds but they too have problems.

I was hard-coding some sample clues and answers for testing purposes, with the intent to move to some kind of Core Data approach later. I'm pretty sure that continuing with my present approach will be a lot of work for testing purposes. These are the options I'm considering; any recommendations?

  1. Drop the temporary dictionary singleton approach and replace it with a couple of arrays that more or less mimic a dictionary for testing purposes?
  2. Switch to NSMutableDictionary even though I don't intend to mutate? The mutable version looks like it might be easier to subclass, but I'm not sure.
  3. Go to some kind of global constant arrays and skip the singleton approach for testing purposes (I hope these are the right terms).
  4. Write/over ride the needed methods?
  5. Bite the bullet and figure out the Core Data part of the app?
  6. Something else?
Community
  • 1
  • 1
Bryan Hanson
  • 6,055
  • 4
  • 41
  • 78
  • 1
    What are you really trying to accomplish? What's the purpose of this singleton? And for the love of all things Apple, why are you trying to subclass NSDictionary? – Caleb Mar 15 '12 at 02:27
  • Purpose is in the first sentence. Dictionary was and does hold an array of clues and an array of answers. And why? Well, I'm learning as I go and frankly I still don't see any real clues that you shouldn't do this: the recommendationx not to do it are in help files. – Bryan Hanson Mar 15 '12 at 02:32
  • Yes, I got that, but why do you think that NSDictionary by itself isn't sufficient for that task? As for advice on subclassing, see the "Subclassing Notes" section in the [NSDictionary reference page](https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/nsdictionary_Class/Reference/Reference.html). – Caleb Mar 15 '12 at 02:37
  • 1
    Ouch... down voted for asking a well-considered question after having done my homework. – Bryan Hanson Mar 15 '12 at 02:38
  • Well, it needs to be globally accessible. And while I know that some folks really don't like singletons, others do and I understand how to make them work. So to make a singleton, I need to subclass something, and the dictionary of arrays seemed like the objective-c way to do things. As I said, I'm learning as I go which is not always an efficient process. I have been to that page on subclassing, that's where I learned I have to override if I continue with this approach. – Bryan Hanson Mar 15 '12 at 02:40
  • 1
    Agreed that there's no (good) reason to a) subclass `NSDictionary` or b) make it a singleton. Composition is the way to go here. Even a global pointer to your dictionary would be better. Also, subclassing `NSMutableDictionary` isn't any easier than `NSDictionary`. In fact, I think there are _more_ required methods. – jscs Mar 15 '12 at 05:55
  • Alright, first, thanks to all of you who answered. I got some good ideas that got me to thinking more clearly. I think I see the problem, but can't experiment until later. I was originally trying to use my singleton to create my 'NSDictionary' as the singleton was created. So my singleton had class 'NSDictionary'. But later, I made a separate instance method that created the dictionary, and didn't change the singleton class to 'NSObject'. I think the proper approach is the singleton should be 'NSObject' and the instance method returns something of class 'NSDictionary' and voila. – Bryan Hanson Mar 15 '12 at 12:14

2 Answers2

4

To avoid the headaches of subclassing NSDictionary I would go with a separate singleton object that uses composition to own an NSDictionary. Whether that will work or not depends on what else you hope to do with this singleton, though.

See What should my Objective-C singleton look like? for a discussion on various Objective-C singleton options.

Community
  • 1
  • 1
devguydavid
  • 3,917
  • 1
  • 19
  • 18
  • I'd definitely go with this. Subclassing NSDictionary is completely unnecessary, in every case I've run into – Daniel Mar 15 '12 at 03:03
  • I've definitely been all over that "What does your singleton look like?" entry! I've found a little about compositions here: https://developer.apple.com/library/mac/#documentation/General/Conceptual/CocoaEncyclopedia/ClassClusters/ClassClusters.html#//apple_ref/doc/uid/TP40010810-CH4-SW79 Do you have another favored resource? Thanks. – Bryan Hanson Mar 15 '12 at 03:24
  • Really all composition means in this case is that you create an object (the singleton) that has its own ivar for the NSDictionary that has your real data. You then provide whatever methods are necessary for accessing the data, or even provide access to the dictionary itself, depending on what your use cases are. – devguydavid Mar 15 '12 at 04:35
  • Thanks, I think I see my problem. See comment to original question. – Bryan Hanson Mar 15 '12 at 12:10
3

It's unusual to need to subclass any of the collection classes that the Foundation framework provides (NSArray, NSDictionary, NSSet, NSMapTable, etc. and their mutable variants). In fact, it's so unusual that if you think you need to do it, your first move should be to carefully think about why you need to do it, and then sleep on it. (If you want to see someone take a shot at it, check out Mike Ash's recent article on reimplementing NSMutableArray.) The reason that it's so unusual to subclass a collection class is that the collection classes already implement the behaviors required by the standard data structures that they represent: NSArray gives you an indexed list of objects; NSDictionary gives you an associative array; and so on.

From your comments, it sounds like you might be well served by some combination of containers. It's difficult to tell exactly what you want from your description, so I'll have to make some assumptions (which I'll try to state) and hope that even if they're wrong, you may still be pointed in the right direction. So, let's say that you've got a list of questions, and for each question you have an answer and some number of clues. In that case, you might choose to use a dictionary to represent each question. The dictionary could have three keys: 'question', 'answer', and 'clues'. The values associated with the first two keys could just be strings, and the value for 'clues' might be an array or set of strings. And since you've got some number of questions, you'd store each of those dictionaries in a common array. In JSON format, it'd look like this:

[
    {
        "question" : "What's the capitol of New York?",
        "answer" : "Albany",
        "clues" : 
            [
                "It's not New York City.",
                "It's near the Hudson river.",
                "Its name begins with 'A'."
            ]
    },
    {
        "question" : "What's the state sport of Maryland?",
        "answer" : "jousting",
        "clues" : 
            [
                "If you try it, you'll need a horse and some armor.",
                "Kids, don't try this at home."
            ]
    }
]

You could build this up programmatically if you wanted to, but since it's all just static data you'd do better to use the Property List Editor to create the data. Then you can just read it into an array using +arrayWithContentsOfFile:.

Now, about that singleton... It sounds like you really just want an object that'll hold your data and which is easy to access. So, create a class that can contain the array described above:

@interface QAModel : NSObject
@property (strong) NSArray *questions;

+ (QAModel*)sharedModel

@end

@implementation QAModel
@synthesize questions = _questions;

+ (QAModel*)sharedModel
{
    if (self.sharedModel == nil) {
        sharedModel = [[QAModel alloc] init];
    }
    return sharedModel;
}

- (id)init
{
    if ((self = [super init])) {
        _questions = [NSArray arrayWithContentsOfFile:pathToMyDataFile];
    }
    return self;
}
@end

This gives you a shared model object that's easy to access. It's not a true singleton since it doesn't prevent you from instantiating it more than once if you really want to, but that's not usually what people are after when they talk about using a singleton.

Note that the only subclass here is a simple subclass of NSObject -- the arrays and dictionaries are right off the shelf.

Caleb
  • 124,013
  • 19
  • 183
  • 272