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的时候还可以。
原文 http://www.cocoachina.com/ios/20170207/18590.html