Skip to content

Commit

Permalink
feat(COPromise): add progress promise to support query the async task…
Browse files Browse the repository at this point in the history
… progress

add progress promise to support query the async task progress

fix alibaba#24
  • Loading branch information
pengyutang125 committed Mar 5, 2019
1 parent 515a73f commit 82551b8
Show file tree
Hide file tree
Showing 3 changed files with 298 additions and 1 deletion.
107 changes: 107 additions & 0 deletions Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcPromiseTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@
#import <OCMock/OCMock.h>
#import <coobjc/coobjc.h>

static dispatch_queue_t test_queue(){
static dispatch_queue_t q = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
q = dispatch_queue_create("xxxtest", NULL);
});
return q;
}

static id testPromise1() {

COPromise *promise = [COPromise new];
Expand Down Expand Up @@ -122,6 +131,23 @@ static id testPromise3() {
}


static COProgressPromise* progressDownloadFileFromUrl(NSString *url){
COProgressPromise *promise = [COProgressPromise promise];
[NSURLSession sharedSession].configuration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
[promise reject:error];
}
else{
[promise fulfill:data];
}
}];
[task resume];
[promise setupWithProgress:task.progress];
return promise;
}


SpecBegin(coPromise)


Expand Down Expand Up @@ -252,5 +278,86 @@ static id testPromise3() {
});
});

it(@"test progress promise", ^{
co_launch(^{
int progressCount = 0;
COProgressPromise *promise = progressDownloadFileFromUrl(@"http://img17.3lian.com/d/file/201701/17/9a0d018ba683b9cbdcc5a7267b90891c.jpg");
for(id p in promise){
double v = [p doubleValue];
NSLog(@"current progress: %f", (float)v);
progressCount++;
}
expect(progressCount > 0);
NSData *data = await(promise);
expect(data.length > 0);
});
waitUntil(^(DoneCallback done) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
done();
});
});
});

it(@"test progress promise 50%", ^{
co_launch(^{
int progressCount = 0;
COProgressPromise *promise = progressDownloadFileFromUrl(@"http://img17.3lian.com/d/file/201701/17/9a0d018ba683b9cbdcc5a7267b90891c.jpg");
for(id p in promise){
double v = [p doubleValue];
if(v >= 0.5){
break;
}
NSLog(@"current progress: %f", (float)v);
progressCount++;
}
expect(progressCount > 0);
NSData *data = await(promise);
expect(data.length > 0);
});
waitUntil(^(DoneCallback done) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
done();
});
});
});

it(@"test progress promise direct", ^{
co_launch(^{
int progressCount = 0;
COProgressPromise *promise = progressDownloadFileFromUrl(@"http://img17.3lian.com/d/file/201701/17/9a0d018ba683b9cbdcc5a7267b90891c.jpg");
NSData *data = await(promise);
expect(data.length > 0);
});
waitUntil(^(DoneCallback done) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
done();
});
});
});

it(@"test progress promise error", ^{
co_launch(^{
int progressCount = 0;
COProgressPromise *promise = progressDownloadFileFromUrl(@"http://img17.3lian.com/d/file/201701/17/9a0d018ba683b9cbdcc5a7267b90891c.jpg1111");
for(id p in promise){
double v = [p doubleValue];
if(v >= 0.5){
break;
}
NSLog(@"current progress: %f", (float)v);
progressCount++;
}
expect(progressCount <= 0);
NSData *data = await(promise);
expect(data == nil);
expect(co_getError() != nil);
});
waitUntil(^(DoneCallback done) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
done();
});
});
});

});
SpecEnd
47 changes: 47 additions & 0 deletions coobjc/promise/COPromise.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,51 @@ typedef void (^COPromiseConstructor)(COPromiseFullfill fullfill, COPromiseReject

@end

/**
COProgressPromise is a subclass of COPromise, use this promise can monitor the progress of a async task,
COProgressPromise realize the NSFastEnumeration Protocol, so you can use the for ... in , like this:
for(id progress in promise){
double value = [progress doubleValue];
}
Usage:
static COProgressPromise* progressDownloadFileFromUrl(NSString *url){
COProgressPromise *promise = [COProgressPromise promise];
[NSURLSession sharedSession].configuration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
[promise reject:error];
}
else{
[promise fulfill:data];
}
}];
[task resume];
// setup progress
[promise setupWithProgress:task.progress];
return promise;
}
co_launch(^{
COProgressPromise *promise = progressDownloadFileFromUrl(@"http://img17.3lian.com/d/file/201701/17/9a0d018ba683b9cbdcc5a7267b90891c.jpg");
for(id p in promise){
double v = [p doubleValue];
NSLog(@"current progress: %f", (float)v);
}
// get the download result
NSData *data = await(promise);
// handle data
});
*/
@interface COProgressPromise<Value>: COPromise<NSFastEnumeration>

//when COProgressPromise is init, you should call setupWithProgress to specify the NSProgress Object,
//COProgressPromise will observe the fractionCompleted value of progress
- (void)setupWithProgress:(NSProgress*)progress;

//get the next progress fractionCompleted value, this method should be called in a coroutine
- (CGFloat)next;

@end

NS_ASSUME_NONNULL_END
145 changes: 144 additions & 1 deletion coobjc/promise/COPromise.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
// Reference code from: [FBLPromise](https://github.com/google/promises)

#import "COPromise.h"
#import "COChan.h"
#import "COCoroutine.h"

typedef NS_ENUM(NSInteger, COPromiseState) {
COPromiseStatePending = 0,
Expand All @@ -37,9 +39,10 @@ @interface COPromise<Value>()
id __nullable _value;
NSError *__nullable _error;
COPromiseConstructor _constructor;
NSLock *_lock;
}

@property (nonatomic, strong) NSLock *lock;

typedef void (^COPromiseOnFulfillBlock)(Value __nullable value);
typedef void (^COPromiseOnRejectBlock)(NSError *error);
typedef id __nullable (^__nullable COPromiseChainedFulfillBlock)(Value __nullable value);
Expand Down Expand Up @@ -303,3 +306,143 @@ - (COPromise *)catch:(COPromiseCatchWorkBlock)reject {
}

@end

@interface COProgressValue : NSObject

@property (nonatomic, assign) CGFloat progress;

@end

@implementation COProgressValue

- (void)dealloc{
NSLog(@"test");
}

@end

@interface COProgressPromise (){
unsigned long enum_state;
}

@property (nonatomic, strong) NSProgress *progress;
@property (nonatomic, strong) COPromise *internalPromise;
@property (nonatomic, strong) id lastValue;

@end

static void *COProgressObserverContext = &COProgressObserverContext;

@implementation COProgressPromise

- (instancetype)init{
self = [super init];
if (self) {
}
return self;
}

- (void)fulfill:(id)value{
[self.internalPromise fulfill:nil];
[super fulfill:value];
}

- (void)reject:(NSError *)error{
[self.internalPromise fulfill:nil];
[super reject:error];
}

- (COProgressValue*)_nextProgressValue{
if (![self isPending]) {
return nil;
}
[self.lock lock];
self.internalPromise = [COPromise promise];
[self.lock unlock];
COProgressValue* result = co_await(self.internalPromise);
self.internalPromise = [COPromise promise];
return result;
}

- (void)setupWithProgress:(NSProgress*)progress{
NSProgress *oldProgress = nil;
[self.lock lock];
if (self.progress) {
oldProgress = self.progress;
}
self.progress = progress;
[self.lock unlock];
if (oldProgress) {
[oldProgress removeObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
context:COProgressObserverContext];
}
if (progress) {
[progress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionInitial
context:COProgressObserverContext];
}
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if (context == COProgressObserverContext)
{
NSProgress *progress = object;
COProgressValue *value = [[COProgressValue alloc] init];
value.progress = progress.fractionCompleted;
[self.lock lock];
COPromise *promise = self.internalPromise;
[self.lock unlock];
[promise fulfill:value];
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
}

- (CGFloat)next {
COProgressValue *value = [self _nextProgressValue];
return value.progress;
}

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len {

if (state->state == 0) {
state->mutationsPtr = &enum_state;
state->state = enum_state;
}

NSUInteger count = 0;
state->itemsPtr = buffer;
COProgressValue* value= [self _nextProgressValue];
if (value) {
self.lastValue = @(value.progress);
buffer[0] = self.lastValue;
count++;
}

return count;
}

- (void)dealloc{
NSProgress *oldProgress = nil;
[self.lock lock];
if (self.progress) {
oldProgress = self.progress;
}
self.progress = nil;
self.internalPromise = nil;
[self.lock unlock];
if (oldProgress) {
[oldProgress removeObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
context:COProgressObserverContext];
}
}

@end

0 comments on commit 82551b8

Please sign in to comment.