MVC模式的目的是實現一種動態的程式設計,使後續對程式的修改和擴充套件簡化,並且使程式某一部分的重複利用成為可能。除此之外,此模式通過對複雜度的簡化,使程式結構更加直觀
- 控制器(Controller)--> 負責轉發請求,對請求進行處理。
- 檢視(View) --> 介面設計人員進行圖形介面設計。
- 模型(Model) --> 程式設計師編寫程式應有的功能(實現演算法等等)、資料庫專家進行資料管理和資料庫設計(可以實現具體的功能)。
以上出自維基百科
資料,下面說點人為描述(簡單易懂的)~
- Model層: 資料處理層,包括網路請求,資料加工
- View層: 所有App上看得到的介面
- Controller層: Model 與 View層的中介,把Model資料在View上展示出來
- 目的: 低耦合,可複用
先看這張圖,這張圖是iOS的MVC
架構中最經常出現的圖了吧,因為IOS中的Controlller
是 UIViewController
,所以導致很多人會把檢視
寫在Controller
中,如下圖:
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//setupUI
//1.createView
UIView *view = [[UIView alloc]init];
view.frame = CGRectMake(100, 100, 100, 100);
view.backgroundColor = [UIColor orangeColor];
[self.view addSubview:view];
//2.createButton
UIButton *btn = [UIButton buttonWithType:UIButtonTypeInfoDark];
btn.center = self.view.center;
[self.view addSubview:btn];
//3...
}
複製程式碼
這種寫法在我剛蹭到iOS
的時候也這樣寫過,先說這樣寫的好處,以及初學者為什麼會這麼寫:
- 比如按鈕,可以在當前控制器直接
add target:
新增點選事件,在當前控制器內就能呼叫到點選方法,不需要設定代理之類的; - 比如要找某個介面,直接切到這個介面對應的
controller
就行,因為View
寫在Controller
裡面,不用去別的地方找,就這裡有; - 比如一個View,裡面有一張圖片,圖片依賴於網路資源,這樣寫的好處,可以直接讓
View
在Controller
中就能拿到資源,不需要傳值
缺點!!:
- 導致
Controller
特別臃腫,裡面程式碼特別多,檢視一複雜起來,程式碼量可能過1000行,不好維護 - 寫在
Controller
裡無法複用,除非你在 VC2裡面 copy 當前VC中的View
的程式碼 - 特別low!!會被懂架構的人瞧不起,噴你根本不是
MVC
,是MC
架構,可能還要你來段喊麥證明一下自己(-。-)
如何告別MC
模式,真正走到MVC
?
- 先給自己洗腦,
iOS
的Controller
不是UIViewController
,而是普通的Controller
,沒有View
。(很關鍵的一步) - 模組化劃分,每個模組對應自己的一個View,例如Demo2模組,View層裡面有個
Demo2View
,將介面元素寫到View中
知識1:如何傳值(引數)
//View
+ (instancetype)viewWithTitleStr:(NSString *)titleStr{
//do createView
//...
}
//Controller
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
/*setupUI*/
//1.createView - 引數通過`View`的函式作為外部引數傳進去
DemoView *view = [DemoView viewWithTitleStr:@"我是引數"];
[self.view addSubview:view];
}
複製程式碼
知識2:控制元件點選事件如何回撥給控制器
//View
@implementation DemoView
- (instancetype)initWithTitleStr:(NSString *)titleStr{
if (self = [super init]) {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeInfoDark];
[self addSubview:btn];
[btn addTarget:self action:@selector(p_clickBtn:) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
- (void)p_clickBtn:(UIButton *)sender{
//通過代理回撥
[_delegate respondsToSelector:@selector(clickBtn:)] ?
[_delegate clickBtn:sender] : nil;
}
//Controller
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//setupUI
//1.createView
DemoView *view = [DemoView viewWithTitleStr:@"我是引數"];
view.delegate = self;
[self.view addSubview:view];
}
#pragma mark - privateDelegate
- (void)clickBtn:(UIButton *)sender{
//View層按鈕的點選事件回撥~
}
複製程式碼
接下來看這張
iOS MVC
架構圖二,這張也是特別常見,在上面解決了View層之後,我們來看下這裡的Model層~
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//loadDatas
[[AFHTTPSessionManager manager]GET:url
parameters:parameters
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject)
{
//重新整理tableView
_datas = responseObject;
[_tableView reloadDatas];
} failure:nil];
}
複製程式碼
這種寫法在我剛蹭到iOS
的時候又這樣寫過,先說這樣寫的好處,以及初學者為什麼會這麼寫:
- 簡單,網路請求完,直接在當前控制器重新整理
TableView
的資料來源 - 比如要找某個介面的網路請求,直接切到這個介面對應的
controller
就行,因為資料請求 寫在Controller
裡面,不用去別的地方找,就這裡有; - 比如當前網路請求介面,需要外部引數,比如前一個介面的
uuid
,這樣寫的好處,可以直接讓當前請求在Controller
中就能拿到資源,不需要傳值
缺點!!:
- 又導致
Controller
特別臃腫,裡面程式碼特別多,如果當前控制器需要多次請求,程式碼量可能過1000行,不好維護 - 寫在
Controller
裡無法複用,除非你在 VC2裡面 copy 當前VC中的網路請求
的程式碼 - 如果某些介面有依賴要求,介面1請求完再請求介面2,巢狀起來,辣眼睛的程度差點治好我多年的近視
- 特別low!!會被懂架構的人瞧不起,噴你根本不是
MVC
,如果你還用了上面的View
寫在Controller
的操作的話,恭喜你,最終大法 -Controller架構
順利完成,並不需要什麼Model
&&View
如何告別
VC
模式,真正走到MVC
?
- 不用洗腦,給自己一個大耳刮子讓自己清醒清醒,這
iOS
的Controller
就算是UIViewController
,也沒看到M
啊,沒有Model
。(很關鍵的一步) - 模組化劃分,每個模組對應自己的一個Model,例如Demo2模組,View層裡面有個
Demo2Model
,將網路請求&&資料處理寫到Model
中
知識1:如何傳值(引數)
@implementation DemoModel
+ (NSArray *)fetchDatasWithUUid:(NSString *)uuid{
//Model傳送網路請求
NSDictionary *parameters = @{@"uuid":uuid}
[[AFHTTPSessionManager manager]GET:url
parameters:parameters
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject)
{
//這是非同步請求,無法return array
} failure:nil];
}
複製程式碼
知識2:如何回撥(網路請求是非同步請求) - 通過Block
//Model
@implementation DemoModel
+ (void)fetchDatasWithUUid:(NSString *)uuid success:(successBlock)block{
//Model傳送網路請求
NSDictionary *parameters = @{@"uuid":uuid}
[[AFHTTPSessionManager manager]GET:url
parameters:parameters
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject)
{
//通過block非同步回撥~
block(responseObject);
} failure:nil];
}
//Controller
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//loadDatas
[DemoModel fetchDatasWithUUid:_uuid success:^(NSArray *array) {
_datas = array;
[_tableView reloadDatas];
}];
}
複製程式碼
基礎的MVC講解完畢,其實本質上就是讓Controller減壓,不該控制器管的他別讓他知道,如上基礎MVC
操作之後的優勢:
- MVC架構分明,在同一個模組內,如果
檢視
有問題,找到該模組的View
就行,其他同理,Controller
程式碼大大減少,負責View
的代理事件就可以 - 可以複用,比如你一個產品列表的資料,首頁也要用,產品頁也要用,直接分別在其對應的
VC1
&&VC2
呼叫函式[ProductModel fetchDatas]
即可,無需寫多次,View的複用同理 - 結構分明,便於維護,擴充也是在此基礎上擴充,程式碼乾淨簡潔。
進階講解 - MVC 配合 繼承,進階提高效率
- 常用的方法,抽一個
基類
出來,繼承
是子類可以擁有父類的方法,重新父類的方法即可,無需宣告
//資料基類
@interface MNBaseDatas : NSObject
//請求資料成功
typedef void (^MNsuccessBlock)(NSArray *array);
+ (void)fetchDatasSuccessBlock:(MNsuccessBlock)block;
+ (void)fetchDatasSuccessBlock:(MNsuccessBlock)block
failureBlock:(MNfailureBlock)failure;
複製程式碼
如果,如果抽出一個資料模型
的基類,比如這裡的MNBaseDatas
,如之前我們舉例的DemoModel
就無需宣告
@interface DemoModel : MNBaseDatas
/**繼承自MNBaseDatas,父類有的就可以不用宣告,這裡的block 和 類方法都可以不用宣告*/
//typedef void (^successBlock)(NSArray *array);
//+ (void)fetchDatasSuccessBlock:(MNsuccessBlock)block;
@end
//Controller
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//loadDatas - DemoModel沒有宣告 -fetchDatasSuccessBlock,一樣可以呼叫,因為父類有此方法
[DemoModel fetchDatasSuccessBlock:^(NSArray *array) {
_datas = array;
[_tableView reloadDatas];
}];
}
複製程式碼
如果父類沒有的方法或屬性,在子類裡面寫就行了,不會影響到父類程式碼,父類一般也是放公共,常用的方法(或屬性),如果是特殊的,直接在子類裡面新增即可,無需新增到父類~
控制器也可以使用繼承,可以減少不少冗餘程式碼
//基類控制器
@interface MNBaseViewController : UIViewController
@property (nonatomic, weak)UITableView *tableView;
@property (nonatomic, copy)NSArray *datas;
- (void)setupUI;
- (void)loadDatas;
@end
//MNBaseViewController.m 檔案
@interface MNBaseViewController ()
<
UITableViewDelegate,UITableViewDataSource
>
#pragma mark - setupUI
- (void)setupUI{
//統一建立tableView,設定當前代理=self
UITableView *tableView = [[UITableView alloc]init];
tableView.frame = Frame(0, DefaultNaviHeight, ScreenW, ScreenH - DefaultNaviHeight);
tableView.delegate = self;
tableView.dataSource = self;
}
複製程式碼
- 根據我們的封裝,基本上所有的控制器都需要設定介面
setupUI
獲取資料loadDatas
,所以將這兩個函式抽到基類MNBaseViewController
中 - 因為
iOS
中,tableView
應該算最常見的控制元件之一,基本上大多數介面都會用它展示資料,所以tableView
也抽到基類中,當公告屬性 - 有
tableView
就跑不了資料來源了,datas
同理,也抽到基類 - 同時,設定
MNBaseViewController
成為tableView
的delegate
和dataSource
,所有的子類都無需再宣告 - 如果有需要用到
tableView
的,一個[super setUI]就能擁有這個tableView
,無需建立
這樣,所有的
UIViewController
,只要繼承自MNBaseViewController
的,都可以有如上的函式和方法(可以根據需要擴充)
進階的MNBaseViewController
//繼承自`MNBaseViewController`
/*資料結構是 - @[],沒有section的tableVIew*/
@interface MNBaseControllerTypeNoSection : MNBaseViewController
@end
/*資料結構是 - @[@[]],有section的tableVIew*/
@interface MNBaseControllerTypeHadSection : MNBaseViewController
@end
複製程式碼
//實現
/**沒有section的tableVIew**/
@implementation MNBaseControllerTypeNoSection
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//沒有section,直接返回資料來源count
return self.datas.count;
}
@end
/**有section的tableVIew**/
@implementation MNBaseControllerTypeHadSection
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return self.datas.count;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [self.datas[section] count];
}
@end
複製程式碼
如上面的兩個基類
MNBaseControllerTypeHadSection
,MNBaseControllerTypeNoSection
,根據我們自己需要的資料來源,選擇繼承自哪個類,他們擁有父類MNBaseViewController
的所有屬性,他們的子類,也都無需在寫比如-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
、-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
之類的方法等,大幅度減少冗餘程式碼~
如圖我隨機抽了幾個介面出來,可能有部分人都有做過類似的介面,通過合理的架構,大部分控制器程式碼可能也就100行,詳情可見Demo~
總結
對於架構來說,仁者見仁智者見智,每個人都有一套適合自己的,並不是說MVC
有多low,MVVM
甩用MVC
技術10086條街,主要還是根據專案,根據自己的使用慢慢進階。
下面有我一個最近花了幾個小時抽出來的Demo,當然實際開發中的,可能Controller
的程式碼會多一些,因為有些點選事件的程式碼我都是封裝呼叫的,再放進去感覺很容易讓看的人跑偏,所以點選事件基本都注掉了。但是,秉著這種思想,其實我最近寫了一個多重過濾袋滑動多控制器的介面,介面相對來說比較複雜,控制器程式碼也才200行,總的來說還算乾淨。
其實TableView
也可以剝離到外部,不放在Controller
中,我也有Demo是那麼做的,後來發現沒必要,感覺還特意封出去感覺有點畫蛇添足,因為我這種架構,其實tableView
很多方法都在基類控制器裡面的,所以Controller
中的tableView
程式碼也不會多。
歡迎star~