Coobjc Open Sourcced: Alibaba’s Open Source Coroutine Framework for iOS Developers

Offering full coroutine support for iOS languages

Image for post
Image for post

A coroutine development framework designed for the iOS platform, Alibaba’s coobjc provides coroutine support to some of the APIs in Foundation and UIKit via a cokit library. It supports Objective-C and Swift.

Image for post
Image for post

Alibaba has now made coobjc available on Github under an Apache open-source license. This article gives an overview of the framework, from its design to use cases.

The Problem of Async Programming

Async programming has progressed rather slowly in the 11 years that have passed since the first iOS platform was launched in 2008.

Image for post
Image for post

Currently, the block-based async programming callback is the most widely used iOS asynchronous programming method. The GCD library provided by the iOS system makes asynchronous programming simple and convenient, but it is not without its flaws. To name a few:

· If you’re not careful, you’ll end up in callback hell

· Error processing is a long and complicated process

· It’s easy to forget to call the completion handler

· Conditional execution is difficult

· Returning combined results from mutually independent calls is extremely challenging

· Sometimes, continuous execution occurs in the wrong thread (e.g. in the sub-thread operation UI)

· Multi-thread crashes occur frequently, and it’s difficult to identify the cause

· Performance issues and crashing due to misuse of locks and signals is common

Alibaba initially tried to combat the multi-thread issue and the related crashing/performance issues by implementing strict programming standards and giving extensive training to new team members. But still the issues occurred, and ultimately async programming proved to be too complex an issue to be solved through standards and training alone.

In fact, these issues are commonplace during the development of many systems and languages, and the trend has been towards solving them by using coroutines. Many languages now support coroutines and the related syntax, including C#, Kotlin, Python, Javascript and more recently C++. This solves the problem of async programming for developers using these languages.

But what about Objective-C and Swift? Developers using these languages also need a way to benefit from the new programming experience that comes with coroutines. This was the case for Alibaba’s mobile Taobao team, responsible for an e-commerce app that serves hundreds of millions of users a month.

Accordingly, the mobile Taobao architecture team spent a long time delving into the bottom-layer libraries and assembly language. Finally, they emerged with coobjc, a robust solution to support Objective-C and Swift coroutines through assembly language and C language.

All About Coobjc: From Core Features to Source Code

Coobjc’s core features are as follows:

· Similar to the Async/Await programming used in C# and Javascript, coobjc obtains the execution results of async programming by calling the await method. This makes it an ideal choice for executing and transforming synced sequences called by IO, network, and other time-consuming async processes.

· Coobjc offers a function similar to Kotlin’s Generator, making it ideal for lazy evaluation to generate interruptible multi-thread serialized data.

· Coobjc can realize Actor Model, enabling the development of more thread-safe modules and avoiding the multi-thread crashes caused by directly calling functions.

· Coobjc supports tuples that allow Objective-C developers to utilize a mechanism similar to multiple return values in Python.

System architecture

The system architecture of coobjc (shown below) is as follows:

· The bottom layer is the coroutine kernel, which manages stack switching, implements coroutine scheduling, and implements a communication channel between coroutines.

· The middle layer is a wrapper based on the operators of the coroutine. Currently, it supports programming models such as async/await, Generator, and Actor.

· The top layer is the coroutine extension to the system library, which currently covers all the IO and time-consuming methods of Foundation and UIKit.

Image for post
Image for post

Coobjc also offers the following built-in coroutine extensions:

· NSArray, NSDictionary, and other container libraries: coroutine extension helps solve async calling problems in serialization and deserialization.

· Data objects such as NSData, NSString, and UIImage: coroutine extension to solve async calling issues in reading and writing IO.

· NSURLConnection and NSURLSession: coroutine extension solves async calling issues during async network requests.

· Resolver libraries such as NSKeyedArchieve and NSJSONSerialization: coroutine extension solves async calling issues throughout the solving process.

Operating principles

The core philosophy of coroutine is to control the active yield and resumption of the call stack.

A typical implementation of coroutine offers two important actions:

· Yield: This means yielding the cpu, which breaks off the current execution and goes back to the previous state where Resume occured.

· Resume: Continues running the coroutine. When Resume is executed, the coroutine returns to the previous point of Yield.

How does this work, given that pausing the execution of thread-based code is not normally possible? Since code execution is a call stack-based model, making code pausable and resumable had to involve saving the status of the current call stack and then resuming it from the cache.

In theory, the team reasoned, there are five ways to do this:

· Option 1: Use glibc’s ucontext assembly (Yunfeng library).

· Option 2: Use assembly code to switch between contexts (realizing C coroutine), in the same way as ucontext.

· Option 3: Exploit the switch-case syntax of C languages to implement protothreads.

· Option 4: Use C languages’ setjmp and longjmp.

· Option 5: Use a compiler to support syntax sugar.

Options 3 and 4 can implement a jump, but cannot guarantee that status of the call stack will be successfully saved. So those options are out as far as reliable coroutine implementation is concerned. Producing a customized compiler, as in option 5, would achieve poor universality without official support from Apple, so that one’s out too. Meanwhile, Option 1’s ucontext has been discarded in iOS and is thus unusable.

That left option B: simulating ucontext through compilation.

The core of ucontext simulation is to save and resume the call stack through getContext and setContext. This requires getting familiar with calling conventions under different CPU architectures if the compilation is to work across different CPUs.

So far, coobjc supports armv7, arm64, i386, and x86_64, both on real iPhone devices and simulators.

Example code

Let’s look at an example of how coobjc is used in writing code for a simple function. The function here is a website requesting to load images.

To start with, here is the most commonly used code for this scenario:

//Asynchronously loading data from the network
[NSURLSession sharedSession].configuration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
NSURLSessionDownloadTask *task = [[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:
^(NSURL *location, NSURLResponse *response, NSError *error) {
if (error) {
return;
}

//Parsing data in child threads and generating images
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSData *data = [[NSData alloc] initWithContentsOfURL:location];
UIImage *image = [[UIImage alloc] initWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
//Dispatch to the main thread to view the image
imageView.image = image;
});
});

}];

Now, here is the code reconstructed for coroutines using the coobjc library:

//Main thread creates coroutine
co_launch(^{
//Asynchronous network data loading
NSData *data = await([NSURLConnection async_sendAsynchronousRequest:request response:nil error:nil]);
//Asynchronous image parsing
UIImage *image = await([UIImage async_imageWithData:data]);
//Show image
imageView.image = image;
});

Not only has the original 20 lines of code been cut in half, the new code has better logic and readability. This is where the real appeal of coobjc lies. With coroutine reconstruction, complicated async code can be transformed to sequential calling with concise and clear logic.

Performance

As well as being concise, logical and readable, coobjc far outperforms traditional multithreading in high concurrency applications.

A stress test was performed by simulating high-concurrency data reading on an iPhone 7 (iOS11.4.1) using coroutine and the conventional multi-thread approaches, respectively. The total file size was 20 MB and the highest number of threads used by the coroutine approach was 4. The test results are in the table below.

Image for post
Image for post

In low-concurrency scenarios where the multi-thread approach can use the computing core of the device, coobjc’s coroutine approach takes marginally longer. However, the difference is negligible, and the benefits of using coobjc become clear as the concurrency increases.

Once the concurrency exceeds 1,000, the multi-thread approach begins to show thread distribution anomalies, and many concurrent tasks do not get executed. In reality, the >20 s shown in the table refers to the fact that some tasks simply cannot be executed at all.

By comparison, coobjc works precisely as intended in high concurrency scenarios.

In a Nutshell

This article has explored coobjc from a range of angles. To summarize, here are the key benefits it offers to developers.

Concision

· Coobjc has just a few operators, compared with dozens of operators in the responsive mode. It doesn’t get much simpler than this!

· Coobjc works on a simple core principle; that’s why there’s only several thousands of lines of code in the entire coroutine library.

Ease of use

· Very few interfaces, even simpler than GCD.

· Code reconstruction is quick: Existing code only needs very a few changes before it can embrace the coroutine approach. Many coroutine interfaces for system libraries are provided ready to use.

Clarity

· Coobjc lets you write async logic in a sync manner. This is the most natural way to write code, and greatly reduces the probability of human error.

· Code written using coroutine has much higher readability than that written using block nesting.

Performance

· Scheduling of coroutines is fast, because they do not require thread switching at the internal core level. Tens of thousands of coroutines can be created without the slightest concern over workload.

· No more crashes and lagging. The use of coroutine reduces the abuse of locks and signals. By packaging coroutine interfaces (such as IO) that can lead to blocking, app performance and speed is boosted to the maximum.

A final thought

“Programs must be written for people to read, and only incidentally for machines to execute.”

— Abelson and Sussman

The best written works are as compelling in form as in content, and the same applies to code. The programming paradigms realized through coroutines help developers write more beautiful, robust, and readable code, as well as making apps more powerful and reliable.

(Original article by Alibaba’s Taobao Technology Team)

Alibaba Tech

First hand and in-depth information about Alibaba’s latest technology → Facebook: “Alibaba Tech”. Twitter: “AlibabaTech”.

Written by

First-hand & in-depth information about Alibaba's tech innovation in Artificial Intelligence, Big Data & Computer Engineering. Follow us on Facebook!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store