Reputation: 2101
I have made a subclass of NSOperation
called ˚ to achieve multiple movie downloads . In the appDelegate.m
, I have made an object of NSOperationQueue
.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
queue = [[NSOperationQueue alloc] init];
[queue setMaximumConcurrentOperationCount:5]
}
MovieDownloadOperation
depends on a class called Downloader
which actually downloads the movie
and gives callback movieCompletelyDownloadedWithUrl:
.
Then , I have made a property called downloadState
in MovieDownloadOperation
. It has different values like "STARTED"
, "DOWNLOADING"
, "COMPLETED"
, "ERROR"
.
MyDownloadOperation looks like
-(id)initWithUrl:(NSURL *)url
{
if (self = [super init])
{
_downloader = [[Downloader alloc] initWithUrl:url];
_downloadState = @"STARTED" ;
}
}
-(void)main
{
while(1)
{
if ([_downloadState isEqualToString:@"COMPLETED"])
{
NSLog(@"movie downloaded successfully");
break ;
}
}
}
-(void)movieCompletelyDownloadedWithUrl:(NSURL *)url
{
_downloadState = @"COMPLETED" ;
}
This works well for one movie , but when I try to download more than one movie , the UI freezes until the first is downloaded . I think the the problem is the while
loop inside the main
method , is there a better way to check if the _downloadState
is changed to "COMPLETED"
??
Upvotes: 0
Views: 353
Reputation: 438437
It's unclear why the UI freezes with multiple operations, but not with only one download. But, your code sample provokes a couple of thoughts:
Concurrent Operation:
Rather than having a while
loop in main
, and you'd generally would define your operation to be concurrent (i.e. return YES
from isConcurrent
). Then movieCompletelyDownloadedWithUrl
would post the isFinished
event, which would trigger the completion of the operation.
In terms of how to make a concurrent operation, you might define properties for executing
and finished
:
@property (nonatomic, readwrite, getter = isFinished) BOOL finished;
@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
You'd probably want to have a strong
property for the URL and the downloader:
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, strong) Downloader *downloader;
And then you might have the following code in the operation subclass:
@synthesize finished = _finished;
@synthesize executing = _executing;
- (id)init
{
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (id)initWithUrl:(NSURL *)url
{
self = [self init];
if (self) {
// Note, do not start downloader here, but just save URL so that
// when the operation starts, you have access to the URL.
_url = url;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)main
{
// start the download here
self.downloader = [[Downloader alloc] initWithUrl:self.url];
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
// you haven't shown how this is called, but I'm assuming you'll fix the downloader
// to call this instance method when it's done
- (void)movieCompletelyDownloadedWithUrl:(NSURL *)url
{
[self completeOperation];
}
#pragma mark - NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
So, with these methods, you might then have movieCompletelyDownloadedWithUrl
call completeOperation
like above, which will ensure that isExecuting
and isFinished
notifications get posted. You'd also want to respond to cancellation event, too, making sure to cancel the download if the operation is canceled.
See Configuring Operations for Concurrent Execution section of the Concurrency Programming Guide for more details.
Don't initiate download until main
:
I don't see your main
method initiating the download. That makes me nervous that your Downloader
initialization method, initWithURL
, might be initiating the download, which would be bad. You don't want downloads initiating when you create the operation, but rather you shouldn't do that until the operation starts (e.g. start
or main
). So, in my above example, I only have initWithURL
save the URL, and then main
is what starts the download.
Using NSURLConnectionDataDelegate
methods in NSOperation
:
As an aside, you didn't share how your operation is doing the network request. If you're using NSURLConnectionDataDelegate
methods, when you get rid of that while
loop in main
, you might have problems if you don't schedule the NSURLConnection
in a particular run loop. For example, you might do:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
If you're not using NSURLConnectionDataDelegate
methods, or if you've already addressed this run loop issue, then ignore this counsel, but, bottom line, when you fix the main
method in your operation, you might expose the NSURLConnection
issue that your old main
might have hidden from you.
How does Downloader
invoke moveCompleteDownloadedWithUrl
?
BTW, you're not showing how Downloader
could possibly invoke moveCompleteDownloadedWithUrl
. That looks suspicious, but I'm just hoping you simplified your code when you posted it. But if you're not using a protocol-delegate pattern or completion block pattern, then I'd be very nervous about how your multiple Downloader
objects are informing the respective MyDownloadOperation
objects that the download is done. Personally, I might be inclined to refactor these two differ classes into one, but that's a matter of personal taste.
Upvotes: 2
Reputation: 7207
You can use NSTimer
to check whether your download is completed or not. It'll not freezes your UI
NSTimer *localTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(checkDownloadStatus) userInfo:nil repeats:YES];
-(void)checkDownloadStatus
{
if ([_downloadState isEqualToString:@"COMPLETED"])
{
NSLog(@"movie downloaded successfully");
[localTimer invalidate];
}
}
Upvotes: 0