Coobjc
coobjc provides coroutine support for Objective-C and Swift. We added await method、generator and actor model like C#、Javascript and Kotlin. For convenience, we added coroutine categories for some Foundation and UIKit API in cokit framework like NSFileManager, JSON, NSData, UIImage etc. We also add tuple support in coobjc.
Install / Use
/learn @alibaba/CoobjcREADME
This library provides coroutine support for Objective-C and Swift. We added await method、generator and actor model like C#、Javascript and Kotlin. For convenience, we added coroutine categories for some Foundation and UIKit API in cokit framework like NSFileManager, JSON, NSData, UIImage etc. We also add tuple support in coobjc.
0x0 iOS Asynchronous programming problem
Block-based asynchronous programming callback is currently the most widely used asynchronous programming method for iOS. The GCD library provided by iOS system makes asynchronous development very simple and convenient, but there are many disadvantages based on this programming method:
-
get into Callback hell
Sequence of simple operations is unnaturally composed in the nested blocks. This "Callback hell" makes it difficult to keep track of code that is running, and the stack of closures leads to many second order effects.
-
Handling errors becomes difficult and very verbose
-
Conditional execution is hard and error-prone
-
forget to call the completion block
-
Because completion handlers are awkward, too many APIs are defined synchronously
This is hard to quantify, but the authors believe that the awkwardness of defining and using asynchronous APIs (using completion handlers) has led to many APIs being defined with apparently synchronous behavior, even when they can block. This can lead to problematic performance and responsiveness problems in UI applications - e.g. spinning cursor. It can also lead to the definition of APIs that cannot be used when asynchrony is critical to achieve scale, e.g. on the server.
-
Multi-threaded crashes that are difficult to locate
-
Locks and semaphore abuse caused by blocking
0x1 Solution
These problem have been faced in many systems and many languages, and the abstraction of coroutines is a standard way to address them. Without delving too much into theory, coroutines are an extension of basic functions that allow a function to return a value or be suspended. They can be used to implement generators, asynchronous models, and other capabilities - there is a large body of work on the theory, implementation, and optimization of them.
Kotlin is a static programming language supported by JetBrains that supports modern multi-platform applications. It has been quite hot in the developer community for the past two years. In the Kotlin language, async/await based on coroutine, generator/yield and other asynchronous technologies have become syntactic standard, Kotlin coroutine related introduction, you can refer to:https://www.kotlincn.net/docs/reference/coroutines/basics.html
0x2 Coroutine
Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed. Coroutines are well-suited for implementing familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes
The concept of coroutine has been proposed in the 1960s. It is widely used in the server. It is extremely suitable for use in high concurrency scenarios. It can greatly reduce the number of threads in a single machine and improve the connection and processing capabilities of a single machine. In the meantime, iOS currently does not support the use of coroutines(That's why we want to support it.)
0x3 coobjc framework
coobjc is a coroutine development framework that can be used on the iOS by the Alibaba Taobao-Mobile architecture team. Currently it supports the use of Objective-C and Swift. We use the assembly and C language for development, and the upper layer provides the interface between Objective-C and Swift. Currently, It's open source here under Apache open source license.
0x31 Install
- cocoapods for objective-c: pod 'coobjc'
- cocoapods for swift: pod 'coswift'
- cocoapods for cokit: pod 'cokit'
- source code: All the code is in the ./coobjc directory
0x32 Documents
- Read the Coroutine framework design document.
- Read the coobjc Objective-C Guide document.
- Read the coobjc Swift Guide document.
- Read the cokit framework document, learn how to use the wrapper api of System Interface.
- We have provided coobjcBaseExample demos for coobjc, coSwiftDemo for coswift, coKitExamples for cokit
0x33 Features
async/await
- create coroutine
Create a coroutine using the co_launch method
co_launch(^{
...
});
The coroutine created by co_launch is scheduled by default in the current thread.
- await asynchronous method
In the coroutine we use the await method to wait for the asynchronous method to execute, get the asynchronous execution result
- (void)viewDidLoad {
...
co_launch(^{
// async downloadDataFromUrl
NSData *data = await(downloadDataFromUrl(url));
// async transform data to image
UIImage *image = await(imageFromData(data));
// set image to imageView
self.imageView.image = image;
});
}
The above code turns the code that originally needs dispatch_async twice into sequential execution, and the code is more concise.
- error handling
In the coroutine, all our methods are directly returning the value, and no error is returned. Our error in the execution process is obtained by co_getError(). For example, we have the following interface to obtain data from the network. When the promise will reject: error
- (COPromise*)co_GET:(NSString*)url parameters:(NSDictionary*)parameters{
COPromise *promise = [COPromise promise];
[self GET:url parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[promise fulfill:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[promise reject:error];
}];
return promise;
}
Then we can use the method in the coroutine:
co_launch(^{
id response = await([self co_GET:feedModel.feedUrl parameters:nil]);
if(co_getError()){
//handle error message
}
...
});
Generator
- create generator
We use co_sequence to create the generator
COCoroutine *co1 = co_sequence(^{
int index = 0;
while(co_isActive()){
yield_val(@(index));
index++;
}
});
In other coroutines, we can call the next method to get the data in the generator.
co_launch(^{
for(int i = 0; i < 10; i++){
val = [[co1 next] intValue];
}
});
- use case
The generator can be used in many scenarios, such as message queues, batch download files, bulk load caches, etc.:
int unreadMessageCount = 10;
NSString *userId = @"xxx";
COSequence *messageSequence = co_sequence_onqueue(background_queue, ^{
//thread execution in the background
while(1){
yield(queryOneNewMessageForUserWithId(userId));
}
});
//Main thread update UI
co_launch(^{
for(int i = 0; i < unreadMessageCount; i++){
if(!isQuitCurrentView()){
displayMessage([messageSequence next]);
}
}
});
Through the generator, we can load the data from the traditional producer--notifying the consumer model, turning the consumer into the data-->telling the producer to load the pattern, avoiding the need to use many shared variables for the state in multi-threaded computing. Synchronization eliminates the use of locks in certain scenarios.
Actor
The concept of Actor comes from Erlang. In AKKA, an Actor can be thought of as a container for storing state, behavior, Mailbox, and child Actor and Supervisor policies. Actors do not communicate directly, but use Mail to communicate with each other.
- create actor
We can use co_actor_onqueue to create an actor in the specified thread.
COActor *actor = co_actor_onqueue(q, ^(COActorChan *channel) {
... //Define the state variable of the actor
for(COActorMessage *message in channel){
...//handle message
}
});
- send a message to the actor
The actor's send method can send a message to the actor
COActor *actor = co_actor_onqueue(q, ^(COActorChan *channel) {
... //Define the state variable of the actor
for(COActorMessage *message in channel){
...//handle message
}
});
// send a message to the actor
[actor send:@"sadf"];
[actor send:@(1)];
tuple
- create tuple we provide co_tuple method to create tuple
COTuple *tup = co_tuple(nil, @10, @"abc");
NSAssert(tup[0] == nil, @"tup[0] is wrong");
NSAssert([tup[1] intValue] == 10, @"tup[1] is wrong");
NSAssert([tup[2] isEqualToString:@"abc"], @"tup[2] is wrong");
you can store any value in tuple
- unpack tuple we provide co_unpack method to unpack tuple
id val0;
NSNumber *number = nil;
NSString *str = nil;
co_unpack(&val0, &number, &str) = co_tuple(nil, @10, @"abc");
NSAssert(val0 == nil, @"val0 is wrong");
NSAssert([number intValue] == 10, @"number is wrong");
NSAssert([str isEqualToString:@"abc"], @"str is wrong");
co_unpack(&val0, &number, &str) = co_tuple(nil, @10, @"abc", @10, @"abc");
NSAssert(val0 == nil, @"val0 is wrong");
NSAssert([number intValue] == 10, @"number is wrong");
NSAssert([str isEqualToString:@"abc"], @"str is wrong");
co_unpack(&val0, &number, &str, &number, &str) = co_tuple(nil, @10, @"abc");
NSAssert(val0 == nil, @"val0 is wrong");
NSAssert([number intValue] == 10, @"number is wrong");
NSAssert([str isEqualToString:@"abc"], @"str is wrong");
NSString *str1;
co_un
Related Skills
openhue
334.1kControl Philips Hue lights and scenes via the OpenHue CLI.
sag
334.1kElevenLabs text-to-speech with mac-style say UX.
weather
334.1kGet current weather and forecasts via wttr.in or Open-Meteo
tweakcc
1.4kCustomize Claude Code's system prompts, create custom toolsets, input pattern highlighters, themes/thinking verbs/spinners, customize input box & user message styling, support AGENTS.md, unlock private/unreleased features, and much more. Supports both native/npm installs on all platforms.
