Awesome
Objective-Chain <a href="https://flattr.com/submit/auto?user_id=Tricertops&url=http%3A%2F%2Fgithub.com%2FiMartinKiss%2FObjective-Chain" target="_blank"><img src="https://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0"></a>
Object-oriented reactive framework written in Objective-C, that abstracts production, transformation and consumption of values in a declarative way.
Project is inspired by ReactiveCocoa, but takes more object-oriented approach.
Aim is to build reusable and scalable solution for MVVM bindings. Article about MVVM implementation using Objective-Chain.
Project can be reliably used in production. Follow the Roadmap for progress.
Concept
Everything happens for a reason and this is especially true in software. Basic principle of software is to receive input and provide output. Reacting to events with actions, but our actions can trigger new events. A reactive framework should allow you to write the rules declaratively. This means you write it once and it works forever (or at least until cancelled).
In iOS and OS X applications, we know multiple way to react on events: Target + Action, Notifications, Key-Value Observing, Delegation and Blocks. All of them have different characteristics and therefore they are used in different cases and using different APIs. Objective-Chain attempts to unify these callback mechanisms and allows you to easily receive events, filter or transform their values and subsequently execute actions or chain them to other events.
Event vs. Value
To avoid confusion, we should clarify the difference between Event and Value. The difference, for purpose of Objective-Chain, is none. Producing a Value is an Event and Events usually have some Value associated with them. And if not, No Value is still a Value.
Main Components
Core concept is really simple: Producers send values and Consumers receive them. Producer and Consumer are abstract terms, so the true functionality is provied by their concrete implementations.
Producers
-
Timer – Periodically sends time intervals to Consumers until stopped.
-
Property – Observes KVO notifications of given object and key-path and sends latest values to Consumers. It's one of the Core features.
-
Notificator – Observes
NSNotifications
with given name and sends them to Consumers. -
Target – Receiver of target-action callbacks that sends the sender to Consumers.
-
Command – Generic Producer to be used manually by invoking its methods.
-
Hub – Special Producer, that takes multiple other Producers and forwards their values. There are currently three kinds fof Hub: merging, combining and depending. More on those later.
-
In addition, you can easily subclass Producer with custom implementation. If there are other sources of events/values that should be implemented, feel free to suggest it.
Consumers
-
Property – Yes, the same Property as the Producer above, but this time it set received values using KVC. Setting usually triggers KVO event, that is immediately produced. It's one of the Core features.
-
Invoker – Invokes regular invocations optionally replacing the arguments with received values. Don't worry, it has never been easier to create and use
NSInvocations
! It's one of the Core features. -
Subscriber – Most versatile Consumer, that can be customized using blocks. Allows you to easily create ad-hod implementations of consumers, if there is no better alternative (and trust me, there usually is).
-
Switch – Similar to
switch
orif-else
control statements, Switch takes multiple Consumers with one Predicate for each. Once it receives value, it invokes all sub-consumers whose predicates evaluate toYES
. -
There are some more provided Consumers, but they usually only uses Subscriber to perform their task. If there are other special cases, that need custom subclass, suggest them.
These were only the endpoints of Chain, now the fun begins…
Mediators
Mediator is simply a Producer and Consumer that can stand in between and make changes to the values. It never produces new values and never uses them in a meaningful way.
- Bridge – Basic Mediator that passes all values further. It is best when you want to expose a Producer in object's interface. Optionally, and this is important, Bridge can use a Value Transformer to convert values before passing them to Consumers. This is one of the Core features. More on Transformers later.
- Filter – Mediator that evaluates a Predicate on the values. Those that evaluates to
YES
are passed without changes, otherwise they are ignored (discarded). - Context – Interesting and flexible Mediator, that simply forwards the values. The point is, it sends them in a known context. For example, inside of animation block, or inside of
@synchronized
statement, or even send them on another Queue. Context object is really simple, but allows you to do powerful things. More on Queues later. - Throttle – Time-aware Mediator. It forwards values with reduced frequency, for example when user types fast on keyboard, Throttle can be configured to send latest entered text after 0.3 s pause.
Creating Chains
You can use any of those provided components or create your own and chain them together to build the logic of your application. Examples:
-
Listen for notification and invoke a selector:
[[OCANotificator notify:NSUserDefaultsDidChangeNotification] connectTo:OCAInvocation(self, reloadPreferences)];
+notify:
– Creates a Producer that listens for given notification.OCAInvocation
– Macro that createsNSInvocation
for[self reloadPreferences]
call.-invoke:
– Internally creates a ConsumerOCAInvoker
with given invocation and attaches it to the Notificator. Target of the invocation is stored weakly.
-
Anytime the name of user changes, display it in a label:
[OCAProperty(self, user.name, NSString) connectTo:OCAProperty(self, label.text, NSString)];
OCAProperty
– Macro that creates anOCAProperty
object. It takes a target, key-path and a class of values (NSString
in both cases, more about class-validation later). Property object can act as both Producer and Consumer. This macro uses Xcode autocompletion and is validated during build time.-connectTo:
– Adds the argument to the receiver's list of Consumers. Any changes inuser.name
will be reflected bylabel.text
.
-
When text of text field doesn't change for 0.3 seconds, initiate search:
[[[self.textField producerForText] throttle:0.3] connectTo:OCAInvocation(self, startSearchWithText: OCAPH(NSString) )];
-producerForText
– CreatesOCATarget
Producer configured for the text field (receiver). Sends the entered text every time it changes.-throttle:
– Internally createsOCAThrottle
Mediator with given delay, attaches it to the receiver and returns it (so we can continue chaining). Throttle will send the entered text only after it didn't change (nothing is received) for 0.3 seconds.- (
-invoke:
andOCAInvocation
are described in the first example above) OCAPH
– Macro that is a short alias forOCAPlaceholder
. When used as an argument of invocation passed toOCAInvoker
, it will be replaced by real value. In this case, we used only one Placeholer, so the received text from text field (and throttled) will be passed to the invocation. Argument of the macro is class used for validation.
Example Project
For more code examples see included Chain Examples project. You can use it as a sandbox for experimenting with Objective-Chain and even submit a Pull Request, so your experiments will be merged into the master repo.
Additional Features
To be described later:
- Class validation.
- Transformers.
- Predicates.
- Queues.
- Value boxing/unboxing.
- Invocation catcher.
- Decomposer.
Follow the Roadmap for progress.
Check it, load it, link it, use it,
View it, code it, quick - combine it.Chain it, branch it, merge it, fork it,
Switch it, send it, bridge - transform it.Catch it, change it, call it, tune it,
Drag and drop it, box - unbox it.
Licensed under The MIT License (MIT)
Copyright © 2014-2015 Martin Kiss