<em><strong>“不為炫技而炫技画切,不為架構(gòu)而設(shè)計(jì)架構(gòu)溺职,只為寫(xiě)出一個(gè)接地氣返劲、通俗易懂的使用方法”</strong></em>
(注:了解整個(gè)設(shè)計(jì)模式體系請(qǐng)查看我上篇文章實(shí)際項(xiàng)目中的MVVM(積木)模式:序章)
這篇文章講解Model和ViewModel唉窃。
本著易擴(kuò)展、易理解的前提摩瞎,講解中Model和ViewModel都用最基礎(chǔ)的方法和易理解的思維圖拴签。
為何為何會(huì)將View和ViewModel合并在一起講解?為何我會(huì)將有些文章說(shuō)的的網(wǎng)絡(luò)層與數(shù)據(jù)層合并一起叫數(shù)據(jù)層旗们?
很簡(jiǎn)單蚓哩,我們需要明白一個(gè)道理,無(wú)論是網(wǎng)絡(luò)請(qǐng)求還是本地緩存上渴,本質(zhì)上都是傳遞數(shù)據(jù)岸梨;因此,我們要做的就是將各種來(lái)源的數(shù)據(jù)通過(guò)數(shù)據(jù)加工(ViewModel)形成統(tǒng)一的格式(Model)再通過(guò)一個(gè)統(tǒng)一的接口傳遞給需要的地方稠氮。我所講的數(shù)據(jù)層曹阔,我把這形象地叫為數(shù)據(jù)工廠。
首先隔披,我們先開(kāi)始說(shuō)說(shuō):Model次兆。
<strong><h4>一、Model--項(xiàng)目的信息承載與傳遞者</h4></strong>
一個(gè)多人協(xié)同開(kāi)發(fā)的項(xiàng)目锹锰,保證數(shù)據(jù)結(jié)構(gòu)的一致性和穩(wěn)定是很有必要的芥炭。而Model則是很好的實(shí)現(xiàn)了這一需求。
首先恃慧,我們先通過(guò)三個(gè)大的方面將字典與Model作一個(gè)比較园蝠,更直觀了解Model的特點(diǎn)。
<strong>1痢士、字典與模型的比較</strong>
<strong>a彪薛、取值:</strong>字典會(huì)因?yàn)闆](méi)有取值的這個(gè)key或者這個(gè)錯(cuò)誤的key剛好是這個(gè)字典中其他類(lèi)型的值對(duì)應(yīng)的key(因輸入錯(cuò)誤等原因),通過(guò)這個(gè)key取出來(lái)的值為nil或者其他類(lèi)型的值,如賦值給label之類(lèi)的文字控件怠蹂,可能會(huì)導(dǎo)致程序崩潰善延,而Model不會(huì)出現(xiàn)這樣的問(wèn)題;
<strong>b城侧、數(shù)據(jù)展示:</strong>字典無(wú)法再不改變數(shù)據(jù)源的前提下易遣,改變數(shù)據(jù)的格式,而Model則可以通過(guò)get方法實(shí)現(xiàn)這個(gè)需求嫌佑,保證了數(shù)據(jù)的原始性和可變性豆茫;
<strong>c、后期維護(hù):</strong>字典每個(gè)key的具體含義和有多少key要通過(guò)接口文檔去了解屋摇,而Model體現(xiàn)在具體的屬性和每個(gè)屬性的備注上揩魂;
可能有同學(xué)會(huì)問(wèn)建立Model一個(gè)個(gè)去復(fù)制粘貼屬性好麻煩济竹,還有像數(shù)據(jù)緩存之類(lèi)還要一個(gè)個(gè)寫(xiě)解擋歸檔好麻煩呢/(ㄒoㄒ)/~~
這么多好的優(yōu)點(diǎn)的前提下尉共,這幾個(gè)小麻煩肯定會(huì)通過(guò)方法解決噻巴席,且看下面:
<strong>2饼酿、解決Model的一些小麻煩</strong>
<strong>a荒澡、如何快速建立Model:</strong>這里有份代碼枉侧,可以將網(wǎng)絡(luò)請(qǐng)求下來(lái)的字典里的key在控制臺(tái)打印成Model里的屬性格式哦缸剪。(打印效果在代碼塊下面)
- (void)writeInfoWithDict:(NSDictionary *)dict
{
NSMutableString *strM = [NSMutableString string];
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
// NSLog(@"%@,%@",key,[obj class]);
NSString *className = NSStringFromClass([obj class]) ;
if ([className isEqualToString:@"__NSCFString"] | [className isEqualToString:@"__NSCFConstantString"] | [className isEqualToString:@"NSTaggedPointerString"]) {
[strM appendFormat:@"@property (nonatomic, copy) NSString *%@;\\\\n",key];
}else if ([className isEqualToString:@"__NSCFArray"] |
[className isEqualToString:@"__NSArray0"] |
[className isEqualToString:@"__NSArrayI"]){
[strM appendFormat:@"@property (nonatomic, strong) NSArray *%@;\\\\n",key];
}else if ([className isEqualToString:@"__NSCFDictionary"]){
[strM appendFormat:@"@property (nonatomic, strong) NSDictionary *%@;\\\\n",key];
}else if ([className isEqualToString:@"__NSCFNumber"]){
[strM appendFormat:@"@property (nonatomic, copy) NSNumber *%@;\\\\n",key];
}else if ([className isEqualToString:@"__NSCFBoolean"]){
[strM appendFormat:@"@property (nonatomic, assign) BOOL %@;\\\\n",key];
}else if ([className isEqualToString:@"NSDecimalNumber"]){
[strM appendFormat:@"@property (nonatomic, copy) NSString *%@;\\\\n",[NSString stringWithFormat:@"%@",key]];
}
else if ([className isEqualToString:@"NSNull"]){
[strM appendFormat:@"@property (nonatomic, copy) NSString *%@;\\\\n",[NSString stringWithFormat:@"%@",key]];
}else if ([className isEqualToString:@"__NSArrayM"]){
[strM appendFormat:@"@property (nonatomic, strong) NSMutableArray *%@;\\\\n",[NSString stringWithFormat:@"%@",key]];
}
}];
NSLog(@"\\\\n\\\\n%@\\\\n",strM);
}
看下圖打印效果敷鸦,那么打印出來(lái)的效果大家知道了吧白修,直接復(fù)制粘貼就OK啦:
<strong>b妒峦、怎么解決繁瑣的解擋歸檔呢</strong>
在BaseModel(Model的基類(lèi))中寫(xiě)一個(gè)統(tǒng)一的解擋、歸檔兵睛,這里就要用到runtime中非常有用的兩個(gè)方法:
class_copyIvarList(Class cls, unsigned int *outCount) //遍歷該類(lèi)成員變量列表
ivar_getName(Ivar v) //獲取該類(lèi)某個(gè)成員變量的名字
那么具體怎么在基類(lèi)寫(xiě)一個(gè)肯骇,所有適用呢,且看下面:
- (void)encodeWithCoder:(NSCoder *)encoder
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
// 歸檔
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[encoder encodeObject:value forKey:key];
}
free(ivars);
}
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
// 解檔
NSString *key = [NSString stringWithUTF8String:name];
id value = [decoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
至此祖很,所有繼承于這個(gè)Model基類(lèi)的Model都自動(dòng)實(shí)現(xiàn)了解檔歸檔的方法笛丙。
既然解決了建立Model的一些小麻煩,我們就來(lái)構(gòu)建一個(gè)項(xiàng)目中標(biāo)準(zhǔn)的BaseModel(基類(lèi)模型)假颇。
<strong>3胚鸯、何為基類(lèi)Model建立要求?</strong>
<strong>a笨鸡、數(shù)據(jù)格式讀取統(tǒng)一與寫(xiě)入統(tǒng)一姜钳;</strong>
<strong>b坦冠、模型屬性值可批量修改;</strong>
這樣才能保證在“千奇百怪哥桥、朝令夕改”的數(shù)據(jù)源中辙浑,進(jìn)入到這個(gè)項(xiàng)目體系后,面向業(yè)務(wù)工程師的時(shí)候拟糕,是統(tǒng)一整齊的標(biāo)準(zhǔn)模型判呕,然后業(yè)務(wù)工程師才會(huì)在這個(gè)基礎(chǔ)之上擴(kuò)展其他子Model。
其中讀寫(xiě)統(tǒng)一則是通過(guò)上面的解檔歸檔解決送滞,而模型屬性值批量修改則是通過(guò)李明杰大神的MJExtension(這個(gè)三方庫(kù)可以在不用繼承其他Model前提下使用侠草,保證了Model獨(dú)立性),具體代碼在BaseModel如下:
- (id)mj_newValueFromOldValue:(id)oldValue property:(MJProperty *)property
{
if ([property.name isEqualToString:@"buy_price"]|| [property.name isEqualToString:@"buy_sale_price"]||[property.name isEqualToString:@"info_price"]||[property.name isEqualToString:@"sale_price"] ) {
if (oldValue == nil || [oldValue isKindOfClass:[NSNull class]])
return @"價(jià)格面議";
}//可將所有模型在生成時(shí)將涉及相應(yīng)字段的值變成“價(jià)格面議”
else if (property.type.typeClass ==[NSString class]) {
if (oldValue == nil || [oldValue isKindOfClass:[NSNull class]])
return @"--";//可將所有模型在生成時(shí)將string類(lèi)型從nil或Null變成“--”
} else if (property.type.typeClass == [NSArray class]) {
if (oldValue == nil)
return [[NSArray alloc] init];//可將所有模型在生成時(shí)將array類(lèi)型從nil變成[[NSArray alloc] init]
}
return oldValue;
}
以上代碼舉了部分替換的例子犁嗅,目的是告訴大家通過(guò)這方法可以在數(shù)據(jù)源傳給客戶端是非友好數(shù)據(jù)的時(shí)候边涕,我們客戶端能夠進(jìn)行處理,給業(yè)務(wù)工程師一個(gè)友好的數(shù)據(jù)愧哟。這對(duì)數(shù)據(jù)工廠這個(gè)模式來(lái)說(shuō)是非常有必要的奥吩。
<strong>至此,BaseModel就只有寫(xiě)兩個(gè)方法:解檔與歸檔 以及 屬性值批量修改蕊梧。因?yàn)槲覀兪冀K要明白:Model是來(lái)保證整個(gè)項(xiàng)目架構(gòu)數(shù)據(jù)結(jié)構(gòu)的一致性和穩(wěn)定性霞赫。那么繼承這個(gè)BaseModel的子Model就都具備了面向業(yè)務(wù)工程師友好數(shù)據(jù)的特點(diǎn)。</strong>
那么如何正確使用子Model呢肥矢?
我舉兩個(gè)例子:
<strong>1端衰、如何將模型源數(shù)據(jù)的時(shí)間 20161010 變成 2016-10-10(正確使用get方法)</strong>
- (NSString *)pub_date{
return [_pub_date formatDateString];//formatDateString是NSString的Category
}
這樣的好處有兩方面:一方面,業(yè)務(wù)工程師不會(huì)修改源數(shù)據(jù)甘改,保證了源數(shù)據(jù)的安全性旅东;另一方面,類(lèi)似formatDateString的方法十艾,是通過(guò)Category(也就是我后面要說(shuō)的工具類(lèi))使用的抵代,保證了代碼的低耦合性。
<strong>2忘嫉、使用子Model分離數(shù)據(jù)的一個(gè)例子(這個(gè)例子感謝我的同事 張爾柏 同學(xué)提供荤牍,展示這個(gè)例子主要目的是為了表達(dá)子Model其中一個(gè)在cell樣式數(shù)據(jù)分離的作用,可能會(huì)因?yàn)闆](méi)有demo庆冕,大家不太好理解康吵。所以,大家可以在后期demo上傳后再次詳細(xì)了解)</strong>
這里我們舉個(gè)tableView中Cell顯示(整個(gè)tableView的demo將會(huì)在View篇結(jié)束后放出)访递,其中Model要做的事情晦嵌,我們先看在Model中的代碼:
- (void)setupInfo {
self.xibName = @"SaleInfoTableViewCell";/指定Cell的Cell樣式
self.cellHeight = @(76);//指定Cell的高度
self.ideltifier = @"cell";//指定Cell的ideltifier
}
通過(guò)在Model生成時(shí)執(zhí)行這個(gè)方法,實(shí)現(xiàn)了Cell樣式與樣式數(shù)據(jù)分離,做到了每個(gè)Cell的View樣式與Model的綁定惭载。
<strong>在講解Model的結(jié)尾旱函,總結(jié)起來(lái)就是:Model存在的目的是為了給業(yè)務(wù)工程師一個(gè)友好穩(wěn)定的數(shù)據(jù),讓業(yè)務(wù)工程師在相應(yīng)的模塊內(nèi)獨(dú)立地做相應(yīng)的數(shù)據(jù)操作棕兼。</strong>
<strong><h4>二陡舅、ViewModel--做一個(gè)優(yōu)秀的數(shù)據(jù)工廠</h4></strong>
如文章開(kāi)始的圖就知道,ViewModel更像一個(gè)食品工廠一樣伴挚,將不同的原料通過(guò)不同的制作工藝產(chǎn)出為統(tǒng)一的產(chǎn)品。
那么作為工廠的框架BaseViewModel應(yīng)該是怎樣的呢灾炭?
首先我們應(yīng)該先想到茎芋,我們的原料(數(shù)據(jù))來(lái)自哪里?
基本都是來(lái)自網(wǎng)絡(luò)了噻蜈出!
既然來(lái)自網(wǎng)絡(luò)田弥,那么就明確了BaseViewModel應(yīng)該實(shí)現(xiàn)三個(gè)事情:網(wǎng)絡(luò)通信、上傳铡原、下載偷厦。(都用af第三方庫(kù)實(shí)現(xiàn))
廢話不多說(shuō),直接上代碼:
<strong>1燕刻、BaseViewModel實(shí)現(xiàn)的三個(gè)方法</strong>
<strong>網(wǎng)絡(luò)請(qǐng)求:</strong>
- (void)serviceNetWorkWithUrlStr:(NSString *)urlStr//請(qǐng)求網(wǎng)絡(luò)地址
Params:(NSMutableDictionary *)params//請(qǐng)求參數(shù)
Success:(void(^)(id result))successBlock
Failure:(void(^)(NSError *error))failBlock {
AFHTTPRequestOperationManager * manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer.timeoutInterval = 10;//請(qǐng)求時(shí)長(zhǎng)
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
//如果需要加解密只泼,可引入加解密的工具類(lèi)將params加密實(shí)現(xiàn)
[manager POST:urlStr parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
successBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"%@", [error localizedDescription]);
failBlock(error);
}];
}
<strong>上傳:</strong>
該上傳方法需要求能同時(shí)多傳,且傳不同類(lèi)型的文件卵洗,以適應(yīng)不用的場(chǎng)景需要
- (void)uploadFileWithfileList:(NSMutableArray *)params//存放上傳文件的數(shù)組
Option:(NSDictionary *)optiondic//請(qǐng)求參數(shù)
Url:(NSString *)requestURL//上傳網(wǎng)絡(luò)地址
Success:(void(^)(id responseObject))successBlock
Failure:(void(^)(NSError *error))failBlock
progress:(void (^)(float progress))progress{
AFHTTPRequestOperationManager *manager=[AFHTTPRequestOperationManager manager];
//設(shè)置返回的數(shù)據(jù)解析格式
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"application/json"];
AFHTTPRequestOperation *operation = [manager POST:requestURL parameters:optiondic constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
//下方舉了一個(gè)極端例子:當(dāng)數(shù)組里存放了不同類(lèi)型的文件如何上傳
for (int i = 0; i<params.count; i++) {
if ([params[i] isKindOfClass:[UIImage class]]) {
UIImage *image = [params[i] imageCompressForWidth:params[i] targetWidth:375];
NSData *imageData =UIImagePNGRepresentation(image);
[formData appendPartWithFileData:imageData name:@"file_content" fileName:[NSString stringWithFormat:@"anyImage_%d.png",i] mimeType:@"image/png"];
}else if ([params[i] isKindOfClass:[NSString class]]) {
NSURL *url = [NSURL fileURLWithPath:params[i]];
NSData *data = [NSData dataWithContentsOfURL:url];
[formData appendPartWithFileData:data name:@"file_content" fileName:@"11.aac" mimeType:@"audio/x-mei-aac"];
}else if ([params[i] isKindOfClass:[NSURL class]]) {
NSData *data = [NSData dataWithContentsOfURL:params[i]];
[formData appendPartWithFileData:data name:@"file_content" fileName:@"11.aac" mimeType:@"audio/x-mei-aac"];
}
}
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
successBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failBlock(error);
}];
//獲得上傳進(jìn)度
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
NSLog(@"百分比:%f",totalBytesWritten*1.0/totalBytesExpectedToWrite);
progress(totalBytesWritten*1.0/totalBytesExpectedToWrite);
}];
}
<strong>下載:</strong>
- (void)downloadFileWithOption:(NSDictionary *)paramDic//請(qǐng)求參數(shù)
withUrl:(NSString*)requestURL//下載地址
savedPath:(NSString*)savedPath//保存路徑
downloadSuccess:(void (^)(id responseObject))success
downloadFailure:(void (^)(NSError *error))failure
progress:(void (^)(float progress))progress
{
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
NSMutableURLRequest *request =[serializer requestWithMethod:@"POST" URLString:requestURL parameters:paramDic error:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
[operation setOutputStream:[NSOutputStream outputStreamToFileAtPath:savedPath append:NO]];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
float p = (float)totalBytesRead / totalBytesExpectedToRead;
progress(p);//下載進(jìn)度
NSLog(@"download:%f", (float)totalBytesRead / totalBytesExpectedToRead);
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
success(responseObject);
NSLog(@"下載成功");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
NSLog(@"下載失敗");
}];
[operation start];
}
<strong>2.如何寫(xiě)好一個(gè)子ViewModel</strong>
不知道大家注意到?jīng)]有请唱,其實(shí)我們真實(shí)的項(xiàng)目中,網(wǎng)絡(luò)請(qǐng)求返回來(lái)的狀態(tài)其實(shí)可能是會(huì)存在三種狀態(tài)的:<strong>請(qǐng)求成功过蹂;請(qǐng)求失敗十绑,服務(wù)器返回錯(cuò)誤信息;請(qǐng)求失敗酷勺,網(wǎng)絡(luò)不通本橙。</strong>同時(shí),<strong>我們會(huì)在某些地方做緩存讀寫(xiě)</strong>脆诉。那既然如此甚亭,繼承BaseViewModel的子ViewModel的對(duì)外接口(<strong>唯一對(duì)外接口</strong>)應(yīng)是如下所寫(xiě):
- (void)serviceNetWorkMessageListWith
Success:(void(^)(id responseObject))successBlock//請(qǐng)求成功回調(diào)
ReceiveFail:(void (^)(id responseObject))ReceiveFailBolck//請(qǐng)求失敗,服務(wù)器返回錯(cuò)誤信息
Failure:(void(^)(NSError *error))failBlock//請(qǐng)求失敗库说,網(wǎng)絡(luò)不通 {
NSMutableDictionary *tempDic = [@{@"service_code":Message_Service_Code} mutableCopy];
[tempDic addEntriesFromDictionary:[self getPrivateParameters]]//所有接口的公共訪問(wèn)參數(shù);
[tempDic addEntriesFromDictionary:@{"":""}//對(duì)應(yīng)接口的對(duì)應(yīng)參數(shù)];
//這里舉個(gè)Archive做數(shù)據(jù)緩存的讀取與存儲(chǔ)的例子狂鞋,方便大家理解在某些場(chǎng)景下需要某個(gè)ViewModel作緩存時(shí),如何只對(duì)外提供一個(gè)數(shù)據(jù)接口潜的,里面作多級(jí)緩存的原理骚揍,具體實(shí)現(xiàn)大家可以結(jié)合自身優(yōu)化,比如數(shù)據(jù)庫(kù)的緩存,比如在這個(gè)類(lèi)單獨(dú)歸一個(gè)方法信不。
if ([[NSUserDefaults standardUserDefaults] objectForKey:Message_Service_Code]) {
NSArray *priarr = [[NSUserDefaults standardUserDefaults] objectForKey:Message_Service_Code];
NSMutableArray *priMuarr = [NSMutableArray arrayWithCapacity:0];
for (NSData *data in priarr) {
MessageInfoModel *message = [NSKeyedUnarchiver unarchiveObjectWithData:data];//解檔
[priMuarr addObject:message];
}
successBlock(priMuarr);//返回緩存數(shù)據(jù)
}
[self serviceNetWorkWithUrlStr:[UrlGenerator queryNewMessageUrl] Params:tempDic Success:^(id responseObject) {
if ([responseObject[@"code"] isEqualToString:@"0000"]) {
NSArray *array = [MessageInfoModel mj_objectArrayWithKeyValuesArray:responseObject[@"data"]];
NSMutableArray *enArr = [NSMutableArray arrayWithCapacity:0];
for (MessageInfoModel *messageModel in array) {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:messageModel];
[enArr addObject:data];//歸檔
}
[[NSUserDefaults standardUserDefaults] setObject:enArr forKey:Message_Service_Code];
successBlock(array);//返回網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)
}else {
ReceiveFailBolck(responseObject[@"msg"]);
}
} Failure:^(NSError *error) {
failBlock(error);
}];
}
<strong>3嘲叔、到底ViewModel放在哪里合適?創(chuàng)建多少個(gè)ViewModel合適抽活?</strong>
開(kāi)門(mén)見(jiàn)山直說(shuō)硫戈,個(gè)人認(rèn)為只要涉及數(shù)據(jù)的View的模塊,最理想的情況應(yīng)該一個(gè)View模塊一個(gè)ViewModel下硕。
因?yàn)槎∈牛挥羞@樣,在后期項(xiàng)目越來(lái)越龐大梭姓,維護(hù)的人員越來(lái)越多的時(shí)候霜幼,才能保證模塊之間的絕對(duì)獨(dú)立。
(如果是一兩個(gè)人開(kāi)發(fā)的中小型項(xiàng)目誉尖,也沒(méi)必要一個(gè)模塊對(duì)一個(gè)ViewModel罪既,本身功能不多,沒(méi)有必要铡恕。這時(shí)可以一個(gè)Controller對(duì)一個(gè)ViewModel,或者幾個(gè)同需求功能的Controller對(duì)一個(gè)ViewModel琢感。項(xiàng)目死的,人是靈活的探熔,具體情況具體分析驹针。)
我舉兩個(gè)例子:
a.某個(gè)模塊因?yàn)榫S護(hù)人員頻次多,需求改動(dòng)頻繁祭刚,到最后需要大改某個(gè)模塊的時(shí)候牌捷,因?yàn)橹暗闹辽系较拢〝?shù)據(jù)到界面)的絕對(duì)獨(dú)立,可以完全抽出來(lái)涡驮,重新制作一個(gè)新的模塊暗甥,重新替換進(jìn)去;
b.某些模塊需要數(shù)據(jù)緩存捉捅,某些模塊又需要網(wǎng)絡(luò)狀態(tài)判斷(含大圖片展示的模塊),完全可以針對(duì)不同情況針對(duì)這些相應(yīng)模塊對(duì)應(yīng)的ViewModel數(shù)據(jù)工廠做相應(yīng)的事情撤防。
以上兩個(gè)例子,我想做過(guò)項(xiàng)目的棒口,尤其是遇到頻繁更改需求的寄月,應(yīng)該是深有體會(huì)(哎,我本人就特別有體會(huì)/(ㄒoㄒ)/~~)无牵。
借用一位大神的話結(jié)束Model篇:
<em><strong>“你必須得清楚你要做什么漾肮,業(yè)務(wù)方希望要什么。而不是為了架構(gòu)而架構(gòu)茎毁,也不是為了體驗(yàn)新技術(shù)而改架構(gòu)方案克懊。以前是MVC忱辅,最近流行MVVM,如果過(guò)去的MVC是個(gè)好架構(gòu)谭溉,沒(méi)什么特別大的缺陷墙懂,就不要推倒然后搞成MVVM“缒睿”</em></strong>
后面幾天我在各位大神的建議下會(huì)不斷優(yōu)化文章各個(gè)細(xì)節(jié)损搬,歡迎多多關(guān)注,共同學(xué)習(xí)柜与。幾天后會(huì)發(fā)出<strong> view--業(yè)務(wù)與界面結(jié)合的模塊開(kāi)發(fā) </strong>請(qǐng)多關(guān)注巧勤。