I have a series of dictionaries stored in property list ("plist") files, and I was wondering if there was a way for me to make this code more efficient, especially for debugging purposes...
The header file contains a section like this:
//////////////////////////////////////////////////////////////////////////////////////
//
// dictionary objects retrieved from Property List ("plist") files...
//
- ( NSDictionary * ) dictionaryAdvertising;
- ( NSDictionary * ) dictionaryBuildings;
- ( NSDictionary * ) dictionaryCampus;
- ( NSDictionary * ) dictionaryLockerItems;
- ( NSDictionary * ) dictionaryRandomScreenObjects;
- ( NSDictionary * ) dictionarySchools;
- ( NSDictionary * ) dictionarySounds;
- ( NSDictionary * ) dictionarySupport;
In the main code section, I have the following:
//////////////////////////////////////////////////////////////////////////////////////////////
//
// use a single macro to create multiple instances of the same basic method with subtle
// variations to avoid code replication (if there's an issue, fix it once instead of
// multiple cut/paste instances)...
//
#define __CREATE_DICTIONARY_ACCESS_METHOD( _method_, _key_, _filename_ ) \
- ( NSDictionary * ) _method_ \
{ \
/* actual pointer to the "singleton" instance of this dictionary... */ \
static NSDictionary * dict = nil; \
/* once-only identifier for instantiating this "singleton" item... */ \
static dispatch_once_t onceToken = 0l; \
/* allocate and load this object, if it hasn't been done already... */ \
dispatch_once( &onceToken \
, ^{ \
dict = [ self loadDictionaryFromFile: _key_ ]; \
/* idiot check... */ \
NSAssert( ( dict ) \
, @"error allocating dictionary \"%@\"" \
, _key_ \
); \
/* if there is a field that lists an image file... */ \
if ( _filename_ ) \
{ \
/* load it into the texture cache... */ \
[ self cacheFromDictionary: dict \
name: _key_ \
key: _filename_ \
]; \
} /* end field name given for image file names */ \
} /* end run load process only once */ \
); \
return dict; \
}
__CREATE_DICTIONARY_ACCESS_METHOD( dictionaryAdvertising , CS_STRING_DICTIONARY_KEY_ADVERTISING , nil )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionaryBuildings , CS_STRING_DICTIONARY_KEY_BUILDINGS , @"Filename" )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionaryCampus , CS_STRING_DICTIONARY_KEY_CAMPUS , nil )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionaryLockerItems , CS_STRING_DICTIONARY_KEY_LOCKER_ITEMS , @"Imagename" )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionaryRandomScreenObjects, CS_STRING_DICTIONARY_KEY_RANDOM_OBJECTS, nil )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionarySchools , CS_STRING_DICTIONARY_KEY_SCHOOLS , nil )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionarySounds , CS_STRING_DICTIONARY_KEY_SOUNDS , nil )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionarySupport , CS_STRING_DICTIONARY_KEY_SUPPORT , nil )
They each need to be created as separate methods so that each will have their own once-only initialization tokens. Since these objects are not actually stored as member variables, they will not be released during normal clean-up of parent objects, so they will persist across multiple instances of the parent class.
What I would like to do is find a more elegant solution to replace the macro defined in the main code module, especially one that would allow me to step through the code while debugging.
BTW, for those of you who want to use this, it's a workable process for creating/adding compile-time member access methods in Objective-C, mostly all of the C and C++ variant languages... The pseudo-singleton code ensures that the member access method is only created/allocated/initialized once.
If it helps, here is the code for loading the dictionary object from the plist file (this code allows for an override file in the user's "documents" directory, if you use this you may want to remove that capability, or at least keep it in mind):
// load the given dictionary from the associated Property List ("plist") file...
- ( NSDictionary * ) loadDictionaryFromFile: ( NSString * const ) className
{
// assume that there is a problem...
NSDictionary * dict = nil;
// build the file name for the "plist" file...
NSString * const fullFileName = [ NSString stringWithFormat: @"%@.plist"
, className
];
// idiot check...
NSAssert1( ( fullFileName )
, @"error allocating object for file \"%@\""
, className
);
// get the path to the "plist" files for this application in the "documents" folder...
NSString * const rootPath = [ NSSearchPathForDirectoriesInDomains( NSDocumentDirectory
, NSUserDomainMask
, YES
) objectAtIndex: 0
];
// idiot check...
NSAssert( ( rootPath )
, @"error allocating object"
);
// build the fully-qualified path to the requested file...
NSString * plistPath = [ rootPath stringByAppendingPathComponent: fullFileName ];
// idiot check...
NSAssert1( ( plistPath )
, @"error allocating object for file \"%@\""
, fullFileName
);
// if the file doesn't exist in the "documents" folder...
if ( ! [ [ NSFileManager defaultManager ] fileExistsAtPath: plistPath ] )
{
// then pull it from the resources bundled with the application...
plistPath = [ [ NSBundle mainBundle ] pathForResource: className
ofType: @"plist"
];
// idiot check...
NSAssert1( ( plistPath )
, @"error allocating object for file \"%@\""
, className
);
} // end file not in "documents" folder
// read the "plist" file into a dictionary object (statically allocate it so that it
// doesn't get automatically dropped when moving between scenes)...
dict = [ [ NSDictionary alloc ] initWithContentsOfFile: plistPath ];
return dict;
}
Any assistance or comments would be greatly appreciated. Also, I'm not sure if I've used the proper tags for this question....
=========================================================================
I modified the code based on the answer given, and it now looks like this:
// once-only method to access and load the contents of a Property List file into a dictionary...
- ( NSDictionary * ) accessDictionaryWithKeyAndFilename: ( NSString * const ) _key_
filename: ( NSString * const ) _filename_
token: ( dispatch_once_t * const ) onceToken
dict: ( NSDictionary * * const ) dict
{
// allocate and load this object, if it hasn't been done already...
dispatch_once( onceToken
, ^{
*dict = loadDictionaryFromFile( _key_ );
// idiot check...
NSAssert( ( *dict )
, @"error allocating dictionary \"%@\""
, _key_
);
// if there is a field that lists an image file...
if ( _filename_ )
{
// load it into the texture cache...
[ self cacheFromDictionary: *dict
name: _key_
key: _filename_
];
} /* end field name given for image file names */
} /* end run load process only once */
);
return *dict;
} // end accessDictionaryWithKeyAndFilename
//////////////////////////////////////////////////////////////////////////////////////////////
//
// use a single macro to create multiple instances of the same basic method with subtle
// variations to avoid code replication (if there's an issue, fix it once instead of
// multiple cut/paste instances)...
//
#define __CREATE_DICTIONARY_ACCESS_METHOD( _method_, _key_, _filename_ ) \
- ( NSDictionary * ) _method_ \
{ \
/* actual pointer to the "singleton" instance of this dictionary... */ \
static NSDictionary * dict = nil; \
/* once-only identifier for instantiating this "singleton" item... */ \
static dispatch_once_t onceToken = 0l; \
[ self accessDictionaryWithKeyAndFilename: _key_ \
filename: _filename_ \
token: &onceToken \
dict: &dict \
]; \
return dict; \
}
__CREATE_DICTIONARY_ACCESS_METHOD( dictionaryAdvertising , CS_STRING_DICTIONARY_KEY_ADVERTISING , nil )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionaryBuildings , CS_STRING_DICTIONARY_KEY_BUILDINGS , @"Filename" )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionaryCampus , CS_STRING_DICTIONARY_KEY_CAMPUS , nil )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionaryLockerItems , CS_STRING_DICTIONARY_KEY_LOCKER_ITEMS , @"Imagename" )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionaryRandomScreenObjects, CS_STRING_DICTIONARY_KEY_RANDOM_OBJECTS, nil )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionarySchools , CS_STRING_DICTIONARY_KEY_SCHOOLS , nil )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionarySounds , CS_STRING_DICTIONARY_KEY_SOUNDS , nil )
__CREATE_DICTIONARY_ACCESS_METHOD( dictionarySupport , CS_STRING_DICTIONARY_KEY_SUPPORT , nil )
There is still the drawback of having the macro for creation of the methods, but the loading code is now available in debug mode. I need to maintain distinct static values for each dictionary/once_token so that we can have 0->n instances of the parent class but still have the loaded dictionary persist across all instances. Even when all instances of the parent class are dismissed and later more are created, the original single dictionary load is still available, streamlining processing.
Thanks!
For me, the easiest thing to do would be to just make the macro a function, and write 8 very small wrapper methods. So start with a free function like this:
NSDictionary* accessDictionaryWithKeyAndFilename (NSString* key, NSString* filename, dispatch_once_t* onceToken)
{
... everything from your macro here ...
}
Then for any given method you want to create, just call the above by doing the following:
-(NSDictionary*)dictionaryAdvertising
{
return accessDictionaryWithKeyAndFilename (CS_STRING_DICTIONARY_KEY_ADVERTISING, nil, &once_token);
}
and so on. (The once_token
can either be a static in the method, or an ivar.) This is much more readable and much more debuggable. Writing the 1-line method to access the function is no harder than writing the 1-line macro to auto-create the method. It has the advantage of keeping the method with the rest of the class code, rather than being declared in some header or other source file. And of course, now you can step into each method and debug whatever's happening inside it.
Your -loadDictionaryFromFile:
method doesn't appear to use any ivars from the class, so it could also become a free function. (Unless I missed something?)