前言
好孕幫APP截止到現(xiàn)在已經(jīng)更新過數(shù)十個版本刮便,隨著需求的增多和功能的變化疙驾,原有的架構(gòu)已經(jīng)不再適合現(xiàn)有的業(yè)務邏輯贼急。原架構(gòu)采用MVC的設計模式,大量的代碼都放在了ViewController中或渤,有的ViewController中的代碼甚至達到了2000多行,導致MVC最終演變成了Massive View Controller奕扣。本次重構(gòu)旨在解決以下問題薪鹦,以期在以后的開發(fā)中能保證代碼質(zhì)量,降低維護成本。
???1池磁、ViewController中代碼過于臃腫奔害;
???2、代碼書寫不規(guī)范地熄;
???3华临、缺少緩存及熱更新策略;
???4端考、功能相近的三方庫的引用雅潭;
設計模式
在傳統(tǒng)的MVC設計模式中M負責數(shù)據(jù)封裝,View負責數(shù)據(jù)的展示却特,像網(wǎng)絡請求寻馏、數(shù)據(jù)緩存及大量的膠水代碼都放在了Viewcontroller中,導致Viewcontroller越來越臃腫核偿。MVP中新增了Presenter層負責網(wǎng)絡請求及數(shù)據(jù)緩存诚欠,減輕Viewcontroller的負擔。
MVC漾岳、MVP轰绵、MVVM模式的比較鏈接1 鏈接2
M層
數(shù)據(jù)組裝及一些簡單的數(shù)據(jù)處理,如時間戳轉(zhuǎn)時間
#import "PostDetailModel.h"
@implementation PostDetailModel
+ (instancetype)postDetailModelWithDict:(NSDictionary *)dict {
return [[PostDetailModel alloc] initWithDict:dict];
}
- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super init];
if (self) {
NSDictionary *authorDict = [dict objectForKey:@"author"];
self.avatarUrl = [authorDict objectForKey:@"avatar"];
self.contentStr = [dict objectForKey:@"content"];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self.avatarUrl = [aDecoder decodeObjectForKey:@"avatarUrl"];
self.contentStr = [aDecoder decodeObjectForKey:@"contentStr"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.avatarUrl forKey:@"avatarUrl"];
[aCoder encodeObject:self.contentStr forKey:@"contentStr"];
}
@end
View層
數(shù)據(jù)展示尼荆、點擊事件的傳遞
//PostDetailTableViewCell.h
@interface PostDetailTableViewCell : UITableViewCell
+ (instancetype)cellWithTableView:(UITableView *)tableView;
@property (nonatomic, strong) PostDetailModel *detailModel;
@end
//PostDetailTableViewCell.m
- (void)setDetailModel:(PostDetailModel *)detailModel {
_detailModel = detailModel;
self.contentLabel.text = detailModel.contentStr;
[self.avatarImageView setImageWithURL:[NSURL URLWithString:detailModel.avatarUrl]];
}
Presenter層
數(shù)據(jù)獲取及數(shù)據(jù)緩存,數(shù)據(jù)緩存采用YYCache
//PostDetailPresenter.h
@class PostDetailPresenter;
@protocol PostDetailPresenterDelegate <NSObject>
- (void)presenter:(PostDetailPresenter *)presenter status:(ResponseStatusModel *)statusModel error:(NSError *)error;
@end
@interface PostDetailPresenter : NSObject
@property (nonatomic, weak) id<PostDetailPresenterDelegate> delegate;
@property (nonatomic, strong) NSMutableArray<PostDetailModel *> *detailArray;
- (void)loadListData;
@end
//PostDetailPresenter.m
- (void)loadListData {
@weakify(self)
[NetworkManager postUrl:@"topic/info/58f821feee07cf0007f3bafb" type:NetWorkTypeForum params:self.requestParams callBack:^(id content, NSError *error) {
@strongify(self)
ResponseStatusModel *statusModel = [ResponseStatusModel modelWithDictionary:content];
if (!error) {
if (statusModel.status == 1) {
NSDictionary *dataDict = [content objectForKey:@"data"];
NSArray *replyArray = [dataDict objectForKey:@"replies"];
if (_refreshHead) {
[self.detailArray removeAllObjects];
}
NSMutableArray *dataArray = [NSMutableArray array];
for (NSDictionary *dict in replyArray) {
PostDetailModel *postDetailModel = [PostDetailModel postDetailModelWithDict:dict];
[dataArray addObject:postDetailModel];
}
if (dataArray.count > 0) {
[self.detailArray addObjectsFromArray:dataArray];
[self.cache setObject:dataArray forKey:[NSString stringWithFormat:@"PostDetailReplyCache%ld",_pageIndex]];
}
_pageIndex++;
}
} else {
if (_refreshHead) {
[self.detailArray removeAllObjects];
}
NSString *key = [NSString stringWithFormat:@"PostDetailReplyCache%ld",_pageIndex];
if ([self.cache containsObjectForKey:key]) {
NSArray *cacheArray = (NSArray *)[self.cache objectForKey:key];
[self.detailArray addObjectsFromArray:cacheArray];
}
_pageIndex++;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(presenter:status:error:)]) {
[self.delegate presenter:self status:statusModel error:error];
}
}];
}
Viewcontroller層
View左腔、Presenter初始化,點擊事件跳轉(zhuǎn)處理等等
- (instancetype)init {
self = [super init];
if (self) {
self.presenter = [[PostDetailPresenter alloc] init];
self.presenter.delegate = self;
}
return self;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
PostDetailTableViewCell *detailCell = [PostDetailTableViewCell cellWithTableView:tableView];
PostDetailModel *detailModel = self.presenter.detailArray[indexPath.row];
detailCell.detailModel = detailModel;
return detailCell;
}
#pragma mark - PostDetailPresenterDelegate
- (void)presenter:(PostDetailPresenter *)presenter status:(ResponseStatusModel *)statusModel error:(NSError *)error {
if (error) {
//有緩存顯示緩存捅儒,無緩存顯示錯誤信息
if (self.presenter.detailArray.count > 0) {
[self.listTableView reloadData];
}
} else {
if (statusModel.status == 1) {
[self.listTableView reloadData];
} else {
//顯示msg
}
}
}
代碼規(guī)范
1液样、多用#pragma mark - XXXXX便于代碼區(qū)域區(qū)分。
@interface TestViewController ()
{
NSInteger _testIndex;
}
@property (nonatomic, strong) UIButton *testButton;
@end
@implementation TestViewController
#pragma mark - life cycle
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
#pragma mark - CustomDelegate
- (void)xxxxxx {
}
#pragma mark - event response
- (void)yyyyyy {
}
#pragma mark - lazy load
- (UIButton *)testButton {
}
2巧还、減少#define的使用鞭莽,多使用如下方式:
const常量有數(shù)據(jù)類型,而宏常量沒有數(shù)據(jù)類型麸祷。編譯器可以對前者進行類型安全檢查澎怒,而對后者只進行字符替換,沒有類型安全檢查阶牍,并且在字符替換時可能會產(chǎn)生意料不到的錯誤(邊際效應)喷面。StackOverflow鏈接
//支付寶支付后的通知
extern NSString *const hybAliPayNotification;
//微信支付后的通知
extern NSString *const hybWeChatPayNotification;
//支付寶支付后的通知
NSString *const hybAliPayNotification = @"ReceiveAliPayNotification";
//微信支付后的通知
NSString *const hybWeChatPayNotification = @"ReceiveWeChatPayNotification";
3、@IBOutlet 的 didSet
如果我們由于某種原因走孽,確實需要在代碼中設置一些 view 的屬性惧辈,在連接 @IBOutlet 后,不少開發(fā)者會選擇在 viewDidLoad 中進行設置磕瓷。其實個人認為一個更合適的地方是在該 @IBoutlet 的 didSet 中進行盒齿。@IBoutlet 所修飾的也是一個屬性,這個關鍵詞所做的僅只是將屬性暴露給 IB,所以它的各種屬性觀察方法 (willSet县昂,didSet 等) 也會被正常調(diào)用肮柜。
@IBOutlet var myTextField: UITextField! {
didSet {
// Workaround for https://openradar.appspot.com/28751703
myTextField.layer.borderWidth = 1.0
myTextField.layer.borderColor = UIColor.lineGreen.cgColor
}
}
4、關于View的布局
1倒彰、純代碼或Xib或SB的方式都可以审洞,復雜界面推薦使用純代碼布局。關于SB
2待讳、純代碼方式用Autolayout可以考慮使用Masonry芒澜。
5、目錄結(jié)構(gòu)
緩存及熱更新
緩存
列表頁如首頁feed流创淡、圈子帖子列表及好孕醫(yī)院添加緩存痴晦,帖子詳情暫不緩存。
緩存策略:
1琳彩、網(wǎng)絡請求之前先加載緩存誊酌,若存在緩存,顯示緩存數(shù)據(jù)露乏。網(wǎng)絡請求成功后碧浊,更新緩存,刷新列表瘟仿,展示最新請求的數(shù)據(jù)箱锐。若不存在緩存且網(wǎng)絡請求失敗,展示空頁面提示劳较;
2驹止、只緩存首屏數(shù)據(jù),如第一次網(wǎng)絡請求20條观蜗,則緩存20條數(shù)據(jù)臊恋,主要是確保列表不為空;
3嫂便、根據(jù)緩存的帖子ID來區(qū)分已讀捞镰、未讀UI樣式闸与;