前言
很多的app使用MVC設(shè)計(jì)模式來將“用戶交互”與“數(shù)據(jù)和邏輯”分開册烈,而model其中一個(gè)重要作用就是持久化。下文中設(shè)計(jì)的Model可能不是一個(gè)完美的婿禽,擴(kuò)展性強(qiáng)的model范例赏僧,但在我需要重構(gòu)的app中,這樣的設(shè)計(jì)能夠滿足我的需要扭倾。
關(guān)于Model
Model層包含了app的數(shù)據(jù)與邏輯淀零,Model層中的類需要關(guān)心的是數(shù)據(jù)的表現(xiàn),存儲(chǔ)膛壹,以及操作驾中。Model層是整個(gè)app生態(tài)中相對(duì)獨(dú)立的一個(gè)部分,因?yàn)樗粫?huì)直接與controller層或者是View層進(jìn)行通訊模聋,而是在其他層需要請(qǐng)求它的信息的時(shí)候進(jìn)行間接通訊肩民。
Model有什么用?
想要寫好一個(gè)model,首先要清晰Model的作用撬槽。
屬性存却烁摹:將文件中的一些特性和數(shù)據(jù)以屬性的形式存儲(chǔ)
可變性:屬性可以readwrite,所以能夠被改變侄柔,并保存到本地
KVO:可以觀察一個(gè)屬性的值并在它改變的時(shí)候受到通知共啃,并以此對(duì)UI或其他地方進(jìn)行控制
處理數(shù)據(jù):根據(jù)業(yè)務(wù)邏輯處理網(wǎng)絡(luò)獲取數(shù)據(jù)與本地存儲(chǔ)數(shù)據(jù)
如何定義Model類
我們可以創(chuàng)建一系列的Model類,它們之間可以互相繼承暂题,同時(shí)每一個(gè)Model類是與當(dāng)前app中的實(shí)體對(duì)應(yīng)移剪。比如在我當(dāng)前需要重構(gòu)的app中,用戶數(shù)據(jù)對(duì)應(yīng)的是UserInformationModel薪者,班級(jí)信息對(duì)應(yīng)的是StudentClassModel纵苛。
另一方面,在Model類的實(shí)現(xiàn)過程中,有許多問題需要解決攻人,所以我下面會(huì)根據(jù)我當(dāng)前正在重構(gòu)的app的一些情況結(jié)合來解釋取试。
信息的存儲(chǔ)格式處理
數(shù)據(jù)可以以各種不同的格式儲(chǔ)存,在我重構(gòu)的app中怀吻,數(shù)據(jù)等信息是使用普通的數(shù)據(jù)結(jié)構(gòu)來存放瞬浓,比如使用數(shù)據(jù)或者是字典來保存Model的信息。在一開始建立Model的時(shí)候并沒有太大的問題蓬坡,但是當(dāng)需求不斷增加猿棉,一個(gè)Model類的信息開始變得龐大起來的時(shí)候,問題就開始浮現(xiàn)了屑咳。比如下面我要輸出用戶的姓名萨赁、年齡、班級(jí)兆龙、班主任杖爽、年級(jí)等數(shù)據(jù)。
// never do this !!!
- (void)printInformation
{
NSLog(@"name: %@", [user objectForKey:@"name"]);
NSLog(@"age: %@"m [user objectForKey:@"age"]);
NSLog(@"class: %@"m [user objectForKey:@"clazz"]);
NSLog(@"teacher: %@"m [user objectForKey:@"teacher"]);
NSLog(@"grade: %@"m [user objectForKey:@"grade"]);
}
這樣取出數(shù)據(jù)似乎沒什么問題详瑞,但是當(dāng)數(shù)據(jù)多起來的時(shí)候就會(huì)發(fā)現(xiàn)代碼會(huì)十分混亂掂林,而且不!美坝橡!觀泻帮!,同時(shí)最主要的問題是计寇,在從字典中取出數(shù)據(jù)的過程中會(huì)把key打錯(cuò)锣杂,導(dǎo)致數(shù)據(jù)取出失敗。
所以在設(shè)計(jì)Model過程中番宁,盡可能將Model設(shè)計(jì)成一個(gè)類而不是一個(gè)結(jié)構(gòu)元莫,在類中可以使用屬性(Property)來存取信息,它能夠提供給開發(fā)者基本的拼寫檢查蝶押,完全杜絕打錯(cuò)key導(dǎo)致的數(shù)據(jù)獲取失敗的情況踱蠢,同時(shí)方便其他開發(fā)者看到Model中存儲(chǔ)的數(shù)據(jù)類型,也方便日后的擴(kuò)展與維護(hù)棋电。
// YES! do this!
- (void)printInformation
{
NSLog(@"name: %@", user.name);
NSLog(@"age: %@", user.age);
NSLog(@"class: %@", user.clazz);
NSLog(@"teacher: %@", user.teacher);
NSLog(@"grade: %@", user.grade);
}
網(wǎng)絡(luò)數(shù)據(jù)處理
由于我重構(gòu)的app大部分功能是基于網(wǎng)絡(luò)的茎截,所以網(wǎng)絡(luò)數(shù)據(jù)在Model層的處理會(huì)是重點(diǎn)。因?yàn)榫W(wǎng)絡(luò)數(shù)據(jù)是異步獲取的赶盔,而且獲取過程很容易失敗企锌。所以網(wǎng)絡(luò)數(shù)據(jù)的獲取比本地?cái)?shù)據(jù)的獲取難度要更大。另一方面于未,很多app的網(wǎng)絡(luò)請(qǐng)求框架中都提供了緩存功能撕攒,可以在下一次請(qǐng)求中從緩存中更快的獲取數(shù)據(jù)陡鹃,而不需要再次進(jìn)行網(wǎng)絡(luò)請(qǐng)求,這里又涉及到本地?cái)?shù)據(jù)獲取等問題抖坪,使得網(wǎng)絡(luò)數(shù)據(jù)的處理顯得尤其繁瑣萍鲸。
在一個(gè)同步的網(wǎng)絡(luò)環(huán)境下,我們可以把錯(cuò)誤處理放到其他地方擦俐,可以簡(jiǎn)單的做緩存猿推,甚至可以像處理本地?cái)?shù)據(jù)一樣來更新、刪除捌肴、添加新的網(wǎng)絡(luò)數(shù)據(jù)。但很不幸的是藕咏,網(wǎng)絡(luò)是異步的状知,所以我們需要處理這個(gè)重要問題。
首先我在上文提到的緩存孽查、失敗處理饥悴,這些應(yīng)該是交給網(wǎng)絡(luò)請(qǐng)求框架來進(jìn)行處理,最后得到一個(gè)responseObject盲再,再對(duì)responseObject進(jìn)行model轉(zhuǎn)換等操作西设。
比如我在當(dāng)前的項(xiàng)目中,是利用了MJExtension來進(jìn)行字典與模型之間的轉(zhuǎn)換答朋,而這個(gè)轉(zhuǎn)換贷揽,我是放到了每個(gè)單獨(dú)的API中,比如對(duì)于庫存的請(qǐng)求梦碗,我在InventoryAPI中進(jìn)行對(duì)responseObject轉(zhuǎn)換成InventoryModel的操作禽绪,而不是放到vc中進(jìn)行,這樣在vc中僅僅是進(jìn)行網(wǎng)絡(luò)請(qǐng)求洪规,請(qǐng)求完成后返回的是我想要的model印屁。
InventoryAPI.m
- (id)modelingFormJSONResponseObject:(id)JSONResponseObject
{
NSUInteger count = ((NSArray *)JSONResponseObject).count;
NSMutableArray *modelsArray = [NSMutableArray array];
for(int i = 0; i < count; i ++) {
InventoryModel *model = [InventoryModel mj_objectWithKeyValues:JSONResponseObject[i]];
[modelsArray addObject:model];
}
return modelsArray;
}
InventoryViewController
- (void)requestInformation
{
[api startWithBlockSuccess:^(__kindof YXYBaseRequest *request) {
// request.responseObject 將會(huì)返回modelsArray
} failure:^(__kindof YXYBaseRequest *request, NSError *error) {
}];
}
本地?cái)?shù)據(jù)處理
本地?cái)?shù)據(jù)有多種方式存儲(chǔ),比較常見的做法是使用.plist文件存儲(chǔ)非常簡(jiǎn)單的數(shù)據(jù)斩例,例如設(shè)置雄人,而會(huì)使用SQLite數(shù)據(jù)庫來存儲(chǔ)其他復(fù)雜的數(shù)據(jù)。另一方面念赶,可以試著使用Core Data來對(duì)存儲(chǔ)數(shù)據(jù)model础钠,雖然Core Data會(huì)帶來更多問題,甚至?xí)绊懶阅芫牵腘SFetchResultsController珍坊、懶加載、數(shù)據(jù)處理工具等也是十分的好用正罢,所以……看自己阵漏。
在本地?cái)?shù)據(jù)處理中驻民,最重要的是如何獲取和修改數(shù)據(jù)。在我現(xiàn)在需要重構(gòu)的項(xiàng)目中并沒有太多的本地內(nèi)容履怯,絕大部份的數(shù)據(jù)都是通過網(wǎng)絡(luò)獲取回还,所以我并沒有準(zhǔn)備詳細(xì)講對(duì)Data Model的重構(gòu)與規(guī)范化處理。但基本的原則是對(duì)每個(gè)model配備一個(gè)存取器叹洲,里面可以提供fetchALl柠硕、fetchAllUsingPredicate、createInstance运提、save等操作蝗柔,每個(gè)model都可以使用這些操作來獲取數(shù)據(jù),而這些操作的邏輯隱藏起來民泵,在調(diào)用的時(shí)候我不需要知道我這個(gè)model究竟是存放在數(shù)據(jù)庫中癣丧,還是.plist文件中,還是在緩存中栈妆。我只需要知道當(dāng)我調(diào)用這些方法的時(shí)候胁编,我可以獲取到我想要的model,并可以取出我想要的數(shù)據(jù)鳞尔。
而這篇博文里面詳細(xì)講到了SQLite嬉橙、Core Data、FMDB寥假,想要了解數(shù)據(jù)庫存儲(chǔ)方面內(nèi)容的朋友可以看一下市框。
業(yè)務(wù)邏輯處理
在model中不只是能夠處理數(shù)據(jù)的存儲(chǔ),還可以對(duì)業(yè)務(wù)邏輯進(jìn)行處理昧旨。在我需要重構(gòu)的項(xiàng)目中拾给,無論是強(qiáng)弱業(yè)務(wù)邏輯都有散落在vc的情況,導(dǎo)致了vc十分臃腫兔沃,如下面的代碼中蒋得,就是需要實(shí)現(xiàn)從DateModel中取出一個(gè)時(shí)間,將NSDate格式的時(shí)間轉(zhuǎn)換到NSString乒疏,并在View中展示出來的情況:
DateModel.h
@interface DateModel : BaseModel
@property (copy, nonatomic) NSDate *currentDate;
@end
DateModel.m
- (instantcetype)init
{
if (self = [super init]) {
currentDate = [NSDate date];
}
return self;
}
aViewController.m
- (void)showTheDate
{
DateModel *dateModel = [DateModel new];
NSDate *date = dateModel.currentDate;
NSDateFormatter *format = [[NSDateFormatter alloc] init];
format.dateFormat = @"yyyy年MM月dd號(hào) HH:mm:ss";
NSString *string = [format stringFromDate:date];
self.dateLabel.text = string;
}
事實(shí)上這段代碼完全可以分開放置在model層中额衙,讓整個(gè)vc的代碼更加清晰,如:
NSDate+dateTransform.h
@interface NSDate (dateTransform)
+ (NSString *)transformStringFromDate:(NSDate *)date;
@end
NSDate+dateTransform.m
+ (NSString *)transformStringFromDate:(NSDate *)date
{
NSDateFormatter *format = [[NSDateFormatter alloc] init];
format.dateFormat = @"yyyy年MM月dd號(hào) HH:mm:ss";
NSString *string = [format stringFromDate:date];
return string;
}
創(chuàng)建一個(gè)NSDate的分類并將轉(zhuǎn)換的代碼放到其中怕吴,并由DateModel調(diào)用他來完成轉(zhuǎn)換
DateModel.h
@interface DateModel : BaseModel
@property (copy, nonatomic) NSString *currentDate;
@end
DateModel.m
- (instantcetype)init
{
if (self = [super init]) {
currentDate = [NSDate transformStringFromDate:[NSDate date]];
}
return self;
}
然后在vc中窍侧,我們只需要簡(jiǎn)單的調(diào)用:
aViewController.m
- (void)showTheDate
{
DateModel *dateModel = [DateModel new];
self.dateLabel.text = dateModel.currentDate;
}
這樣整個(gè)vc的代碼結(jié)構(gòu)就清晰了很多,model層取出的屬性也能直接交付到view層去顯示转绷,而日期轉(zhuǎn)換這類代碼也能輕易的被復(fù)用伟件。總之议经,針對(duì)這類的model斧账,可以把調(diào)用方法盡可能的抽象谴返,當(dāng)需要解耦這部分代碼的時(shí)候可以應(yīng)用IOC模式,否則咧织,在日后改變這些邏輯會(huì)變得十分困難嗓袱。
結(jié)論
iOS的app中通常很少考慮model層的設(shè)計(jì)功能,而然习绢,由于各種原因渠抹,比如各種散落的代碼或者是容易出錯(cuò)的網(wǎng)絡(luò)連接,讓問題變得十分復(fù)雜闪萄。所以為了避免這些問題梧却,model層的設(shè)計(jì)必須嚴(yán)格遵循這些原則,比如是把處理數(shù)據(jù)的代碼獨(dú)立開來败去、設(shè)計(jì)一個(gè)給本地?cái)?shù)據(jù)的存取器篮幢、設(shè)計(jì)一個(gè)給網(wǎng)絡(luò)數(shù)據(jù)的存取器等等。但不管我們?cè)趺丛O(shè)計(jì)這個(gè)model層为迈,最重要一點(diǎn)是這些功能的實(shí)現(xiàn)要完全對(duì)調(diào)用者隱藏起來,而且這些功能的實(shí)現(xiàn)要足夠簡(jiǎn)單缺菌,以便將來對(duì)其進(jìn)行修改與更新葫辐。
總而言之,在開發(fā)一段時(shí)間后很可能會(huì)更換網(wǎng)絡(luò)庫伴郁、業(yè)務(wù)邏輯耿战、數(shù)據(jù)庫工具等等,良好的Model層設(shè)計(jì)能讓你不用修改任何一行controller層和view層的代碼焊傅,就完成了底層庫的更新?lián)Q代。
文章轉(zhuǎn)載于:http://www.cocoachina.com/ios/20180906/24807.html
如有任何不妥,請(qǐng)聯(lián)系QQ:411282623授帕,會(huì)第一時(shí)間進(jìn)行修改或刪除巷查。