I decided that for the rest of my time at Hacker School, I’m going to work on rewriting from scratch my very first iPhone app, BeerChooser, including the entire iOS app, the server-side API, and a simple responsive web app.
I wrote the BeerChooser website as my first major development project back in 2009. It took existing user ratings of different beers to predict what someone would think of a beer they hadn’t tried yet. I taught myself Objective-C in 2010 and released it as an iPhone app that fall. It was featured in the App Store and several thousand users signed up, recording over 200,000 beer ratings.
I’m in the process of rewriting BeerChooser using all of the things I’ve learned in the intervening years. It has unit tests using the Specta framework, continuous integration through GitHub and Travis CI, and a highly modular, testable architecture inspired by the new VIPER model.
This week, I learned how to use a bunch of new iOS development tools, and finished writing the main module of the app using a VIPER-inspired architecture, which is a list of beers with their predicted ratings connected to the server API.
VIPER is an application of Clean Architecture to iOS apps. It attempts to solve the common issue of the way the software architectural pattern MVC (Model-View-Controller) works out in iOS to result in Massive View Controllers.
This results in code that is very difficult to reuse and even to maintain. The data is mixed up in the view controller, which may be a delegate for many other things that are happening in the app other than just displaying the view. The User Interface code tends to get mixed up within the same file that is controlling much of the application’s behavior, making it a huge undertaking to make changes to the user interface, and forcing you to rewrite tests to ensure that your application’s basic behavior is still working after you change the look of the app.
VIPER separates out each basic function of the app into its own module, and within the module it separates out methods pertaining to the view/user interface (the View) from methods which react to user inputs and prepares content for display (the Presenter), from methods which contain the main business logic of the app (the Interactor), from Entities (the data objects used by the Interactor), from Routing (the wireframes that present and dismiss views at appropriate times), from the Data Managers that talk to the Data Store.
This makes it MUCH easier to change out one of these pieces entirely (such as changing the UI from a list of items to a grid of thumbnails, or changing the data store from Core Data to simple SQLite, without affecting the other pieces at all. It also means that, when starting a new app project, you can reuse much of your well-tested code from an older project, and only change out the pieces related to different types of data or a different interface look in the new app.
Moving forward, I’m going to work on re-creating the multiple tabs of the original app, which had different beer lists including suggested beers you’d like, your previous ratings, nearby beers, search, and a list of widely available beers to get started with rating. It’s been intersting thinking about the best way to structure the data to account for the separate lists, figuring out when and how the data should refresh, and how to integrate the network calls to the API with the app’s data managers to follow the spirit of the VIPER model.
On Friday, I worked on some Recursion exercises. I decided to implement these in Swift, to get a chance to practice with the new language, and also because Swift playgrounds are perfect for implementing new algorithms and data structures and getting immediate feedback on how they are working. I found that the trickier problems were a lot easier if I first broke them down into a few simple cases, such as for 2 or 3 items. I wrote functions for those cases, then could visualize much more easily what the general recursive function would look like for
n items, and was able to write that.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
I also learned about memoizing from working on a Fibonacci function (where each number is the sum of the previous two numbers). My original simple implementation was easy, but since it calculated every number from scratch, it got extremely slow with numbers that were slightly larger.
1 2 3 4 5 6 7 8 9
“Memoizing” just meant implementing the function the same way I would do the calculations by hand. I wouldn’t look all the way back to
fib to calculate
fib – I would save the first time I calculated
fib and use those stored values to quickly calculate
fib. I wrote a new function that used an array to store previously calculated values of
fib, and because Swift automatically compiles, I could see right away how dramatically faster this was than the original implementation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
I also learned how to take advantage of Tail Calls in recursive functions. Here’s one way to set up a simple function that returns the sum of numbers in an array.
1 2 3 4 5 6 7 8
This works, but the problem is that the function ends up stacking up a whole bunch of function calls until it finally gets a value for the last listSum (when
numbers.count finally is zero), then it has to go back up through each level and add in
thisNum until it gets back to the top. This is fine if you have, say, 10 numbers to add up, but if you had a very large array, remembering all of those nested functions would overwhelm the computer.
Instead, there’s a tricky way to rewrite this so that the entire calculation is passed along to the next call of the recursive function. Then, the entire sum calculation happens when the recursion gets to the bottom (when
numbers.count is zero), and it doesn’t ever need to come back up to the highest-level function call, it just needs to return that final calculation. The trick is that the computer realizes it can forget about all of those nested functions, and just move the return along to the new recursive function call each time the entire calculation is passed along. Here’s an example of how to rewrite the above sum function to take advantage of a tail call.
1 2 3 4 5 6 7 8 9
This way, it skips all of those nested calculations and just returns the value calculated at the end.
In other news, Anatoly visited, and we got a chance to explore the city a bit outside of Hacker School. We checked out an amazing experimental theater project called Then She Fell, which might have been the best theatrical performance I’ve ever been to. It is a totally immersive, participatory experience based on the writings of Lewis Carroll, with only 15 audience members per show. You are frequently alone with the performers, there are delicious “elixers” served throughout the 2-hour performance, and everyone’s experience is different. I’d highly recommend checking it out!