【问题标题】:Implementing polymorphism inside the cellForRowAtIndexPath method for UITableViewCells在 UITableViewCells 的 cellForRowAtIndexPath 方法中实现多态性
【发布时间】:2014-12-24 02:27:33
【问题描述】:

我有一个genericTableViewController - GTBVC - 类,它被当前另外两个视图控制器所占用,每个视图控制器都有自己的数据数组,这些数据数组被传递到这个GTBVC。

目前我的代码如下所示:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    id object = [self.genericArray objectAtIndex:indexPath.row];
    NSString *textNameOfObject = [[self.genericArray objectAtIndex:indexPath.row] performSelector:@selector(name)];

#warning - todo: Create a polymorphistic method for each uitableviewcells.

    if([object isMemberOfClass:[OXPersonModel class]]){
        OXFriendTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kTableViewCellFriendIdentifier forIndexPath:indexPath];
        cell.friendNameLabel.text = textNameOfObject;


        [PKQuickMethods setCornerRadiusForView:cell.friendImageView withCornerRadius:CGRectGetWidth(cell.friendImageView.frame)/2];
        cell.friendImageView.image = [UIImage imageNamed:@"image"];
        [cell.friendImageView setContentMode:UIViewContentModeScaleToFill];
        return cell;
    }
    else if([object isMemberOfClass:[OXTagPlaceCountModel class]]){
        OXTagTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kTableViewCellTagIdentifier forIndexPath:indexPath];
        cell.tagNameLabel.text = textNameOfObject;
        cell.tagPlacesCountLabel.text = [[self.genericArray objectAtIndex:indexPath.row] performSelector:@selector(totalPlacesCountString)];
        return cell;
    }
    return nil;
}

到目前为止,这种方法还不错。然而,因为我希望让我的代码在未来具有可扩展性——当我扩展程序以适应其他单元和产品类型时——我需要确保它易于实现。

想到了多态性。我在想每个 UITableView 子类都需要实现一个通用方法名称,例如-[cell updateForModelObject:],这样最终cellForRowAtIndexPath 将只有几行代码。

但我发现自己想象我仍然需要根据它们的标识符找出哪些单元格要出列。要出列的单元格取决于 self.genericArray 中的对象类型。

如何在不使用任何 if 语句的情况下使 cellForRowAtIndexPath 多态中的当前代码?

【问题讨论】:

    标签: ios objective-c uitableview ios7 polymorphism


    【解决方案1】:

    您所描述的方法通常可以使用协议来实现。方法如下:

    在您的 GTBVC 标头中,声明一个协议,例如:

    @protocol GTBVCCellConfiguration
       -(void)updateForModelObject:(id)anObject;
    @end
    

    然后创建您的 UITableViewCell 子类,使它们符合该协议并让它们实现该方法。在子类中,您可以将“id”参数类型换成您期望的实际数据类型。

    由于您的通用表格视图控制器将处理不同类型的数据,您可以声明一个 Enum 来指定您的通用表格视图控制器可以采用的各种模式:

    typedef NS_ENUM(NSInteger, GenericTableViewMode) {
      case GenericTableViewModePerson,
      case GenericTableViewModePlace
    };
    

    然后在您的 GTBVC 的标头中,为该类型声明一个属性:

     @property (assign, nonatomic) GenericTableViewMode tableViewMode
    

    在 feeder 视图控制器中,导入 GTBVC 标头并实现为 segue 做准备:

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
        if ([segue.identifier isEqualToString:#yourSegueIdentifierForPerson#]) {
           ((GenericTableViewController *)segue.destinationViewController).mode = GenericTableViewModePerson; 
       } else {
           // check for the next storyboard identifier and set the mode
       }
     }
    

    然后在你的 cellForRowAtIndexPath 中(有点速记):

    -(void)tableView:cellForRowAtIndexPath: {
       id object = [self.genericArray objectAtIndex:indexPath.row];
    
       UITableViewCell <GTBVCellConfiguration> *cell = nil;
    
       switch (self.mode) {
         case GenericTableViewModePerson:
             cell = [tableView dequeueResuableCellWithIdentifier:@"PERSON" forIndexPath:indexPath];
         case GenericTableViewModePlace:
             cell = [tableView dequeueResuableCellWithIdentifier:@"PLACE" forIndexPath:indexPath];
       }
    
       [cell updateForModelObject:object];
       return cell;
     }
    

    如果您愿意,您甚至可以创建一个方法,为 tableview 的当前模式返回正确的标识符。

    【讨论】:

    • 嘿伍德斯特。 1) 请您详细说明&lt;&gt; 语法并解释它在您的代码sn-p 中的用途,也许请添加一些对简洁阅读材料的引用。因为.. &lt;GTBVCellConfiguration&gt;.. 让我很困惑。 2)这可能与第一个问题有关,但为什么我们所有的子类UITableViewCells 都符合GTBVCCellConfiguration 协议。这样做的目的是什么?
    • 让你的子类符合一个协议是一种执行假多重继承的方法。您定义一个带有一个或多个方法签名(协议)的接口,然后让您的表格视图单元实现这些方法。 “UITableViewCell *cell”的语法意思是“一个tableview cell,它拥有所有普通的tableview cell方法,加上这个中描述的方法。”
    • 3) 你正在使用 switch 语句,这又会变得非常丑陋,它只是一个美化的 if 语句块。无论如何,我们可以通过多态来做到这一点吗?我知道我们需要能够使表格视图中的正确单元格出列,因为我们正在使用通用表格视图控制器来显示大量单元格和未来,但是如果有一种方法可以不用 if/switch块然后那将是真棒。我曾希望多态性在这里会有所帮助,不是吗?请看:http://bit.ly/1vaFfh8
    • 要放弃开关,也可以在模型对象上使用协议,正如另一位海报所说。该协议将具有类似“-(NSString *)cellIdentifier”的方法,您可以直接调用它。在 Objective-C 中,协议(如委托)优于子类化,因为它们使消息传递合约保持松散耦合。这是 Protocols 的官方文档:developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
    • 对于它的价值,如果您在 Swift 中执行此代码,您可以在 Enum 中包含一个函数,该函数返回当前枚举值的单元格标识符。在内部它将是一个开关,但该开关将被封装到枚举数据类型中。
    【解决方案2】:

    我的建议还包括protocol,而是您放置在模型对象上的协议。它可能有一个像-(NSString*)cellIdentifier 这样的方法,它返回该模型类的唯一标识符。然后,您只需通过获取该索引的模型对象来从表视图中出列。此外,您可以在模型协议上定义单元用于配置的方法。

    这意味着您可以定义任何对象以符合此协议,并且现在可以在任何地方与您的单元一起使用。

    【讨论】:

    • 使用所有模型对象都符合的协议与使用performSelector: 方法和所有模型对象符合通用方法名称的区别是什么?难道他们都不会达到同样的目的吗?我看到这里的所有答案都指向符合协议的所有模型对象,这有什么原因吗?
    • 使用 performSelector 是不安全的,因为系统没有关于被调度的方法的信息。最好尽可能明确地表达意图。另外,如果要将方法名称放在所有标题上,为什么还要打扰 performSelector?
    • 你说Additionally you could define methods on your model protocol that are used by the cell for configuration.你能详细说明一下吗?据我了解,您是说我应该 import 将自定义数据模型放入其关联的子类 tableview 单元格中,以便我可以使用类似 [cell updateForModelObject:] 的方法来配置单元格的方法?视图和模型不应该分开吗?
    • 这意味着您的协议在符合要求的类上需要尽可能多的方法。因此,例如,假设您的单元格需要显示两条不同的信息行,您的协议可以定义三种方法:-(NSString*)cellIdentifier、-(NSString*)cellLine1 和-(NSString*)cellLine2。然后你的单元格配置方法定义为-(void)configureForObject:(id&lt;PROTOCOLNAME&gt;)obj。在该方法中,您只需要调用那些协议方法,并且适用于任何符合要求的对象。
    • 但是你如何区分哪些对象被传递到configureForObject 参数,然后能够相应地更新单元格,因为不同的子类单元格具有不同的属性?
    【解决方案3】:

    使用convention over configuration 并以模型类型确定的方式命名单元标识符,例如,

    NSString *modelClassName = NSStringFromClass(model.class);
    NSString *modelCellClassIdentifier = [modelClassName stringByAppendingString:@"TableViewCellIdentifier"];
    UITableViewCell<ModelUpdatable> *cell = [tableView dequeuReusableCellWithIdentifier:modelCellClassIdentifier];
    [cell updateWithModel:model];
    

    因此,如果您的模型名为 Person,则您的单元格标识符将为 PersonTableViewCellIdentifier。 (我建议您拨打您的手机PersonTableViewCell。)

    我经常使用这种技术,我有一个宏来声明标识符,我将尝试从内存中复制它,因此将其视为伪代码:

    PRODeclareCellIdentifier(model) extern NSString *const model##TableViewCellIdentifier
    PRODefineCellIdentifier(model) NSString *const model##TableViewCellIdentifier = @#model##TableViewCellIdentifier
    

    在单元格的标题中,您会说PRODeclareCellIdentifier(Person)。在它的实现中你会说PRODefineCellIdentifier(Person)。我很少使用宏,但它们对于像这样的样板文件非常非常有用。

    【讨论】:

    • 嗨格雷戈里。这似乎很脆弱,因为它依赖于那里的命名约定。此外,我的单元格是 UITableViewCell 的子类 - 因此它们具有自己的类名,具有自己的独特属性。 UITableViewCell 不符合updateWithModel 方法。
    • 我不同意。 :) 约定优于配置是众所周知的并且被广泛使用。许多现代应用程序框架都依赖它,例如 Ruby on Rails。 updateWithModel 的事情并不难解决。毕竟,ObjC 是一种动态语言。
    • 我添加了一个协议,以便您可以在完美的类型安全的情况下调用updateWithModel:。其他一些答案也显示了如何做到这一点。
    • 大声笑......我的 cmets 是为了你的原始答案。现在,您刚刚编辑了代码,所以我的观点不再有效;)
    • 不用担心。 :) 做你认为最好的事情。所有这些都可以工作,但其他的对我来说似乎有点冗长,并且更适合静态类型的语言,如 C#、Java 或 Swift,而不是动态类型的语言,如 ObjC。
    猜你喜欢
    • 2021-11-25
    • 2018-10-25
    • 1970-01-01
    • 2018-05-22
    • 1970-01-01
    • 2014-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多