Coder Social home page Coder Social logo

alibaba / coobjc Goto Github PK

View Code? Open in Web Editor NEW
4.0K 98.0 528.0 2.85 MB

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.

Home Page: https://github.com/alibaba/coobjc

License: Apache License 2.0

Ruby 0.69% Objective-C 83.73% Assembly 1.46% C 3.82% Objective-C++ 4.03% Swift 6.27%
coroutine coroutine-library await await-promises actor-model actor generator tuple objective-c c

coobjc's Introduction

coobjc

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.

cooobjc 中文文档

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

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_unpack(nil, nil, &str1) = co_tuple(nil, @10, @"abc");
NSAssert([str1 isEqualToString:@"abc"], @"str1 is wrong");
  • use tuple in coroutine first create a promise that resolve tuple value
COPromise<COTuple*>*
cotest_loadContentFromFile(NSString *filePath){
    return [COPromise promise:^(COPromiseFullfill  _Nonnull resolve, COPromiseReject  _Nonnull reject) {
        if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
            NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];
            resolve(co_tuple(filePath, data, nil));
        }
        else{
            NSError *error = [NSError errorWithDomain:@"fileNotFound" code:-1 userInfo:nil];
            resolve(co_tuple(filePath, nil, error));
        }
    }];
}

then you can fetch the value like this:

co_launch(^{
    NSString *tmpFilePath = nil;
    NSData *data = nil;
    NSError *error = nil;
    co_unpack(&tmpFilePath, &data, &error) = await(cotest_loadContentFromFile(filePath));
    XCTAssert([tmpFilePath isEqualToString:filePath], @"file path is wrong");
    XCTAssert(data.length > 0, @"data is wrong");
    XCTAssert(error == nil, @"error is wrong");
});

use tuple you can get multiple values from await return

Actual case using coobjc

Let's take the code of the Feeds stream update in the GCDFetchFeed open source project as an example to demonstrate the actual usage scenarios and advantages of the coroutine. The following is the original implementation of not using coroutine:

- (RACSignal *)fetchAllFeedWithModelArray:(NSMutableArray *)modelArray {
    @weakify(self);
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        @strongify(self);
        //Create a parallel queue
        dispatch_queue_t fetchFeedQueue = dispatch_queue_create("com.starming.fetchfeed.fetchfeed", DISPATCH_QUEUE_CONCURRENT);
        dispatch_group_t group = dispatch_group_create();
        self.feeds = modelArray;
        for (int i = 0; i < modelArray.count; i++) {
            dispatch_group_enter(group);
            SMFeedModel *feedModel = modelArray[i];
            feedModel.isSync = NO;
            [self GET:feedModel.feedUrl parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
                dispatch_async(fetchFeedQueue, ^{
                    @strongify(self);
                    //parse feed
                    self.feeds[i] = [self.feedStore updateFeedModelWithData:responseObject preModel:feedModel];
                    //save to db
                    SMDB *db = [SMDB shareInstance];
                    @weakify(db);
                    [[db insertWithFeedModel:self.feeds[i]] subscribeNext:^(NSNumber *x) {
                        @strongify(db);
                        SMFeedModel *model = (SMFeedModel *)self.feeds[i];
                        model.fid = [x integerValue];
                        if (model.imageUrl.length > 0) {
                            NSString *fidStr = [x stringValue];
                            db.feedIcons[fidStr] = model.imageUrl;
                        }
                        //sendNext
                        [subscriber sendNext:@(i)];
                        //Notification single completion
                        dispatch_group_leave(group);
                    }];
                    
                });//end dispatch async
                
            } failure:^(NSURLSessionTask *operation, NSError *error) {
                NSLog(@"Error: %@", error);
                dispatch_async(fetchFeedQueue, ^{
                    @strongify(self);
                    [[[SMDB shareInstance] insertWithFeedModel:self.feeds[i]] subscribeNext:^(NSNumber *x) {
                        SMFeedModel *model = (SMFeedModel *)self.feeds[i];
                        model.fid = [x integerValue];
                        dispatch_group_leave(group);
                    }];
                    
                });//end dispatch async
                
            }];
            
        }//end for
        //Execution event after all is completed
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            [subscriber sendCompleted];
        });
        return nil;
    }];
}

The following is the call to the above method in viewDidLoad:

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
self.fetchingCount = 0;
@weakify(self);
[[[[[[SMNetManager shareInstance] fetchAllFeedWithModelArray:self.feeds] map:^id(NSNumber *value) {
    @strongify(self);
    NSUInteger index = [value integerValue];
    self.feeds[index] = [SMNetManager shareInstance].feeds[index];
    return self.feeds[index];
}] doCompleted:^{
    @strongify(self);
    NSLog(@"fetch complete");
    self.tbHeaderLabel.text = @"";
    self.tableView.tableHeaderView = [[UIView alloc] init];
    self.fetchingCount = 0;
    [self.tableView.mj_header endRefreshing];
    [self.tableView reloadData];
    if ([SMFeedStore defaultFeeds].count > self.feeds.count) {
        self.feeds = [SMFeedStore defaultFeeds];
        [self fetchAllFeeds];
    }
    [self cacheFeedItems];
}] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(SMFeedModel *feedModel) {
    @strongify(self);
    self.tableView.tableHeaderView = self.tbHeaderView;
    self.fetchingCount += 1;
    self.tbHeaderLabel.text = [NSString stringWithFormat:@"正在获取%@...(%lu/%lu)",feedModel.title,(unsigned long)self.fetchingCount,(unsigned long)self.feeds.count];
    feedModel.isSync = YES;
    [self.tableView reloadData];
}];

The above code is relatively poor in terms of readability and simplicity. Let's take a look at the code after using the coroutine transformation:

- (SMFeedModel*)co_fetchFeedModelWithUrl:(SMFeedModel*)feedModel{
    feedModel.isSync = NO;
    id response = await([self co_GET:feedModel.feedUrl parameters:nil]);
    if (response) {
        SMFeedModel *resultModel = await([self co_updateFeedModelWithData:response preModel:feedModel]);
        int fid = [[SMDB shareInstance] co_insertWithFeedModel:resultModel];
        resultModel.fid = fid;
        if (resultModel.imageUrl.length > 0) {
            NSString *fidStr = [@(fid) stringValue];
            [SMDB shareInstance].feedIcons[fidStr] = resultModel.imageUrl;
        }
        return resultModel;
    }
    int fid = [[SMDB shareInstance] co_insertWithFeedModel:feedModel];
    feedModel.fid = fid;
    return nil;
}

Here is the place in viewDidLoad that uses the coroutine to call the interface:

co_launch(^{
    for (NSUInteger index = 0; index < self.feeds.count; index++) {
        SMFeedModel *model = self.feeds[index];
        self.tableView.tableHeaderView = self.tbHeaderView;
        self.tbHeaderLabel.text = [NSString stringWithFormat:@"正在获取%@...(%lu/%lu)",model.title,(unsigned long)(index + 1),(unsigned long)self.feeds.count];
        model.isSync = YES;
        SMFeedModel *resultMode = [[SMNetManager shareInstance] co_fetchFeedModelWithUrl:model];
        if (resultMode) {
            self.feeds[index] = resultMode;
            [self.tableView reloadData];
        }
    }
    self.tbHeaderLabel.text = @"";
    self.tableView.tableHeaderView = [[UIView alloc] init];
    self.fetchingCount = 0;
    [self.tableView.mj_header endRefreshing];
    [self.tableView reloadData];
    [self cacheFeedItems];
});

The code after the coroutine transformation has become easier to understand and less error-prone.

Swift

coobjc fully supports Swift through top-level encapsulation, enabling us to enjoy the coroutine ahead of time in Swift. Because Swift has richer and more advanced syntax support, coobjc is more elegant in Swift, for example:

func test() {
    co_launch {//create coroutine
        //fetch data asynchronous
        let resultStr = try await(channel: co_fetchSomething())
        print("result: \(resultStr)")
    }

    co_launch {//create coroutine
        //fetch data asynchronous
        let result = try await(promise: co_fetchSomethingAsynchronous())
        switch result {
            case .fulfilled(let data):
                print("data: \(String(describing: data))")
                break
            case .rejected(let error):
                print("error: \(error)")
        }
    }
}

0x4 Advantages of the coroutine

  • Concise
    • Less concept: there are only a few operators, compared to dozens of operators in response, it can't be simpler.
    • The principle is simple: the implementation principle of the coroutine is very simple, the entire coroutine library has only a few thousand lines of code
  • Easy to use
    • Simple to use: it is easier to use than GCD, with few interfaces
    • Easy to retrofit: existing code can be corouted with only a few changes, and we have a large number of coroutine interfaces for the system library.
  • Clear
    • Synchronous write asynchronous logic: Synchronous sequential way of writing code is the most acceptable way for humans, which can greatly reduce the probability of error
    • High readability: Code written in coroutine mode is much more readable than block nested code
  • High performance
    • Faster scheduling performance: The coroutine itself does not need to switch between kernel-level threads, scheduling performance is fast, Even if you create tens of thousands of coroutines, there is no pressure.
    • Reduce app block: The use of coroutines to help reduce the abuse of locks and semaphores, and to reduce the number of stalls and jams from the root cause by encapsulating the coroutine interfaces such as IOs that cause blocking, and improve the overall performance of the application.

0x5 Communication

  • If you need help, use Stack Overflow. (Tag 'coobjc')
  • If you'd like to ask a general question, use Stack Overflow.
  • If you found a bugand can provide steps to reliably reproduce it, open an issue.
  • If you have a feature request, open an issue.
  • If you want to contribute, submit a pull request.
  • If you are interested in joining Alibaba Taobao-Mobile architecture team, please send your resume to junzhan

0x6 Unit Tests

coobjc includes a suite of unit tests within the Tests subdirectory. These tests can be run simply be executed the test action on the platform framework you would like to test. You can find coobjc's unit tests in Examples/coobjcBaseExample/coobjcBaseExampleTests. You can find cokit's unit tests in cokit/Examples/coKitExamples/coKitExamplesTests.

0x7 Credits

coobjc couldn't exist without:

0x8 Authors

0x9 Contributing

0xA License

coobjc is released under the Apache 2.0 license. See LICENSE for details.

coobjc's People

Contributors

frankkair avatar jieliangma avatar kealdishx avatar nianji avatar oldratlee avatar pengyutang125 avatar valiantcat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

coobjc's Issues

线程卡死现象

多次切换页面会出现假死, 回到后台重新进来恢复线程, 但依然卡死~

如果获取网络请求的进度

看起来好像不能通过Promise获取网络请求的进度,比方说批量上传图片,想知道当前的上传进度这样的场景

如何在别的地方获取co_launch的执行状态

about:
如何获取一个网络请求的状态(请求前、请求中、请求完成)?或者说获取co_launch的执行状态(执行前、执行中、执行后)?

如下代码:我想在另一个地方获取该网络请求执行状态,应该如何获取?

- (void)requestServerDataSignalWithPage:(NSUInteger)page {
    co_launch(^{
        NSDictionary *parm = @{};
        id result = await([[ApiManager sharedInstance] co_messageListWithparameters:parm]);
        NSError *error = co_getError();
        if (error) {
            return;
        }
    });
}

Support for macOS?

[!] The platform of the target TestOfMacOS (macOS 10.13) is not compatible with coobjc (1.0.1), which does not support osx.

关于生成器使用的疑问

1
2
3
如图我在阅读生成器使用的时候,使用测试用例里面的代码看效果,能从代码中看出循环下载10次同样的文件,但是看到我控制台日志输出显示我使用了11次下载。能解释一下吗?

COCoroutine 初始化方法中的 coObj.queue = queue;

在 COCoroutine.m 文件中 +coroutineWithBlock:onQueue:stackSize: 方法的第 184 行:

COCoroutine *coObj = [[self alloc] initWithBlock:block onQueue:queue];

此处已经在初始化方法 -initWithBlock:onQueue: 中对 coObj 对象的 queue 属性进行赋值了,

- (instancetype)initWithBlock:(void (^)(void))block onQueue:(dispatch_queue_t)queue {
    self = [super init];
    if (self) {
        _execBlock = [block copy];
        _queue = queue; // 此处已经对 queue 进行赋值了
    }
    return self;
}

所以第 185 行的 coObj.queue = queue; 应该是多余的。

关于coobjcBaseExample中的COActor的使用问题

代码中有这样一段,在DataService类中:

_networkActor = co_actor_onqueue(_networkQueue, ^(COActorChan *channel) {
            for (COActorMessage *message in channel) {
                NSString *url = [message stringType];
                if (url.length > 0) {
                    message.complete(await([self _getDataWithURL:url]));
                }
                else{
                    message.complete(nil);
                }
            }
        });

不知道是否是我理解的问题,每次向这个actor发送消息只有等到_getDataWithURL网络请求方法回调完成之后才能继续下一个请求,这样网络的请求过程是不是不能并发。

在协程里面不能使用JsContext?

在协程里面使用JsContext会出现exception

Environment Details

  • coobjc version:master
  • iOS version:ios12
  • Xcode version:10
  • Other informations:

Steps to Reproduce

co_launch(^{
        JSContext *mJsContext = [[JSContext alloc] init];
        mJsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
            NSLog(@"%@", exception);
        };
        [mJsContext evaluateScript:@"var a  = 1"];
    });

运行上述代码,会出现jsException

卡死

Discover项目列表push pop 多次,界面卡死。。

batch_await

p2

p1

batch_await不会回调,目前是第二个,比第一个快回调时候出现的问题

在.mm文件中使用编译报错

Undefined symbols for architecture arm64:
"co_await(objc_object*)", referenced from:
___29-[ViewController viewDidLoad]_block_invoke in ViewController.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
我在.mm文件中使用await会产生上面的错误.在新建的工程中使用也是这样,我想问是否支持objective-c++

userdata_dispose 调用时机不对

在方法 coroutine_setuserdata 中传入的 userdata_dispose,文档上写的是coroutine释放时才会调用,但是在设置userdata时就调用了

Thread 2: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

使用 concurrent queue 做promis函数,崩溃了

Context and Description

Environment Details

  • coobjc version: coswift 1.1.2
  • iOS version: iOS 12
  • Xcode version: 10.1
  • Other informations:

code

class CoobjcDemoViewController: BaseViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        co_launch {
            let result = try await(closure: { () -> Promise<String> in
                self.asyncFunc(name: "Han MeiMei")
            })
            switch result {
            case .fulfilled(let name):
                print(name)
            case .rejected(let error):
                print(error)
            }
        }
    }
    
    func asyncFunc(name: String) -> Promise<String> {
        let promise = Promise<String>()
        DispatchQueue.init(label: "concurrent queue", attributes: .concurrent).async {
            print("current: \(name), thread: \(Thread.current)")
            promise.fulfill(value: "Li Lei")
        }
        promise.onCancel { (_) in
            
        }
        return promise
    }
}

多次�快速进入这个VC的时候,就会崩溃掉

method call stacks

image

image

concurrent await promise bug

在concurrent await promise test中,在两个await()之间插入代码NSLog(),就无法继续执行下面的代码。
p1
p2
p3

beach_await

像下面这种模拟网络请求调用,如果 -func1 睡眠的时间比 -func2 睡眠的时间长时,为什么不会执行到NSLog(@"num = %@ -- error: %@",num,error);这句代码?

co_launch(^{
NSLog(@"co_launch1 前 -- %@",[NSThread currentThread]);
NSNumber *num;
NSError *error;
NSArray *array = batch_await(@[[self func1], [self func2]]);
NSLog(@"num = %@ -- error: %@",num,error);
co_unpack(&num, &error) = array[0];
NSLog(@"co_launch1 后 -- %@",[NSThread currentThread]);
});

  • (COPromise *)func1 {
    return [COPromise promise:^(COPromiseFullfill _Nonnull fullfill, COPromiseReject Nonnull reject) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"前 func1 -- %@",[NSThread currentThread]);
    [NSThread sleepForTimeInterval:5.0];
    fullfill(co_tuple(@(10), [NSError errorWithDomain:@"co
    " code:100 userInfo:nil]));
    NSLog(@"后 func1 -- %@",[NSThread currentThread]);
    });
    }];
    }

  • (COPromise *)func2 {
    return [COPromise promise:^(COPromiseFullfill _Nonnull fullfill, COPromiseReject _Nonnull reject) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"前 func2 -- %@",[NSThread currentThread]);
    [NSThread sleepForTimeInterval:3.0];
    fullfill(nil);
    NSLog(@"后 func2 -- %@",[NSThread currentThread]);
    });
    }];
    }

Support carthage in swift project?


name: medisean
about: Support carthage in swift project


Context and Description

Environment Details

  • coobjc version:
  • iOS version:
  • Xcode version:
  • Other informations:

Expected behavior

Actual behavior

Steps to Reproduce

method call stacks

co_launch描述不准确(in next run loop)

/**
Create a coroutine, then resume it asynchronous on current queue.
co_launch will run the block in next run loop
@param block the code execute in the coroutine
@return the coroutine instance
*/
框架没有为非主线程创建运行环。
对于没有运行环的线程,框架是直接对已有栈进行了保存与消除,运行完任务后,又恢复之前的栈。
而主线程具有运行环,的确是运行环驱动的任务调度。同样也进行了栈的消除与恢复。

coobjcAutoreleaseArcTests run fail.


name: coobjcAutoreleaseArcTests run fail.
about: run unit tests in coobjcBaseExample get fail.


Context and Description

image

Environment Details

  • coobjc version: 1.0.0
  • iOS version: iOS12
  • Xcode version: Xcode10.1
  • Other informations: none

当用 then/catch 链式写法组织业务时发生 crash

crash 代码如下:

[[[COPromise promise:^(COPromiseFulfill _Nonnull fullfill, COPromiseReject _Nonnull reject) {
fullfill(@"step 1");
}] then:^id _Nullable(id _Nullable value) {
NSLog(@"eIIIIIII = %@", value);
return [COPromise promise:^(COPromiseFulfill _Nonnull fullfill, COPromiseReject _Nonnull reject) {
reject([NSError errorWithDomain:@"step 2" code:99 userInfo:nil]);
}];
}] catch:^(NSError * _Nonnull error) {
NSLog(@"eIIIIIII = %@", error.domain);
}];

Promise should execute direct.

We have a design problem with Promise. If you create COPromise with promise:(COPromiseConstructor)constructor method. COPromise does not execute directly.

It may cause problem like this.
We expect this code can concurrent:

// concurrent
id promise1 = getPromise1();
id promise2 = getPromise2();

id result1 = await(promise1);
id result2 = await(promise2);

But it's not concurrent, I think Promise should execute direct, instead of execute at first then.

If you have any idea, replay.

好人做到底,送佛送到西

阿里的大牛们,你就好人做到底,送佛送送到西 干脆整一套完整的App框架出来,把网络请求、数据库存储、常用UI控件、各种小工具、各种基础功能都整合到一起,以后我们再开发App的时候就不用自己再东挪西套地搭建了,拿来直接用。
此乃程序员之福、万民之福!

fishhook使用

void co_autoreleaseInit(void){
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        rebind_symbols((struct rebinding[3]){{"objc_autoreleasePoolPop", (void*)co_hook_autorelease_pop, (void **)&orig_autorelease_pop},{"objc_autoreleasePoolPush", (void*)co_hook_autorelease_push, (void **)&orig_autorelease_push}, {"objc_autorelease", (void*)co_hook_autorelease_obj, (void **)&orig_autorelease_obj}}, 3);
        [NSArray co_hook_autorelease];
    });
}

void**是不是更改为void*比较好?

When will Cocoapods be updated?

I saw the coobjc.podspec version and tag has been 1.1.0. But the git tag only has 1.0.0, the coobjc download version is 1.0.1 with cocoapods.

dispatch_semaphore_wait导致GCD假死防护

我们知道GCD的线程池最多有64个线程,如果它们全都在等待信号,
而信号的发送者这时候又分发到全局队列上了,这将导致GCD假死的现象产生。

协程应该有这个能力来解决这个问题。
在dispatch_semaphore_wait或是之前某个时刻做检测,
当需要等待时,让出线程,在有信号发出时,重新分发任务。

各位大神,不知道这个提议是否可行?

coroutine_t 初始化时 stack_size 对齐 16kb 的 Bug

COCoroutine.m 中的 coroutineWithBlock:onQueue:stackSize: 方法的第 188 行:

+ (instancetype)coroutineWithBlock:(void(^)(void))block onQueue:(dispatch_queue_t _Nullable)queue stackSize:(NSUInteger)stackSize {

    // ...

    coroutine_t  *co = coroutine_create((void (*)(void *))co_exec);
    if (stackSize > 0 && stackSize < 1024*1024) {  // Max 1M
        co->stack_size = (uint32_t)((stackSize % 16384 > 0) ? ((stackSize/16384 + 1) * 16384) : stackSize/16384);  // Align with 16kb
    }

    // ...
}

此处对 stack_size 进行 16kb 对齐的写法好像不对,如果外部传入的 stackSize16384 的倍数,假设 stackSize = 16384 * 2,上述计算结果为 co->stack_size = 2,显然不对,应该改为:

co->stack_size = (uint32_t)((stackSize % 16384 > 0) ? ((stackSize/16384 + 1) * 16384) : stackSize); 

对比 Swift 代码 Coroutine.swift 中的写法也可以看出(Swift 中的写法是对的):

if let ss = stackSize {
    co?.pointee.stack_size = (ss % 16384 > 0) ? ((ss/16384 + 1)*16384) : ss
}

co_batch_await

snip20190303_8
snip20190303_7

你好,这是我测试时发现的问题,是哪里使用不当吗

batch_await的崩溃问题, Thread 1: EXC_BAD_ACCESS (code=1, address=0x20)

在一个循环中多次调用如下代码中的beginTaskWithTimeout: completed:方法会产生崩溃,例如

for (NSInteger i = 0; i < 10; ++i) {
        [dispatchGroup beginTaskWithTimeout:5 completed:^(NSArray * _Nonnull array) {

        }];
    }

崩溃的时间点在某个Timer的事件被触发的时候,即调用 [co cancel];的时候,其中groupArray属性中存储的task是任意代码块,事先已存入,COPromise中的fulfill方法被task传递到外部去调用,代码如下:

@interface HYDispatchGroupQueue ()
{
    NSRecursiveLock  *_lock;    //同步锁
}
@property (nonatomic, strong) NSMutableArray    *groupArray;
@end

@implementation HYDispatchGroupQueue

#pragma mark - Init&LifeCircle

- (instancetype)init
{
    self = [super init];
    if (self) {
        _groupArray = [NSMutableArray new];
        _lock = [NSRecursiveLock new];
    }
    return self;
}

#pragma mark - Public

- (void)addTask:(HYDispatchGroupQueueComplete)task
{
    [_lock lock];
    [self.groupArray safeAddObject:task];
    [_lock unlock];
}

//每次进入都新建协程和timer
- (void)beginTaskWithTimeout:(NSTimeInterval)timeout completed:(void(^)(NSArray *))completed
{
    __block NSTimer *t = nil;   //超时定时器
    //新建协程
    COCoroutine *co = co_launch(^{
        NSMutableArray *taskArray = [NSMutableArray new];
        [self->_lock lock];
        for (HYDispatchGroupQueueComplete task in self.groupArray) {
            COPromise *promise = [self coDotask:task];
            [taskArray addObject:promise];
        }
        [self->_lock unlock];
        NSArray *results = batch_await(taskArray);
        if (co_isCancelled()) {//取当前协程判断是否cancel
            results = nil;
        }
        completed ? completed(results) : nil;
        [t invalidate];
        t = nil;
    });
    //新建timer
    if (timeout > 0) {
        if ([[NSThread currentThread] isMainThread]) {  //主线程
            t = [NSTimer timerWithTimeInterval:timeout target:self selector:@selector(timeoutTrigger:) userInfo:co repeats:NO];
            [[NSRunLoop mainRunLoop] addTimer:t forMode:NSRunLoopCommonModes];
        } else {
            t = [NSTimer timerWithTimeInterval:timeout target:self selector:@selector(timeoutTrigger:) userInfo:co repeats:NO];
            [[NSRunLoop currentRunLoop] addTimer:t forMode:NSRunLoopCommonModes];
            [[NSRunLoop currentRunLoop] run];
        }
    }
}

#pragma mark - event

- (void)timeoutTrigger:(NSTimer *)sender
{
    COCoroutine *co = sender.userInfo;
    [co cancel];
}

#pragma mark - Private

- (COPromise *)coDotask:(HYDispatchGroupQueueComplete)task
{
    COPromise *promise = [COPromise promise:^(COPromiseFulfill  _Nonnull fulfill, COPromiseReject  _Nonnull reject) {
        task(fulfill);
    }];
    return promise;
}

@end

image

COPromise 问题

  • (COPromise*)downloadImageWithError{
    COPromise promise = [COPromise promise];
    long total = 0;
    for (int i = 0; i < 100000000; i++) {
    total += i;
    }
    NSError
    error = [NSError errorWithDomain:@"wrong" code:20 userInfo:@{@"h":@"yc"}];
    [promise reject:error];
    return promise;
    }

co_launch(^{
id dd = await([self downloadImageWithError]);
if(co_getError()){
NSLog(@"33");------>这里进不了!!!!
}
});

HELP: how to use then in coswift

I have two function asyncA and asyncB.

In asyncA callback block do asyncB request, eg:

original callback block

    func asyncA(parameter: String, callback: @escaping (String)->Void) {
        DispatchQueue.init(label: "serial.queue").async {
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                callback(parameter + "-asyncA")
            }
        }
    }
    
    func asyncB(parameter: String, callback: @escaping (String)->Void) {
        DispatchQueue.init(label: "serial.queue").async {
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                callback(parameter + "-asyncB")
            }
        }
    }

use asyncA and asyncB

      self.asyncA(parameter: "A") { (msg) in
            self.asyncB(parameter: msg, callback: { (msg) in
                print("msg: \(msg)")
            })
        }

promise then

I do it like this:

    func promiseAsyncA(parameter: String) -> Promise<String> {
        return Promise<String>(constructor: { (fulfill, _) in
            fulfill(parameter + "promiseAsyncA")
        })
    }
    
    func promiseAsyncB(parameter: String) -> Promise<String> {
        return Promise<String>(constructor: { (fulfill, _) in
            fulfill(parameter + "promiseAsyncB")
        })
    }

example

            self.promiseAsyncA(parameter: "A")
                .then { (msg) in self.promiseAsyncB(parameter: msg) }
                .then { (promiseB) -> Void in
                    print("promiseB: \(promiseB)")
            }

output:

promiseB: coswift.Promise<Swift.String>

but result is not the correct value that I want to.

There is two question about then:

  1. where should I use this then, in co_launch closure or not
  2. how to do then pipeline work

crash if run co_launch in background thread


name: crash if run co_launch in background thread
about: run co_launch in a background thread triggerred by performInBackground method, app crashed


Context and Description

Sample code:

  • (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self performSelectorInBackground:@selector(test) withObject:nil];
    }

  • (void)test {
    co_launch(^{ //Crashed here. EXC_BAD_ACCESS(code=1, address=0x54)
    NSLog(@"111111");
    NSLog(@"22222");
    NSLog(@"33333");
    });
    NSLog(@"44444");
    NSLog(@"55555");
    }

Environment Details

  • coobjc version:1.1.3
  • iOS version:iPhoneX simulator
  • Xcode version:Xcode 10
  • Other informations:

为什么没有co_repeat??


name: Issue template
about: Describe this issue template's purpose here.


Context and Description

为什么没有co_repeat??

Environment Details

  • coobjc version:
  • iOS version:
  • Xcode version:
  • Other informations:

Expected behavior

Actual behavior

Steps to Reproduce

method call stacks

关于await的一点小疑问

最近在研究了一下coobjc框架,很优秀,学到了很多东西。但是还有一点疑问,希望能交流一下。
协程可以避免多线程切换带来性能消耗,但是await会等待一个promise,promise还是会在子线程去执行,结果回调到主线程,这个操作算不算线程切换呢?当然多个协程在一个线程中切换肯定是没有压力的。所以我的疑问是await等待一个promise到执行结束回调的过程中,哪些仍然算线程操作,哪些算协程减少的操作,或者说协程带来的收益。谢谢

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.