I have a hierarchy of classes like this:
MyBox | |->ImageBox |->GalleryBox |->MovieBox |-> ...
Each one identified by a string like @"image-box" or @"gallery-box" coming from an xml block like this:
<image-box><property1>value1</property1>...</image-box>
.
So i decided to create a factory class, which can create for me the correct object given the xml block:
[BoxFactory.h]
@interface BoxFactory {
NSMutableDictionary* stringToClassMapping;
}
+(BoxFactory)sharedBoxFactory;
-(void)registerClass:(Class)clazz withKey:(NSString*)key;
-(MyBox*)getBoxFromXml:(NSString*)xmlBlock;
@开发者_JS百科end
[.m]
#import "BoxFactory.h"
#import "MyFantasticXmlLibrary.h"
BoxFactory* gInstance=nil;
@implementation BoxFactory
+(BoxFactory*)sharedBoxFactory {
if (gInstance==nil)
gInstance=[[BoxFactory alloc]init];
return gInstance;
}
-(id)init {
self=[super init];
stringToClassMapping=[[NSMutableDictionary alloc]initWithCapacity:10];
return self;
}
-(void)registerClass:(Class)clazz withKey:(NSString*)key {
[stringToClassMapping setObject:clazz forKey:key];
}
-(MyBox*)getBoxFromXml:(NSString*)xmlBlock {
NSString* key=[MyFantasticXmlLibrary getRootNodeName:xmlBlock];
return [[[stringToClassMapping objectForKey:key]alloc]initWithXml:xmlBlock];
}
@end
Now the problem is: where should the concrete classes call the registerClass:withKey:
method? it seems like the correct place would be in the init method of the BoxFactory, but this means that the Factory must be modified for each class added, which is not scalable for big hierarchies.
I really would like to find a way to put the registration call in the concrete class itself, keeping a better organized code and less dependencies. But until now i didn't find a way to execute code when a class is loaded, without init
ing an instance of it.
Ok, i found it out 5 minutes after posting the question, but i still think it can help somebody so i'll leave it here:
There is a method +(void)load
in NSObject called exactly when the class is loaded in the image, at the very start of the execution. So the best place to register the classes with their factory is the +(void)load
method of the concrete class itself. For example:
@implementation ImageBox
+(void)load {
[[BoxFactory sharedBoxFactory]registerClass:[ImageBox class] withKey:@"image-box"];
}
-(id)initWithXmlBlock:(NSString*)xmlBlock {
[...]
I've got a few ideas:
Option 1: Change the XML
If you have control over the file format, you can then use NSClassFromString to get the Objective-C class to work with that way.
Option 2: Store you registration information in a .plist
If you store the mapping of Tag -> Class or Class -> Tag in a .plist, you can simply load that up at boot time with NSDictionary dictionaryWithContentsOfFile rather than have it all over the place in the code
Option 3: Attribute oriented approach
Decorate your classes with an property either via a category, or by protocol, this way you can query the object model at boot time and use Type Introspection to discover what classes are available and what their tags are. This avoids having to manage two things (plist and classes) and also avoids having to change the xml (sometimes a no-go because it isn't yours to muck with). It's a bit more heavy handed at boot time, but it may be an ok trade of depending on what you are doing.
精彩评论