I am using reactive cocoa to create download file from server. I have a DownloadMapFileOperation.m file with a progress property (progress value, currentBytes, totalBytes). It changes very rapidly in my next RACSignal
method and float progress (it's unnecesarry, but
@property (nonatomic, strong, readwrite) NSMutableDictionary *progressDictionary;
@property (nonatomic, assign, readwrite) float progress;
and RACSignal
creating method
- (RACSignal *)signalForDownloadMap:(Place *)place
{
return [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSString *mapUrlString = [NSString stringWithFormat:@"http://myurl/%@", place.mapFileName];
NSURL *url = [NSURL URLWithString:mapUrlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
_downloadFileOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:[url lastPathComponent]];
[_downloadFileOperation setOutputStream:[NSOutputStream outputStreamToFileAtPath:fullPath append:NO]];
[_downloadFileOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation , id response)
{
NSLog(@"Downloaded via compl block");
[subscriber sendNext:nil];
[subscriber sendCompleted];
} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
[subscriber sendError:error];
}];
@weakify(self)
self.progressDictionary = [[NSMutableDictionary alloc] init];
self.progressDictionary[@"progress"] = @(0);
self.progressDictionary[@"current"] = @(0);
self.progressDictionary[@"total"] = @(0);
self.progress = @(0);
[_downloadFileOperation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead)
{
@strongify(self)
self.progressDictionary[@"current"] = @(totalBytesRead);
self.progressDictionary[@"total"] = @(totalBytesExpectedToRead);
self.progressDictionary[@"progress"] = @((float)totalBytesRead / (float)totalBytesExpectedToRead);
self.progress = @((float)totalBytesRead / (float)totalBytesExpectedToRead);
self.progress = @((float)totalBytesRead / (float)totalBytesExpectedToRead);
}];
[_downloadFileOperation start];
return nil;
}] filter:^BOOL(id value) {
return !(.mapFileName == nil);
}]
deliverOn:[RACScheduler mainThreadScheduler]
];
}
I just want to show the progress in my view via RACObserve
in my ViewController.
[[[DownloadMapFileOperation sharedInstance] signalForDownloadMap:self.place]
subscribeNext:^(AFHTTPRequestOperation *operation) {
NSString *alertMessage = @"Download complete";
[[[UIAlertView alloc] initWithTitle:@"Complete" message:alertMessage delegate:self cancelButtonTitle:@"Close" otherButtonTitles: nil] show];
downloadMapView.progressLabel.text = @"Download Complete";
}];
[[[DownloadMapFileOperation sharedInstance] signalForDownloadMap:self.place] subscribeError:^(NSError *error) {
[[[UIAlertView alloc] initWithTitle:@"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil] show];
}];
RAC(downloadMapView, progressView.progress) = [RACObserve([DownloadMapFileOperation sharedInstance], progress)
map:^id(id value) {
return value;
}];
RAC(downloadMapView, progressLabel.text) = [RACObserve([DownloadMapFileOperation sharedInstance], progressDictionary)
map:^id(NSDictionary *value) {
return [value[@"progress"] isEqual: @(1)] ? @"Download Complete" : [NSString stringWithFormat:@"Downloaded %@ of %@", value[@"current"], value[@"total"]];
}];
But my progress view jumps from 60% to 50% (or 45 - 35 etc.) sometimes and NSLog(@"Downloaded via compl block");
shows before progress reaches 100% (sometimes it reaches and starts again from about 70%).
And if I use progressDictionary
(like in the second RACObserve
) - it doesn't work (shows 0 progress and Downloaded 0 of 0)
EDITED
I also try to add methods in my DownloadMapFileOperation.m file which pauses, resumes, cancels download operation.
- (RACSignal *)signalForPauseDownloadMap
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[_downloadFileOperation pause];
[subscriber sendNext:nil];
[subscriber sendCompleted];
return nil;
}];
}
- (RACSignal *)signalForResumeDownloadMap
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[_downloadFileOperation resume];
[subscriber sendNext:nil];
[subscriber sendCompleted];
return nil;
}];
}
- (RACSignal *)signalForCancelDownloadMap
{
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[_downloadFileOperation cancel];
[subscriber sendNext:nil];
[subscriber sendCompleted];
return nil;
}] filter:^BOOL(id value) {
return (_downloadFileOperation);
}];
}
But: My download file operation doesn't stop. When I use in my VC
[[_downloadMapFileOperation signalForPauseDownloadMap]
subscribeNext:^(id x) {
[downloadMapView setTitleForStartDownloadButton:@"Resume download"];
_currentDownloadState = kHTDownloadStatePaused;
}];
My progress value works correct (doesn't jump from value to value). Help me out please. Thanks.
For your original question about the jumping download progress, the problem is you're creating subscriptions to two copies of the signal, which is resulting in two downloads. Namely, this bit of code:
[[[DownloadMapFileOperation sharedInstance] signalForDownloadMap:self.place]
subscribeNext:^(AFHTTPRequestOperation *operation) {
NSString *alertMessage = @"Download complete";
[[[UIAlertView alloc] initWithTitle:@"Complete" message:alertMessage delegate:self cancelButtonTitle:@"Close" otherButtonTitles: nil] show];
downloadMapView.progressLabel.text = @"Download Complete";
}];
[[[DownloadMapFileOperation sharedInstance] signalForDownloadMap:self.place] subscribeError:^(NSError *error) {
[[[UIAlertView alloc] initWithTitle:@"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil] show];
}];
Each subscribeNext
/subscribeError
/etc. would create a new subscription, and start a download. What you want to use for subscription is as follows:
[[[DownloadMapFileOperation sharedInstance] signalForDownloadMap:self.place]
subscribeNext:^(AFHTTPRequestOperation *operation) {
NSString *alertMessage = @"Download complete";
[[[UIAlertView alloc] initWithTitle:@"Complete" message:alertMessage delegate:self cancelButtonTitle:@"Close" otherButtonTitles: nil] show];
downloadMapView.progressLabel.text = @"Download Complete";
} error:^(NSError *error) {
[[[UIAlertView alloc] initWithTitle:@"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil] show];
}];
For pause and resume, I don't think creating further signals make sense. If you do have access to the download file operation, then just call pause/resume directly on it. The original subscription will continue to be valid, and should pause/resume updating based on behavior within the created signal.
As for cancellation, what you really want to do is use disposables. In the [RACSignal createSignal:]
call, you do not want to return nil
. Instead, set up a disposable that'd cancel the download when the disposable is disposed.
Edit:
Ideally you want to send the progress through the signal. In the setDownloadProgressBlock
you would instead:
NSMutableDictionary *progressDictionary = [[NSMutableDictionary alloc] init];
progressDictionary[@"current"] = @(totalBytesRead);
progressDictionary[@"total"] = @(totalBytesExpectedToRead);
progressDictionary[@"progress"] = @((float)totalBytesRead / (float)totalBytesExpectedToRead);
[subscriber sendNext:[progressDictionary copy]];
You would then be able to read the progressDictionary when you subscribe, like so:
[[[DownloadMapFileOperation sharedInstance] signalForDownloadMap:self.place]
subscribeNext:^(NSDictionary *progressDictionary) {
// Do stuff with dictionary here, like update UI with progress
} error:^(NSError *error) {
// Error related stuff
} completed:^{
// Completed stuff
}];
Note: you should not be sending nil through sendNext without good reason. Completion logic should only be done in the completion block.