I need in my application to download directories and their content. So I decided to implement a NSOperationQueue and I subclassed NSOperation to implement NSURLRequest etc...
The problem is I add all the operations at once and I can't figure out when all the files for one directory are downloaded in order to update the UI and enable this specific directory.
Now I have to wait that all the files from all the directories are downloaded in order to update the UI.
I already implemented key-value observing for the operationCount of the NSOperationQueue and the isFinished of the NSOperation but I don't know when a directory has all the files in it !
Do you have any idea ?
Thanks a lot
Add a "Done" NSOperation which has all other NSOperations for one directory as dependency.
Something like this:
NSInvocationOperation *doneOp = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(done:) object:nil];
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(doSomething:) object:nil];
[queue addOperation:op1];
[doneOp addDependency:op1];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(doSomething:) object:nil];
[queue addOperation:op2];
[doneOp addDependency:op2];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(doSomething:) object:nil];
[queue addOperation:op3];
[doneOp addDependency:op3];
[queue addOperation:doneOp];
doneOp will only run after op1, op2 and op3 have finished executing.
[opQueue operationCount]
Hope this helps
One approach would be to create some sort of Directory class with a properties such as loadedCount (initially 0) and fileCount (initialized to however many files are in the directory) and create a dictionary mapping each NSOperation to the appropriate Directory before adding the operation to the queue. Inside your callback for isFinished, you could pull the Directory object for the given NSOperation out of the dictionary and increment the directory.loadedCount by 1. If your directory.loadedCount == directory.fileCount, trigger an update to the UI.
You can refactor your code to avoid enqueuing all requests at once. Enqueue only requests for a single directory at a time. When operationCount reaches zero, you can be sure that all the requests either completed or failed, update the UI and enqueue the requests for the next directory. Proceed until the array of directories is empty.
The advantages are:
relative simplicity
you don't have to query the file system only to figure out what has been downloaded
if need be, you can re-enqueue failed requests without changing other logic.
Related
I have a long running task that reads a file from the documents directory, parses it, deletes the file, then reads the next file from the doc directory, parses, deletes. I'm trying to put this code in the background but having trouble. I got so far as:
(pseudocode)
while (fileExistsInDocDirectory) {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setName:#"LongTask"];
[queue addOperationWithBlock:^{
parseSuccessful = [self doLongTask];
}];
[queue addOperationWithBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateProgress];
}];
}];
}
It will work for one file only since I'm using FMDB my doLongTask is inserting rows into the db. So when the next queue comes in and tries to insert its data and the db is already in use, it doesn't work. Am I able to not run the 2nd queue until the first one finishes within a while loop? I saw some thing about setting up dependent queues, but wasn't sure if there was an easy way to do it without subclassing NSOperation. Thanks!
I am writing an application that periodically fetches data from a web server using ASI HTTP and then processes that data to display something relevant to the user on the UI. The data is retrieved from different requests on a single server. The data itself needs to be processed in a specific order. One of the blocks of data is much bigger than the other ones.
In order not to lock the UI while the data is being processed, I have tried to use the NSOperationQueue to run the data processing on different threads. This works fine about 90% of the times. However, in the remaining 10% of the time, the biggest block of data is being processed on the main thread, which cause the UI to block for 1-2 seconds. The application contains two MKMapViews in different tabs. When both MKMapViews tabs are loaded the percentage of time the biggest block of data is being processed on the main thread increases above 50% (which seems to point to the assumption that this happens when there is more concurrent activity).
Is there a way to prevent the NSOperationQueue to run code on the main thread?
I have tried to play with the NSOperationQueue –setMaxConcurrentOperationCount:, increasing and decreasing it but there was no real change on the issue.
This is the code that starts the periodic refresh:
- (void)refreshAll{
// Create Operations
ServerRefreshOperation * smallDataProcessor1Op = [[ServerRefreshOperation alloc] initWithDelegate:_smallDataProcessor1];
ServerRefreshOperation * smallDataProcessor2Op = [[ServerRefreshOperation alloc] initWithDelegate:_smallDataProcessor2];
ServerRefreshOperation * smallDataProcessor3Op = [[ServerRefreshOperation alloc] initWithDelegate:_smallDataProcessor3];
ServerRefreshOperation * smallDataProcessor4Op = [[ServerRefreshOperation alloc] initWithDelegate:_smallDataProcessor4];
ServerRefreshOperation * smallDataProcessor5Op = [[ServerRefreshOperation alloc] initWithDelegate:_smallDataProcessor5];
ServerRefreshOperation * hugeDataProcessorOp = [[ServerRefreshOperation alloc] initWithDelegate:_hugeDataProcessor];
// Create dependency graph (for response processing)
[HugeDataProcessorOp addDependency:smallDataProcessor4Op.operation];
[smallDataProcessor5Op addDependency:smallDataProcessor4Op.operation];
[smallDataProcessor4Op addDependency:smallDataProcessor3Op.operation];
[smallDataProcessor4Op addDependency:smallDataProcessor2Op.operation];
[smallDataProcessor4Op addDependency:smallDataProcessor1Op.operation];
// Start be sending all requests to server (startAsynchronous directly calls the ASIHTTPRequest startAsynchronous method)
[smallDataProcessor1Op startAsynchronous];
[smallDataProcessor2Op startAsynchronous];
[smallDataProcessor3Op startAsynchronous];
[smallDataProcessor4Op startAsynchronous];
[smallDataProcessor5Op startAsynchronous];
[hugeDataProcessorOp startAsynchronous];
}
This is the code that sets the ASI HTTP completion block that starts the data processing:
[_request setCompletionBlock:^{
[self.delegate setResponseString:_request.responseString];
[[MyModel queue] addOperation:operation]; // operation is a NSInvocationOperation that calls the delegate parse method
}];
I have added this block of code in all NSInvocationOperation Invoked method at the entry point:
if([NSThread isMainThread]){
NSLog(#"****************************Running <operation x> on Main thread");
}
The line is printed every time the UI freezes. This shows that the whole operation is run on the main thread. It is actually always the hugeDataProcessorOp that is run on the main thread. I assume that this is because it is the operation that always receives its answer last from the server.
After much investigation in my own code, I can confirm that this was a coding error.
There was an old call remaining that did not go through the NSInvocationOperation but was calling the selector that NSInvocationOperation should have called directly (therefore not using the concurrent NSOperationQueue.
This means that the NSOperationQueue DOES NOT use the main thread (except if it is the one retrieved by +mainQueue).
Override isConcurrent on your NSOperation and return YES. According to the documentation this will cause your NSOperation to be run asynchronously.
Application Specific Information:
com.my-app failed to launch in time
Elapsed total CPU time (seconds): 20.090 (user 20.090, system 0.000), 100% CPU
Elapsed application CPU time (seconds): 17.598, 87% CPU
I've made a modification to my app and as a result I now run a function from applicationDidFinishLaunching which will do some database processing.
I'm basically creating some new records and updating some existing ones.
For one of my existing beta testers / real customers, this is taking 20 seconds to complete.
Although in this case this is a one off, users could experience this situation if they haven't used the app for a while.
Normally the process wouldn't take long at all, as there would only be a few transactions to process.
I'm unsure how to proceed, any suggestions ?
I suggest you to do your db processing in the background. Maybe you could disable the interface or display a waiting indicator while you are updating the db in the background thread. Then, once finished you could enable the interface or hide the indicator.
There are different ways to create background thread.
Create a thread manually using NSThread class
Using NSOperation and NSOperationQueue classes
Using Grand Central Dispatch (GCD)
Hope it helps.
Edit
Here simple code for your goal (following #JeremyP suggestion).
First, create a NSOperation subclass
// .h
#interface YourOperation : NSOperation
{
}
//.m
#implementation YourOperation
// override main, note that init is executed in the same thread where you alloc-init this instance
- (void)main
{
// sorround the thread with a pool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// do your stuff here..
// you could send a notification when you have finished to import your db,
// the notification is sent in a background thread,
// so in the place where you listen it, if you need to update the interface,
// you need to do it in the main thread (e.g. performSelectorOnMainThread)
[[NSNotificationCenter defaultCenter] postNotificationName:kImportComplete object:self];
[pool drain];
pool = nil;
}
Then, in your application delegate for example call [self import]; that could be defined as follow:
if (!(self.operationQueue)) {
NSOperationQueue* q = [[NSOperationQueue alloc] init];
[q setMaxConcurrentOperationCount:1];
self.operationQueue = q;
[q release];
YourOperation *op = [[YourOperation alloc] init];
[self.operationQueue addOperation:op];
[op release], op = nil;
}
I would (and have in the past in a few apps) perform the DB update on a background thread and show the user a 'please wait' screen while the updates complete.
I want to handle freezing my program, when it load an xml from bad address.
I tryed it with using #try and #catch, but it doesn't work.
Can I use some alternative handling?
#try{
NSString *test=[[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://%#:%#",addressLabel.text,portLabel.text]] encoding:NSUTF8StringEncoding error: nil];
}
#catch (NSException *ex) {
NSLog(#"Bad IP address");
return;
}
Run your XML Parser in NSThread and use notification for errors.
initWithContentsOfURL is a synchronous call. The control will return back from the function only on complete. Try this function is a worker thread so that your main thread will not be blocked.
If you use use NSThread then you have to dive into the memory management unless you are working in XCode 4.2 and using ARC.
So there are two ways for fetching the XML from the server.
1) Use NSURLConnection to get the xml as a NSData object and when you finish loading the data you can simply use that data to initialize an NSString Object. NSURLConnection sends asynchronous call to the server so it will not freeze your view.
2) You can use NSIncovationOperation and NSQueue to fetch your XML and it will also not effect your main thread. like
-(void)myMethod{
NSString *test=[[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://%#:%#",addressLabel.text,portLabel.text]] encoding:NSUTF8StringEncoding error: nil];
[self performSelectorOnMainThread:#selector(handleString:) withObject:test];
}
You can use NSInvocationOperation object as follow
NSInvocationOperation *opr = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(myMethod) object:nil];
NSOperationQueue *queue= [NSOperationQueue new];
[queue addOperation:opr];
When the perform selector will be call then you can pass that XML string object to the handleString: method. like
-(void)handleString:(NSString*)xmlString{
// Do something with string
}
I hope that it clarifies a little bit of your confusion. All this was to give you an idea how can you achieve your goal without freezing your interface i.e main thread.
regards,
Arslan
You need to launch all long time operations on a second thread to avoid blocking the main thread. Use [self performSelector:#selector(yourXmlDownloadMethod)].
In my app i have a large number , around 70000 records to load in a tableview. It takes a lot of time to load like ten minutes. Since it blocks the main UIthread, I am unable to go back or access any buttons. Is there any alternate way like using a separate thread for this purpose or any alternate approach ? Please show me some way.
Thanks,
Vinod.
Use a NSThread.
Your code will look something along the lines of:
NSThread *thread = [NSThread initWithTarget:self selector:#selector(loadData:) object:nil];
[thread start];
[thread release];
-(void) loadData:(id) obj {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// load data
[pool release];
}
If you need to do anything on the main UI thread from the newly created thread, use the performSelectorOnMainThread:withObject method on the current object.