理解:字典转模型,代理,UItableView的数据源,自定义cell

来源:互联网 发布:企业财务分析软件 编辑:程序博客网 时间:2024/06/07 06:42


好友初学iOS,对于字典转模型,代理,UItableView的数据源,自定义cell有些困扰,下面发表一下我自己的理解!


1⃣️字典转模型


一)有一个模型的情况

   1、看plist文件的根节点是否是NSArray类型的;

    2、如果是:根据文件中的字典元素,创建一个对应的模型类,类中的属性就是字典中每一个key值,类型是键值对应的类型(模型类的属性一定要跟字典中的key值一致,否则使用KVC会出错)。

    3、自定义一个类的init方法,该方法是对象方法,

eg- (instancetype)initWithDict:(NSDictionary *)dict

    {

       if (self = [super init])

        {

            [self调用KVC的方法];

        }

       return self;

    }


    定义一个类方法,用来把字典转为模型对象

eg: + (instancetype)类名WithDict:(NSDictionary *)dict

    {

       return [[self alloc] initWithDict:dict];

    }


到此为止,模型对像创建成功。



☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆懒加载模式☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆


    1、在控制器中,创建一个属性,类型为NSArray或者NSMutableArray类型的(视情况而定,如果在后面需要在该数组中增加模型对象,就创建可变的数组,如果不需要往数组中增加模型,就创建不可变数组),用来存放模型数据。(下文中称该数组为【数据数组】)

    2、用懒加载的方式加载数据

            加载数据的实质就是,重写数据数组的getter方法,getter方法中写的内容就是:

            ①先判断数据数组中是否有数据,如果没有数据就加载数据,如果有数据就不用加载数据,直接返回数据数组。

           ②加载数据的过程:

                i)先加载plist文件,用一个数组NSArray *dictArray接收加载完成plist文件返回的数据(为甚么用数组接收?因为plist文件的根节点是数组类型的);此时dictArray数组中存放的对象是字典对象。


                ii)用for-in循环遍历【dictArray】遍历到的对象是一个个的字典,此时在循环中用创建的【字典模型类】把字典转换为模型对象;


                iii)字典转换后的模型对象需要用一个可变数组来存储,这时候再拐回头来创建一个可变数组【NSMutableArray *ModelArray】,然后再for-in循环中把转变后的模型添加到可变数组【modelArray】中。(不需要一开始就创建可变数组,当你需要的时候,发现没有一个可以使用的可变数组,才去创建可变数组)。


                iiii)把【ModelArray】赋值给【数据数组】


                iiiii)返回【数据数组】


懒加载数据实例程序:

- (NSArray *)modelArray

{

   if (_modelArray == nil)

    {

        NSArray *dictArray =  [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:fileName ofType:nil]];

        NSMutableArray *modelArray = [NSMutableArray array];

        

       for (NSDictionary *dict in dictArray)

        {

           模型类 *model = [模型类模型类WithDict:dict];  //把字典转换为模型对象

            [modelArray addObject:model];

        }

        _modelArray = modelArray

    }

   return _modelArray;

}





二)有两个模型的情况(plist文件中一个字典中嵌套字典的情况)

            1、首先先查看嵌套在最里面的字典,然后根据一个模型的情况,创建一个类来描述最里面的字典对象,这里暂且称这个嵌套最里面的模型称为【小模型】;

            2、然后同样的道理,根据一个模型的方式,再创建一个模型,类型跟外层字典的键值的类型一致,这里就称外层模型为【大模型】;

            3、到此为止,嵌套的两个模型创建成功了。

            4【注意】在创建模型的时候,一定要从【小模型】开始创建,因为在【大模型】中要用到【小模型】,还需要【注意】的一点就是,【小模型】所描述的字典需要在【大模型】的初始化方法中转换为【小模型】


具体请看以下【示例程序】:

【小模型】

声明文件

#import <Foundation/Foundation.h>


@interface JOANCarsModel : NSObject

@property (nonatomic,copy) NSString *name;

@property (nonatomic,copy) NSString *icon;


- (instancetype)initWithDict:(NSDictionary *)dict;

+ (instancetype)carModelWithDict:(NSDictionary *)dict;


@end


实现文件

#import "JOANCarsModel.h"


@implementation JOANCarsModel

- (instancetype)initWithDict:(NSDictionary *)dict {

   if (self = [super init]) {

        [self setValuesForKeysWithDictionary:dict];

    }

    return self;

}


+ (instancetype)carModelWithDict:(NSDictionary *)dict {

   return [[self alloc] initWithDict:dict];

}

@end


【大模型】

声明文件

#import <Foundation/Foundation.h>

#import "JOANCarsModel.h"


@interfaceJOANGroupsModel : NSObject

@property (nonatomic,strong) NSArray *cars;

@property (nonatomic,copy) NSString *title;


- (instancetype)initWithDict:(NSDictionary *)dict;

+ (instancetype)groupWithDict:(NSDictionary *)dict;

@end


实现文件

#import "JOANGroupsModel.h"


@implementationJOANGroupsModel

- (instancetype)initWithDict:(NSDictionary *)dict {

   if (self = [super init]) {

        [self setValuesForKeysWithDictionary:dict];

        

        NSMutableArray *cars = [NSMutableArray array];

       for (NSDictionary *dict in self.cars) {

           JOANCarsModel *car = [JOANCarsModel carModelWithDict:dict];

            [cars addObject:car];

        }

       self.cars = cars;

        

    }

    return self;

}


+ (instancetype)groupWithDict:(NSDictionary *)dict {

   return [[self alloc] initWithDict:dict];

}

@end


【注意】注意理解大模型中初始化方法的写法,【为什么要这样写?】【因为在控制器中不会去直接使用小模型,他使用的是大模型,如果需要使用小模型也是使用大模型间接的去使用】


请再观察以下此时的懒加载的写法:


- (NSArray *)groupArray {

   if (_groupArray == nil) {

        NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"cars_total.plist" ofType:nil]];

        

        NSMutableArray *tempArray = [NSMutableArray array];

       for (NSDictionary *dict in array) {

           JOANGroupsModel *group = [JOANGroupsModel groupWithDict:dict];

            [tempArray addObject:group];

        }

        _groupArray = tempArray;

        

    }

   return _groupArray;

}


可以发现懒加载的写法和一个模型的懒加载的写法完全的一样,【为什么会这样?因为小模型在大模型中被处理了,根本不需要外界再对它进行处理,也是说内层嵌套的那个字典在大模型内部初始化的时候就自动转换为模型了,外界懒加载的时候只需要把大模型对应的字典转换为大模型就可以了】。




关于字典转模型和懒加载的讨论,over。



2⃣️代理模式

    代理存在的条件,①被代理的对象②代理对象③被代理对象规定的协议


    被代理对象做的三件事:①规定协议

                      ②包含一个代理属性(需要是id类型的,并且遵循代理协议)

                      ③有一个事件可以触发协议中的代理事件(就是在一个方法中调用代理属性的代理方法)


    代理对象做的三件事: ①遵循协议

                    ②设置代理对象

                     ③实现协议中的方法


补充1:一般在控制器中都会把代理对象设置为控制器本身,也是就self

例如:self.tableView.delegate =self;

        这句话的意思就是:设置当前控制器中tableview控件的代理对象是当前的控制器(self)


补充2:在写协议的时候,一般要把被代理对象当做一个参数传入,如果方法还需要其他的参数,就再方法的后面继续声明参数。【不要问我为甚么非要把被代理对象当做参数,因为这是规范,苹果官方就是这样写的,回忆一下你所实现的所有的代理方法,都有把被代理对象作为参数的情况。】【我的理解,因为这样可以在需要的时候调用被代理对象的方法,或者可以拿到被代理对象的属性】。


【最后补充一点】,在什么时候我们需要用到代理去做事情?当在自己的类中很难去实现一件事情,或者说可以实现的事情不是当前类的【职责】范围内,这时候就可以使用代理,至于选择谁去做我的代理,这个时候就需要考虑,谁来做这件事请比较方便,或者说这件事是谁的【职责】所在,这时候就可以找它来做代理,这时候就需要在自己类中声明协议,增加代理属性即可。



【再补充一点】再声明协议的时候,协议名一般是【类名后跟上delegate】的形式,

声明的代理属性是@property (nonatomic,weak) id<遵循的协议> delegate;


【注意】delegate属性一定要用【weak】来修饰,不能用【strong】,否则会引起循环引用的问题。


【最后一点】 代理一般是一对一的关系,而通知是多对多的关系。


关于代理模式的浅浅讨论,over。



3⃣️关于UItableView的数据源的简单讨论

    1、想要控制器成为tableview的数据源,要先让控制器遵循tableview的数据源协议

        <UITableViewDataSource>

       然后在- (void)viewDidLoad方法中设置tableview的数据源:self.tableView.dataSource = self;

        (当然也可以使用脱线的方法设置数据源,也可以设置代理,在这里不再赘述)

    2、是时候实现数据源协议中的方法来为tableview设置数据了。必须实现的方法有:

@required

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;


// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:

// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

可选实现的方法比较多

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;             // Default is 1 if not implemented



tableview的数据设置非常简单,只要按照正确的语法和操作,就能程成功让tableview显示你所要想显示的数据。


其他的一些属性的设置,在这里就不再赘述了。



【追加】//实现设置行高的代理方法

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

{

    

    LXLQQChatCellFrameModel *QQChatCellFrameModel =self.frameArray[indexPath.row]; 

    

   return QQChatCellFrameModel.rowHeight;

}

以上的代理方法,是设置tableview的行高的,想要实现这个方法,就需要让控制器遵循<UITableViewDelegate>的代理协议,就是<UITableViewDelegate>协议遵循了<UIScrollViewDelegate>协议,想要实现一些tableview的拖拽方法,就需要去<UIScrollViewDelegate>协议中查询。



【补充】为每行增加cell的时候需要用到cell的重用方法。不太好描述,直接上代码


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

   static NSString *ID = @"abc";

    

   JOANTgCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

   if (cell == nil) {

        cell = [JOANTgCell getCell];

    }

    

    //    获得一个模型

   JOANTgsModel *model = self.modelArray[indexPath.row];

    

    cell.model = model;

   return cell;

    

}


①首先声明一个重用的标志,他时静态类型的,目的是为了不重复为这个变量分配内存,延长了该变量的声明周期。【为什么要这么做?因为以上方法为被重复调用很多次,不需要每次都为声明的重用标志分配内存】

②调用[tableView dequeueReusableCellWithIdentifier:ID]方法,去重用缓冲池中查看手否有可以重用的cell,如果有就返回,没有就返回nil

③判断cell是否为空,如果为空就重新初始化一个cell

④为cell中的各个控件赋值

⑤返回cell


关于UITableView的数据源的一些简单讨论,over。




4⃣️关于自定义cell的简单讨论

   自定义cell很有意思。为什么说自定义cell很有意思呢,因为我们可以根据我们的需要想让cell中显示什么内容就显示什么内容。


一)通过xib文件自定义cell

    cell的样式是固定的,但是系统提供的无法满足我们的要求的时候,我们就可以选择通过xib的方法自定义cell


   步骤:1、创建一个空得xib文件,然后拖入一个UITableViewCell控件;

         2、根据需求在UITableViewCell中拖入需要得控件,然后设置相关的属性即可;在这里需要着重强调设置cell的重用标志。

        3、创建一个类来描述我们刚才创建的UITableViewCell,【注意】创建的类需要继承UITableViewCell这个类

         4、让描述类和刚才自定义的cell相关联,怎么让他们关联在一起?【在xib文件中选中UITableViewCell,然后在属性检测器窗口中把class修改为刚才创建的UITableViewCell的描述类即可】

         5、在描述类中增加一个模型对象的属性,然后重写模型对象的setter方法,在setter方法中为自定义的cell中的各个控件进行赋值。

         6、如果能力可以达到的话,就把获得自定义cell的步骤直接封装在与描述类同名的类方法中,看代码

+ (instancetype)getCell {

    //    加载自定义的xib文件,并且返回

    JOANTgCell *cell = [[[NSBundle mainBundle] loadNibNamed:@"JOANCell" owner:nil options:nil] lastObject];

   return cell;

}

         7、然后就是简单的为tableview设置数据源和实现数据源协议的几个简单的方法了。详情请看以上关于UITableView的数据源的讨论一节



关于通过xib文件自定义cell的方法,over。



二)通过纯手写代码的方式自定义cell

    使用纯代码方法自定义cell的情况就是,UITableView中的每个cell显示的数据可能不统一,这是就需要用纯代码的方式来自定义cell了。


    关于用纯代码的方法实现自定义的cell的方法,其实也很简单,跟用xib实现自定义cell的区别就是,xib使用拖控件的方式来布局自定义的cell的,而纯代码的方式是用代码的方式来布局自定义的cell的。思路都是一样的。


    1、用纯代码方式实现自定义的cell最少要有两个模型。①数据模型②frame模型

            i)数据模型使用来把字典转换为模型的,这就是简单的字典转模型。

            iiframe模型就是为自定义的cell中自定义的控件设置frame的,还有获得行高的。

    2、还需要新建一个类,让这个类来继承UITableViewCell,这个类所描述的就是我们要自定义的cell

       在类中要重写UITableViewCell - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier的方法,具体重写init方法的写法我就不再这里赘述了。

                   我想说的是,我们初始化这个方法的目的是做什么的。

        目的:①创建我们自定义的cell中可能会出现的所有的控件,并设置一些可以一次性设置的属性;

             ②把我们创建的控件添加到self.contentView中。这两个目的就是相当于在xib文件拖控件一样,只是在这里我们是用代码而已。


【注意】【接下来我们要先屡清楚四个对象之间的关系】哪四个对象呢?

       ①数据模型   frame模型    ③自定义的cell  ④控制器


        从左到右开始说:

        ①数据模型,他谁也管不了,只有一个作用,那就是把字典转换为一个模型;

        ②接下来是frame模型,他就比数据模型厉害,因为在它的内部会引用一个数据模型,【只要引用一个模型就要重写模型这个属性的setter方法】。那么在frame模型中我们都需要做什么事情呢,首先我们要声明一个数据模型的属性,因为我们需要数据模型中的数据;然后我们要声明跟控件数量一样多得CGRect类型的属性,因为我们要计算各个控件的frame,保存在对应的属性中,最后要复制给相对应的控件的frame

         最后要声明一个CGFloat类型的属性,这个用来记录cell的行高。

        【注意】,我们在外部是不允许随便修改控件的frame的,所以,在frame模型中,除了那个数据模型的属性外,其他的属性都要声明为readonly

         最后再frame中做的事情就非常简单了,就是在数据模型的setter方法中分别计算相应控件的frame,并且记录;

        ③自定义cell类就更加的厉害了,他可以直接或者间接的访问frame模型和数据模型。

            在自定义的cell类中,同样也需要引入一个frame模型类型的属性,并且需要重写他得setter方法。

           那么重写setter方法中需要做什么事情呢?

            i)为cell中定义的控件进行frame值的设置;

            ii)为cell中定义的控件进行赋值;

       最后我不得不重申一遍,一定要重写- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier这个方法。


        ④最后终于到压轴的出场了,那就是控制器,他控制了各种逻辑的实现,包括懒加载数据、作为tableview的数据源、作为tableview的代理等等,所有他是整个程序的控制者。

        所以通过观察以上的分析步骤,所有的东西都很简单,只要一步一步来,所有的东西都可以变的很简单。




1 0