iOS開發,公司事情不多。半年前涉獵RN開發,開始學習React,之後學習了Redu,很喜歡用。
全局數據管理,單一數據流,所有數據只讀,修改必須通過action來實現。很適合我們公司的業務。智能家居,工具類應用。所有的操作和控制都是圍繞著設備來進展,設備不會被隨意刪除,設備狀態隨時可能發生變化,可以多個因素對同一個設備發生影響,用戶App操作,設備直接操作,其他已設定服務操作等。一個設備的數據同時被多個對象使用,設備本身,設備所有的設備組,設備所在的房間等都會讀取設備狀態,一旦設備發生變化,多個地方的UI都需要發生變化。
iOS App以前的架構主要是數據單例+通知。所有的數據扔到單例裡面,由多個可變對象構成,單例接口區別讀取和寫入。一旦有新的狀態被寫入,寫入的同時發出一個通知。自建一個“通知Message”,這個Message接收所有的通知,並對其進行處理,然後激活不同的delegate方法。在各個需要的VC中實現delegate方法,VC一旦觸發Refresh或者Update方法,刷新UI。
工程中一大坨通知。到處都是代理。
後來考慮直接在iOS中使用類似於Redux的結構來管理數據。搜了下github,只有swift。准備自己寫點東西出來。思想上差不多就行。
適用面還是比較窄的,代碼開始讀起來也比較麻煩,本來幾行就能搞定的東西現在需要好幾個類。借用RAC實現了主要功能,如果對Redux或RAC不了解,對這樣數據管理架構沒大的需求。沒必須讀下去了,可能有點累。
如上圖所示。Redux在iOS中主要由4各模塊構成,Store(存儲所有數據),Action(傳遞最新數據),Reducers(處理獲取到的action,更新store的內容),state(store提供給外界的數據讀取接口)。這裡省略了Middleware。
Store:由於App中只存在唯一一個store,它保存所有的數據。直接上單例。
Action:action是傳遞數據的唯一通道,各種action高度統一。這裡描述為type和params兩個屬性的Item。
State:是對外接口,可以是一些數組或者字典。
Reducers:Reducer是整個的重點部分,它處理action傳遞來的信息,根據type分類交給不同的reducer來處理,並將處理完的數據更新到store。
感覺都很好理解嘛。
action怎麼被接收?多個Reduce怎麼處理一個action?更新的數據怎麼通知需要知道的VC呢?VC怎麼獲取更新後的數據來重新渲染頁面呢。。
做了一個很簡單的ToDo。
基本功能:
1、添加新的任務
2、更改當前任務狀態
3、刪除任務
4、所有操作快照
前置條件-
RAC 如果不用RAC就需要使用其他的通知類工具。(RAC配合AFNetworing還是挺好用的,不過這裡並沒有使用AFNetworking)
JSONModel 或其他
1、首先自定義一組ToDo,作為初始化的任務列表。
創建ToDo的Item class
ToDoItem.h
這裡使用了JSONModel解析數據。初始化數據扔到了一個plist中。同時添加了一個使用todoName新建ToDo的初始化方法
ToDoItem.m
ViewControl.m
2、現在獲取到了初始數據,怎麼把ToDoInitDataList扔到store中呢?
首先創建一個store。
裡面僅有一個單例實現。
Store.h
Store.m
然後創建一個state。
聲明一個數組todoList(這裡可以使用字典,用Identifier作為key,方便後期查找特定的item),然後該state遵守
State.h
State.m
store和state兩個沒有任何關系呀。怎麼將初始化的數據放到store中呢?
下面繼續。
*上面提到了,store所有中數據的改變都必須使用action。
我們創建一個action。
聲明兩個屬性,一個為type,另一個params。兩個都是只讀的,另外創建一個初始化方法。其中params可以為空(為空在這裡其實沒意見),但是type必須有值。type可以使用string或者int,這裡建立使用枚舉類型。那麼我們順手創建一個ActionType.h,裡面寫入一些枚舉值。
Action.h
Action.m
ActionType.h
然後我們就可以用todoList創建一個action了。
問題來了。由誰來接收這個action呢?這裡我們可以交給store來完成這個任務。反正它作為一個單例不會隨便消失。為store添加一個新的方法。dispatchWithAction:
這樣我們將action組裝好後,就可以扔給store了。
同時,為了讓store能依次處理action,我們添加一個queue來處理。
Store.m
創建action並dispath出去
ViewControl.m
我們這裡是希望通過action將initData交給store。
然後呢?我們怎麼在需要的地方拿到這個array呢?
我們已經在state中聲明了對應todo的數組,應該可以通過state的todoList來拿到數組。
所以關鍵在於dispatchWithAction:方法怎麼處理這個數據。
上面提到了,store僅僅是存儲數據,所有的action處理是由reducer完成的。
來創建reducer吧。
首先。Reducers是由一組reducer組成的。它們分別被授予處理不同類型action的實名。有的reducer專門處理ToDo相關的,有的reducer專門處理DoTo相關的。
其實,我在使用的時候是按照state進行分組的。前面提到的state是由一些數組或字典組成,那麼有幾個數組或字典就創建幾個reducer就可以了。
Reducer.h
Reducer.m
必須明白的是:reducer是唯一可以處理action,並將action中的信息更新設置到state的工具,我們將通過讀取action信息,然後返回新的state信息。
這裡使用block實現處理和返回的功能
Reducer.h
Reducer.m
裡面的內容就是將action裡的信息處理然後填入state中了。
貼一段自己的代碼上去。通過對action type的判斷,使用萬惡的switch case方法處理。基本就是增刪改三種
現在你需要完成一個方法處理剛才傳入的action.
附上一個完整的todoReducer
這裡需要注意:修改的時候,建議使用mutableCopy,拷貝一份原有數據,在新數據上做修改,再覆蓋返回。這樣確實會占用大量內存空間,不過可以完整記錄整個數據變化過程。用來做時間旅行還是不錯的。
Item使用的JSONModel可以直接copy,為了方便只用於少量信息的的Item更新原有Item,自己寫一個JSONModel的category方法。
注:本文中大量的命名是借鑒Redux或者JS中類似方法:例如dispathc, Object.assign等。
我們的reducer方法完成了。現在可以處理action並更新最新數據到state中了。是時候更新我們的dispatchWithAction: 方法了。
從剛才的時候您應該已經發現了。我們在reducer中的block中傳入的state帶了兩個**,指針的指針,所有我們在reducer中修改的時候傳入的state指向位置的內容。
我們先聲明兩個新的屬性,State* state和NSArray
我們的store就可以接收action,並將接收到的扔給所有的,注意是所有的reducer去處理這個action,直到有一個具有處理該action type的reducer出現。它獲取action的內容並將數據更新到newState中,然後我們將newState賦值給我們的state。這樣,每一個action被正確處理後就會更新state。由於它們在queue中,必須依次進行。不會產生混亂。
我們這裡已經可以獲取到數據的變化了。那麼我們如何在數據變化的是發送通知,讓對數據變化感興趣的對象收到這個通知並拿到它想要的最新數據呢?這裡我使用了RAC,當然,你可以使用其他的。
在store中創建一個RACSignal,當檢測到state發生變化的時候發送發出信號——之後的通知改成信號了。
這樣。整個Redux-OC的部分的就完成了。
下面介紹怎麼使用。
我們接下來會做一個如圖的簡易ToDo應用
頁面很簡單。一個ViewController中添加一個UITableView。這裡會把DataSource從VC中分離,這一步可做可不做。
做好以上前置後,創建一個遵守UITableViewDataSource協議的NSObject類。添加初始化方法。
DetailTableVDataSource.h
DetailTableVDataSource.m
這個類主要用來完成UITableViewDataSource協議的兩個方法,同時對數據進行操作。這樣我們的VC就僅僅是展示View,完全不需要接觸到數據。
我們需要一個ToDoList來渲染TableView,這個數據在store的state中,我們需要拿到這個數據。怎麼拿到呢?我們不能直接初始化state對象,沒有任何可見的接口。使用我們剛才創建的RACSignal,一旦state中的數據發生變化,我們就可以通過訂閱者捕獲到這個信號。這個信號中帶了我們想要的數據。
我們順便再創建一個ViewModel類。
將數據扔給它,再讓DataSource初始化該類的對象來完成代理方法。
ToDoViewModel.h
.h創建一個只讀的array屬性,該屬性可以讓dataSource完成代理。
下面的那個方法是用來通知VC更新UITableView的。由於TableView仍由VC持有,一旦數據變化我們就需要通知VC Reload。這裡需要從VM到DS到VC。比較煩。但是使用NSCenter通知就RAC的意義了。
ToDoViewModel.m
我們先創建一個訂閱者來監聽store中RACSignal的變化。
和.h中對應的讀寫數組來存儲todoList的數據。
剩下的那個東西發送信號通知感興趣的人
-bindRAC的作用一個就是監聽state變化來改變allToDoListArray,另一個一旦改變就發信號通知別人可以刷新UI了。
回到DataSource.m 利用viewModel完善代碼
自定義的Cell
再貼下VC中TableView的部分代碼
這時候你就應該看到一個基本的VC-TableView頁面了。
然後呢????
我們繞了這麼一大圈子你就給我看這個。
添加一個添加新todo的UIBarButtonItem
在Reducer中完成action.type == ReduxActionTypeAddToDo的方法
現在。點擊添加按鈕,然後創建一個新的todoItem試試。
然後是改和刪了。
實現-tableView:editActionsForRowAtIndexPath:方法。設置四個RowAction。分別為3種狀態和一個移除。這裡講刪除和移除區分開來,其實沒多大區別。
在dataSource 中聲明這4個方法,並實現它們
其中狀態轉換設置為一種action typeJ就可以了。params中攜帶了期望狀態。用字面量就可以了。我說我說按代碼字符數支付工資的不知道有沒有人信。
在reducer中實現
*這裡在調用JSONModel的category方法的時候,可能產生一個unrecogizeds selector sent to class的崩潰。需要在target中設置Biild Setting中設置
然後就可以更改狀態了。
這也沒什麼嗎。
先照著剛才的VC,DS,VM原樣來一份。
在state中添加一個可變數組
在Reducer中修改下todoReducer的方法。
增加的代碼已重點標示
ViewModel
這次不需要數據更新主動更新UI,需要手動更新。就利用上剛才的更新subject。
僅僅是改了的state.XX ,隱藏主動刷新通知。其他不變
DataSource
添加一個int屬性,默認為0,記錄下當前位置。
改變了兩個代理方法中數據的讀取方式。從[] -> [[]..]其他不變。
.h 添加一個新的方法給VC,切換當前顯示的todo。
VC添加新的UIBarButtonItem來切換就好了。
VC這個類可以直接拷貝過來用,沒有一點耦合。
然後就沒有任何了。
先第一個VC操作todo,再第二個VC查看所有操作。
沒了。。
寫了好久,寫了個“然並卵”的東西。
這個好像沒啥用,寫小東西還浪費內存。
一些特定的產品需求還是可以試試的。比如硬件控制這種一個Item被多個位置使用或者組合使用,所有UI都需要實時更新,多種觸發方法。。
跟著做基本都能達到效果。
最後附上github地址:https://github.com/ColdJuzi/Redux-OC
現在使用Carthage的還不是很多,建議將依賴改為CocoaPods。倉庫中包括了所需要的3個framework。就是下的時候可能花點時間。下完需要安裝下Carthage。這個東西安裝下載還是比較慢的。但是用起來感覺比CocoaPods方法。最起碼新建功能用AFNetwordking的時候還可以。