Model的設(shè)計(jì)

前言

很多的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)行修改或刪除巷查。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市握巢,隨后出現(xiàn)的幾起案子晕鹊,更是在濱河造成了極大的恐慌,老刑警劉巖暴浦,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溅话,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡歌焦,警方通過查閱死者的電腦和手機(jī)飞几,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來独撇,“玉大人屑墨,你說我怎么就攤上這事躁锁。” “怎么了绪钥?”我有些...
    開封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵灿里,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我程腹,道長(zhǎng)匣吊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任寸潦,我火速辦了婚禮色鸳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘见转。我一直安慰自己命雀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開白布斩箫。 她就那樣靜靜地躺著吏砂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪乘客。 梳的紋絲不亂的頭發(fā)上狐血,一...
    開封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音易核,去河邊找鬼匈织。 笑死,一個(gè)胖子當(dāng)著我的面吹牛牡直,可吹牛的內(nèi)容都是我干的缀匕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼碰逸,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼乡小!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起饵史,我...
    開封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤劲件,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后约急,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體零远,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年厌蔽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牵辣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奴饮,死狀恐怖纬向,靈堂內(nèi)的尸體忽然破棺而出择浊,到底是詐尸還是另有隱情,我是刑警寧澤逾条,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布琢岩,位于F島的核電站,受9級(jí)特大地震影響师脂,放射性物質(zhì)發(fā)生泄漏担孔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一吃警、第九天 我趴在偏房一處隱蔽的房頂上張望糕篇。 院中可真熱鬧,春花似錦酌心、人聲如沸拌消。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽墩崩。三九已至,卻和暖如春侯勉,著一層夾襖步出監(jiān)牢的瞬間泰鸡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工壳鹤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饰迹。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓芳誓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親啊鸭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锹淌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354