在运行时根据类类型动态解析对象

时间:2014-04-13 19:47:12

标签: ios objective-c-runtime

我有4个ivars:

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

我希望能够以动态的方式allocinit,而不是:

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

所以,我尝试使用数组并将这些ivars的名称存储在其中:

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

所以在循环中我会这样做:

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

以上产生错误:

  

表达式不可分配。

希望有人能告诉我为什么上述情况无法完成。

我的问题是:

我觉得这不是一种聪明的做事方式。

现在我只有4个观点,但是谁知道将来我可能会有更多观看次数。

所以,我的想法是,我想找到一种更有活力的方法来实现这一目标,而不是硬编码。

我的想法是,在编译时,所有这些视图都只是UIViews

直到运行时才能将这些视图解析为各个类类型 (即MyView1MyView2等等)以及allocinit他们并指定他们 相应于我班上的ivars(即view1view2view3等。

我使用数组的原因是,如果将来我添加了另一个名为view5的类MyView5的视图,我可以循环alloc和{{1}使用init进行处理。如果这种方式仍然不是最佳的,请纠正我。

总而言之,我想以一种只在编译时才知道这些对象只是类型[array count]的方式来设置我的控制器。只有在运行时才能将它们单独解析为UIViewMyView1MyView2的子类)等,并将它们分配给我的控制器中的ivars(同样,它们被命名为{{1 ,},UIView等)。

如果我将来添加另一个视图,我就不必在这个控制器中查看所有地方并进行硬编码:view1

有人能告诉我如何以最佳方式(未来)和动态地完成这项工作吗?

修改

我刚刚想到:如果我只能在运行时创建这些ivars会更好,以便将来可以动态创建所有东西。

2 个答案:

答案 0 :(得分:2)

如果我理解您的要求,请允许我提供您可能喜欢的其他方法:

// 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]];

请注意谨慎使用NSClassFromString,因为您可能会意外地将-initWithFrame:消息发送到未实现该消息的类型。

希望这有帮助!

编辑:我看到我已经给你一个过于实现的答案,而你似乎在寻找程序设计方面。

当你设计你的控制器课程时,你考虑将来如何使用这门课程是件好事。也就是说,你需要对你想要这个类的抽象有一个特定的想法。换句话说,不要试图让控制器类完全解耦,因为在某些时候你的类将是大量无用的管理代码。

那么你如何编写一个同时解耦和功能的类呢?我建议您在Apple的课程中查找示例。以下是一些:

  1. UIViewController ,可能是iOS上最重要,最通用的课程。他们将其设计为易于子类化,但也有许多预制的子类,如导航控制器和表视图控制器品种。

  2. UIDocument ,您需要的所有文档模型对象的模板。系统实现处理iCloud同步,文件管理等的所有细节,同时不了解文档内容本身。但是,用户子类在NSData对象中提供必要的信息。

  3. UIGestureRecognizer ,基于触摸的UI的基础。大多数人只使用系统提供的tap / swipe / pinch子类,但抽象超类本身(无论是否是子类)检测到你想要的任何手势并发送必要的消息。同样,手势识别器不知道您将它附加到哪个视图,但它仍然可以执行其工作。

  4. 你看到我在这里得到了什么吗? Apple的课程表明,有些方法可以提供必要的功能,同时保持抽象,但不需要进入运行时杂技。正如其中一位评论者建议的那样,您真正需要的只是一组视图对象。您可以让客户端对象执行此操作,而不是让您的控制器类实例化视图。这一切都是为了在抽象和功能之间找到平衡。

答案 1 :(得分:1)

问题是,你正在考虑这个问题,好像你对数组元素的调用会在编译之前替换你的代码(就像宏一样)。它只是不起作用。例如:

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

编译器将[self valueForKey:[array objectAtIndex:x]]视为,并且“认为”嘿,你不能为某个值提供帮助。

尝试以下内容(我已将其拆分为多个语句以提高可读性,并使代码更加不言自明):

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

请注意,使用此方法,属性名称(和顺便说一下,它们也必须是属于非KARC才能工作的属性)必须与您的类名匹配,即如果您的类名为{ {1}}您的财产也必须被称为MyView1。您可能不希望这样(事实上根据您在文中的说明,您没有)。因此,为了使其工作,您可以创建一个字典,将您的属性名称映射到您的类名,并枚举它的键:

MyView1