項目重構(gòu)

前言

好孕幫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)

目錄結(jié)構(gòu).png

緩存及熱更新

緩存

列表頁如首頁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樣式闸与;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毙替,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子践樱,更是在濱河造成了極大的恐慌厂画,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拷邢,死亡現(xiàn)場離奇詭異袱院,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門忽洛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腻惠,“玉大人,你說我怎么就攤上這事欲虚〖啵” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵复哆,是天一觀的道長欣喧。 經(jīng)常有香客問我,道長梯找,這世上最難降的妖魔是什么唆阿? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮锈锤,結(jié)果婚禮上驯鳖,老公的妹妹穿的比我還像新娘。我一直安慰自己久免,他們只是感情好臼隔,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著妄壶,像睡著了一般摔握。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丁寄,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天氨淌,我揣著相機與錄音,去河邊找鬼伊磺。 笑死盛正,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的屑埋。 我是一名探鬼主播豪筝,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼摘能!你這毒婦竟也來了续崖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤团搞,失蹤者是張志新(化名)和其女友劉穎严望,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逻恐,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡像吻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年峻黍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拨匆。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡姆涩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惭每,到底是詐尸還是另有隱情阵面,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布洪鸭,位于F島的核電站样刷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏览爵。R本人自食惡果不足惜置鼻,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜓竹。 院中可真熱鬧箕母,春花似錦、人聲如沸俱济。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蛛碌。三九已至聂喇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蔚携,已是汗流浹背希太。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酝蜒,地道東北人誊辉。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像亡脑,于是被迫代替她去往敵國和親堕澄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容