The Duolingo team’s Swift coding practices

The Duolingo team’s Swift coding practices

[[127033]]

We recently released a new Swift-based app that was featured by Apple and has gained a lot of users. In this article, we want to share our experience, present our views on this new language, and point out the new features in Swift that can help us write better programs.

This is not a Swift Getting Started Guide. The audience of this article is developers who are not very familiar with Swift and are curious about how Swift works in real programming. We will refer to some technical concepts and provide links to getting started guides and documentation about them where appropriate.

First, we'll briefly introduce what this new application does and what our main goals are.

New applications

You may already be familiar with our main app Duolingo, a very popular language learning app with over 60 million users (as of December 2014) and was named Apple's App of the Year in 2013. If you want to learn a new language, Duolingo should be your go-to app on your iPhone or iPad.

Later, we released Duolingo Test Center (hereinafter referred to as Test Center), which is a very useful application that allows you to test your mastery of a language. For example, if you are a foreigner and want to seek a job at a university in the United States or the United Kingdom, these jobs usually require you to have some official certificates to prove that you can use English fluently. Users of this application can take some tests to determine their language level, and in order to prevent cheating, there will be real people to supervise the test.

As soon as this app was released, it was recommended by Apple in the "Best New Apps" of the APP Store in more than 50 countries.

Target

In terms of performance, Test Center does not have high performance requirements. Most of the application is static content and a small number of controls. In addition, to prevent cheating, the entire test process will be recorded, and that's basically it for Test Center. We did not encounter any performance issues when using Swift, but we still have to pay attention to performance.

More important to us was the stability and robustness of the app. Since the tests lasted about 20 minutes and they were paid, a crash in the middle of a test would create a very poor user experience1. In addition, once a test is started, you must complete it (that is, the user cannot pause or exit the app; this is to prevent cheating). Therefore, we needed to minimize the possibility of crashes.

General thoughts on Swift

When Swift was first released, many people started comparing it to other languages ​​and drawing conclusions just by looking at its syntax. Some people said that now they "don't have to put up with Objective-C syntax anymore" and can just start iOS development. Honestly, this is a wrong view. Who cares about syntax (as long as it's not too weird)? There are more important things about a language than just syntax, such as making it easier for you to express your ideas and discouraging bad behavior.

Swift is more inspired by Objective-C or any other language than it was before. If you follow some of the Swift authors on Twitter, you’ll know that they’ve taken a lot of great concepts from other places, including functional programming, and they’ve also discarded a lot of existing (but not very good) concepts where appropriate.

Since we are used to programming in Objective-C, Swift is a nice and friendly improvement for us. If you are used to Haskell (or similar languages), you may feel that Swift still has room for improvement. At the same time, we are also looking forward to what more improvements will be brought by future versions of Swift.

advantage

Swift supports many new features that developers are already accustomed to in other languages, such as custom operators and function overloading. Value types (types with literal values, such as Swift structures) make it easier for you to understand the code.

We also really like using the more powerful static type system in Swift, as well as type inference, especially since there are no generics in Objective-C, and in Swift we finally have type-safe collections, rather than having to hope that we can store a certain type of object in an NSArray.

Next, let’s take a closer look at some of the very useful features we found in Swift.

No Exception

Until now, there has been no error handling in Swift. We don't know if the Swift authors deliberately didn't include error handling when designing the language, or if they just didn't have enough time. In any case, we think that the lack of error handling is a very good thing, because (unhandled) exceptions make the code harder to read (well-handled exceptions make the code clearer and let the developer know where the exception will occur, but they are too cumbersome, and Objective-C doesn't support exception handling anyway).

In fact, the seventh most common reason for app crashes we encounter is because an Apple-provided method throws an exception (-[AVAssetWriterInputHelper markAsFinished]). This method is not marked as throwing an exception, nor is it documented, so we have no idea that it behaves this way until we actually see the crash report, and by then some users' apps have already crashed.

Experienced Cocoa developers will know that while Objective-C provides a mechanism for throwing and handling exceptions, it is only used in rare cases, and these cases are often unrecoverable (although there are some examples). In this case, the better solution may not be to catch and handle the exception, but to improve the code so that the exception is not thrown at all. Some people may argue that this makes the exception seem like a failure assertion method, but perhaps this concept is designed to be like this, so in a new language with assert() and fatalError(), why keep it?

Normally, we want to avoid forgetting to handle an error, and ideally, we want to find any problems at compile time, not after our app has already crashed. Exceptions just make this difficult, so why do we need them in Swift?

Optional

There are many very important basic concepts in Swift, and Optional (which you may know is very similar to the Maybe type in Haskell) is one of them. Apple's documentation says:

Optional is an enumeration type with two values, None and Some(T), which represent no value and value respectively. All types can be explicitly (or implicitly converted to) an Optional type.

At the same time, Swift provides simple and convenient syntax sugar for using Optional types, such as using nil in the case of None, special expansion syntax, operators, etc. In addition, Optional chains also allow you to write simple and clear code containing multiple Optional dependencies.

So how do we use it? Optional is a very good way to indicate that "a value may be empty". You can use it as the return value type of a function to indicate that the function may not return any results (as long as you are not curious about why this is the case)

Why is this better than assigning null to a pointer in Objective-C? Because the compiler can now ensure (at compile time) that we are operating with the correct type. In other words, a value that is not an Optional can never be null in Swift. Also, because Optionals in Swift are more than just simple pointer types, they are much more useful.

Here is a small example of the use of Optional: In Objective-C, all methods that return a direct pointer type, such as object initialization methods (such as -init), may legally return nil (for example, when an object cannot be initialized). An obvious example is + (UIImage *)imageNamed:(NSString *)name. Just by looking at the method name, you can't be sure whether it will return nil.

However, in Swift you can. Apple introduced the concept of failable initializers in Swift, which makes it easy to express at the type level that a method will not return nil. In Swift, the same example looks like this: init?(named name: String) -> UIImage. Note that there is a question mark here, which means that if the variable with the identifier name cannot be found, the init method may return nil.

We use this feature a lot where it makes sense (we try to avoid explicit or forced unpacking of Optionals). If an expression might return nil (e.g. on failure) and we don’t need to know why, then Optional is a good choice.

Result

If you have a function call that might fail, and you want to know why it failed, then using Swift’s Result (which, for functional programmers, is like a subtype of Either) is a simple and practical choice.

Similar to Optional, Result allows you to express at the type level that something may be a value of a certain type, or an NSError.

Like Optional, Result is also a simple enumeration type with two enumeration values: Success(T) and Failure(NSError). Normally, the success enumeration value will contain the normal value you are interested in. If there is an error, you will get a .Failure and a descriptive NSError.

Unlike Optional, Result is not part of the Swift standard library, which means you have to define it yourself. (At this stage, the compiler still lacks some related features, and you need to find a workaround.)

We use Result in many places in our network communication, I/O, and code analysis modules. This solution is much better than the old NSError pointer passed in and out of functions, or using a completion block to contain a success value and an error pointer (or a more complex solution that uses a boolean return value and an NSError pointer together).

Result is a very elegant solution that allows you to write better, more concise and safer code. In our application, any expression that may fail (non-fatal failure) will return an Optional or Result.

Interoperability with Objective-C

Interoperability with Objective-C was a very important consideration when designing Swift. If Apple simply released a new programming language and wanted to completely replace all previous code bases with Swift's implementation, it would not work - at least not yet. In addition, there is still a large amount of Objective-C code in the development community. If there is no good interoperability with Objective-C, no one may be willing to use Swift.

Fortunately, interoperability between Swift and Objective-C is pretty straightforward, and we’ve already done some practice on a small scale, and it works pretty well. But it’s worth noting that some Swift concepts (such as enumerations) are not directly available in Objective-C.

For example, we have a small functional component in our application that needs to operate PDF files. We wrote this component in Swift, and then we want to use this module in the main application, which is written in Objective-C. Alas, some methods use features that are only available in Swift, which means that these methods cannot be automatically bridged in Objective-C. Why do we bypass this problem? We simply make a wrapper method for the Swift method, which can be used in Objective-C2.

Of course, it’s also very easy to use existing Objective-C code from our main app directly in Swift. If you want to do this, you can simply take that code out of the app (or better yet, make it a separate module in its own right) and import it into your Swift code via a bridging header file.

shortcoming

Although Swift has made a lot of progress compared to Objective-C, it still has some areas that need improvement. For example, this new language lacks some of the high expressiveness common in other modern languages. But as a new language, this situation may change quickly.

Apple guarantees compatibility, but also says they may change some features of the language when appropriate (in fact, they have done this several times). This means that after updating the compiler you may have to modify your code, otherwise it will not compile. We know this will happen, and it doesn't matter. Fortunately, for our existing code that used to work well, "fixing" them usually doesn't take too much time.

Our biggest gripe with Swift—and the source of our frustration—is probably not the language itself, but the tools that go with it. Using Swift in Xcode (Apple’s IDE for Objective-C and Swift) is not a very good experience. Xcode often gets stuck or crashes during our development. There is no (or very slow) code hinting most of the time, there is basically no debugger, syntax highlighting is unstable and unreliable, the editor is slow (once the project reaches a certain size), and there are no refactoring tools.

In addition, the error messages reported by the compiler are often difficult to understand, and there are some bugs and missing features in the compiler (for example, type inference often fails).

Xcode has improved a lot since we started using it, and it’s mostly good, but there are a few little things that ruin the programming experience. We hope Apple will pay more attention and continue to improve this development tool.

Some numbers

Apple announced Swift at WWDC in June 2014, and we launched Test Center, our first app written entirely in Swift, in late July of that year, and released it in mid-November. It took a little over three months to get to version 1.0 (one programmer; the Android and web versions already existed, so we did have a complete backend and design).

Like we said before, robustness and stability are very important to us, so let’s talk about how we do it in this regard.

collapse

At the time of writing this article, Test Center has been out for about two and a half months, and has already had a considerable number of downloads and users (probably thanks to being recommended by Apple).

As with any first release, we ran into a lot of issues that we hadn't seen before, but luckily we didn't seem to have overlooked any major bugs. As of today, the crash rate in the test center is around 0.2%, which seems pretty good.

If you look at the crash groups (crashes due to the same reason): the number one crash group (causing about 30% of crashes) is due to external Objective-C libraries. In fact, four of the top five crash groups are due to Objective-C (the fifth was due to a failed assertion that we forgot to turn off in the final release).

Another thing worth noting is that the seventh place is because the Objective-C function provided by Apple mentioned above sometimes throws an exception, which is not reflected in the documentation (-[AVAssetWriterInputHelper markAsFinished]).

We attribute this low crash rate to a solid software architecture and our adherence to good programming principles, but Swift’s excellent design also reduces the likelihood of many bugs, which helps us build our software architecture. For example, using Swift’s type system, many errors can be found at compile time instead of when the released product is running4.

Compiler performance

We have to ask the question, for a project of our size, how does the compiler compile? According to sloc, our project now has 10634 lines of actual code (excluding blank lines, comments, etc.).

Clearing Xcode's cache and then running time xcodebuild -configuration Release takes 2 minutes, and a debug run takes about 30 seconds to compile. All tests were done on a mid 2013 Retina MacBook Pro. Note that compiling xib also takes some time, and not all Swift 5.

You can definitely feel Xcode getting slower as your project grows, and we're not the only ones to have this problem. The cycle time (the time it takes from pressing CMD+R after making a change to the app opening in the simulator) is also longer than Objective-C. In a simple test, adding a line to the code took 14 seconds to compile, depending on what was done in the line, while making a similar change in an Objective-C project took only 2 or 3 seconds.

Of course, this isn't a sophisticated compiler benchmark, so take these numbers with a pinch of salt. Hopefully, you'll at least get a rough idea of ​​current compiler performance.

in conclusion

For long-time Objective-C developers — especially those interested in modern programming languages ​​— Swift is a welcome and exciting advancement, but it can also be frustrating at times due to the (current) development tools.

We’ve shown that (at least in our kind of apps) Swift can be used to write stable, robust, and high-volume apps. Our main app, Duolingo, already uses some Swift code, and we plan to use it more in the future.

So why would you choose Swift? As long as you have the patience to keep up to date with your users (you can only support iOS 7 and above) and develop large projects, Swift provides a fresh, well-structured programming language choice. We sincerely recommend that you try it, especially to understand the programming philosophy that Apple wants to promote.

If you are using Objective-C, then the transition to Swift is relatively simple and straightforward. You can use Swift in the same way as Objective-C. It will be fun when you use some of the new concepts in Swift. Especially now there seems to be a trend to embrace functional programming, which we think is great.

If you already have an existing Objective-C app, you may not want to completely rewrite the entire app to use Swift, but you may want to consider using Swift as you add modules.

If you could go back in time and rewrite this app, would you still use Swift? Yes.

<<:  Wandoujia releases "Snap Efficiency Lock Screen": Focusing on efficiency and content integration

>>:  The five best features Android should steal from iOS

Recommend

Why is it not recommended to use the refrigerator as a storage room?

Refrigerators are a necessity for almost every ho...

Jack Ma: "I had a sleepless night in Seattle last night"

[[150517]] On September 24, on the morning of Sep...

Code Review from Scratch

[[156391]] This post is not about introducing the...

Detailed explanation of the promotion and operation strategies of the loan industry!

First, let’s analyze Daichao’s business processes...

AppTalk: How to build an efficient entrepreneurial team and high-quality apps?

Beijing is a city full of entrepreneurial atmosph...

Don’t panic! These “abnormal physical examinations” are not diseases

Physical examination is an important channel for ...

One article to understand: What exactly is updated in Google Android Q

In the early morning of May 8, the 2019 Google I/...

Review | 0 basic community fission, 4 days to attract 700+ traffic!

Friends who are familiar with me know that I have...

How to make use of private domain traffic for brand promotion and marketing?

Through this article, you will be popularized wit...

Resource satellites: the "eyes" in space

Resource satellites are China's earliest tran...