Here's a quick attempt at trying to build off of some quick tweets:
@cdzombak @vitalekj @a_j_r nice! Another underlying point is that the delegate example is easy. I've seen experienced devs struggle w/ it.
— Andrew Sardone (@andrewa2) February 15, 2014
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
@cdzombak @vitalekj @a_j_r I think many devs' love of callbacks helps demonstrate this
— Andrew Sardone (@andrewa2) February 15, 2014
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
I think the original post takes for granted familiarity of Cocoa's delegate pattern with being better code. In my limited experience, established and knowledgeable developers have struggled to grasp's Cocoa's routine usage of this pattern, especially within UIs. It takes a little bit of reasoning to:
- Jump to the definition of
-[RSOWebServices fetchQuestionsWithDelegate:]
and view the required & optional messages defined in the protocol, processing which ones are needed for the task at hand. - For implementation and later maintenance, jump to a method implementation elsewhere in the delegate object, grasping what belongs here and what doesn't, and fighting off intermingling of concerns.
As a Cocoa developer, you do this a lot, practically to the point of muscle memory. But I think with the introduction of blocks in iOS 4, we saw many APIs ditch the explicit delegate pattern in a lot of scenarios. For many developers, they just find it a lot easier to grasp the notion of some block when used as a callback for completion/failure of some asynchronous chunk of work. I think AFNetworking's early success is an example of this phenomenon, along with things like BlocksKit and other blocks-all-the-things projects.
Anyway, these thoughts haven't been properly put together, but I just don't want us all to assume that protocol delegation is somehow natural easy and approachable.
Also, just for the hell of it, here's a stab at restructuring the Reactive Cocoa example code. Reactive Cocoa versus delegates versus callbacks versus etc. misses the point. As Chris's post outlines, it's all about better ways to express intent and structure code for better locality, reduced mutable state, and ultimately fewer bugs and better software.
- Remove the need for a
@weakify
/@strongify
song-and-dance withself
. Try to limit explicit side-effects to thedoError:
anddoCompleted:
operations. - Improve how we express the intent of a refresh signal: it's the next values
from the
topQuestionsSignal
whenever the refresh control fires off itsUIControl
event. - Define a relationship between the refresh signal and two plain-old methods:
-loadQuestions:
will continuously be passed the next values from therefreshSignal
.-displayError:title:
will be passed some next values that we've transformed from the error events ofrefreshSignal
.
- (void)viewDidLoad {
[super viewDidLoad];
self.refreshControl = ({
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
RACSignal *refreshSignal = [[[topQuestionsSignal
// only send the next top questions when the refresh control triggers a
// refresh via UIControlEventValueChanged
sample:[refreshControl rac_signalForControlEvents:UIControlEventValueChanged]]
// Inject a side-effect of stoping the refresh control's animation
// whenever the signal errors
doError:^(NSError *error) {
[refreshControl endRefreshing];
}]
// Inject a side-effect of stopping the refresh control's animation
// whenever the signal errors
doCompleted:^{
[refreshControl endRefreshing];
}];
// Lift the `-loadQuestions:` method into the reactive world.
// Set up a binding relationship that whenever the `refreshSignal` sends a
// next value, pass it in as the parameter for `-loadQuestions:`.
[self rac_liftSelector:@selector(loadQuestions:) withSignals:refreshSignal, nil];
// Lift the `-displayError:title:` method into the reactive world. Set up
// a binding relationship that whenever the `refreshSignal` sends an error,
// let's catch it an massage the value into parameters that can be passed
// to `-displayError:title:`
[self
rac_liftSelector:@selector(displayError:title:)
withSignalsFromArray:@[
[refreshSignal catch:^RACSignal *(NSError *error) {
return [RACSignal return:error.localizedDescription];
}],
[RACSignal return:@"An error occurred"],
]];
refreshControl;
});
}