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,项目中并没有使用就不一一详述了。