MVC模型优化方案

MVC模型优化方案
          UIViewController通常是项目中最大的文件,很容易包含极多重复的代码,代码复用率低。因此,需要特定的方法来让代码更容易被复用管理。

(1)把如dataSource、delegate等protocol协议分离出来
     以UITableView为例子,项目中通常都要使用多个UITableView,但UITableView中的某些代码是重复的,若开发人员花时间写这些一模一样的代码,其实是一件非常浪费时间的事情,如下的UITableView的代码就是一段重复率非常高的代码。
    
- ( UITableViewCell *)tableView:( UITableView *)tableView cellForRowAtIndexPath:( NSIndexPath *)indexPath{
   
static NSString *identification = @"hehe" ;
   
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier :identification];
   
if (cell == nil ) {
        cell = [[
UITableViewCell alloc ] initWithStyle : UITableViewCellStyleDefault reuseIdentifier :identification];
    }
    cell.textLabel.text =@"";
    return cell;
}
       因此可以通过在别的对象中实现相关协议,从而把协议中需要实现的通用部分转移到别的对象中。并且通过block代码块将需要定制的部分实现即可。
       以下是一个实现UITableView的dataSource的例子:
 
//.h文件部分
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>      //这里需要引入 <UIKit/UIKit.h>,不然无法调用 < UITableViewDataSource >接口

@interface ArrayDataSource : NSObject < UITableViewDataSource >

@property ( readonly , nonatomic ) NSArray *items;               //items表示通用的数据结构
@property ( readonly , nonatomic ) NSString *cellIdentifier;
@property ( readonly , nonatomic ) void (^configureCell)( id , id );

-(
instancetype ) initWithItems:( id ) items
               cellIdentifier:(
NSString *) cellIdentifier
           configureCellBlock:(
void (^)( id cell, id data)) configureCell;

@end
 
//.m文件部分
#import "ArrayDataSource.h"

@implementation ArrayDataSource

- (
instancetype )initWithItems:( id )items cellIdentifier:( NSString *)cellIdentifier configureCellBlock:( void (^)( id , id ))configureCell{
   
if ( self = [ super init ]) {
       
_items = items;
       
_cellIdentifier = cellIdentifier;
       
_configureCell = configureCell;
    }
    return self ;
}

- ( id )itemAtIndexPath:( NSIndexPath *) indexPath{
   
return _items [( NSUInteger )indexPath. row ];
}

- (
NSInteger )tableView:( UITableView *)tableView numberOfRowsInSection:( NSInteger )section{
   
return _items . count ;
}

- (
UITableViewCell *)tableView:( UITableView *)tableView cellForRowAtIndexPath:( NSIndexPath *)indexPath{
   
id cell = [tableView dequeueReusableCellWithIdentifier : _cellIdentifier forIndexPath :indexPath];
   
id item = [ self itemAtIndexPath :indexPath];
   
    //自定义方法
    _configureCell(cell,item);    //通过调用指向block的指针,实现不同类型的cell的定制部分
   
   
return cell;
}

@end

实现了以上的dataSource类之后,可以通过在需要调用该方法来实现简化的地方实现以下代码:

- ( void )viewDidLoad {
    [superviewDidLoad];
    self.dataSource = [@[@"a",@"b",@"c",@"d",@"e"]mutableCopy];    
    [self.tableViewregisterClass:[UITableViewCellclass] forCellReuseIdentifier:@"cell"];

    //设置一个指向block的指针,并设定其指向cell的定制初始化的block
    void (^configureCell)( id , id ) = ^( UITableViewCell *cell, NSString *data){
        cell.
textLabel . text = data;
    };
    
    //通过在初始化方法中,设置该cell的数据、cell复用id、自定义初始化的block指针
    self . temp = [[ ArrayDataSource alloc ] initWithItems : _dataSource cellIdentifier : @"cell" configureCellBlock :configureCell];
    //将tableView的dataSource设置为目标对象,那么当UIView需要填充数据时就会到temp对象中调用相应的方法实现数据显示
    self . tableView . dataSource = _temp ;
}
此处有一个问题必须要注意,
@property ( nonatomic , assign )   id < UITableViewDataSource > dataSource;
@property ( nonatomic , assign )   id < UITableViewDelegate >   delegate;
从库中可得, dataSource、 delegate的property属性的修饰词为 assign,因此若你定义实现dataSource的对象是局部的,那么当对象度过了生命周期后,就自动释放,而UIView依然向该对象的指针地址发送消息,就会发生野指针错误。

(2)更加深度的分离model与view的耦合度,实现更优质的MVC模式
 如果理解不好MVC模式,那么就很容易让view的实现与model的管理全集中在controller中,这样的代码风格是十分糟糕的,因此,必须要尽量降低model、view、controller三者的耦合度。
 iOS中的MVC模型图:
 
 在图中,可以清晰地看出三者是分开,并且通过不同的访问机制通信,因此在设计中最好也遵从上图的原则。
 在lms项目中的日志管理模块中,cell均需要自定义的,而且由于布局的灵活,必须根据model来决定布局,因此必须通过代码来实现cell。
以下是logCell的部分实现代码:
@implementation LogCell

-(
void ) setupStatusFrameWithModel:( LMSListElement *)modelData{
   
   
// 设置 cell model
   
self . dataModel = modelData;
   
   
// 设置 comments data source
   
self . commentsDataSource = [ self getCommentsList ];
   
   
//subView1: 头像
   
self . icon = [[ UIImageView alloc ] initWithFrame : CGRectMake ( paddingLeft , paddingTop , 44 , 44 )];
   
self . icon . image = [ LmsGetBuddyInfoHelper lmsIconImageWithID :modelData. staffNO ];
   
self . icon . userInteractionEnabled = YES ;
   
UITapGestureRecognizer *tapGR = [[ UITapGestureRecognizer alloc ] initWithTarget : self action : @selector (jumpToPersonalList)];
    [
self . icon addGestureRecognizer :tapGR];
   
   
//subView2: 用户名
   
self . username =[[ UIButton alloc ] initWithFrame : CGRectMake ( paddingLeft + CGRectGetWidth ( _icon . frame )+ intervalLenght ,
                                                            
paddingTop , 200 , 20 )];
    [
self . username setTitle :modelData. staffName forState : UIControlStateNormal ];
    [
self . username setTitleColor :[ UIColor redColor ] forState : UIControlStateNormal ];
    [
self . username addTarget : self action : @selector (jumpToPersonalList) forControlEvents : UIControlEventTouchUpInside ];
    [
self . username setContentHorizontalAlignment : UIControlContentHorizontalAlignmentLeft ];
   
   
   
//subView3: 最后更新时间
   
self . lastUpdateTime = [[ UILabel alloc ] initWithFrame : CGRectMake ( paddingLeft + CGRectGetWidth ( _icon . frame )+ intervalLenght ,
                                                                   
paddingTop + CGRectGetHeight ( _username . frame ), 200 , 20 )];
   
self . lastUpdateTime . text = modelData. updateDate ;
   
   
//subView4: 日志内容
   
self . logContentView = [ self setupLogContentViewWithContent :modelData. contentDataArray ];
   
   
CGRect logView = _logContentView . frame ;
    logView.
origin . x = paddingLeft ;
    logView.
origin . y = _icon . frame . origin . y + CGRectGetHeight ( _icon . frame ) + intervalLenght ;
   
self . logContentView . frame = logView;
   
   
//subView5: 评论列表 ( 需要根据是否点击了评论来显示 )
   
self . commentsView = [ self setupCommentsListWithCommentsArray : _commentsDataSource ];
   
   
CGRect commentsFrame = _commentsView . frame ;
    commentsFrame.
origin . x = 0 ;
    commentsFrame.
origin . y = _logContentView . frame . origin . y + CGRectGetHeight ( _logContentView . frame ) + intervalLenght ;
   
self . commentsView . frame = commentsFrame;
   
   
//subView6: 控制条 , 包括分享、点赞、点差、评论
   
self . commandBarView = [ self setupCommandBarView ];
   
   
CGRect commandFrame = _commandBarView . frame ;
    commandFrame.
origin . x = paddingLeft ;
   
// 评论为空的处理方法
   
if ( _commentsView == nil ) {
        commandFrame.
origin . y = _logContentView . frame . origin . y + CGRectGetHeight ( _logContentView . frame ) + intervalLenght ;
    }
else {
   
// 评论不为空的处理方法
        commandFrame.
origin . y = _commentsView . frame . origin . y + CGRectGetHeight ( _commentsView . frame ) + intervalLenght ;
    }
   
   
self . commandBarView . frame = commandFrame;
   
   
// 添加子图进 cell
    [
self . contentView addSubview : _icon ];
    [
self . contentView addSubview : _username ];
    [
self . contentView addSubview : _lastUpdateTime ];
    [
self . contentView addSubview : _logContentView ];
    [
self . contentView addSubview : _commentsView ];
    [
self . contentView addSubview : _commandBarView ];
   
   
// 设置 cell frame
   
CGFloat width = [ UIScreen mainScreen ]. bounds . size . width ;
   
CGFloat height = paddingTop + intervalLenght * 3 + CGRectGetHeight ( _icon . frame )+ CGRectGetHeight ( _logContentView . frame )+ CGRectGetHeight ( _commentsView . frame )+ CGRectGetHeight ( _commandBarView . frame );
   
self . frame = CGRectMake ( 0 , 0 , width, height);
   
}
。。。。
     从上述的代码,可以看出logCell实现了cell图中的各个子视图的显示,因此只需要把数据model传进来即可实现cell的自定义显示。而且logCell还实现了各种交互行为的处理,只有当这些交互行为已经设计model数据的更新时,就尊崇MVC模型,通过delegate、dataSource、notification、target-action等通信方法告知controller实现数据的更新。
     需要注意的是,直接使用stroyBoard、xib实现的视图都是固定的不能修改布局、大小等。因此若布局很灵活时只能通过代码实现,又或者通过多个xib实现局部静态部分并且与代码结合实现灵活布局,后者代码量较少,可以加快开发速度,更加推荐。

(3)把 与某些类相关的方法可以转移到categroy类
     
#import <Foundation/Foundation.h>

@interface NSDate (LmsDateMethod)
- (NSDate *) dateWithOffsetDays:(NSUInteger) days;
-( NSString *) getStringFromFormatter:( NSString *) formatter;
-( NSDate *) getLocalZoneDate;
@end

#import "NSDate+LmsDateMethod.h"

@implementation NSDate (LmsDateMethod)

// 计算与本对象偏移若干天的 NSDate 对象
- (
NSDate *) dateWithOffsetDays:( NSUInteger ) days{
   
NSTimeInterval intervalTime = self . timeIntervalSince1970 -( 60 * 60 * 24 *days);
   
NSDate *retDate = [ NSDate dateWithTimeIntervalSince1970 :intervalTime];
   
return retDate;
}

// 从制定的 formatter 中获取日期字符串
-(
NSString *) getStringFromFormatter:( NSString *) formatter{
   
NSDateFormatter *dateFormatter = [[ NSDateFormatter alloc ] init ];
    dateFormatter.
dateFormat = formatter;
   
NSString *retStr = [dateFormatter stringFromDate : self ];
   
return retStr;
}

// 当前时区修正
-(
NSDate *) getLocalZoneDate{
   
// 时区修正
   
NSTimeZone *sysZone  = [ NSTimeZone systemTimeZone ];
   
NSInteger interval = [sysZone secondsFromGMTForDate : self ];
   
NSDate *localDate = [ self dateByAddingTimeInterval :interval];
   
return localDate;
}

@end
从上述代码可以看出上面的三个方法分别实现 计算与本对象偏移若干天的 NSDate 对象、 从制定的 formatter 中获取日期字符串、 当前时区修正,在项目中这三个方法都是必须重复使用的,即使可以通过私有方法的形式实现,但这样一来就会增加UIViewController需要实现的代码量,而且在别的 UIViewController需要调用这些方法只能通过再在自身文件中实现多一次这些方法,这无疑不是一个很好的解决方案。因此最好的方法就是通过category实现,一来让UIviewcontroller的代码风格更加简洁,二来代码的复用变得更加简单。

(4)总结
         通过上述的三种方法可以一定程度低减少UIviewcontroller的代码量,并提高代码的复用率,因此下次还需要实现类似的MVC模型,就能直接调用以上的通用的方法快速实现。
          当然,还有很多方法可以更加优化UIViewController,项目中并没有使用就不一一详述了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值