iOS design patterns have evolved a lot over the years. In the three short years I've been involved in the mobile dev world, different trends have come and gone and best practices once used by many have been replaced by newer, more efficient ways of solving a problem. If you work in software development you know this... no big deal... that's just how it is. I agree. I wanted to lay out a few of the patterns for my own benefit, and include a look into one of my favorite new* patterns, functional reactive programming.
There are several different approaches you can take in communicating information between objects. Borrowed from NSHipster, this list highlights differences in coupling and intended audience under different approaches.
- Intimate (One-to-One)
- Weakly coupled
- Strongly coupled
- Direct method invocation
- Weakly coupled
- Broadcast (One-to-Many)
- Weakly coupled
- Strongly coupled
- Key-value Observing
- Weakly coupled
I don't intend for this post to only hit the listed patterns, and accordingly don't plan to just walk through each of these approaches. However, I will briefly touch on several and add some thoughts on a few other patterns along the way.
The target-action pattern is probably most familiar from using different UI controls hooked up to their respective IBActions. When a target receives an event, such as a button receiving a tap or a date picker landing on a new date, an action is triggered.
The trusty delegation pattern. I'll never forget this question from a job interview with Apple while I was still a student at BYU. Why are delegates protocols? Delegates are extremely powerful because any object can say hey, I conform to this protocol, I'll be the delegate. Delegates can also conform to multiple protocols (friendly tip: this was the answer the interviewer was looking for). If delegates were subclasses instead of protocols then the view controller would be forced to choose one set of instructions to support. It's not uncommon for an object to conform to 3, 4, or 5. The delegating object keeps a reference to the delegate object and sends messages to it at the appropriate time. Some of the more common UIKit protocols include UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, and UIAlertViewDelegate.
Delegates are so old school, who uses them anymore? I mean, srsly:
- RestKit 0.10.x to 0.20.x deprecated delegates in favor of blocks
- Dozens (or hundreds) or other custom UI controls and libs on github
Blocks are nice to use because they can keep the completion code/logic nestled up nice and close to the responsible party. In the words of Ryan Hodson:
For simple tasks it can end up feeling a lot cleaner than using delegate counterparts or creating and calling private methods. One downside: the syntax is impossible to remember. F***ing Block Syntax is your friend.
The notification pattern is so core to Cocoa that I think I've yet to see a code base that doesn't use NSNotificationCenter. As the list above suggests, notifications are a broadcast form of communication. A notification is sent out and any object listening for it at that time will receive it and can respond accordingly.
While powerful, I've seen notifications abused more than once or twice. What happens is the developer thinks to him or herself, "hmmm, I could do it this (other, more preferable) way, ORRR I could just setup a notification and be listening for it in this one (or five) other places and be done with it." The problem is that program execution suddenly goes from readable, flowing, easy-to-follow, to JMP over here and JMP over there. I'm not saying don't use NSNotificationCenter, but know when and when not to. If you are setting up notifications, listening for more notifications, and routing those to other view controllers all in your app delegate... you may be doing it wrong. Want a good use case for notifications? Currently I use NSNotificationCenter to notify listeners that network reachability has changed. It seems (to me) to be an appropriate use of a one-to-many broadcast.
Key-value observing is another of those patterns entrenched deep in the heart of Cocoa. The biggest problem in my book: it's extremely easy to write bugs into KVO code. The first time I did Startup Weekend I joined a team with an experienced, long-time Mac dev and KVO was almost the death of me that weekend. The idea is powerful: observe keypaths and get notified about state changes in any other object. At CocoaHeads last week we were on the topic of KVO in Swift and began hopeful speculation for next year's WWDC providing a new API built on top of KVO that makes KVO super nice in Swift.
Speaking of super nice KVO... enter ReactiveCocoa (RAC). What is RAC? The first question is what is functional reactive programming (FRP)? The best explanation of FRP I've hear thus far, from one of the creators of RAC:
When I mentioned FRP in the first paragraph I put an asterisk next to new. That's because FRP is not new. The principles of reactive programming have existed since at least the 90s, and KVO in many ways accomplishes similar goals in eliminating state and observing objects for changes. ReactiveCocoa includes a powerful API that achieves many of the goals in reacting to property changes but without the pain points of KVO.
The de facto RAC example seems to be observing a UITextField (or perhaps many) for text changes and updating say, a login button, to an enabled/disabled state based on the contents of those text fields. The same approach without RAC requires, no joke, checking the contents of fields in viewDidLoad as well as the use of at least two different UITextField delegate methods (namely, textField:shouldChangeCharactersInRange: and textFieldShouldClear:).
One of my favorite other use cases so far is reloading tables in UITabBarController's root view controllers. If you've used UITabBarController before, you may have been surprised, confused, frustrated (or just not cared) to find out that the root view controllers in each tab are only loaded once (or you may have been smarter than me and realized from the beginning that it actually makes more sense that way). You don't get a full refresh of the view lifecycle, so you can't rely on viewDidLoad being called... ever again, really, unless the app is FCed. But at the same time putting a fetch operation in viewWillAppear just feels like a bad idea, right? Do you really want to fetch all that data again if the user leaves the screen and comes back 2 seconds later? Of course not. Okay what next... setup an NSTimer or some sort of timestamp and only fetch it if it's been at least 60 seconds? All that hairiness for something as simple as fetching some data? And what if on top of that you fetch that same data on a different screen or on appDidResume, how do you notify that view controller?
ReactiveCocoa has made my life so much easier when it comes to this. I setup an observer for the data cache property that feeds the table on that screen, then simply fetch ze datas on viewDidLoad. Once the data returns and updates the cache, the observer reacts to that change and reloads the tableview. I've also added a call to fetch that same data on applicationDidBecomeActive so it's periodically updated. It works great, is super clean, and avoids some ugliness in subclassing UITabBarController and using timers.
Summing it up
Functional reactive programming and ReactiveCocoa is the latest of many new patterns to influence iOS development and Objective-C. It borrows good ideas from other paradigms and brings new solutions for solving familiar problems. Each of the patterns mentioned above have their own characteristics and intricacies. I'm not advocating for a complete functional revolution, but learning functional programming does make you a better programmer. For those who just haven't bought into the FRP hype, I leave you with this story from Rob Napier's post, Swift is Not Functional: