MVC模型优化方案
UIViewController通常是项目中最大的文件,很容易包含极多重复的代码,代码复用率低。因此,需要特定的方法来让代码更容易被复用管理。
(1)把如dataSource、delegate等protocol协议分离出来
以UITableView为例子,项目中通常都要使用多个UITableView,但UITableView中的某些代码是重复的,若开发人员花时间写这些一模一样的代码,其实是一件非常浪费时间的事情,如下的UITableView的代码就是一段重复率非常高的代码。
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath{
static NSString *identification =@"hehe"; UITableViewCell *cell = [tableViewdequeueReusableCellWithIdentifier:identification]; if (cell == nil) { cell = [[UITableViewCellalloc]initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:identification]; }
cell.textLabel.text =@"喵";
return cell;
}
|
因此可以通过在别的对象中实现相关协议,从而把协议中需要实现的通用部分转移到别的对象中。并且通过block代码块将需要定制的部分实现即可。
以下是一个实现UITableView的dataSource的例子:
//.h文件部分
#import<Foundation/Foundation.h>
#import<UIKit/UIKit.h> //这里需要引入<UIKit/UIKit.h>,不然无法调用<UITableViewDataSource>接口
@interfaceArrayDataSource : 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(^)(idcell, id data)) configureCell; @end
|
//.m文件部分
#import"ArrayDataSource.h"
@implementationArrayDataSource - (instancetype)initWithItems:(id)items cellIdentifier:(NSString*)cellIdentifier configureCellBlock:(void(^)(id,id))configureCell{ if (self= [superinit]) { _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 = [tableViewdequeueReusableCellWithIdentifier:_cellIdentifierforIndexPath:indexPath]; id item = [selfitemAtIndexPath: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= [[ArrayDataSourcealloc]initWithItems:_dataSourcecellIdentifier:@"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 = [selfgetCommentsList]; //subView1:头像 self.icon = [[UIImageViewalloc]initWithFrame:CGRectMake(paddingLeft,paddingTop,44,44)]; self.icon.image = [LmsGetBuddyInfoHelperlmsIconImageWithID:modelData.staffNO]; self.icon.userInteractionEnabled = YES; UITapGestureRecognizer *tapGR = [[UITapGestureRecognizeralloc]initWithTarget:selfaction:@selector(jumpToPersonalList)]; [self.iconaddGestureRecognizer:tapGR]; //subView2:用户名 self.username =[[UIButtonalloc]initWithFrame:CGRectMake(paddingLeft + CGRectGetWidth(_icon.frame)+intervalLenght, paddingTop,200,20)]; [self.usernamesetTitle:modelData.staffNameforState:UIControlStateNormal]; [self.usernamesetTitleColor:[UIColorredColor]forState:UIControlStateNormal]; [self.usernameaddTarget:selfaction:@selector(jumpToPersonalList)forControlEvents:UIControlEventTouchUpInside]; [self.usernamesetContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft]; //subView3:最后更新时间 self.lastUpdateTime = [[UILabelalloc]initWithFrame:CGRectMake(paddingLeft + CGRectGetWidth(_icon.frame)+intervalLenght, paddingTop +CGRectGetHeight(_username.frame),200,20)]; self.lastUpdateTime.text = modelData.updateDate; //subView4:日志内容 self.logContentView = [selfsetupLogContentViewWithContent: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 = [selfsetupCommentsListWithCommentsArray:_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 = [selfsetupCommandBarView]; 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.contentViewaddSubview:_icon]; [self.contentViewaddSubview:_username]; [self.contentViewaddSubview:_lastUpdateTime]; [self.contentViewaddSubview:_logContentView]; [self.contentViewaddSubview:_commentsView]; [self.contentViewaddSubview:_commandBarView]; //设置cell frame CGFloat width = [UIScreenmainScreen].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 = [NSDatedateWithTimeIntervalSince1970:intervalTime]; return retDate; } //从制定的formatter中获取日期字符串 -(NSString *) getStringFromFormatter:(NSString *) formatter{ NSDateFormatter *dateFormatter = [[NSDateFormatteralloc]init]; dateFormatter.dateFormat = formatter; NSString *retStr = [dateFormatterstringFromDate:self]; return retStr; } //当前时区修正 -(NSDate *) getLocalZoneDate{ //时区修正 NSTimeZone *sysZone = [NSTimeZonesystemTimeZone]; NSInteger interval = [sysZonesecondsFromGMTForDate:self]; NSDate *localDate = [selfdateByAddingTimeInterval:interval]; return localDate; } @end
|
从上述代码可以看出上面的三个方法分别实现计算与本对象偏移若干天的NSDate对象、从制定的formatter中获取日期字符串、当前时区修正,在项目中这三个方法都是必须重复使用的,即使可以通过私有方法的形式实现,但这样一来就会增加UIViewController需要实现的代码量,而且在别的UIViewController需要调用这些方法只能通过再在自身文件中实现多一次这些方法,这无疑不是一个很好的解决方案。因此最好的方法就是通过category实现,一来让UIviewcontroller的代码风格更加简洁,二来代码的复用变得更加简单。
(4)总结
通过上述的三种方法可以一定程度低减少UIviewcontroller的代码量,并提高代码的复用率,因此下次还需要实现类似的MVC模型,就能直接调用以上的通用的方法快速实现。
当然,还有很多方法可以更加优化UIViewController,项目中并没有使用就不一一详述了。