Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Katana – Modern Swift framework for creating iOS apps, inspired by React/Redux (github.com/bendingspoons)
162 points by Feanim on Nov 22, 2016 | hide | past | favorite | 52 comments


This feels a bit like a solution without a problem, or maybe more like a giant sledgehammer-sized solution to 10 or 15 smaller ant-size problems.

I guess I'm just not sure what advantage I'd get by taking a (frankly) mind-bending approach to building an iOS app instead of following normal iOS conventions. And just to be clear I've done React development on the web, and I've thoroughly enjoyed it. Modern web development is mind-bending enough that React (and other approaches) felt like they brought some sanity to the table, but this doesn't feel like that. It feels overly complicated for iOS development, which feels like a largely solve problem.

What am I missing? I'm really not trying to be critical, I'm really trying to understand why you'd recommend I use this approach over standard iOS patterns. In the 8 years I've been developing for iOS I've never felt like I needed anything I've seen in here (or in any of the getting started tutorials I read through), but I'd like to try to see what I'm missing here.


I think a big problem in standard iOS land is not doing shit on the main thread. It's too easy and after a while you accumulate small bits of main thread cruft, especially as your team / code size grows bigger.

It's a similar problem that AsyncDisplayKit was trying to solve, who's ideas later moved into the react world in general.

Another general design problem in iOS land is the MVC (massive view controller). View controllers tend to get huge as time goes on and become large 5000 line god objects that do everything. A general structure that encourages you to separate things out helps in avoiding this.

There is also a general trend of dependency management being singleton sharedInstance objects everywhere, which can make unit testing a pain. Especially in swift land today with it's lack of dyanmic mocking with OCMock.

Also I notice a general lack of unit / integration testing in mobile apps.

Another one is autolayout being a layout system that crashes your app. It doesn't let you recover in a more elegant way.

---

Now it's totally possible to avoid these caveats with skill, but the architecture of things in iOS land tends to land you into these problems naturally as your team and code size grows bigger. I don't know if react's architecture solves some of this, but those are general problems with standard iOS conventions I've noticed over 7 years with iOS dev.


I mostly agree (though I think massive view controllers are a code quality/poor developer discipline issue).

I've seen nothing in React's (or Katana's) architecture that avoids doing things on the main thread when you shouldn't. I'd also argue that GCD offers a nice, elegant approach to not doing things on the main thread when you shouldn't.

But I'd say that MVC-discipline and thread-discipline are baseline skills that I'd expect from competent iOS devs.

RE: dependency management/unit testing, that's an area I agree needs improvement in general for iOS (though I do not see anything in Katana that significantly helps with that). Swinject (as one example) is alright. I'm not hugely impressed.


(though I think massive view controllers are a code quality/poor developer discipline issue)

After almost 4 decades, I'd like to think that the "programming field" would finally get the clue and realize that "massive view controllers" are the result of powerful short term incentives. (MVC was popularized by Smalltalk, and "massive view controller" was a problem even then.)

Remember, if you are a genius being outsmarted by laziness, something is seriously wrong -- the other side is putting in so much less effort!


I recently worked on an app that adopted a similar framework (ReSwift) and I hated it for the same reasons you are skeptical. It was needlessly complex, horrible to try and debug, and was incompatible with the "status-quo" of standard app development practices.

At the end of the day it was just a bad replacement for Core Data / Realm / whatever.

I wish I could tell you more but like yourself I really had absolutely no idea what the point was or what it was supposed to make easier.


I too considered ReSwift for my last iOS project, felt it would be an overkill, so didn't use it eventually (I've worked with React/Redux). Though I inculcated a Redux like approach into the App directly and it seems to be working really well! Keeps data clean. I would recommend front end devs to check out the Redux pattern: how it manages "app state" and "user actions".


I've been working on an app that's more or less using ReSwift (I've hacked on it a bit). Can you elaborate on what you ran into that was complex/horrible?


I couldn't fix a lot of UI problems because when trying to trace what was happening through this system of actions and states I could only find when these bugs occurred when stepping between lines of machine code.

I can't offer a fair point of view because I don't think the project I was working on made proper use of ReSwift. It was very difficult to follow the flow of data and understand why certain things wouldn't work.


What you're describing doesn't really make any sense... the whole point of these frameworks is to make your application more predictable. If your layout isn't right for a particular state, you can rewind back to that exact state again and again. It sounds like you were using ReSwift wrong, to be honest. There's not a lot of magic going on in it that would make it hard to debug.


RxSwift are super callback objects. I don't know why you would compare it with storage libraries.


ReSwift is a unidirectional data flow library that brings Redux-like behavior to Swift. RxSwift is not the same thing. :)


It may be an overkill for super simple apps with just few views. But it works great with a larger codebase, at least for us. We have apps built with it with hundreds of views and it works great, it has halved our development time and helped us to have a more structured and clean approach to the state management.


How do you know it halved your development time? Do you have metrics from projects executed using standard iOS conventions vs. projects using the Katana approach? Or, asked another way, what - specifically - about this approach halved your development time?

In my experience, state management hasn't really been a bottleneck in iOS development, at least not to the point where it could account for half the effort of a project (or even 25%), and I've worked on some monster iOS apps.

Forgive me for being skeptical here, but I think this is one of those extraordinary claims requires extraordinary evidence situations. "It works great" sounds great and all, but I'd have a hard time convincing a dev team to adopt an approach because someone says it works great :)


I don't blame you for being skeptical.

The main advantages we've noticed using this pattern are:

1) every time there is a crash or a problem we can look at the the state and the actions that lead to that situation. This improved our ability to spot and fix bugs.

2) we can easly reproduce any appearance of the UI by providing a struct the rappresents the state of the app. This has improved our ability to QA the entire UI quickly.

3) with this architeture we've been able to refactor the code in simpler unit of logic rather then creating big VC. This has improved the readibility of our codebase.

4) it's easier to reason about the UI with the Katana (that is, declarative) approach rather than having to handle mutations

I'm not saying this is a solution for every problem but I can tell you that we internally used it and liked a lot so we felt like sharing our work with the world :)


Hi lucaquerella. I've been working on a similar bit (minus the React-inspired UI) here (combining ReSwift with app coordinators): https://github.com/willowtreeapps/cordux

We've also found reproducing the UI via state to be a huge win. We're using FBSnapshotTestCase to generate snapshots of our app across different controllers with different states, automated to take snapshots on different devices and in different orientations.

Glad to see other people are pursuing these ideas!


Those are legitimately interesting advantages - thank you for replying. Those would make really interesting deep dives blog posts / videos for making a case for Katana.


We haven't (yet) blog posts about Katana advantages, but if you want to try it yourself here you can find a very simple tutorial that will guide you in creating a very simple game

https://github.com/BendingSpoons/katana-tutorial-swift

It explores the core concepts of the framework and gives you an hint about how it feels to develop applications with this approach


I've been working with and on a project that uses a similar architecture. At least, it's using a Redux-like dataflow borrowed from ReSwift; views are standard iOS.

The benefits I've seen so far:

- Swift's type safety means you can validate at the type level that your app state is correct. As in, you could have an enum with separate cases for your authenticated and unauthenticated states, instead of optional values to indicate what state you're in. This makes your state stronger, in that the types hold only valid states rather than some possible states being invalid (e.g. unauthenticated with non-nil user info).

- State transitions -- reducer methods, (state,action) -> state -- are purely functional which makes (IMHO) feature work straightforward, testing trivial, and debugging fairly easy.

- Your view controllers become super dumb. All they have to do is render the data and relay input events. This is a nice clean separation of concerns.

- If your navigation route is managed in state, you get deep linking "for free". This is especially helpful in one of our apps for handling global errors that could occur on any network request (update state, route to error).

- Every bit of code has an easily identifiable home.

- Communicating between view controllers is simple. Rather than "passing the baton" as state is accumulated (say a multistep form), state accumulates naturally in the store.

I'm working on combining ReSwift with app coordinators here: https://github.com/willowtreeapps/cordux

I've written about it a little bit here: http://willowtreeapps.com/blog/app-coordinators-and-redux-on...

All that said, I still consider this an experiment. We've written about 40-50k lines of Swift across two apps with 8 developers so far, and opinions are mixed between considering it excellent and considering it burdensome.


What are the normal iOS conventions?

MVC pattern in iOS usually creates Massive View Controllers that are really hard to maintain and test.


I'd argue that "massive view controllers" aren't "normal", and if you find yourself in situations with massive view controllers, those are likely excellent candidates to refactor into smaller view controllers that can be composed together using something like iOS view controller containment. Well-disciplined iOS developers can keep this under control.

I'll turn the question around - don't you see the potential for the same kinds of poor development practices that lead to "massive view controllers" to be just as prevalent with an approach like Katana? So far, I see nothing in Katana that will prevent poor developer discipline.

So - (and I say this mostly in jest) - if you have poorly disciplined developers, now, with Katana, you have two problems: 1) the same kinds of code quality issue that you'd have without Katana, with 2) the added downside of no one understanding yet another overly complex application framework.


You have a lot of comments on this article, with some good questions and good points. However, I think there's a theme in some of them:

> Well-disciplined iOS developers can keep this under control.

Well-disciplined iOS developers with good development practices were also able to manage manual reference counting. However, I think it's widely agreed that ARC was a step forward for iOS development. Similarly, the Swift language has eliminated whole classes of bugs - ones that expert developers didn't often struggle with but nevertheless were problematic for the platform as a whole.

Personally, I love developing for iOS. I don't think there's anything broken with Apple's frameworks. I tend to be conservative and would prefer to rely on first-party provided frameworks & libraries when writing an application, with judicious use of 2nd & 3rd party code.

However, I believe there will be a better way to write iOS apps in the future. I think there's a lot of accidental complexity involved that must be simplified eventually. I don't think Katana is the solution. But I'm very curious to find out if it's a step in the right direction.


Comparing referencing counting and following a disciplined convention in coding are not the same thing. ARC was a step forward because it eliminated a nuisance inconvenience from your development which most of the time was not related to the problem you were solving.

Following a good and disciplined programing convention is more of an overarching theme of how you solve your problems. It practically defines your style and no amount of forced framework convention will be a step forward unless you're willing to take it.


That is one of the most common complaints about iOS.

Unidirectional data flow greatly reduce complexity because you don't to reason about several places making changes of the state.

I'm programming Elm after 3 years of iOS and, man, it's great. The amount of code I'm reasoning about is always contained in a in single vim window. It's something that can be done in iOS, but it would require very high discipline. In Elm it came naturally.


This seems like a reinvention of MVC -- your view is supposed to render your model (the state) and the controller is supposed to act on the model (actions).

The web complicates MVC but in standard desktop/mobile apps MVC is pretty straight forward.


As an iOS user, do you ever find that you have to kill an app in the app switcher and then restart it, to get some corner of the UI to refresh?

Also, if I understand correctly, when you open an iOS app, it should always come back to where you left it, regardless of whether it was killed in the meantime (possibly by the OS).

I'm guessing these things are easier to deal with correctly if you use something like React+Redux or Katana.


The one issue I see with bringing Flux/Redux to iOS is that iOS shipped with unidirectional data flow from day 1 with KVO: view => IBAction on controller => model method (incl. any API or CoreData work) => controller is notified of changes via KVO => rerenders view via a pure function. I don't think it was struggling for a new, clean solution here in the same way the web was.

That said, for people used to the React patterns, this could be a somewhat easy way to make the transition over to iOS programming! So that's cool :D


Except kvo doesn't have a sane threading model, nor is it typed, and can crash your program if you fail to unregister properly. It makes the programs a lot harder to debug,not simpler.


I'm one of the author of the framework :) please let me know if you like our approach to apps development we internally use it for creating our own apps at Bending Spoons (http://bendingspoons.com)


This is great! Is there a way to use with an existing project?



Yes, absolutely. You can use it to implement a single view or the entire app. Just render a NodeDescription in an existing UIView.

EDIT: https://gist.github.com/lucaquerella/74d8bbb5855f26249a27b4a...


> logic: the app state is entirely described by a single serializable data structure

I'm curious how frameworks like this handle dealing with large amounts of data? Let's say my data source has 25,000 rows that I need to search and filter through at any given time.

Using standard iOS paradigms I might store the data in a SQLite database using Core Data, use an NSFetchedResultsController to search the data, and display it using a UITableView. Any changes to the underlying data (insertions/deletions/updates) are automatically tracked and dealt with so the UITableView is kept up to date as the data changes. Behind the scenes there are lots of optimizations occurring in Core Data and the NSFetchedResultsController so only the data for visible rows are loaded into memory at any given time.

How would I achieve this behavior using a framework like this? How big/complex can this "single serializable data structure" get?


Can people please be honest and say "trendy" instead of "modern" if that's what they actually mean?


If you're willing to take a leap like this for an iOS project, why not just use React Native? At least with that you get a much more portable codebase, not to mention the benefits that come with its growing ecosystem.


ReactNative wraps up the native controls, often with lowest common denominator APIs that work across iOS + Android. You can get a basic app up and running quickly, but when you want to do something more advanced it really slows you down.


Any examples? I've been working with RN for the past few months and it's been pretty good. Only issues where I had to do some native wrapping were around background location polling and a client using a Microsoft websocket clone called signalr. Those things slowed me down.


Looking cool, I think iOS development in general could learn a lot from React + JS ecosystem. :+1:


What problems do you think the React + JS ecosystem could solve in the iOS world that haven't already largely been solved by standard iOS development approaches? I'm legitimately asking here - I been developing for iOS for years (and more recently with React from time to time for the web), and I've never once felt that standard iOS conventions/patterns were lacking compared to the React/JS ecosystem, but I would like to understand your point of view.


My favorite thing about react/react native development is the unidirectional flow of data. Specifically I like how it gives you:

1) Deterministic View Renders 2) Deterministic State Reproduction

I think the following article gives a good overview of why this is a good idea:

https://medium.com/javascript-scene/10-tips-for-better-redux...

Quoting from the article:

"When your view render is isolated from network I/O and state updates, you can achieve a deterministic view render, meaning: given the same state, the view will always render the same output. It eliminates the possibility of problems such as race conditions from asynchronous stuff randomly wiping out bits of your view, or mutilating bits of your state as your view is in the process of rendering."

ComponentKit is another example of a native library inspired by react (also developed by FB):

http://componentkit.org/


Biggest problem is the lack of a proper way to update data thru the app. More often than not, we end with a big mess of singleton, notification, badly implemented kvo... and if you're not lucky, you'll have to fight to get an authoritative source for the data, or the latest one. When I see what's possible with solution like Om, I'm very envious (not that it's a silver bullet, but it's a major step forward in my point of view)


You should try Realm. We radically simplified the architecture of our app by using Realm as the authoritative source of data (equivalent to a redux store) and then having all the views reacting to changes in the model. This also allowed us to have all writes happening background threads.


I am a huge fan of Realm; I have personally moved to a model where I use Realm as the authoritative source of data. It works very well.


I've been a Cocoa dev for 10+ years, and I have been doing React Native for a few months while the team I work in has been doing it longer. ( http://artsy.github.io/series/react-native-at-artsy/ )

The general things Cocoa could improve from JS:

* State Management * View lifecycle management * JSON -> UI (this became a lot worse with Swift) * Developer Happiness ( e.g. compare https://github.com/orta/vscode-jest to TDD in Xcode ) * Open toolchains (Swift is improving this)

The JS ecosystem focused on building apps that talk to APIs and act as simpler clients, the Cocoa one is about building apps that don't.


I found the following repo example of something React inspired too, but using f#. There's also a youtube video with a great explanation of the concepts on the repo. This guy also created an IDE for F# for the iOS. It is a great resource too... github: https://github.com/praeclarum/FunctionalMeetup youtube video: https://www.youtube.com/watch?v=_Qk7ZcyYZwA ios app: https://itunes.apple.com/us/app/continuous-.net-c-and-f-ide/...


I have kind of gone half-way between a full-blown React model and MVC for iOS development.

The last big project I worked on in Swift used an MVVM-style design. But I encapsulated the application state in a Swift enum.

The enum adhered to a protocol `AppState` which defined a method to turn an `AppState` into a view controller, as well as a static function to transition a view controller to a new `AppState`. Swift's strong type system ensured that it was very easy to guarantee that every view controller / view model had access to well-defined data types and that the flow of information only happened in one direction.

This meant that the entire transition logic for the application sat in one relatively small file that handled how each state could be displayed (and whether special cases were needed if coming from a particular state).

So for example, to present an alert in the application a view controller would ask its view model for the next AppState, and then ask to transition to that state.

E.g., in the view controller:

    let nextState = viewModel.nextStateForSomeButtonPressed()

    AppState.transitionViewToState(self, nextState)
And the view model's next state logic might be:

    return MyAppState.BasicAlert("Title", "Message")
The enum .BasicAlert has all the logic needed to transform into a view controller (in this case a `UIAlertController`), and the transitionViewToState has all the logic needed to present that controller.

I have since found that this is one of the most pleasurable projects to maintain and update.

Edit:

This also allowed some nice functions to be written, like:

    func initialApplicationState() -> [AppState]
Which the AppDelegate uses on startup to display the correct array of view controllers (in this case, the app displays a tutorial sequence on first-run, but that logic is completely decoupled from the AppDelegate).


How do you deal with the main thread and thread safety? Being able to 'modify' views off the main thread would be the largest advantage of something like this, kind of like async display kit


hey mahyarm, thanks for the feedback

this is definitely something interesting. We started looking into that at the beginning of the project but later we dropped it since we haven't found a use case when that was really needed, the performance are quite satisfying at the moment! Katana already has a diffing algorithm in place and some other optimizations.

If you have a project that could benefit from it, please open an issue so we can take a look at it!


From the code samples in README:

var payload: ()

What does that mean? What type has payload? I throwed it inside Xcode, it does compile but i still don't understand it.


It's Void. In that example there is no need for the action to have a payload.


Ah, thanks, didn't know you could declare a Void variable. And Payload is an associated type of the Protocol, now i got it ;)


How does diffing work?


It's quite long to explain it here, why don't you take a look at the Node.swift source code, if you have trouble understanding a specific part I'll be happy to help you.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: