美文网首页iOS
iOS CollectionView/TableView使用多个

iOS CollectionView/TableView使用多个

作者: 人生若只如初见丶_a4e8 | 来源:发表于2020-08-05 08:59 被阅读0次

    本文摘自:数据显示和事件处理与controller解耦

    在日常开发时,经常需要使一个列表通过多种不同样式的cell来展示,比如下图中的情况:


    0EE7330D-BDE2-4950-A008-3FA6AAEC3A1F.png

    问题描述

    多种cell就会造成cellForRowAtIndexPath/cellForItemAtIndexPath大量的if /else,再加上数据和事件的处理简直是灾难,不利于以后的扩展,难以维护。

    不同cell的数据显示

    通过protocol依赖的方式,无需将子view属性暴露出来。
    1. 定义一个协议

    //  BFDisplayEventProtocol.h
    /**
     显示数据协议
     */
    @protocol BFDisplayProtocol <NSObject>
    
    - (void)em_displayWithModel:(BFEventModel *)model;
    
    @end
    

    2. 创建数据传递模型

    //  BFEventModel.h
    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    @interface BFEventModel : NSObject
    
    @property (nonatomic,strong) id                               model;       //!< 数据模型
    
    @property (nonatomic,strong) NSIndexPath                      *indexPath;  //!< 序号
    
    @property (nonatomic,assign) NSInteger                        eventType;   //!< 事件类型
    
    @property (nonatomic,assign) NSInteger                        index;       //!< 事件int标识
    
    @property (nonatomic,assign) NSString                         *identifier; //!< 事件标识
    
    @property (nonatomic,strong) id                               target;      //!< target
    
    @end
    
    
    //  BFEventModel.m
    #import "BFEventModel.h"
    
    @implementation BFEventModel
    
    @end
    

    3. cell中实现BFDisplayProtocol协议

    //  BFCell1TableViewCell.m
    @implementation BFCell1TableViewCell
    
    #pragma mark - BFDisplayProtocol
    - (void)em_displayWithModel:(BFEventModel *) theModel {
        BFModel *model = theModel.model;
        self.textLabel.text = model.title;
    }
    
    @end
    

    4. 在代理中调用显示数据方法

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell<BFDisplayProtocol> *cell = [tableView dequeueReusableCellWithIdentifier:!indexPath.section ? @"BFCell1TableViewCell" : @"BFCell2TableViewCell"];
    
        id object = ((NSMutableArray *)self.objects[indexPath.section])[indexPath.row];
            
        BFEventModel *model = [[BFEventModel alloc] init];
        model.model = object;
        model.indexPath = indexPath;
    
        [cell em_displayWithModel: model];
        
        return cell;
    }
    

    如果需要再加一种cell,就只需要创建新的cell,然后实现BFDisplayProtocol协议就行了。减少cell对controller的依赖,将controller中的逻辑分散道每个cell中自己实现,减少view对controller的耦合。
    至此完成了不同cell的数据显示。但是cell对model是有依赖的,如果另外一个列表用到这个cell,而且model不同,就做不到重用。


    相同的cell展示不同的model

    针对上面提到的重用问题,采用了消息转发机制来实现

    1.定义一个model基类BFPropertyExchange

    //  BFPropertyExchange.h
    #import <Foundation/Foundation.h>
    
    @interface BFPropertyExchange : NSObject
    
    - (NSDictionary *)em_exchangeKeyFromPropertyName;
    
    @end
    

    2.在基类中实现消息转发

    //  BFPropertyExchange.m
    
    #import "BFPropertyExchange.h"
    #import "objc/runtime.h"
    #import "objc/message.h"
    
    static void *kPropertyNameKey = &kPropertyNameKey;
    
    @implementation BFPropertyExchange
    
    - (NSDictionary *)em_exchangeKeyFromPropertyName{
        return nil;
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        return NO;
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        return nil;
    }
    
    /**
     消息转发
     
     @param aSelector 方法
     @return 调用方法的描述签名
     */
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        
        NSString *propertyName = NSStringFromSelector(aSelector);
        
        NSDictionary *propertyDic = [self em_exchangeKeyFromPropertyName];
        
        NSMethodSignature* (^doGetMethodSignature)(NSString *propertyName) = ^(NSString *propertyName){
            //创建新签名
            NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:"@@:"];
            objc_setAssociatedObject(methodSignature, kPropertyNameKey, propertyName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            return  methodSignature;
        };
        
        if ( [propertyDic.allKeys containsObject:propertyName] ) {
            NSString *targetPropertyName = [NSString stringWithFormat:@"em_%@",propertyName];
            if ( ![self respondsToSelector:NSSelectorFromString(targetPropertyName)] ) {
                // 如果没有em_重写属性,则用转换字典中属性替换
                targetPropertyName = [propertyDic objectForKey:propertyName];
            }
            
            return doGetMethodSignature(targetPropertyName);
        }
        
        return [super methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        
        NSString *originalPropertyName = objc_getAssociatedObject(anInvocation.methodSignature, kPropertyNameKey);
        if ( originalPropertyName ) {
            //NSInvocation对原来签名的方法执行新的方法,必须指定Selector和Target,invoke或invokeWithTarget执行
            anInvocation.selector = NSSelectorFromString(originalPropertyName);
            [anInvocation invokeWithTarget:self];
        }
    }
    
    @end
    

    3. 赋值模型继承基类BFPropertyExchange实现em_exchangeKeyFromPropertyName方法

    方法原理解析

    当cell通过不同模型赋值的时候,下面的theModel.model除了是BFModel外,还有可能是BFModel1,BFModel2...等等。

    #pragma mark - BFDisplayProtocol
    
    - (void)em_displayWithModel:(BFEventModel *) theModel {
        BFModel *model = theModel.model;
        self.titleLabel.text = model.title;
        ......
    }
    

    这里我们统一用BFModel来接收,当传入为BFModel1的时候,BFModel1中没有title属性,这时候就会报找不到方法的错误。这个时候就用了上面的消息转发机制。首先会走到+ (BOOL)resolveInstanceMethod:(SEL)sel;方法,先征询消息接收者所属的类也就是BFModel1,看其是否能动态添加方法,以处理当前这个无法响应的selector。这边我们不做处理,所以返回NO

    + (BOOL)resolveInstanceMethod:(SEL)sel {
        return NO;
    }
    

    然后方法走到- (id)forwardingTargetForSelector:(SEL)aSelector;看看有没有其它对象能处理此消息。如果有,则把消息发给那个对象,转发结束;如果没有,则启动完整的消息转发机制。因为处理模型多变,所以我们也不在这边做处理。返回nil

    - (id)forwardingTargetForSelector:(SEL)aSelector {
        return nil;
    }
    

    接下来进入最关键的一步方法- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;,完整的消息转发机制。
    BFModel1继承基类BFPropertyExchange实现em_exchangeKeyFromPropertyName方法。返回字典代表调用属性与本地属性的映射关系,cell的调用属性是title,此时传入BFModel1,但是BFModel1并没有title属性,则通过映射关系自动调用本地属性newtitle。

    - (NSDictionary *)em_exchangeKeyFromPropertyName {
        return @{@"title":@"newtitle"};
    }
    

    通过方法名在em_exchangeKeyFromPropertyName中取得替换属性targetPropertyName,然后将新的调用方法封装进NSMethodSignature中。

        NSString *propertyName = NSStringFromSelector(aSelector);
        
        NSDictionary *propertyDic = [self em_exchangeKeyFromPropertyName];
        
        NSMethodSignature* (^doGetMethodSignature)(NSString *propertyName) = ^(NSString *propertyName){
            //创建新签名
            NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:"@@:"];
            objc_setAssociatedObject(methodSignature, kPropertyNameKey, propertyName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            return  methodSignature;
        };
        
        if ( [propertyDic.allKeys containsObject:propertyName] ) {
            NSString *targetPropertyName = [NSString stringWithFormat:@"em_%@",propertyName];
            if ( ![self respondsToSelector:NSSelectorFromString(targetPropertyName)] ) {
                // 如果没有em_重写属性,则用转换字典中属性替换
                targetPropertyName = [propertyDic objectForKey:propertyName];
            }
            
            return doGetMethodSignature(targetPropertyName);
        }
    

    最后转发至- (void)forwardInvocation:(NSInvocation *)anInvocation;,通过封装到NSInvocation对象中的属性,再给接收者最后一次机会,解决当前还未处理的问题。

    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        
        NSString *originalPropertyName = objc_getAssociatedObject(anInvocation.methodSignature, kPropertyNameKey);
        if ( originalPropertyName ) {
            //NSInvocation对原来签名的方法执行新的方法,必须指定Selector和Target,invoke或invokeWithTarget执行
            anInvocation.selector = NSSelectorFromString(originalPropertyName);
            [anInvocation invokeWithTarget:self];
        }
    }
    

    相关文章

      网友评论

        本文标题:iOS CollectionView/TableView使用多个

        本文链接:https://www.haomeiwen.com/subject/xnkdrktx.html