Code Injection In Swift
One feature that iOS purists envy and the primary argument for alternative solutions to Swift is code injection or as others like to call it, hot reloading. It cuts down development significantly as you don’t need to recompile to see your changes. For developers that work closely with designers or have issues visualizing the result beforehand, it can be a game changer.
“With hot reloading, you can even run new code while retaining your application state. Give it a try — it’s a magical experience” — React Native README
Personally, this is feature is what makes React Native great. I know, there are more features that React Native (or insert any other alternative here) possesses but right now we are talking about code injection so bare with me. It opens up for being able to test different states at a glance; and with all the time it saves, you can focus on what matters, the things that set your app apart from its competitors.
So what if I told you that you could achieve hot reloading without jumping ship. A bright gentleman named John Holdsworth has made an application called Injection (not to be confused with dependency injection). The app is available for free in the Mac App Store.
InjectionIII on the Mac App Store
Read reviews, compare customer ratings, see screenshots and learn more about InjectionIII. Download InjectionIII for…
The application lives in the menu bar and observers whatever Xcode project you tell it to monitor. So the gist is that any file that changes will recompile and injected into your running application. What is does under the hood can feel quite magical, mainly because it is. John has done a great job of describing the process, and if you are feeling adventures, you can always jump into the source code as it is available on GitHub.
Before we go any further, I just want to note that injection only works in the simulator, I suggest you build in the simulator and test on a device.
Playground-driven development that was made famous by the fantastic team at Kickstarter put a tremendous spin on app development. It meant that you could manipulate the state of your application without the need to rebuild and rerun it.
Each screen could have their playground so that you could quickly test and work with all states related to that screen in isolation. However, because the technique is dependent on your app being compiled as a framework, it means that you will have to recompile before you see your code changes appear. Playgrounds can be fast or slow depending on the apps sizes, my biggest concern with this approach is not the setup or the hurdles you need to jump over but rather the core stability of playground themselves.
The concept of playgrounds is excellent, being able to run and test code without the weight or overhead of a project. Sadly, it does not always deliver. If you are buying into playgrounds and using them for development, make sure you learn all the tips and tricks as you will find yourself in scenarios where you need to turn to blood magic to understand what is going on and more importantly, get it to work again.I don’t want to downplay anything that the Kickstarter team has done because they indeed have created magic. I’m not saying that Injection is superior or the defacto way here, but in my personal experience, it has always delivered why more stable results than playgrounds ever have.
Just like playgrounds, if you want to leverage for its full potential then learn how it what it does, how it works and what limits it has. I would recommend to read up on both techniques, find the one best suited to you. Perhaps combine them and create something truly magical.
There are some limitations with injection, one of them being that you cannot inject structs, extensions or final classes. Once you get past this, there is still a lot you can do to decrease your development time. For me, there are multiple entry points where injection truly shines, here are a couple of them.
The examples use Vaccine to set up injection.
To get the most out of code injection, you need be able to provide your application with a new instance of the class that you are injecting. A good point of entry for injecting code is to reinitialize your app at the application delegate level.
It increases the likely-hood of getting the desired effect of code injection as your root objects are recreated using the newly injected code. It also provides with a point of entry for displaying the target view controller(s) that you are modifying.
So what it means in practice is that you can push or present the relevant view controller directly from your application delegate cutting out the need to recreate the view controller stack by manually navigating to the view controller you are editing. Working with InjectionIII is very similar to how playground-driven works, without having to wait for the playground to load or recompile your app as a framework.
The devil is in the details and for every second saved, you get additional time to apply polish.
When applying injection to this app layer, you are free to change the bounds of the window which means that you can set the bounds to be a different device size. This opens up for testing multiple devices using the same simulator. Kickstarter has done a lot of great innovation in this area, so I suggest you head over and take a peek at what they did.
There is no secret that the majority of the work that we do usually end up in a view controller. That is probably the main reason for us often ending up with massive view controllers, but that is an unrelated topic.
The good thing with about view controllers is that you can quickly create a system around them because of the view lifecycle it inherits. Most of the set up for our view controllers happen in viewDidLoad. Because of this reason, we can do some preprocesses to clean up before we invoke the method again. What this means is that we can change our set up instructions and invoke them again and see the new behavior appear on the screen.
Working with views is relatively similar to view controllers, except that they don’t inherit the view lifecycle as view controller does.
So here we need to do some additional work to introduce a streamline. When doing vanilla programming you do a lot of work in the initializer, the issue here is that you can’t invoke the initializer again as it will only run once for the lifetime of a view.
The most straightforward fix is to create a method that is invoked directly inside of the initializer. The same method is then used as the responder for the injection notification. When wired correctly, we can change the code inside our view’s load method and set the changes reflected on the screen.
Because we don’t always create new instances of our views, we could end up in a scenario where we add additional constraints that will render the views layout constraints ambiguous.
The reason is that we add constraints that are conflicting or duplicated which the layout engine cannot figure out how to make satisfiable.
Fixing it does not take a lot of effort, you need to do your book-keeping by storing your views constraints in an array. When the load view method runs, we need to deactivate or remove the previous layout constraints before we add the new ones.
Take a look at the view example for details on how this could be implemented.
If you add new constraints on
line 22, activate them and later store them in the
layoutConstraints array, this should help to fix any ambiguous constraint warnings from occuring.
Making great animation sequences relies heavily on fine-tuning. What sets a great and tailored animation apart from using a regular UIView animation block is usually the fact that more love is put into having great timing.
You can learn a lot of tips and tricks on what generally looks good, but there is no one-size fits all if you want to stand out. You need to iterate to make the animation fit with the apps tone of voice.
When applying injection with animations, it helps to think about your code like an orchestra. Set the stage and use your injection point, the method that will be called when the file is injected to both reset the stage, cleaning up previous animations and applying new ones and watch your orchestra play your masterpiece. That way you can sit together with your fellow designers and together decide what looks and ultimately feels right.
You can also reset your animations by injecting your view controller.
Code injection can easily be applied to your data sources. Just like all your other objects, you need to listen to incoming notifications and reload with the same data that you already have when one arrives.
The easiest way is to call
reloadData on your collection view or table view, that will invoke your
cellForItemAt indexPath: IndexPath method and configure your cell with the newly injected code.
Debugging or live patching
Logic should be unit tested, and I can’t stress that enough. But there are situations where you quickly want to check the rendering of a state to see what happens if x is equal to y and not z and vice-versa. Layout algorithms can also be a lot easier to reason with using live results rather than simple unit testing, depending on what kind of person you are.
As a developer, it is your job to create an environment where you and your fellow designers can work together. The set up will define how rapidly you allow your team to move.
Minimizing the number of times, you need to recompile your app you open up for more fine-grained tuning of things like; animation timings, tweaking fonts, colors, line-heights or changing layout constraints. Anything that you or your designer friends want to iterate over.
Thinking outside the box and continuously improving your tools and best practices can give you a competitive advantage. Having great tools can also work as a stress reliever as repetitive or mundane tasks are more easily handled, it opens up for collaboration and fast iteration which will have a positive effect on the end product. The devil is in the details and for every second saved, you get additional time to apply polish.
Because injection works using notifications, there is some additional setup needed to get the desired output that you want.
To make things easier, I created a series of templates that have injection already enabled. Using templates can help you streamline the way you build views, controllers, models, etc. Templates are great for more than just adding injection
I strongly encourage creating your templates and incorporating them into your workflow.
I’ve been working a lot with injection over the years, and it has become an essential part of my workflow. So crucial in fact that I’ve invested time into making a framework to provide more natural way to implement injection in new or existing projects.
Seeing as there are different ways to use injection depending on which component you inject, different rules should apply.
Vaccine provides an easy way to load the injection bundle in your application delegate, has view controller extensions for recreating the view controllers hierarchy and necessary sugars on top of NSObject.
The framework does its best to cover all the areas mentioned above in a lean and straightforward way yet keeping it maintainable.
Vaccine is available via CocoaPods; you can find the repository here:
Maybe injection is not for you, but you don’t know that unless you tried. If you find any neat ways of implementing this, be sure to hit me up on Twitter (@zenangst) as I’m super passionate about the subject. Also be sure to give a shoutout to John Holdsworth (@Injection4Xcode) as this wouldn’t even have been possible without his genius mind and all the hard work he has poured into the tool.
I want to end this article with a twitter quote from John Holdsworth himself.
Thanks for reading!