我避免使用UITableViewController
,因为它将很多责任归于一个对象。因此,我将UIViewController
子类与数据源和委托分开。视图控制器的职责是准备表视图,创建包含数据的数据源,并将这些东西连接在一起。无需更改视图控制器即可更改表视图的表示方式,实际上,同一视图控制器可用于所有遵循此模式的多个数据源。同样,更改应用程序工作流程意味着更改视图控制器,而不必担心表发生了什么。
我曾尝试将UITableViewDataSource
和UITableViewDelegate
协议分隔为不同的对象,但是通常最终会导致错误的拆分,因为委托上的几乎每个方法都需要挖掘数据源(例如,在选择时,委托需要知道对象所代表的对象)选定的行)。因此,我最终得到了一个既是数据源又是委托的对象。这个对象总是提供一种-(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPath
数据源和委托方面都需要知道它们正在做什么的方法。
那是我的“ 0级”关注点分离。如果我必须在同一张表视图中表示不同种类的对象,则使用1级。例如,假设您必须编写“联系人”应用程序-对于单个联系人,可能有代表电话号码的行,代表地址的其他行,代表电子邮件地址的其他行等等。我想避免这种方法:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
if ([object isKindOfClass: [PhoneNumber class]]) {
//configure phone number cell
}
else if …
}
到目前为止,已经提出了两种解决方案。一种是动态构造选择器:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
NSString *cellSelectorName = [NSString stringWithFormat: @"tableView:cellFor%@AtIndexPath:", [object class]];
SEL cellSelector = NSSelectorFromString(cellSelectorName);
return [self performSelector: cellSelector withObject: tableView withObject: object];
}
- (UITableViewCell *)tableView: (UITableView *)tableView cellForPhoneNumberAtIndexPath: (NSIndexPath *)indexPath {
// configure phone number cell
}
在这种方法中,您无需编辑史诗if()
树即可支持新类型-只需添加支持新类的方法即可。如果此表视图是唯一需要表示这些对象或需要以特殊方式呈现的对象,则这是一种很好的方法。如果相同的对象将在具有不同数据源的不同表中表示,则此方法将失效,因为单元格创建方法需要跨数据源共享—您可以定义提供这些方法的公共超类,或者可以执行以下操作:
@interface PhoneNumber (TableViewRepresentation)
- (UITableViewCell *)tableView: (UITableView *)tableView representationAsCellForRowAtIndexPath: (NSIndexPath *)indexPath;
@end
@interface Address (TableViewRepresentation)
//more of the same…
@end
然后在您的数据源类中:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
return [object tableView: tableView representationAsCellForRowAtIndexPath: indexPath];
}
这意味着任何需要显示电话号码,地址等的数据源都可以询问表视图单元格所代表的对象是什么。数据源本身不再需要有关所显示对象的任何信息。
“但是等等,”我听到一个假设的对话者的感言,“这不会破坏 MVC吗?您不是将视图细节放入模型类中吗?”
不,它不会破坏MVC。在这种情况下,您可以将类别视为Decorator的实现;所以PhoneNumber
是一个模型类,但是PhoneNumber(TableViewRepresentation)
是一个视图类。数据源(控制器对象)在模型和视图之间进行中介,因此MVC架构仍然适用。
您也可以在Apple的框架中看到使用类别作为装饰。NSAttributedString
是一个模型类,其中包含一些文本和属性。AppKit提供了NSAttributedString(AppKitAdditions)
,UIKit提供了NSAttributedString(NSStringDrawing)
,装饰器类别,可将绘图行为添加到这些模型类中。