MVVM架構(gòu)
自然是按照ViewModel、ViewController桂敛、View奈附、Model
的順序依次構(gòu)建。
RACObserve循環(huán)引用
self.rac_willDeallocSignal信號—>block—->self—->rac_willDeallocSignal
信號
MVVM構(gòu)造更加輕量化ViewController
控制器里面所有屬于事件處理的邏輯和計算通通搬到
ViewModel
里面去说订,換句話說,控制器就干兩件事情潮瓶,第一、響應(yīng)用戶的交互钙姊;第二毯辅,呈現(xiàn)給用戶交互結(jié)果。問題的復(fù)雜性就在于此煞额,典型的一個購物車思恐,你選中一個商品,這是一個交互膊毁,而交互的結(jié)果卻是多個的胀莹,包括總價格要變化,按鈕的使能要變化婚温,商品的總數(shù)要發(fā)生變化描焰,交給
ViewModel
來處理,好了你至少要計算出三個交互結(jié)果栅螟,然后三次使用Block
或是委托來實現(xiàn)逆向傳值荆秦,才能把結(jié)果ViewModel
的處理結(jié)果傳遞到控制器去,如果不是三個而是5個6個力图,那很有可能帶來的結(jié)果是步绸,本來是想精簡控制器的代碼所以設(shè)置ViewModel
,最后因為事件處理的結(jié)果和多吃媒,使用了一大堆的Block
和委托來傳值瓤介,反而把控制器給弄亂了吕喘,因此很多開發(fā)者如果不會使用RAC卻使用MVVM
的框架是很考驗?zāi)托牡模€不如直接把事件處理寫在控制器來的直接呢刑桑,雖然控制器冗余了點氯质,但是還談不上亂呀。如果使用
RAC
來處理控制器和ViewModel
之間的協(xié)作漾月,所有的問題迎刃而解病梢。原因就是用戶交互終究只會觸發(fā)一個信號,無論最后這個用戶交互事件需要呈現(xiàn)多少個連鎖反應(yīng)梁肿,所有與這個操作有關(guān)的UI
元素訂閱這個信號不就行了么蜓陌,一個信號對應(yīng)多個連鎖反應(yīng),就是這么神奇吩蔑。那么問題來了钮热,都有哪些方法把控制器的事件傳遞到
ViewModel
里面去呢 ?現(xiàn)在最常用到的就是第一:RACObserve
觀察者主要傳遞控制器的輸入內(nèi)容變化烛芬;第二:RACCommand
主要用于耗時事件隧期;第三:RACSubject
用于立即響應(yīng)按鈕事件,例如簡單地跳轉(zhuǎn)個控制器啥的赘娄。
ViewModel和ViewController的交互
ViewModel
處理網(wǎng)絡(luò)請求仆潮,網(wǎng)絡(luò)請求的最終結(jié)果通過ViewModel
的RACComand
信號傳遞到控制器。控制器持有
ViewModel
對象遣臼,意味著控制器持有ViewModel
對象的所有屬性性置,ViewModel
對象的屬性可是實時通過RACObserver
記錄控制器的值,對ViewModel
的屬性做更新監(jiān)聽揍堰,一旦屬性變化一次鹏浅,就釋放一次信號,進(jìn)行判斷后給控制器一個反饋信號屏歹。完整的邏輯鏈條就是輸入框輸入內(nèi)容隐砸,釋放以內(nèi)容為值的信號,
ViewModel
的屬性綁定了這個信號蝙眶,將輸入框值自動綁定了自己的屬性上季希、ViewModel
的另一個信號屬性又訂閱了這個屬性的值的變化,一旦變化械馆,則發(fā)送一個信號出去胖眷,然后控制器的輸入框和登錄按鈕的使能狀態(tài)又訂閱了這個信號、再然后就是一個完整的響應(yīng)鏈條霹崎,實現(xiàn)控制器最初發(fā)送信號珊搀,ViewModel
處理信號,然后再發(fā)出信號尾菇,控制器再響應(yīng)信號的閉環(huán)生態(tài)境析。簡單地說囚枪,控制器發(fā)A信號同時訂閱
ViewModel
處理完A信號發(fā)送的B信號。如果需要及時反饋劳淆,那么直接發(fā)送
RACSingle
信號链沼,如果對于A信號的處理是一個異步耗時操作,那么RACCommand
命令內(nèi)嵌RACSingle
的形式返回B信號沛鸵。RACComand
里面的信號執(zhí)行異步耗時操作括勺,返回成功或失敗,成功就發(fā)送值為字典的信號曲掰,失敗就發(fā)送錯誤信號疾捍。
BaseViewModel
BaseViewModel
的屬性包括title、jumpTool栏妖、paramsDict
乱豆。初始化BaseViewModel
時就需要傳入paramsDict
,意義在于self.title = paramsDict[@"title"]
給控制器的導(dǎo)航欄標(biāo)題賦值吊趾,除此之外宛裕,paramsDict
還可以傳遞其它的參數(shù),當(dāng)然是以字典鍵值對的形式進(jìn)行傳遞论泛。jumpTool
作為一個繼承于NSObject
的對象揩尸,持有navigationController
屬性值,封裝一系列控制器跳轉(zhuǎn)的方法屁奏,意義在于處理navigationController
或是待跳轉(zhuǎn)viewController
不存在時的異常疲酌。baseViewModel
作為初始化baseViewController
的形參,意味著初始化baseViewcontroller
之前必須先實例化一個baseViewModel
了袁,因為baseViewModel
里面就持有baseViewController
所需要的所有數(shù)據(jù),包括最重要的導(dǎo)航欄標(biāo)題湿颅,這就是相對于屬性正向傳值的優(yōu)點载绿,可以在初始化viewController
的時候就把數(shù)據(jù)賦值給控制器。當(dāng)然為了全局的使用和對傳進(jìn)來的viewModel
進(jìn)一步操作油航,就需要將初始化控制器傳進(jìn)來的viewModel
賦值給self.baseViewModel
崭庸。MVC
都是在viewController
創(chuàng)建控制器,然后self.navigationController
跳轉(zhuǎn)控制器谊囚。為了在viewModel
里面跳轉(zhuǎn)控制器怕享,這里則是通過ViewModel.jumpTool
來調(diào)用方法跳轉(zhuǎn)控制器。 一個tabarItem對應(yīng)著一個navigationController
镰踏,創(chuàng)建navigationController
導(dǎo)航控制器的時候函筋,需要初始化一個繼承于BaseViewController
的普通控制器作為根控制器,在BaseViewController
的viewDidLoad
方法里將navigationController
存在baseViewModel.jumpTool.navigation
屬性里奠伪。如此一來跌帐,JumpTool
控制器跳轉(zhuǎn)幫助類就可以在持有navigationController
的基礎(chǔ)上隨意封裝任何形參的控制器跳轉(zhuǎn)方法首懈。只要誰持有了控制器跳轉(zhuǎn)幫助類對象,誰就可以在任意地方執(zhí)行控制器跳轉(zhuǎn)谨敛。
繼承于
BaseViewModel
基類的viewmodel都會重寫基類的init方法究履,意義是子類ViewModel
相比父類BaseViewModel
擁有更多的屬性需要初始化,而且必須是在初始化ViewModel
對象的同時就來實例子類里面的這些新增屬性脸狸。擴(kuò)展這些屬性都挺簡單的的最仑,能想到的唯一稍微麻煩的地方就是初始化VeiwModel
的新增RACComand
屬性了。UI控件持有
ViewModel
目的是利用viewModel
響應(yīng)控制器的用戶交互事件或者說是為了將觸發(fā)事件傳遞到viewModel里炊甲,比如按鈕點擊命令啥的泥彤。
BaseViewcontroller
首頁控制器是通過
StoryBoard
初始化的,意味著沒有按照BaseViewController
基類里面的初始化方法進(jìn)行初始化蜜葱。添加了右滑退出控制器的手勢全景,這對于控制器的退出是十分重要且必要的。BaseViewController
父類里的初始化方法寫了重要的一步牵囤,傳入navigationController
參數(shù)初始化BaseViewModel
屬性爸黄。自然ViewMode
屬性值為空,自然在調(diào)用父類viewDidLoad
方法給BaseViewModel.jumpTool
屬性賦值無法實現(xiàn)揭鳞,因此難以跳轉(zhuǎn)炕贵。BaseViewController
設(shè)置ViewModel
屬性,因為每個子類控制器都會設(shè)置這個ViewModel
屬性野崇,干脆在基類里面設(shè)置ViewModel
屬性称开,帶來的問題就是子類的ViewModel
屬性本質(zhì)上與BaseViewController
里面的viewModel
屬性是子類父類的關(guān)系,意味著子類控制器去調(diào)用BaseViewController
的ViewModle
屬性的方法乓梨,會造成水土不服鳖轰。解決方法就是在繼承于
BaseViewController
的子類控制器里面聲明@dynamic viewModel
,如此一來扶镀,ViewModel
所屬Class
不再是BaseViewModel
蕴侣,而是屬于子類Class
。這樣來看臭觉,并沒有起到父類幫子類聲明統(tǒng)一屬性的方法昆雀,如果子類用的屬性跟父類用的屬性聲明的方法相同,自然沒問題蝠筑,可是子類的屬性與父類的屬性是子類父類的關(guān)系狞膘,這就需要子類改寫父類的成員變量的類型,變量名稱不變什乙,Xcode
提示你父類和子類聲明了重復(fù)的成員變量挽封,使用@dynamic
告訴Xcode
成員變量所屬Class
的關(guān)系就可以了。BaseViewController
本來不該寫這個屬性的稳强,但是不得已而為之场仲,十分需要BaseViewModel
屬性來保存實例控制器時傳進(jìn)來的ViewModel
和悦,必須依賴baseViewController.baseViewModel.jumpTool
保存navigationController
才能實現(xiàn)控制器的跳轉(zhuǎn)。否則渠缕,每一個繼承于BaseViewController
基類控制器的子類控制器都需要重寫一些基類的init方法鸽素,不然根本就沒法保存初始化控制器時傳進(jìn)來的viewModel
。其實后來的通過子類Class
再次聲明viewModel和@dynamic
本質(zhì)就是為了擴(kuò)展基類BaseViewController
的viewModel
屬性的方法和屬性亦鳞。
- 由類方法初始化控制器變成了由類名字符串
[[NSClassFromString(ClassName) alloc]
來初始化控制器馍忽。封裝控制器跳轉(zhuǎn)邏輯的唯一原因就是統(tǒng)一規(guī)避所有可能會遇到的控制器跳轉(zhuǎn)異常,異常主要有兩點:一是導(dǎo)航控制器不存在燕差,二是待跳轉(zhuǎn)的控制器構(gòu)建不出來遭笋。
商品Cell
- Cell下方存在加入購物車的邏輯,扯出了購物車管理器徒探、購物車管理器又扯出了用戶管理器瓦呼、用戶管理器有扯出了地址管理器、現(xiàn)在迷失在地址管理器测暗,難以自拔央串!
RACComand
UIButton
擴(kuò)展了rac_command
的屬性,將button
的enable
狀態(tài)與command
命令的執(zhí)行來狀態(tài)綁定碗啄,用戶點擊按鈕時质和,command
命令會自動執(zhí)行,同時按鈕enable
置為NO
稚字。如果手動執(zhí)行command
命令饲宿,則可以發(fā)送參數(shù)。使用
RACComand
命令的時候的傳入的參數(shù)胆描,這個參數(shù)到底是什么地方傳進(jìn)來的瘫想,有什么用,就像屬性傳值是的昌讲,只要有RACComand
這個對象殿托,就意味著可以把對象傳遞到RACComand
里面去。誰調(diào)用excue
這個消息剧蚣,誰就有資格傳遞信息進(jìn)行正向傳值。使用
RACComand
的過程中旋廷,如果只是創(chuàng)建了一個信號鸠按,那么直接返回這個信號就可以了,同時這個信號在異步操作執(zhí)行完成之后發(fā)送執(zhí)行結(jié)果給那些訂閱此信號的人饶碘。信號在把消息發(fā)送出去的同時目尖,也銷毀了信號本身,一旦RACComand
檢測到內(nèi)部持有的信號已經(jīng)銷毀扎运,必然改變自己的執(zhí)行狀態(tài)瑟曲,表示信號里面的耗時任務(wù)執(zhí)行完畢饮戳。于是RACComand
的狀態(tài)改成了執(zhí)行完畢。那么現(xiàn)在是一個RACConmand
里面三個信號洞拨,信號3是一個刷新UI的信號扯罐,依賴信號1和信號2去請求數(shù)據(jù),信號1和信號2同時成功后再去觸發(fā)信號3烦衣。
RACCommand
的初始化必須實現(xiàn)Block
參數(shù)代碼塊歹河,而且這個Block
參數(shù)代碼塊很特別,既有輸入值id
類型數(shù)據(jù)花吟,又有輸出值RACSignal
秸歧,輸入值來自于ViewModel
操作RACCommand
屬性執(zhí)行Excute
命令時,可以帶一個參數(shù)過來衅澈,這個參數(shù)可以是任何形式键菱,用來區(qū)分這個命令操作到底是屬于誰。返回值必須是一個信號今布,就算你什么都不做经备,也必須在Block
參數(shù)代碼塊里面返回一個[RACSignal empty]
空信號,這就是一個典型的有輸入值有返回值的Block
函數(shù)形參了险耀。如果是要在
RACCommand
事件里面進(jìn)行一個異步操作弄喘,就不能返回空信號了,不能返回空信號甩牺,必須返回一個冷信號蘑志,冷信號作用就是先把信號創(chuàng)建出來,暫且不發(fā)送任何的內(nèi)容贬派,直接在創(chuàng)建信號后的Block
形參代碼塊里面寫入將要執(zhí)行的異步操作急但,然后根據(jù)異步操作的執(zhí)行結(jié)果通過訂閱者subscriber
發(fā)送不同的信號值,當(dāng)然我們創(chuàng)建的冷信號RACsignal
是需要以RACCommand
的Block
形參返回值返回出去的搞乏。冷信號RACsignal
的Block
形參的返回值則是一個RACDisposable
對象波桩,唯一的意義就是在訂閱者subscriber
發(fā)布錯誤信號error
或是結(jié)束信號complete
之后會銷毀我們創(chuàng)建的RACsignal
冷信號,銷毀這個信號的同時會進(jìn)入RACDisposable
的Block
參數(shù)代碼塊里面请敦。如果需要在冷信號被銷毀之后執(zhí)行某些代碼镐躲,那么RACDisposable
顯得特別暖心,平時直接return
一下RACDisposable
的實例就可以了侍筛。接下來就是邏輯處理異步請求的結(jié)果萤皂,方式一直接在異步耗時操作的
Block
回調(diào)進(jìn)行處理;方式二是訂閱這個RACSignal
冷信號匣椰,然后根據(jù)不同的信號值裆熙,做出處理。
登錄注冊首先實例
RACCommand
,后面的Block
代碼塊里面直接就return
創(chuàng)建RACSignal
冷信號入录。RACSignal
的Block
代碼塊里蛤奥,就是異步網(wǎng)絡(luò)請求,同時類方法Block
回調(diào)網(wǎng)絡(luò)請求結(jié)果僚稿,請求成功則subscriber
發(fā)送YES
凡桥,反之失敗則subscriber
發(fā)送NO
。創(chuàng)建
RACsignal
冷信號帶有一個Block
代碼塊贫奠。RACDisposable
作為與異步網(wǎng)絡(luò)請求并行的關(guān)系必須作為Block
代碼塊的返回值進(jìn)行return
唬血。訂閱
RACComand
的Block
代碼塊里面創(chuàng)建的RACSignal
。方式一subscriber
訂閱RACCommand.executionSignals.switchToLatest
信號唤崭;方式二直接在Blcok
代碼塊里subscriber
訂閱前面創(chuàng)建的RACSignal
信號拷恨。獲取
RACCommand
執(zhí)行狀態(tài)。方式一subscriber
訂閱[RACCommand.executing skip:1]信號來判斷RACCommand
執(zhí)行狀態(tài)谢肾。方式二直接在創(chuàng)建RACCommand
時默認(rèn)RACCommand
執(zhí)行狀態(tài)開始腕侄,在訂閱到RACSignal
發(fā)出值信號或RACSignal
被銷毀時默認(rèn)執(zhí)行狀態(tài)結(jié)束。subscriber
發(fā)送的值信號格式芦疏。如果網(wǎng)絡(luò)請求數(shù)據(jù)成功冕杠,就發(fā)送@{@"code":@100,@"data":responseObject}
這個字典,如果請求數(shù)據(jù)失敗酸茴,就發(fā)送@{@"code":@400,@"data":@"請求失敗"}
RACSubject
通過
RACSubject
信號把View
子視圖上的按鈕事件傳遞到ViewModel
中區(qū)分预,同時傳遞的參數(shù)主要按鈕的標(biāo)識符Tag
號。類似于給View
子視圖添加按鈕事件Block
回調(diào)薪捍。TableView
子視圖持有ViewModel
笼痹,在Cell
點擊回調(diào)方法里將Cell
點擊事件轉(zhuǎn)變成RACSubject
信號發(fā)送到ViewModel
。
購物車數(shù)據(jù)本地化
讀取酪穿。初始化購物車的
ViewModel
時訂閱全局ShoppingManager
單例類change
屬性值RACObserve([ShoppingManager manager]凳干,change)
信號,獲取[ShoppingManager manager]
的goodsDic
數(shù)據(jù)更新UI
被济。保存救赐。單例類的意義在于實時讀寫數(shù)據(jù)。以商品模型
id
為鍵只磷、商品模型model
為值存入單例的[ShoppingManager manager].goodsDic
屬性中经磅。添加。當(dāng)用戶在
GoodManagerView
視圖上點擊增加或減少商品數(shù)量的時候钮追,重寫[ShoppingManager manager].goodsDic
馋贤。更新。首先
array
數(shù)組保存[ShoppingManager manager].goodsDic
字典的所有值allValue
畏陕,一個值代表一個商品模型model
,字典所有模型轉(zhuǎn)移到數(shù)組之后清空goodsDic
仿滔;遍歷商品模型數(shù)組array
的每一個商品模型model
惠毁,通過model.isSelected
判斷商品是否被選中犹芹;未選中狀態(tài)則繼續(xù)以商品模型id
為鍵、模型model
為值存入[ShoppingManager currentUser].goodsDic
字典中鞠绰;處于選中狀態(tài)則首先獲取被選中商品模型model
的單個商品數(shù)量腰埂,更新用戶管理單例類的[UserManager currentUser].bageValue
商品總數(shù)屬性;最后蜈膨,在商品模型數(shù)組array的每一個商品模型model都遍歷一遍之后屿笼,需要將[ShoppingManager manger]
和[UserManager currentUser]
這兩個單例類模型對象重新保存到本地沙盒,保證退出應(yīng)用翁巍,購物車數(shù)據(jù)依然存在驴一。