
이 라이브러리는 Objective-C 및 Swift에 대한 코 루틴 지원을 제공합니다. C#、 JavaScript 및 Kotlin과 같은 Await Method method 생성기 및 액터 모델을 추가했습니다. 편의를 위해 NSFIleManager, JSON, NSDATA, UIIMAGE 등과 같은 Cokit 프레임 워크에 일부 기초 및 Uikit API에 대한 Coroutine 카테고리를 추가했습니다. COOBJC에도 튜플 지원을 추가했습니다.
쿠브 jc c
블록 기반 비동기 프로그래밍 콜백은 현재 iOS에 가장 널리 사용되는 비동기 프로그래밍 방법입니다. iOS 시스템에서 제공하는 GCD 라이브러리는 비동기 개발을 매우 간단하고 편리하게 만들지만이 프로그래밍 방법에 따라 많은 단점이 있습니다.
콜백 지옥에 들어가십시오
일련의 간단한 작업은 중첩 블록에서 자연스럽게 구성됩니다. 이 "콜백 지옥"은 실행중인 코드를 추적하기가 어렵고 폐쇄의 스택은 많은 2 차 효과로 이어집니다.
취급 오류는 어려워지고 매우 장점이됩니다
조건부 실행은 단단하고 오류가 발생하기 쉽습니다
완성 블록을 호출하는 것을 잊어 버리십시오
완료 처리기가 어색하기 때문에 너무 많은 API가 동기식으로 정의됩니다.
이것은 정량화하기가 어렵지만 저자는 비동기 API (완료 처리기 사용)를 정의하고 사용하는 어색함으로 인해 많은 API가 차단할 수있을 때에도 명백한 동기 동작으로 정의되었습니다. 이로 인해 UI 응용 프로그램과 같은 문제가 발생하는 성능 및 응답 성 문제가 발생할 수 있습니다. 또한 Asynchrony가 스케일을 달성하는 데 중요한 경우 (예 : 서버에서) API의 정의로 이어질 수 있습니다.
찾기 어려운 다중 스레드 충돌
차단으로 인한 자물쇠 및 세마포어 남용
이러한 문제는 많은 시스템과 많은 언어에서 직면 해 왔으며, 코 루틴의 추상화는이를 해결하는 표준 방법입니다. 이론에 너무 많이 파고 들지 않으면 서 Coroutines는 함수가 값을 반환하거나 일시 중지 할 수있는 기본 함수의 확장입니다. 생성기, 비동기 모델 및 기타 기능을 구현하는 데 사용될 수 있습니다. 이론, 구현 및 최적화에 대한 많은 작업이 있습니다.
Kotlin은 현대적인 멀티 플랫폼 응용 프로그램을 지원하는 JetBrains가 지원하는 정적 프로그래밍 언어입니다. 지난 2 년간 개발자 커뮤니티에서는 매우 뜨거웠습니다. Kotlin 언어로, 코 루틴, 발전기/수율 및 기타 비동기 기술을 기반으로 한 비동기/대기, Kotlin Coroutine 관련 소개가 참조 할 수 있습니다.
코 루틴은 실행을 일시 중단 및 재개하여 비 배제 멀티 태스킹에 대한 서브 루틴을 일반화하는 컴퓨터 프로그램 구성 요소입니다. Coroutines
코 루틴의 개념은 1960 년대에 제안되었습니다. 서버에서 널리 사용됩니다. 높은 동시성 시나리오에서 사용하기에 매우 적합합니다. 단일 시스템의 스레드 수를 크게 줄이고 단일 시스템의 연결 및 처리 기능을 향상시킬 수 있습니다. 그 동안 iOS는 현재 코 루틴의 사용을 지원하지 않습니다 (그래서 우리가 그것을 지원하고자하는 이유입니다.)
COOBJC는 Alibaba Taobao-Mobile Architecture Team에서 iOS에서 사용할 수있는 코 루틴 개발 프레임 워크입니다. 현재 목표 C 및 Swift의 사용을 지원합니다. 우리는 조립 및 C 언어를 개발에 사용하고 상단 계층은 Objective-C와 Swift 사이의 인터페이스를 제공합니다. 현재 Apache 오픈 소스 라이센스에 따라 오픈 소스입니다.
co_launch 메소드를 사용하여 코 루틴을 만듭니다
co_launch (^{
...
});Co_launch에서 만든 코 루틴은 현재 스레드에서 기본적으로 예약됩니다.
Coroutine에서 우리는 Await 방법을 사용하여 비동기 방법이 실행될 때까지 대기하고 비동기 실행 결과를 얻습니다.
- ( 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가 필요한 코드를 순차적 실행으로 두 번 돌리고 코드는 더 간결합니다.
코 루틴에서 모든 방법이 값을 직접 반환하고 오류가 반환되지 않습니다. 실행 프로세스에서의 오류는 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_EVESSENCE를 사용하여 생성기를 만듭니다
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 ]);
}
}
});생성기를 통해 소비자 모델을 사용하지 않고 소비자를 데이터로 전환하는 전통적인 생산자의 데이터를로드 할 수 있습니다.> 다중 스레드 컴퓨팅에서 상태에 대한 많은 공유 변수를 사용해야 할 필요가 없습니다. 동기화는 특정 시나리오에서 잠금 장치 사용을 제거합니다.
배우의 개념은 Erlang에서 나옵니다. 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 오픈 소스 프로젝트에서 Feeds 스트림 업데이트 코드를 코드를 사용하여 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 ;
}다음은 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에서 미리 코 루틴을 즐길 수 있습니다. 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에는 테스트 하위 디렉토리 내에 단위 테스트 제품군이 포함되어 있습니다. 이 테스트는 테스트하려는 플랫폼 프레임 워크에서 테스트 조치를 실행할 수 있습니다. 예제/coobjcbaseexample/coobjcbaseexampletests에서 coobjc의 단위 테스트를 찾을 수 있습니다. Cokit/examples/cokitexamples/cokitexamplests에서 Cokit의 단위 테스트를 찾을 수 있습니다.
coobjc는 다음과없이 존재할 수 없었습니다.
COOBJC는 Apache 2.0 라이센스에 따라 릴리스됩니다. 자세한 내용은 라이센스를 참조하십시오.