TestsTested | ✗ |
LangLanguage | Obj-CObjective C |
License | MIT |
ReleasedLast Release | Mar 2016 |
Maintained by Jordane belanger.
Facebook Flux inspired dispatcher architecture tailored for Objective-C + some magic
Before you begin, you may want to read the read and understand the default Flux dispatcher implementation by Facebook around which this library was created. Flux documentation
First lets start with the main differences between this library and the default Flux implementation, most of which being prompted by the differences between Javascript and Objective-C:
Instead, the dispatcher is based around registering Store classes themselves.
[[JBDispatcher sharedInstance] registerStore:[SomeStore class]]
Once a store is registered with a dispatcher (in this case the dispatcher singleton sharedInstance), it will be automatically
instantiated by the dispatcher and will live for the rest of the JBDispatcher
instance life. After a store has been registered with the dispatcher,
it can be retrieved at all time by calling the store:
function on the dispatcher.
[[JBDispatcher sharedInstance] store:[SomeStore class]]
This way, you can technically have multiple dispatchers, but most importantly, you do not need to worry about having to implement the usual Objective-C
Singleton dispatch_once
sharedInstance code in all of your JBStore
subclasses.
This allows for 2 things, first, avoiding having to create a huge switch statement somewhere in your store. Instead, you map your actions directly
to handler methods implemented by your JBStore
subclasses based around the JBStore
protocol. All you have to do is
implement the actionHandlerNamesByActionEventNames
function in your JBStore
subclass.
If you have used the Mantle Model framework in the past, you may find this familiar.
For example:
@implementation CurrentUserStore // JBStore subclass
+ (NSDictionary *)actionHandlerNamesByActionEventNames {
return @{
@"CurrentUserRefreshedAction": @"onCurrentUserRefreshedAction"
};
}
- (void)onCurrentUserRefreshedAction:(JBAction<UserModel *> *)action {
// do something with action.payload, in this case the action payload is casted to your UserModel class automatically using generics.
self.currentUserModel = action.payload;
}
@end
Later on, when the 'CurrentUserRefreshedAction' action is dispatched to the dispatcher, your action handler will be automatically invoked by the dispatcher.
...
[[JBDispatcher sharedInstance] dispatch:[JBAction actionWithEventName:@"CurrentUserRefreshedAction" payload:userModel]]];
...
// - (void)onCurrentUserRefreshedAction:(JBAction<UserModel *> *)action is called
JBAction
This is a small class containing a public NSString *eventName
property, a generic id<PayloadType> payload
property
to which you can pass and cast any type of object, and an NSError *error
property.
This is slightly opiniated compared a default, whatever you want, Flux action object, but it allows for three things:
First, this permits you to precast your JBAction
payload type in your action handler method directly as seen before so that you do not
have to cast your JBAction
payload type in your action handler method implementation.
Second, it allows you to indirectly specify in code the types of the payload returned by a particular action over time so you do not have to later look at an action creator implementation to remember a particular action payload type.
Third, the error parameter available in the JBAction
class will avoid forcing you to create multiple version of the same action depending
on the success or failure of a given action.
For example, you may have seen people implement multiple versions of the same action in a lot of Flux code sort of like this:
'CurrentUserRefreshedAction', 'CurrentUserRefreshedActionSuccess' and 'CurrentUserRefreshedActionFailure'
By having every JBAction
have an optional error property, you can simply assume a failure may have occured when a JBAction
error property is not nil:
- (void)onCurrentUserRefreshedAction:(JBAction<UserModel *> *)action {
if (action.error) {
// React to the action failure, for example:
self.currentUserModel = nil;
}
[self emitChangeWithError:error];
}
That way, you do not have to remember as many pesky constant string associated to your actions and also limits the amount of action handler methods you have to implement.
In a normal application, you should first start by registering all of your store subclasses to your dispatcher
[SomeDispatcher registerStore:[CurrentUserStore class]];
[SomeDispatcher registerStore:[CurrentUserMessagesStore class]];
...
Afterwards, your View Controllers/View Models can bind to your JBStore
change events using a block:
@implementation SomeViewController
- (instancetype)init {
...
// Retrieve the registered store
CurrentUserStore *userStore = [SomeDispatcher store:[CurrentUserStore class]];
// Bind the viewController to this store change events with a callback block
__weak __typeof(self) weakself = self;
[userStore registerObject:self forChangeEvents:^(CurrentUserStore *store, NSError *error) {
// Update some views, react to an error, etc
}];
...
}
Notice we're binding the UIViewController instance itself to the store as well as its callback. This allows for you not to have to worry
about unregistering a viewController store change event callback in the UIViewController's dealloc function like
you usually would when registering a viewController for NSNotification
events.
Afterwards, you can simply create JBActions
, dispatch them to your dispatcher and handle them in your store as described in the
getting started section.
I havn't touched action creators yet, which is something you may or may not want to implement, but a good way
to be consistent if you plan on using such a pattern is to simply create JBAction
categories for all of your action creators.
extern NSString * const CurrentUserRefreshedAction;
@interface JBAction (UserActionCreator)
+ (JBAction<UserModel *>)currentUserRefreshedAction:(UserModel *)userModel;
@end
An example project is provided in the JBFlux workspace and you can run the example from the workspace itself.
Make sure to run a pod install
in the JBFlux root directory beforehand as the Example project has some external dependencies.
JBFlux requires either iOS 7.0+ or Mac OS X 10.7+ or watchOS 2.0.
ARC is required
JBFlux is MIT licensed