Unheilig
Unheilig

Reputation: 16292

Dynamically resolving object according to their class types during runtime

I have 4 ivars:

UIView *view1;
UIView *view2;
UIView *view3;
UIView *view4;

I would like to be able to alloc and init them in a dynamic way, instead of doing:

view1 = [[MyView1 alloc] initWithFrame:....
view3 = [[MyView2 alloc] initWithFrame:....
view4 = [[MyView3 alloc] initWithFrame:....
view4 = [[MyView4 alloc] initWithFrame:....

So, I tried to use a array and store the names of these ivars in it:

[array addObject:@"view1"];
[array addObject:@"view2"];
[array addObject:@"view3"];
[array addObject:@"view4"];

And so that within a loop I would do:

[self valueForKey:[array objectAtIndex:x]] = [[[[self valueForKey:[array objectAtIndex:x]] class] alloc] initWithFrame:(CGRect){.....

The above generates the error:

Expression is not assignable.

Hope someone can tell me why the above cannot be done.

My question is:

I have a feeling that this is not a clever way of doing things.

Right now I have only 4 views, but who knows if in the future I might have more.

So, my idea is that instead of hard-coding things, I would like to find a more dynamic way of accomplishing this.

My idea is that at compile-time, all this views are just of UIViews.

And only until run-time would I resolve these views to the individual class types (i.e. MyView1, MyView2 etc etc) and alloc and init them and assign them accordingly to the ivars (i.e. view1, view2, view3, etc) within my class.

The reason why I use an array is because, if in the future I added another view called view5 of class type MyView5, I could loop the alloc and init process using [array count]. If this way of doing it is still not optimal, please correct me.

To sum up, I would like to set up my controller in a way that it only knows during compile-time that these objects are just of class type UIView. Only until run-time would I resolve them individually to MyView1, MyView2(subclass of UIView) etc and assign them to the ivars within my controller (again, they are named view1, view2 etc).

And if I added another view in the future, I wouldn't have to look all over the place within this controller and hard-code: view5 = [[MyView5 alloc] init....

Can someone show me how to accomplish this optimally (future-wise) and dynamically?

EDIT:

It just occurred to me: it would be even better if I could create these ivars only during runtime, so that in the future everything could be created dynamically.

Upvotes: 3

Views: 225

Answers (2)

architectpianist
architectpianist

Reputation: 2552

If I understand what you're asking, let me provide a different approach which you might like:

// Set up a mutable array of objects
NSMutableArray *views = [[NSMutableArray alloc] init];

// Set up an array of strings representing the classes - you can add more
// later, or use -stringWithFormat: to make the class names variable
NSArray *classes = @[@"MyView1", @"MyView2", @"MyView3", @"MyView4"];

// Now loop through it and instantiate one of each kind
for (NSString *className in classes)
    [views addObject:[[NSClassFromString(className) alloc] initWithFrame:CGRectZero]];

Remember to be careful with NSClassFromString, as you might accidentally send the -initWithFrame: message to a type that doesn't implement that.

Hope this helps!

Edit: I see that I have given you an overly implementation-based answer, while you seem to be looking for the program design aspect.

When you're designing your controller class, it's good that you're considering how you will use the class in the future. That said, you need to have a specific idea of how abstract you want the class to be. In other words, don't go trying to make the controller class completely decoupled, because at some point your class will be a bulk of useless management code.

So how do you go about writing a class that is both decoupled and functional at the same time? I suggest you look for examples in Apple's classes. Here are a few:

  1. UIViewController, probably the most important and versatile class on iOS. They designed it to be easily subclassable, yet there are also many premade subclasses like the navigation controller and table view controller varieties.

  2. UIDocument, a template for all document model objects you will ever need. The system implementation handles all the nitty-gritty of iCloud sync, file management, etc., while not knowing anything about the document contents itself. The user subclass, however, provides the necessary information in an NSData object.

  3. UIGestureRecognizer, the foundation of the touch-based UI. Most people only use the system-provided tap/swipe/pinch subclasses, but the abstract superclass itself (whether subclassed or not) detects any gesture you want and sends the necessary messages. Again, the gesture recognizer doesn't know what views you attach it to, yet it still performs its job.

Do you see what I'm getting at here? Apple's classes illustrate that there are ways to provide the necessary functionality while staying abstract, but without going into runtime acrobatics. As one of the commenters suggested, all you really need is an array of view objects. Instead of having your controller class instantiate the views, maybe you should have your client objects do that. It's all about finding a balance between abstraction and functionality.

Upvotes: 2

Tobi
Tobi

Reputation: 5519

The problem is, that you're thinking about this, as if your calls to the array elements would replace your code before compiling (like macros would do). It just doesn't work that way. For example:

[self valueForKey:[array objectAtIndex:x]] = ...

The compiler sees [self valueForKey:[array objectAtIndex:x]] as a value and "thinks" hey you can't assing sth to a value.

Try the following (i've splitted it into multiple statements for better readability and also to make the code more self-explanatory):

string className = [array objectAtIndex:x];
Class classToInit = NSClassFromString(className);
UIView *viewToInit = [[classToInit alloc] initWithFrame:...];
[self setValue:viewToInit forKey:className];

Keep in mind that with this approach the property names (and btw. they also need to be properties not ivars for KVC to work) must match your class names, i.e. if your class is named MyView1 your property must also be called MyView1. You might not want this (in fact according to you description in the text, you don't). So to make it work you could create a dictionary mapping your property names to your class names and enumerate over it's keys:

NSMutableDictionary classNameMapping = [NSMutableDictionary new];
[classNameMapping setObject:@"MyView1" forKey:@"view1"];
[classNameMapping setObject:@"MyView2" forKey:@"view2"];

//...

foreach (string propertyName in [classNameMapping allKeys])
{
    string className = [classNameMapping objectForKey:propertyName];
    Class classToInit = NSClassFromString(className);
    UIView *viewToInit = [[classToInit alloc] initWithFrame:...];
    [self setValue:viewToInit forKey:propertyName];    
}

Upvotes: 1

Related Questions