
Эта библиотека обеспечивает поддержку Coroutine для Objective-C и Swift. Мы добавили метод ожидания 、 Модель генератора и актера, такую как C#、 JavaScript и Kotlin. Для удобства мы добавили категории Coroutine для некоторых фундамента и UIKIT API в рамках Cokit, таких как NSFileManager, JSON, NSDATA, UIIMAGE и т. Д. Мы также добавляем поддержку в CoobJC.
coobjc 中文文档
Блок-асинхронное обратное программирование в настоящее время является наиболее широко используемым асинхронным методом программирования для iOS. Библиотека GCD, предоставленная системой iOS, делает асинхронную разработку очень простым и удобным, но есть много недостатков на основе этого метода программирования:
попасть в ада обратного вызова
Последовательность простых операций неестественно составлена в вложенных блоках. Этот «обратный обратный ал» затрудняет отслеживание запуска кода, а стек закрытия приводит ко многим эффектам второго порядка.
Ошибки обработки становится трудным и очень многословным
Условное выполнение жестко и подвержено ошибкам
Забудьте позвонить в блок завершения
Поскольку обработчики завершения неловко, слишком много API определяются синхронно
Это трудно количественно оценить, но авторы считают, что неловкость определения и использования асинхронных API (с использованием обработчиков завершения) привела к тому, что многие API определяются с явно синхронным поведением, даже когда они могут блокировать. Это может привести к проблематичной производительности и проблемам с отзывчивостью в приложениях пользовательского интерфейса - например, вращающегося курсора. Это также может привести к определению API, которое не может использоваться, когда асинхронность имеет решающее значение для достижения масштаба, например на сервере.
Многопоточные аварии, которые трудно найти
Замки и семифор злоупотребление, вызванные блокировкой
Эта проблема столкнулась во многих системах и многих языках, и абстракция Coroutines является стандартным способом их решения. Не слишком много углубляя теорию, Coroutines - это расширение основных функций, которые позволяют функции возвращать значение или быть приостановленными. Их можно использовать для реализации генераторов, асинхронных моделей и других возможностей - существует большая часть работы над теорией, их реализацией и их оптимизацией.
Kotlin-это статический язык программирования, поддерживаемый Jetbrains, который поддерживает современные многоплатформенные приложения. В течение последних двух лет в сообществе разработчиков было довольно жарко. На языке котлин асинхроно/ожидание на основе коратины, генератора/урожайности и других асинхронных технологий стало синтаксическим стандартом, введением коратиков Kotlin Coroutin
Coroutines-это компоненты компьютерной программы, которые обобщают подпрограммы для непреодолимой многозадачности, позволяя приостановить и возобновить выполнение. Кораки хорошо подходят для реализации знакомых компонентов программы, таких как кооперативные задачи, исключения, петли событий, итераторы, бесконечные списки и трубы
Концепция Coroutine была предложена в 1960 -х годах. Это широко используется на сервере. Это чрезвычайно подходит для использования в сценариях с высоким содержанием параллелистики. Это может значительно уменьшить количество потоков в одной машине и улучшить возможности подключения и обработки одной машины. В то же время, iOS в настоящее время не поддерживает использование CORUTINES (поэтому мы хотим поддержать его.)
COOBJC-это структура разработки COUROUTINE, которая может использоваться на iOS командой архитектуры Alibaba Taobao-Mobile. В настоящее время он поддерживает использование Objective-C и Swift. Мы используем ассамблею и язык C для разработки, а верхний слой обеспечивает интерфейс между Objective-C и Swift. В настоящее время он находится здесь, по лицензии Apache Open Source.
Создать коратину, используя метод co_launch
co_launch (^{
...
});Корутика, созданная Co_launch, запланирована по умолчанию в текущем потоке.
В Coroutine мы используем метод ожидания, чтобы дождаться выполнения асинхронного метода, получить результат асинхронного выполнения
- ( 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;
});
}Приведенный выше код превращает код, который первоначально нуждается в Dispatch_async дважды в последовательное выполнение, и код более краткий.
В Coroutine все наши методы напрямую возвращают значение, а ошибка не возвращается. Наша ошибка в процессе выполнения получена CO_GETERROR (). Например, у нас есть следующий интерфейс для получения данных из сети. Когда обещание отклонится: ошибка
- (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;
}Тогда мы можем использовать метод в коратике:
co_launch (^{
id response = await ([ self co_GET: feedModel.feedUrl parameters: nil ]);
if ( co_getError ()){
// handle error message
}
...
});Мы используем co_sequence для создания генератора
COCoroutine *co1 = co_sequence(^{
int index = 0 ;
while ( co_isActive ()){
yield_val (@( index ));
index ++;
}
});В других коратиках мы можем вызвать следующий метод, чтобы получить данные в генераторе.
co_launch (^{
for ( int i = 0 ; i < 10 ; i++){
val = [[co1 next ] intValue ];
}
});Генератор может использоваться во многих сценариях, таких как очереди сообщений, пакетные файлы загрузки, кэшировавшие нагрузки и т. Д.:
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 ]);
}
}
});Через генератор мы можем загрузить данные от традиционного производителя, нотируя потребительскую модель, превращая потребителя в данные-> сообщив производителю загружать шаблон, избегая необходимости использовать многие общие переменные для состояния в многопоточных вычислениях. Синхронизация устраняет использование замков в определенных сценариях.
Концепция актера происходит от Эрланга. В Akka актер можно рассматривать как контейнер для хранения состояния, поведения, почтового ящика, а также политик -руководителей. Актеры не общаются напрямую, но используют почту для общения друг с другом.
Мы можем использовать CO_ACTOR_ONQUEUE для создания актера в указанном потоке.
COActor *actor = co_actor_onqueue(q, ^(COActorChan *channel) {
... // Define the state variable of the actor
for (COActorMessage *message in channel){
... // handle message
}
});Метод отправки актера может отправить сообщение актеру
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 )];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 " );Вы можете хранить любую ценность в кортеже
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 " );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));
}
}];
}Тогда вы можете принести значение таким:
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 " );
});Используйте кортеж
Давайте возьмем код обновления потока каналов в проекте с открытым исходным кодом GCDFetchFeed в качестве примера для демонстрации фактических сценариев использования и преимуществ Coroutine. Ниже приводится исходная реализация не использования 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 ;
}];
}Ниже приведен вызов приведенного выше метода в 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 ];
}];Приведенный выше код является относительно плохим с точки зрения читаемости и простоты. Давайте посмотрим на код после использования преобразования коратики:
- (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 ;
}Вот место в Viewdidload, в котором используется Coroutine, чтобы вызвать интерфейс:
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 ];
});Код после преобразования коратики стал проще для понимания и менее подвержена ошибкам.
COOBJC полностью поддерживает Swift через инкапсуляцию верхнего уровня, что позволяет нам насладиться COUTINE заранее в Swift. Поскольку у Swift есть более богатая и более продвинутая поддержка синтаксиса, Coobjc более элегантен в Swift, например:
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 ) " )
}
}
} COOBJC включает в себя набор модульных тестов в подкаталоге. Эти тесты можно запустить, просто выполнять тестовое действие на платформе, которую вы хотели бы протестировать. Вы можете найти модульные тесты COOBJC в примерах/coobjcbaseexample/coobjcbaseexampletests. Вы можете найти модульные тесты Cokit в Cokit/Примеры/Cokitexamples/Cokitexamplestes.
CoObjc не мог бы существовать без:
COOBJC выпускается по лицензии Apache 2.0. Смотрите лицензию для деталей.