Search code examples
objective-cinheritanceprotocols

A better way of instantiating a class that follows a protocol


I'm working on my first iOS app part of which uses a protocol which then has a number of classes that follow it.

@protocol unitLoadoutDataController <NSObject>
@required
- (void)setDefaultLoadout:(NSString *)unitType unitId:(NSNumber *)uniqueUnitId;
- (void)resizeDefaultLoadout:(NSString *)unitType unitId:(NSNumber *)uniqueUnitId number:(NSNumber *)models;
- (UIAlertController *)updateLoadout:(NSInteger)section row:(NSInteger)row unitType:(NSString *)unitType unitId:(NSNumber *)uniqueUnitId number:(NSNumber *)models;
@end

and then a set of classes that use it. e.g.

@interface heavyIntercessorSquad : NSObject <unitLoadoutDataController>

@end
@interface eradicatorSquad : NSObject <unitLoadoutDataController>

@end

Both of these include the three 3 methods my protocol defines with different implementations.

When the user clicks on one of the units from the list in a tableview I need an instance of that class so they can configure it on the next screen. I don't know which class I need until the row is clicked.

To solve this problem I'm using a set of local variables and a big if/else block to get the class I need. selectedUnit.name contains the name of the squad the user clicked and depending on that value I create a class of the appropriate type. Once I have the pointer to the class I need I assign it to squadToConfigure and call the method I need.

Here's the definition of squadToConfigure which holds the class once created.

@property (weak, nonatomic) id <unitLoadoutDataController> squadToConfigure;

Here is a subset of the code that works out which class I need (I have about 30 of these so far) based on selectUnit.name

heavyIntercessorSquad *heavyIntercessorSquadToConfigure;
eradicatorSquad *eradicatorSquadToConfigure;

if ([selectedUnit.name isEqualToString:@"Heavy Intercessor Squad"]) {
        heavyIntercessorSquadToConfigure=[[heavyIntercessorSquad alloc] init];
        self.squadToConfigure=heavyIntercessorSquadToConfigure;
}
else if ([selectedUnit.name isEqualToString:@"Eradicator Squad"]) {
        eradicatorSquadToConfigure =[[eradicatorSquad alloc] init];
        self.squadToConfigure= eradicatorSquadToConfigure;
}
[self.squadToConfigure setDefaultLoadout:self.selectedUnit.name
                                    unitId:newUnitId];
        

Is there a more elegant way of doing this? How can I instantiate a class of a type determined by the value of selectedUnit.name and assign it to self.squadToConfigure without having lots of else statements?

The code above works well at the moment but isn't sustainable once I have a large number of classes - I have about 30 at the moment.


Solution

  • The problem you're having is that you are keying off a literal string, which is really fragile. At the very least, you need to have on hand a dictionary that maps between your selectedUnit.name possibilities and the class you want to instantiate.

    This would be made more bullet-proof if the name possibilities themselves were constants rather than mere literal strings, which can be incorrectly typed, or if the units had some sort of immutable and reliable identifier. But you have not shown anything about these units and how they are used, so it's impossible to say more.

    But a more sophisticated approach might be to plan ahead such that selectedUnit itself has a property that is the class corresponding to this unit, so that you can just read that property directly and instantiate.