iOS架構(gòu)初探

Demo傳送門
此架構(gòu)是本人根據(jù)自己的工作經(jīng)驗及網(wǎng)絡(luò)了解外加同事的意見整理出來的

一千個觀眾眼中有一千個哈姆雷特(There are a thousand Hamlets in a thousand people's eyes.)-- 莎士比亞

一千個程序員中眼里有一千種架構(gòu) (There are a thousand framework in a thousand developer's eyes.)-- BlackStar

和莎士比亞并列有木有很高端, 哈哈哈 有點(diǎn)裝逼了 sorry



言歸正傳,由于老大要求重構(gòu)項目,讓我們研究下架構(gòu),那好吧,雖然啥都不懂,但是對這方面還是希望有所了解的,所以 baidu、google、github ...

經(jīng)過一番 查找 研究,發(fā)現(xiàn)嗯...

架構(gòu)這東西就是為了更好的貼合業(yè)務(wù),便于測試,查找問題,修改,后續(xù)他人接手

反正好處一堆一堆的,但是我是小白啊,一次怎么可能弄那么高端,那就別好高騖遠(yuǎn),先整個初級的,然后經(jīng)過一周的研究,整理出來這個模式。我稱之為 BSMVVM,BS就是個人昵稱BlackStar,之所以這么叫就是因為這個架構(gòu)只是我自己的MVVM,并不是網(wǎng)絡(luò)流傳的MVVM。

UI上全部采用 Masonry 布局,個別需要用到frame的靈活運(yùn)用
Model 全部采用 YYModel 進(jìn)行解析

目錄結(jié)構(gòu):

  • ViewController 處理交互相關(guān)
  • View 單純視圖
  • Model 數(shù)據(jù)模型
  • ViewModel 數(shù)據(jù)展示
  • DataManager 數(shù)據(jù)請求、緩存處理

目前由于老大的要求,我們建立了很多 Base基類 包含ViewController、Model、ViewModel、DataManager,其實(shí)View 也是有的,但是感覺沒有用到,就沒有用,先說下Base里做的事情

  • ViewController tableview 初始化、加載更多、下拉刷新、網(wǎng)絡(luò)請求的加載框等功能都在這里做了,子類只需要調(diào)用父類方法就可以,反正老大的要求就是能提到父類的就都提到父類
    但是我個人并不喜歡這樣做,因為基類會大大增加文件之間的耦合性,如果要做一些公共的東西可以使用category,但是并不能達(dá)到完全解耦的目的,模塊間或多或少都會有業(yè)務(wù)上的聯(lián)系,我們不能因為想要做模塊分離就忽略其帶來的不便,說到底,我們的架構(gòu)是服務(wù)于業(yè)務(wù)的,所以要以業(yè)務(wù)為主,由于找不到更好的方案處理,所以就還是按照老大的要求來了

  • View 這一層只是做視圖處理,不綁定任何數(shù)據(jù),目的就是為了view&model徹底分離,這樣的好處是復(fù)用,UI測試,由于視圖往往跟隨者交互,那么如何處理交互成了需要解決的問題,我們需要考慮點(diǎn)擊后進(jìn)行網(wǎng)絡(luò)請求、進(jìn)行數(shù)據(jù)變更、進(jìn)行提示框彈出、頁面跳轉(zhuǎn)等多種情況,但是很多事情其實(shí)在View里做并不合適,所以決定所有的交互在VC層做,那么View的事件如何傳遞到VC層呢,三種方案:block、protocol、在vc層通過.view.button addTarget 方式執(zhí)行交互,但是考慮到block 的設(shè)計問題(最大的問題:查找bug太費(fèi)勁)所以棄用,如果通過 .view.button 的方式呢,可以做,但是我們有的事情是在view里直接處理的,比如說點(diǎn)擊按鈕數(shù)量+1,這種可能不需要其他的判斷或網(wǎng)絡(luò)交互,那么我們就需要在view 和vc層分別處理不同的事情,這樣與我設(shè)計的代碼規(guī)范不一致(職責(zé)劃分),所以最終采用protocol方式處理,protocol使用時,代碼上要注意,調(diào)用的時候需要做

if ([self.delegate respondsToSelector:@selector()]) {
    [self.delegate ...];
}
  • Model 數(shù)據(jù)模型,自身屬性+數(shù)據(jù)驗證
  • ViewModel 只做數(shù)據(jù)展示,那么如何把 需要展示數(shù)據(jù)的view 和 Model 關(guān)聯(lián)起來呢,直接用一個方法把兩個參數(shù)傳進(jìn)來即可
    示例代碼:
    .h文件
#import "SQBaseViewModel.h"

@class SQPropertyPaymentDetailCell;
@class SQProPayHistoryHeaderView;

@interface SQProPayViewModel : SQBaseViewModel

-(void)propertyPaymentDetailHeaderView:(SQProPayHistoryHeaderView*)headerView houseInfo:(NSDictionary *)houseInfo searchType:(NSString *)searchType;

-(void)propertyPaymentDetailCell:(SQPropertyPaymentDetailCell *)cell  cellInfoKey:(NSString*)key cellInfoValue:(NSString*)value;

@end

.m文件

#import "SQProPayViewModel.h"
#import "SQPropertyPaymentDetailCell.h"
#import "SQProPayHistoryHeaderView.h"

@implementation SQProPayViewModel

-(void)propertyPaymentDetailCell:(SQPropertyPaymentDetailCell *)cell cellInfoKey:(NSString *)key cellInfoValue:(NSString *)value{
    
    if ([key isEqualToString:@"watermarkImg"]) {
        if ([value isEqualToString:@"1"]) {
            cell.watermarkImg.image = [UIImage imageNamed:@"pay_finish_recieve"];
        }else{
            cell.watermarkImg.image = [UIImage imageNamed:@"pay_finish_refund"];
        }
    }else{
        
        if ([key isEqualToString:@"houseLabel"]) {
            
            NSMutableAttributedString *attr = [[NSMutableAttributedString alloc]initWithString:[NSString stringWithFormat:@"房間:%@",value?:@""]];
            [attr addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithHexString:@"666666"] range:NSMakeRange(0, 3)];
            cell.houseLabel.attributedText = attr;
            
        }else if ([key isEqualToString:@"houseKeeper"]){
            
            NSMutableAttributedString *attr = [[NSMutableAttributedString alloc]initWithString:[NSString stringWithFormat:@"業(yè)主:%@",value?:@""]];
            [attr addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithHexString:@"666666"] range:NSMakeRange(0, 3)];
            cell.houseKeeper.attributedText = attr;
            
        }else if ([key isEqualToString:@"telLabel"]){
            
            NSMutableAttributedString *attr = [[NSMutableAttributedString alloc]initWithString:[NSString stringWithFormat:@"手機(jī):%@",value?:@""]];
            [attr addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithHexString:@"666666"] range:NSMakeRange(0, 3)];
            cell.telLabel.attributedText = attr;
            
        }else if ([key isEqualToString:@"timeLabel"]){
            
            NSMutableAttributedString *attr = [[NSMutableAttributedString alloc]initWithString:[NSString stringWithFormat:@"收費(fèi)時間:%@",value?:@""]];
            [attr addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithHexString:@"666666"] range:NSMakeRange(0, 5)];
            cell.timeLabel.attributedText = attr;
            
        }else{
            
            NSMutableAttributedString *attr = [[NSMutableAttributedString alloc]initWithString:[NSString stringWithFormat:@"收費(fèi)人:%@",value?:@""]];
            [attr addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithHexString:@"666666"] range:NSMakeRange(0, 4)];
            cell.tollCollector.attributedText = attr;
        }
    }
}


-(void)propertyPaymentDetailHeaderView:(SQProPayHistoryHeaderView *)headerView houseInfo:(NSDictionary *)houseInfo searchType:(NSString *)searchType{

    if (!houseInfo) return;
    
    if ([searchType isEqualToString:@"1"]) {

        NSString *addresStr = [NSString stringWithFormat:
                               @"%@%@%@%@%@%@%@%@",
                               houseInfo[@"manageAreaName"],
                               houseInfo[@"projectName"],
                               houseInfo[@"buildingNumber"],
                               houseInfo[@"buildingName"],
                               houseInfo[@"unitNumber"],
                               houseInfo[@"unitName"],
                               houseInfo[@"houseNumber"],
                               houseInfo[@"houseName"]];
        
        headerView.addressLabel.text = addresStr;
    }else{
        
        headerView.nameLabel.text = [NSString stringWithFormat:@"業(yè)主:%@   %@",houseInfo[@"name"],houseInfo[@"phone"]];
    }
}

@end

其中需要注意的點(diǎn):

  1. .h文件中使用@class引入,告訴編譯器這個是個類,不要報錯,然后在.m中 #import , 這樣做的目的就是減少編譯時間
  2. 要使用此方式,需要所有的 view 將屬性(控件)全部聲明在.h 中,以便調(diào)用和 復(fù)用的時候UI 微調(diào)。之所以這么設(shè)計是參考了storyboard的設(shè)計理念。xcode創(chuàng)建出來的view 在.m中是沒有@interface的,意思就是建議我們將 視圖屬性添加在.h中,所以我們在用storyboard做view的構(gòu)建時,都是拉線到.h中,只有viewcontroller的才會在.m中。而且這么設(shè)計最大的好處是復(fù)用,不是所有UI都是一樣的設(shè)計,但可能很相近,我們做一個控件應(yīng)該盡可能的讓其勝任多種情況。
  • DataManager 數(shù)據(jù)管理層,這一層其實(shí)做的并不好,因為這里我采用的是block 傳值,這種方式最大的缺點(diǎn)是會延長controller 的生命周期,但是當(dāng)時做的時候只考慮了 代碼追蹤問題,所以不是很理想。這一層主要的代碼都在基類里邊,封裝了緩存方法,還有一些 接口調(diào)用時的公用方法。在使用的時候很簡單 這里不貼Base了
    示例代碼:
#import "SQBaseDataManager.h"

@class SQPropertyPaymentListModel;
@class SQProPaymentPreviewModel;
@class SQProPayHistoryModel;
@class SQProPayBillPreviewDetailModel;
@class SQProPreviewPayInfoModel;

@interface SQPropertyPaymentDataManager : SQBaseDataManager

@property (nonatomic,strong) SQPropertyPaymentListModel *proPaymentListModel;       //物業(yè)繳費(fèi)列表
@property (nonatomic,strong) SQProPaymentPreviewModel *proPaymentPreviewModel;      //物業(yè)繳費(fèi)預(yù)覽模型
@property (nonatomic,strong) SQProPayHistoryModel *historyModel;                    //物業(yè)繳費(fèi)歷史記錄模型
@property (nonatomic,strong) SQProPayBillPreviewDetailModel *tollModel;             //繳費(fèi)單詳情模型
@property (nonatomic,strong) SQProPreviewPayInfoModel *payInfoModel;                //繳費(fèi)信息模型

/**
 獲取物業(yè)繳費(fèi)單

 @param addressId 房屋對硬的地區(qū)ID
 @param categoryId  1:常規(guī)費(fèi)用 | 2:臨時費(fèi)用
 */
-(void)getPropertyPaymentFeeListWithAddressId:(NSString *)addressId categoryId:(NSString *)categoryId block:(void(^)(SQPropertyPaymentListModel *classifyModel,NSString* errMsg))block;

.m文件

-(void)getPropertyPaymentFeeListWithAddressId:(NSString *)addressId categoryId:(NSString *)categoryId block:(void (^)(SQPropertyPaymentListModel *, NSString *))block{
    
    NSString *url = [NSString stringWithFormat:@"%@/api/apps/bpps/bpp/bill/roomFeeSum",LocalCfg.SQ_PROPERTY_BASE];
    
    NSDictionary *param = @{@"addressId":addressId,
                            @"categoryId":categoryId};
    
    SQLeApiRequest *request = [SQLeApiRequest requestWithUrl:url param:param];
    request.requestMethod = REQUEST_GET;
    [request setLeHeader:@{@"Authorization":@"yes"} needSign:NO needAccessToken:YES];
    
    [request startRequestWithSuccess:^(SQApiResponse *response) {
       
        SQPropertyPaymentListModel *classifyModel = [SQPropertyPaymentListModel modelWithOriginData:response.responseObject];
        self.proPaymentListModel = classifyModel;
        block(classifyModel,nil);
        
    } failure:^(NSError *error) {
        block(nil,@"請求失敗,請重試");
    }];    
}

  1. 設(shè)計考慮到數(shù)據(jù)模型使用問題,因為dataManager 是要在VC層聲明的,所以將model聲明在了dataManager里,網(wǎng)絡(luò)回調(diào)后在dataManager里直接賦值,包含加載更多后的數(shù)據(jù)拼接等,都可以在dataManager里去實(shí)現(xiàn),幫controller分擔(dān)了數(shù)據(jù)整合的問題
  2. 緩存的調(diào)用也是在dataManager里實(shí)現(xiàn)的,在調(diào)用方法的同時block先回調(diào)緩存的數(shù)據(jù),數(shù)據(jù)回來后,在此block回調(diào)接口數(shù)據(jù)

目前總結(jié)的差不多就是這此研究架構(gòu)的一些心得,在開發(fā)中我們需要便捷開發(fā),快速開發(fā),很多重復(fù)性的工作我們應(yīng)該盡可能的只做一遍,所以像view層的 protocol 我這里都是創(chuàng)建的時候自帶的,不用一遍一遍去聲明。

Demo傳送門
如何創(chuàng)建文件自動生成我們常用的代碼,點(diǎn)擊進(jìn)入

對于架構(gòu)的初探還有一些沒有寫出來,比如說網(wǎng)絡(luò)層、工具層,后續(xù)可能會補(bǔ)也可能不補(bǔ)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容