Moving Projects from Obj-C to Swift
by Kostiantyn Bilyk
The App Store includes a lot of successful projects written in Obj-C and is used by millions of users, improving their lives and businesses. In today’s article, we are going to discuss the current experience of having to transition app code from one language to another.
Here are the highlights:
- The pros and cons of transitioning.
- Scenarios where transitioning is the best option.
- Timeline for when it’s best to start a new project rather than keep code of both languages in one codebase.
- The pitfalls of transitioning.
- How to build the best possible transition plan to keep projects running while they’re being implemented.
Before making a decision to move a project from Obj-C to Swift, you need to consider a number of reasons and we will review the most important ones in this article.
The main driver behind moving to Swift is Apple itself. Over 4 years ago, it became obvious that software development for iOS and macOS had a big future and needed a modern programming language to speed up the process and acquire ambitious software engineers to rally behind it. Obj-C is a great programming language that worked well for years but it also had its flaws. Some of these flaws include overloaded syntax constructions, lack of flexibility and built-in functionality for growing reactive and functional programming and sending messages instead of calling the function, to name a few. Thus, a decision was made to not switch to another existing language, but to create a new one altogether that could be used and modified deeply by Apple along with the participation of the open-source community. From the earliest versions of Swift, Apple urged developers to give it a try and give their feedback. As an example, WWDC conferences switched their live code demo sessions from Obj-C ASAP to show the capabilities of the new language.
One of the reasons to develop Swift was the new generation of software engineers who were already used to the convenience of modern programming languages. The idea became a reality and today, it is harder and harder to recruit an engineer to work exclusively with Obj-C projects, even for product companies (even more so for outsource or outstaff) as it is considered not only tedious work but also a drawback in their careers. Moreover, new engineers willing to work on a project for the experience and expertise of senior colleagues simply do not know Obj-C at all. Even if a company is able to recruit an experienced person who is ready to work with an older language, it usually costs significantly more.
Apple did a really good job in supporting the Obj-C functionality to be on the same level with Swift by implementing some important features for both languages in iOS and macOS SDKs bringing Obj-C to a new level of capabilities. Introducing generics in Obj-C was a big step forward for its development, but we are facing the tendency when C-based language becomes harder to support in new Apple frameworks. In the far future, chances are they will limit this support to a critical level. One can argue that it will not happen soon but if there are plans to move the project to be successful in the next years, this case should be foreseen and considered as well.
Recently, Apple presented SwiftUI 2.0 - an advanced version of their declarative UI framework which resolves many years of issues building interfaces of iOS, unifying the approach to the macOS as well, speeding up and simplifying software development in general. Truth be told, version 1.0 was really raw and could be used only in commercial development. Today, the framework made a big step up toward being used in real projects. The biggest change, apart from the control flow improvements, is the SwiftUI workflow for the application. By implementing this change, engineers added an alternative way to use and track app lifecycle which was impossible before. Now, there is no more overloaded App Delegate, which existed from the very beginning. Also, the SwiftUI based app can take as much as 144 symbols to work. Obviously, there is not much space for Obj-C here.
For many years, software engineers who work with iOS were advised to use the so-called Apple MVC (Model View Controller) architecture pattern in their applications. This often led to Massive View Controller issues in the app since View and Controller were melted into a single file/class/entity by Apple on an SDK level. To resolve this and many more issues, developers from all over the world practiced many more patterns such as MVP, MVVM, VIPER, Clean Swift and Redux on their projects, experimenting and trying to improve the efficiency of adding new functionality to the existing one. Today, we have new recommendations that are based on using SwiftUI in combination with the MVVM pattern and reactive development framework Combine by Apple. This tight bun does not include Obj-C.
Overall, there are two ways to move your app from Obj-C to Swift. You can either have both languages on one project or rewrite a project to Swift separately.
Two in One
Starting with the earliest versions of Swift, Apple provided mechanisms to call code written in one language from another since both languages are compiled and the main role here is played by special Headers.
Importing Objective-C into Swift
In order to use Objective-C code in Swift projects, you need to use a so-called Bridging Header which must import all headers of “.m” files that are going to be used on the project. If a respective “.h” file will not be imported in "-Bridging-Header.h", it will not be visible to Swift code.
Xcode automatically suggests creating a Bridging Header file when you add any Obj-c files to the Swift-based project:
Alternatively, it can be done manually by choosing File > New > File > [operating system] > Source > Header File and naming a new file using your product module name followed by "-Bridging-Header.h". In this case, you need to make sure the Objective-C Bridging Header build setting has a path to the bridging header file (the path is relative to the project root folder).
The Apple Developer Portal provides an article on importing Objective-C into Swift so you can learn more details on the subject.
Using Swift in Objective-C
Similar to the Bridging Header, there is a special header file to cap all Swift types and be used by importing a header with a name that is generated from your product module name, followed by "-Swift.h". in needed “.h” and “.m” files.
In addition, you need to consider generated headers that contain interfaces for Swift declarations marked with the public or open modifier. If your app target has an Objective-C bridging header, a generated header also includes interfaces marked with the internal modifier. Declarations marked with a private or file private modifier don't appear in a generated header and aren't exposed to the Objective-C runtime unless they are explicitly marked with a @IBAction, @IBOutlet, or @objc attribute. Inside unit test targets, you can access imported internal declarations as if they were public by prepending @testable to the product module import statement. You can read more on importing Swift into Objective-C on Apple’s Developer site.
You can also transform existing projects into a framework to be used inside a new one. In order to do so, you need to create a .workspace by means of Xcode or by using cocoapods which will create a workspace automatically. Turning the project into a framework is not an easy task but it can be beneficial when using an existing code base and by omitting headers. Essentially, you will get the workspace with multiple projects inside, which will depend on one another, and framework projects can be rebuilt.
This approach is useful when you have two teams: one to support the existing code base and another to implement new features based on the current one. In this case, it is possible to create your own pod and include it as an external dependency, but in this case, you will not be able to look inside the framework. If you are interested in this approach, you can learn more about Modular Architecture in iOS.
It’s possible that transforming one project with two languages will become too resource-consuming or even risky as it might bring severe issues. In this scenario, the best solution is to develop a new project from scratch for Swift. New projects must be created in the latest version of XCode and use the latest version of Swift, which may bring another set of issues.
Apple has a new declarative UI framework and suggests using it for new projects. It’s a great technology that’s bound to become the future of Apple software UI. Rebuild an existing project, might create new challenges.
First of all, reusing existing .xib and .storyboard files can be hard. As always, Apple provides ways to do it but it will not be with SwiftUI. This will create a certain amount of work to recreate all of the UI using a new framework. The SwiftUI app is recommended to be based on the MVVM architecture pattern. If your project was written considering Apple MVC as it was previously suggested by Apple itself, then it will be the next step to consider.
Finally, the SwiftUI app written with the MVVM architecture will be the best option to work using some reactive framework for data flow and state management. For example Apple’s Combine.
SwiftUI App Life Cycle
As a new feature of iOS 14, Apple presented a changed Life Cycle approach which no longer uses AppDelegate or the SceneDelegate. Instead, it completely relies on new App protocol confirmation based on SwiftUI. Now, applications can be much smaller and have a completely different way to manage their lifecycle.
One of the possible problems here is the absence of delegate methods to track the lifecycle events which is somewhat resolved by Apple by introducing a new, more powerful switch for SwiftUI:
Another possible issue might be caused by the absence of a common point of initialization, which for many years was the “appDidFinishLaunching:WithOptions” method. When considering this, the UIApplicationDelegateAdaptor was introduced to get access to the missing AppDelegate methods. You can find more details and examples of the SwiftUI new Life Cycle issues in The Ultimate Guide to the SwiftUI 2 Application Life Cycle article.
Many projects rely on existing frameworks and use cocoapods as a dependency manager. It might be the case that some library that was used by the Objective-C project is no longer supported on Swift. This was the case for the earlier versions of Swift.
Now, most of the popular frameworks have the Swift version but this point should be considered when making a decision to go from one language to another. Thinking of the worst-case scenario, it’s also possible to include Objective-C based frameworks into Swift projects especially via the cocoapods. In some rare cases, you might need to fork the repo to change the code of the library to be used by Swift.
Pros and Cons
Apple provides the functionality of bridging headers to call code from both languages but it is as simple as it sounds. This functionality allows the implementation of partial transition and inclusion of code on one language into the project written on another. On the downside, this interconnection might cause build issues and problems with cocoapods integration. In earlier versions of the Swift, and with lack of Swift support in libraries, such issues were a common case, but today it should not be a problem and in the worst case can be resolved by using the “use_frameworks!” parameter in the pod file to omit Static Libraries.
The simplest way to transition from Obj-C to Swift using both languages in the code simultaneously is to go with modular architecture providing only APIs to be called from each module which is pretty similar to the microservice architecture but inside of the iOS/macOS application.
The positive aspect of this approach is code separation, which is the decoupling and ability to refactor it module by module. Generally speaking, such architecture is a good way to advance complex projects with several development teams, each responsible for their own part. On the other hand, managing a multi-project workspace might be challenging and can require some learning and knowledge sharing with new team members.
In a perfect world, your code written with SOLID in mind and fully covered with unit tests is easy to move from one language to another as they are seamlessly incorporated into the iOS SDK. In most of cases, you will have to do some, if not a lot of, refactoring in order to make it happen.
It is a good time to improve your code, make it more protocol-oriented, and cover it with unit tests. Moreover, the best strategy here is to refactor in TDD style by covering existing code with tests and then move it from Objective-C to Swift, ensuring all tests pass. The evident drawback is that this might take more time, but in the long run, considering future benefits, this will be a good trade.
Working in an Agile environment might cause a false feeling of good control over a team’s work and capacity, but when switching from Obj-C to Swift, that can be the most challenging part as it can lead to a lack of estimation experience for such tasks and it also might require additional resources and expertise. Starting to allocate resources for these activities is a good experience for the team. With proper planning and approval, you can make constant refactoring to be a natural part of the development process. Without acquiring additional resources, you will need to reassign existing ones to the language change task which will decrease the progress on other planned features and improvements.
When there is something new being developed, chances are there is always room for a bug. Transitioning to another language is not only a risk but also an opportunity to find and fix existing bugs. Switching to Swift has benefits when finding existing bugs in Objective-C code. As a strict language, Swift forces you to use check variables or constants for being nil and does not allow you to send messages to the nil-object as Objective-C allows. Moreover, the rewriting of methods to functions allows you to review its body and find possible logical bugs which are harder to catch. New code might cause new bugs, but careful implementation combined with detailed Code Reviews and Code Coverage will minimize these risks.
This topic is often omitted in many projects usually by pretending that existing code is self-documented which in most cases is not enough. Once again, to rewrite some function in another programming language means to understand what it is supposed to do and having this fresh knowledge is a good time to document it. This can be done in some wiki page for the project or even in a shared doc. It’s good practice to document a project thoroughly. Creating documentation will take some time for the engineer and might be challenging as not everybody has good skills of expressing their thoughts.
Each of the languages has its own benefits and limitations. For example, the swizzling method that is being used in Obj-C code will not be so easy to replace with Swift capabilities but that is only one side of the problem. One of the reasons to create Swift was a need for better syntax than what Objective-C offered, and it was done successfully. Many common constructions are much shorter in Swift and make it more similar to other modern programming languages. Engineers who are more familiarized with Objective-C will find it challenging to switch to Switch, like finding new ways of doing similar things. Swift is a mature language and most issues are already solved in common ways.
Having two languages in the same project does not benefit the speed of a project. According to Apple, Swift is up to 2.6 times faster than Objective-C and this statement alone should be a good reason to start refactoring right now. But in the migration process, the app will work with the speed of the slowest one and that is Objective-C. In addition, the compilation time might increase because of the management of interactions between the two languages.
All the aforementioned pros and cons so far add complexity to the project compilation and build processing pipeline in a way. You might want to consider using an automation tool for mobile development like fastlane which allows you to configure pipelines using Swift. Once configured on a local Mac of the developer project containing two languages, it will be able to run on remote build machines. This may require some time to configure and test the new automation as you will start to work on transitioning the project.
Existing code might depend not only on used libraries that do not support (yet or in general) integration with Swift, but also might cause problems with usage of dependency managers such as cocoapods. The good news is that since Swift was introduced, cocoapods and most common libraries fully support Swift. In addition, there is a Swift Package Manager that is increasingly becoming more popular. If the project uses a well-written yet outdated library, you may find it hard to find a good, modern replacement or to support the transition with your own team.
Every typewritten on Swift, even if it is a complete duplicate of an Objective-C analog, is considered as a new one for things like encoding and decoding and you need to secure how existing persistent storages will work with new types and entities. The CoreData object class migration does not require significant changes to the schema. With that being said, objects encoded to the file will be considered as new ones so you will need to come up with some migration strategy and keep Objective-C versions of entities in the codebase for some time to perform those migrations for users so they don’t lose their data.
There are a lot of other things that can go wrong or give you some discreet benefits when moving the project from Objective-C to Swift. The main thing to keep in mind is that you will have to deal with complications by spending more of the engineering team’s time to solve them but in the end, things will be worth it. As the saying goes, it is never late to make things right and this is a good place to start doing so.
It’s better to have a plan than not to have any. Considering everything we’ve laid out in this article, you can move forward with planning the best approach to use for specific projects.
1. Review. To determine the future refactoring process, you should first understand what you have right now. Will it be easy to group code into separate modules? What is the current architectural pattern? Are there dependencies that should be moved to Swift? How are things with documentation?
2. Strategy. The next step is to determine a strategy or a way to implement changes. Can you create a separate new project or should you proceed with the existing one? If the latter, should it be refactored to modules? Do you need SwiftUI along with the new lifecycle or it is too immature?
3. Resources. This step is strongly linked to the previous one. Can you use the existing team to transition from Objective-C to Swift? Do they have enough experience and knowledge in new technologies to do it without additional expertise? Is it possible to have a separate sub-team dedicated to the transition task?
4. Planning. The actual planning should be a part of the plan. How much time is the refactoring process going to take? How many new engineers do I need to hire? How many releases should there be until the app is completely switched to a new programming language?
5. Hiring. Most likely, there will be a need for additional engineers and it should be carried out after careful consideration of the previous steps. What are the requirements for the candidates? Should I hire software engineers and QA engineers? Will I need a team lead for the separate team?
6. Implementation. This step concludes all the preparational work and requires some more decisions to make. Should the process of the project transition be considered as a separate project in terms of management? Do I use the same workflow (some agile framework, waterfall, else) as the main one? How do I share project knowledge between new team members and engineers that worked on the project for some time?
7. Review. Finally, after everything is implemented, it is a good practice to review the gathered experience. What was done well, where there was room for improvement and what takeaways can be used in the future. Do I have any other projects where I can reuse the gathered experience? What can I do with the additional resources that were acquired for the transition project?
It almost feels as if we don’t really have a choice when it comes to transition projects from Obj-C to Swift, instead, we can only decide how to do it. It’s important for you to consider all the pros and cons, and to create a transition plan that either combines the two languages in a single project or to create a completely new project using Switch from scratch.
This article gives you a glimpse of the many aspects you need to consider based on your specific project and team. If you follow these recommendations, keep in mind the key aspects herein to make the process of changing Objective-C to Swift as smooth as possible.
Let's meet Svitla
We look forward to sharing our expertise, consulting you about your product idea, or helping you find the right solution for an existing project.
Your message is received. Svitla's sales manager of your region will contact you to discuss how we could be helpful.