Objective-C
Need help with Reflow?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.
Zepo

Description

A unidirectional data flow framework for Objective-C inspired by Flux, Redux and Vue.

210 Stars 27 Forks MIT License 7 Commits 4 Opened issues

Services available

Need anything else?

Reflow

A unidirectional data flow framework for Objective-C inspired by Flux, Redux and Vue.

Motivation

When writing Objective-C code, we are used to defining properties of models as

nonatomic
. However, doing this may lead to crash in multithread environment. For example, calling the method
methodA
below the app will crash with a
EXC_BAD_ACCESS
exception.
// TodoStore.h
@interface TodoStore : NSObject
@property (nonatomic, copy) NSArray *todos;
@end

// xxx.m

  • (void)methodA { TodoStore *todoStore = [[TodoStore alloc] init];

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

      while (1) {
          todoStore.todos = [[NSArray alloc] initWithObjects:@1, @2, @3, nil];
      }

    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

      while (1) {
          NSLog(@"%@", todoStore.todos);
      }

    }); }

A simple approach to the crash here might be defining the properties as

atomic
. However this approach doesn't solve other issues like race condition.

Another danger is not keeping views up to date since propeties are mutable and we can change them any where throughout the app. If the

todos
property from the above example is binding to the cells of a table view, adding or removing a todo item and forgetting to notify the table view to do
reloadData
will cause another crash with a
NSInternalInconsistencyException
in the table view.

Core Concepts

Reflow solves the above problems by imposing a more principled architecture, with the following concepts.

Store

Like Redux, the state of your whole application is stored in a certain layer, store, and you concentrate your model update logic in the store too. Note that store in Reflow is an abstract concept. The state in the store can be in a database or in memory or both. What really concerns is that we only have a single source of truth.

As our application grows in scale, the whole state can get really bloated. Reflow allows us to divide our store into modules to help with that. Each store module is a subclass of

RFStore
.
@interface TodoStore : RFStore

#pragma mark - Getters

  • (NSArray *)visibleTodos;
  • (VisibilityFilter)visibilityFilter;

#pragma mark - Actions

  • (void)actionAddTodo:(NSString *)text;

  • (void)actionToggleTodo:(NSInteger)todoId;

  • (void)actionSetVisibilityFilter:(VisibilityFilter)filter;

@end

Actions

Actions are defined as normal methods on store modules, except with a name prefix "action". Reflow will do some magic tricks with that.

@implementation TodoStore

...

#pragma mark - Actions

  • (void)actionAddTodo:(NSString *)text { Todo *todo = ...

    self.todos = [self.todos arrayByAddingObject:todo]; }

  • (void)actionToggleTodo:(NSInteger)todoId { self.todos = [self.todos map:^id(Todo *value) {

      if (value.todoId == todoId) {
          Todo *todo = ...
          return todo;
      }
      return value;

    }]; }

  • (void)actionSetVisibilityFilter:(VisibilityFilter)filter { self.filter = filter; }

@end

Subscriptions

When subclassing

RFStore
, we can subscribe to all actions on a store module.
@implementation TodoTableViewController

  • (void)viewDidLoad { [super viewDidLoad];

    self.todoStore = [[TodoStore alloc] init]; self.todos = [self.todoStore visibleTodos]; self.filterButton.title = [self stringFromVisibilityFilter:[self.todoStore visibilityFilter]];

    self.subscription = [self.todoStore subscribe:^(RFAction *action) {

      if (action.selector == @selector(actionSetVisibilityFilter:)) {
          self.filterButton.title = [self stringFromVisibilityFilter:[self.todoStore visibilityFilter]];
      }
      self.todos = [self.todoStore visibleTodos];
      [self.tableView reloadData];

    }]; }

... @end

On completion of each call of a action method, a store module will execute the block passed in

subscribe:
, passing in an instance of
RFAction
as a parameter of the block. Each instance of
RFAction
contains infomation like the
object
that the action method is sent to, the
selector
of the action method and the
arguments
that are passed in to the action method.
@interface RFAction : NSObject

@property (nonatomic, readonly) id object; @property (nonatomic, readonly) SEL selector; @property (nonatomic, readonly) NSArray *arguments;

@end

Note that we should retain the returned

subscription
when calling
subscribe:
. Reflow will not retain it internally and when all strong references to the
subscription
are gone, the
subscription
will call
unsubscribe
automatically.

We can subscribe to a single store module multiple times and we can subcribe to all actions of all store modules.

objective-c
[RFStore subscribeToAllStores:xxx];

Principles

Reflow is more of a pattern rather than a formal framework. It can be described in the following principles:

  • Immutable model
  • Single source of truth
  • Centralized model update

Installation

Just copy the source files into your project.

Required * RFAspects.h/m * RFStore.h/m

Optional * NSArray+Functional.h/m

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.