Developing iOS mobile apps and server-based Ruby applications is different on many levels. In this blog post, I will present a high-level overview of different aspects of day-to-day working in these technologies.
Read on if you’re interested in how these technologies differ in terms of the ease of development and problems you have to face every day. Just to warn you, it could also be that I am a bit biased towards favoring Ruby because it is the technology that I originate from.
Developing for the web you are free to build anything you want as long as it’s legal in the country where your servers are located. To contrary Apple can be very moody when it comes to letting your app pass a review process:
Sometimes it could be a simple detail you can fix right away. Sometimes an innovative feature (Astropad). In the worst case, the very fundamentals or your app functionality could be deemed illegal and not permitted to enter the App Store (Flux).
Building user interface
I am not too much of a frontend guy and
important! is one of my favorite CSS features. Still, I have built a couple of HTML interfaces so far.
With tools like browsersync changes in HTML UI can be reflected almost immediately, allowing you to tweak details with an instant visual feedback.
Building UI for an iOS app is a whole different matter. It might seem simple at first with a visual tool allowing you to drag and drop UI elements.
Unfortunately, storyboards and XIBs come with its own set of problems and limitations. Resolving Git merge conflicts when more than one developer is working on the same XIB could be a full-time job in itself. Anything more than standard UI tweaks has to be done from code.
For me, a big drawback of building iOS interface in code is the feedback loop. You need to recompile a project to see your changes.
I’ve been experimenting with Xcode injection and it kind of works but comes with its own set of limitations. It can seriously speed you up when tweaking UI in code, but you need to remember to write the code in a special way to make it work. What’s worse you need to hack Xcode to enable it.
Asynchronicity and threads
The way interaction is handled in web servers vs mobile apps is totally different. Please take those explanations with a grain of salt because they are very brief and general.
Whenever an HTTP request comes to a Ruby web server, it is processed synchronously. Business logic handles changes of the database state, sometimes external APIs are involved. Finally, a response is sent to the client. You rarely have to write an asynchronous code. Even if you use threading e.g. with Sidekiq, different threads usually don’t interact with each other.
A mobile app works in a kind of event loop waiting for interaction events e.g. user taps a screen, push notification arrives. Each of those events can result in a cascade of subsequent events, many of them asynchronous.
Making an HTTP request, performant fetching of Core Data entities, transitioning to a different view, animations… All of those actions require you to write asynchronous code in iOS.
What’s worse you need to consider different threads when developing for iOS. The problem that is virtually non-existent in Ruby (MRI version), because of Global interpreter lock.
Asynchronous nature of iOS apps and the presence of a real threading, adds two additional variables you need to take into account when reasoning about your code.
There are plenty of programming patterns which help you handle it. From GCD, delegates, and callbacks to more complex stuff like promises and functional reactive programming. None of those tools are normally used when building Ruby web apps because you simply do not need them.
Don’t forget about memory reference cycles that could lead to serious app crashes. Another problem that does not exist when coding in Ruby.
Ruby vs Swift (noncomprehensive guide)
I have to give this one to iOS. While I love Ruby for its flexibility, after having tried Swift I keep missing some of its features when coming back to web development:
They are one of my favorite Swift features and I only managed to come up with a very poor imitation of enums in Ruby. You can read about it in my previous post about Service Objects in Ruby on Rails.
Swift compiler protects you from errors like
NoMethodError (undefined method 'xx' for... . Honestly, these are the types of error I keep seeing way too often when working in Ruby.
nil is notorious for popping up when least expected. Swift optionals, as long as you don’t misuse them with force unwrapping, are a great way handle null values.
Not much to say here. Mutating value of a Ruby CONSTANT is permitted and you will get away with a friendly warning. Writing Swift code I try to stay away from using
var keyword whenever possible.
I wish Ruby would be more “swifty”. There is some hope for it with rumors about static types in Ruby 3.
Not everything is so great:
Despite various opinions about it Active Support is de facto standard when building Ruby apps.
From what I’ve noticed it is a common practice to add custom extensions to built-in Swift types when working on a project. I wish there were some opinionated community-wide accepted solutions for common problems in Swift. Barebones Swift is a bit clunky when it comes to handling everyday tasks e.g. string transformations or working with dates.
With Rails project preloaded, I can run unit tests even a couple of times a minute, when TDDying new classes. Running a test suite for a given file takes a fraction of a second:
TDD in iOS is sloooow…
Testing in iOS is a whole different matter.
I am currently working on a legacy app written in both Objective-C and Swift. Clean build on a top-notch MacBook Pro with 4 cores takes around 2 minutes.
Even with an incremental build, it takes over 10 seconds to run a single spec. I find it harder to do a proper TDD when the feedback loop is so slow.
If you make more changes Xcode could decide it’s high time to build your project from scratch and all you can do is wait those 2 minutes staring at the screen. You cannot write more code when the project is building because it often results in a compilation error.
From my experience video hangouts are the most CPU heavy part of a Ruby development. All you need for your work is a lightweight editor (not you VS Code) and a terminal.
To contrary compiling an iOS app usually consumes most of the computer resources and can take a couple of minutes. Professional iOS development on a Mac with less than 4 cores is a waste of a developer’s time because he would just keep waiting for a build to finish twice as long.
One of the things that could slow you down when working in Ruby is running a big test suit, especially the UI Capybara ones. In large Rails projects, they can easily run for more than 0.5h.
Fortunately running large test suits is something you can easily offload to a Continous Integration system, and speed up by running concurrently in multiple pipes. There are plenty of commercial solutions available (CircleCI, Travis CI) or you could just host your own Jenkins on a VPS.
You can offload running iOS test suite to a Continous Integration system as well. The problem is that due to licensing issues you need a physical Mac computer. You cannot legally run MacOS in a cloud. From what I’ve seen iOS specific integration systems are more expensive, and many teams keep a separate Mac in an office for custom continuous integration setup.
If you submit a bug to production, in a Ruby app, you can often just git push a hotfix to resolve it in a matter of minutes.
Messing something up in an iOS app build submitted to the App Store is a whole different matter. Standard review process currently takes around 2 days. You can request an expedited review but from what I know, you cannot do it too often.
Even if your fix gets published to the App Store you have no guarantee when your users will update the app.
Sublime Text together with VIM mode has been my editor of choice for over 4 years now. I’ve had a couple of tries with other tools but nothing comes close in terms of productivity, ease of use and performance. I know that other Ruby devs are happy using full-blown IDEs like RubyMine. There are plenty of other options available because basically what you need is a text editor.
With iOS, you are stuck with Xcode. AppCode could be a viable alternative but I did not give it a try yet.
Xcode is very limited in terms of customizability. XVim has been great for improving my productivity when working in iOS, but it’s still just an illegal hack. After the release of Xcode 9, I had to wait 2 months for library authors to release a compatible version.
Xcode stability is a story in itself. It just breaks. Random crashes and hangups is something you just need to get used to when writing native iOS apps.
Dependencies and debugging
Ruby on Rails development stack is composed of open source libraries. If you find a bug in a gem you’re using, the usual solution is to fork a repository and apply a fix yourself. You can earn some Open Source Karma on the way if your patch gets accepted by the author.
iOS app dependencies are also often open source with one important exception: Apple’s code.
If you find a bug in core libraries you are limited to submitting so-called “radar” and hoping that Apple guys will submit a fix in next iOS release.
Working with debuggers
When it comes to debugging there are a couple of cool options in Xcode e.g. debugging with breakpoints playing different sounds. One big problem for me was that step by step code execution often takes you into an assembly code:
Ruby standard debugger Byebug might not be as sophisticated as Apple tools but usually gets the job done. One big advantage is that all the code is open source so you can examine what is going on at the deepest level if you want to.
With unit tests executing at sub-second speeds I usually use them for debugging. Most of the time I am a puts debugger.
Below is my favorite debugging tool:
BTW you can check out my other blog post to find out how you can optimize your workflow with aliases.
Ruby web applications are centralized and you are in control of dependencies. It means that you can use all the newest bells and whistles as soon as you decide they are production ready.
iOS adoption rate is decent, usually well over 50% in just months after new system release. Still, you have to take users who are not willing to upgrade or use older devices into account. Rule of the thumb is that you try to support at least two last versions of iOS. It means you cannot go crazy with using all the newest APIs right after the WWDC.
Like I said it could be that I am a bit biased towards favoring Ruby. It is entirely possible that if my transition was the other way around I would write a blog post more favorable to iOS.
I’ve been developing hybrid apps in Cordova for over a year and I am well aware that the final effect compared to native apps is… debatable. Going native could be the only way to achieve the desired quality.
Maybe React Native could strike the balance between the ease of development and a product quality. I have yet to try it in a serious project.
Not sure if such comparisons make much sense. Ruby and iOS are totally different worlds after all. Anyway thanks for making it to the end. I might have mixed something up due to the lack of experience.