JBFlux 0.1.0

JBFlux 0.1.0

TestsTested
LangLanguage Obj-CObjective C
License MIT
ReleasedLast Release Mar 2016

Maintained by Jordane belanger.



JBFlux 0.1.0

  • By
  • Jordan Belanger

Facebook Flux inspired dispatcher architecture tailored for Objective-C + some magic

Installation

Getting Started

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:

1. The dispatcher is not based around store registering callbacks with it and then using the returned dispatch token.

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.

2. Stores have to register the actions they want to be invoked for instead of registering a dispatch callback with the dispatcher.

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
3. There is a forced action base class: 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.

Usage

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.

Tip:

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

Testing

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.

Requirements

JBFlux requires either iOS 7.0+ or Mac OS X 10.7+ or watchOS 2.0.

ARC is required

License

JBFlux is MIT licensed