MUKContentRedux
为不可变数据提供一个存储,这些数据只能通过应用操作来更新。它受到了 ReSwift 的启发,但野心要小得多。请参考他们的项目,以了解在代码中简化交互流程的好处。
首先,您应该通过您的 UIViewController
实例描述您向用户暴露的内容。为此,您需要创建一个新的符合 <MUKContent>
协议的不可变对象,实际上是一个空的协议。假设我们正在制作一个计数器屏幕
@interface CounterContent : NSObject <MUKContent>
@property (nonatomic, readonly) NSInteger integerValue;
- (instancetype)initWithIntegerValue:(NSInteger)integerValue NS_DESIGNATED_INITIALIZER;
@end
这个 CounterContent
是一个状态,它不具备行为。唯一改变它的方式是通过定义操作,创建其他符合 <MUKContentAction>
协议的不可变对象,另一个空的协议。
@interface CounterIncrementAction : NSObject <MUKContentAction>
@end
@interface CounterDecrementAction : NSObject <MUKContentAction>
@end
这些操作不具备行为。它们唯一的携带改变状态的需求。这个需求只能分发给存储。 MUKContentStore
是这个库中唯一的具体类。您不应该覆盖它,但您应该创建一个提供还原函数的存储。还原器是一个符合 <MUKContentReducer>
协议的对象,一个只有一个必需方法的协议:-contentFromContent:handlingAction:
。还原器的唯一任务是应用操作到现有内容,以返回一个新的状态。
@implementation CounterReducer
- (nullable CounterContent *)contentFromContent:(nullable CounterContent *)oldContent handlingAction:(id<MUKContentAction>)action
{
if ([action isKindOfClass:[CounterIncrementAction class]]) {
return [[CounterContent alloc] initWithIntegerValue:oldContent.integerValue + 1];
}
else if ([action isKindOfClass:[CounterDecrementAction class]]) {
return [[CounterContent alloc] initWithIntegerValue:oldContent.integerValue - 1];
}
else {
return oldContent;
}
}
现在,您已经在视图控制器中拥有创建存储所需的一切
MUKContentStore<CounterContent *> *const store = [MUKContentStore storeWithReducer:[CounterReducer new]];
self.store = store;
您通过 -dispatch:
方法向存储发送操作
- (IBAction)incrementButtonPressed:(id)sender {
[self.store dispatch:[CounterIncrementAction new]];
}
您通过将一个块注册到 -subscribe:
方法来接收内容更新的通知
__weak __typeof__(self) weakSelf = self;
[self.store subscribe:^(CounterContent * _Nullable oldContent, CounterContent * _Nullable newContent) {
__strong __typeof__(weakSelf) strongSelf = weakSelf;
[strongSelf updateUI];
}];
我鼓励您注意,在这个系统中,数据流是单向的并且总是可预测的:视图控制器将操作分发给存储;存储要求还原器将操作应用到现有内容;还原器将新的内容发送到存储;存储将内容更新发送到订阅者。每个组件都得到了良好的隔离,副作用最小。
在这个美妙的同时世界里,什么是真实的异步世界呢?这是使用算子的典型用例。算子是一个延迟表达式求值的函数。通常,存储只能分发操作,但是 MUKContentThunkMiddleware
扩展了这种功能。
您可以使用这个中间件创建存储
MUKContentStore<CounterContent *> *const store = [[MUKContentStore alloc] initWithReducer:[CounterReducer new] content:nil middlewares:@[ [MUKContentThunkMiddleware new] ]];
self.store = store;
然后,您可以为符合 MUKContentThunk
协议的每个对象分发操作。 MUKBlockContentThunk
是一个方便的算子,它包装了一个块。
+ (id<MUKContentThunk>)requestInfos {
return [MUKBlockContentThunk thunkWithBlock:^id _Nullable(MUKContentDispatcher _Nullable dispatcher, MUKContentGetter _Nonnull getter)
{
Content *const content = getter();
if (content.status == ContentStatusLoading) {
return nil; // Already loading
}
// Start request (e.g.: this will show spinner)
id<MUKContentAction> const action = [ActionCreator requestStart];
dispatcher(action);
[APIClient() fetch:^(NSData *data, NSError *error) {
// Dispatch actions to respond async fetch event
if (data) {
dispatcher([ActionCreator requestFinished:data]);
}
else {
dispatcher([ActionCreator requestFailed:error]);
}
}];
return action; // This is optional. You can also return other objects (e.g.: a token to cancel fetch)
}
}
您的视图控制器将无缝地使用算子
[self.store dispatch:[ActionCreator requestInfos]];
Marco Muccinelli, [email protected]
MUKContentRedux
遵循MIT授权协议。请参阅LICENSE文件获取更多信息。