NSFileManager removeItemAtPath locks main thread - iphone

I'm working on an app that can remove large amounts of files. When I invoke the NSFileManager's removeItemAtPath method, the app's UI locks until the operation finishes (this can take a while).
I tried fixing this by invoking the method using performSelectorInBackground but it didn't work.
Any ideas?
Thanks in advance.

You could try using GCD to do it in a background thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[[NSFileManager defaultManager] removeItemAtPath:path];
});

Related

iPhone - GCD doesn't work for the first time

In my app, I load image from the URL:
-(void)loadImages
{
...
image1 = [UIImage imageWithData:[NSData dataWithContentsOfURL:imgUrl1]];
}
In order to avoid blocking the main thread until the download has completed I call this method in
-viewDidAppear using GCD:
dispatch_async( dispatch_get_global_queue(0,0), ^{
[self loadImages];
});
However, when I open my view controller with the imageView at the first time, the imageView is empty (even if I wait for a long time) but after I open this view controller again and image appears and everything is good to go.
Where is my mistake ?
Sorry, new to multithreading :)
EDIT:
I also forgot to mention, that I use the image in the tableView when I get it:
cell.imageView.image = image1;
This may not be the answer that you were looking for, but that's not the recommended way to load URLs. You should use the URL loading classes available such as NSURLRequest and NSURLConnection.
Try this:
NSURLRequest *imageRequest = [[NSURLRequest alloc] initWithURL:imageURL];
[NSURLConnection sendAsynchronousRequest:imageRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
UIImage *image = [[UIImage alloc] initWithData:data];
[imageView setImage:image];
}
}];
UIElements in iOS should always be updated via main thread.
What you could do is:-
__block NSData *data;
dispatch_queue_t myQueue = dispatch_queue_create("com.appName", NULL);
dispatch_async(myQueue, ^{
data = [NSData dataWithContentsOfURL:imgUrl1];
dispatch_async(dispatch_get_main_queue(), ^(void) {
cell.imageView.image = [UIImage imageWithData = data];
});
});
or Else you there is a better way of fetching images from URL by using AFNetworking. It is faster and easier.
You just have to write one line of code:-
[cell.imageView setImageWithURL:imgUrl1];
You have many problems here:
You can not start networking requests on an thread not running a runloop.
You can not update your UI from a thread other than the main thread.
[NSData dataWithContentsOfURL:imgUrl1] is not a safe way to load an external resource, even on a different thread (but especially on the main thread).
Any time you dispatch to a different thread, you run the risk that your table cell has been recycled and is no longer showing the data you think it is. (It's still the same cell instance, but is now showing some other row's data.)
What you should be doing:
Start your network operation using asynchronous calls on the main thread. (You can use another thread or queue if you want, but you need to make sure it's running a runloop.)
From your delegate messages, dispatch your image decoding on a different thread.
After the image is decoded, dispatch back to the main thread to update it.
Before actually assigning the image, check that the cell is still being used for the purpose you think.
You can solve the first three problems by using AFNetworking. It wraps the delegate methods and lets you just provide a success and failure block. AFNetworking's AFImageRequestOperation in particular bounces code between queues as I've described. (It even runs its main networking loop in a different thread, which isn't necessary but since it does it well, why not?)
You'll still need to verify the cell's identity.
You need to inform the view that the image needs to be redrawn. Add:
[imageView setNeedsDisplay];
to the end of your loadImages method.
Since you are using it in TableView, add [self.tableView reloadData]; in the end of loadImages method.

using dispatch_async from built framework

I am facing the strange problem with dispatch_async
I have two separate projects: one is for building the framework and another is for using that framework for some purposes.
I am trying to execute very simple piece of code (without any additional code even):
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
});
});
so if i put this dispatch_async to my project it works just perfect,
but if put it to some method of my framework project and build it and then call that method from the project which is using that framework then i receive EXC_BAD_ACCESS error on first dispatch_async line on objc_retainAutoreleasedReturnValue stack
Although this dispatch_async will work only with dispatch_get_main_queue() queue
I tried to create different queues with different priorities, i tried static queues,
and they are actually created, but dispatch_async crashes every time
I believe this is a problem related to some flags in build settings in my framework project, but unfortunately i cannot find anything what can help
UPDATE:
I forgot to say, that this perfectly works on simulator, but does not work on the device
And here is an example of my code:
[[SingleTonOfFramework sharedInstance] executeRequest:#"SomeRequest"
success:^(id response) {
NSLog(#"works");
}];
SingleTonOfFramework - is a class from my framework
and this is what it does:
- (void) executeRequest:(id) request success:(void (^)(id response))success {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
success(#"COOL!");
});
});
}

programmatically clearing secondary cache on iPhone

I have created an application , where i ll be downloading the images from server and storing it locally on the iPhone's file system. Its happening fine. Now the problem is , i want to clear the locally cached images on iPhone when ever i quit my application.
How do i delete those cached images on iPhone. it's using Secondary memory on iPhone, how do i access it programmatically ?
Thank You in advance.
Suse.
Clean them up in your applicationWillTerminate method.
- (void)applicationWillTerminate:(UIApplication *)application
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *imagesFiles = [fileManager contentsOfDirectoryAtPath:saveDirectory error:&error];
for (NSString *file in imagesFiles) {
error = nil;
[fileManager removeItemAtPath:[saveDirectory stringByAppendingPathComponent:file] error:&error];
/* do error handling here */
}
}
My answer would be as per Kirky Todds, but don't use applicationWillTerminate -- it's not a good strategy on iOS4+ with multitasking, because this method often won't get called -- an app will be background, and then can be dumped from memory if other apps are run (or the phone is turned off/restarted), without applicationWillTerminate getting called. (OR it will be foregrounded again. Either way, your cache doesn't get cleared.)
Instead, consider either clearing at startup (applicationDidFinishLaunching), or in applicationWillEnterForeground. The latter will tend to get called more frequently, because the former is only called on an actual proper app lauch (and not on a resume from being backgrounded).
For more info on backgrounding and the events generated, see:
http://www.cocoanetics.com/2010/07/understanding-ios-4-backgrounding-and-delegate-messaging/

memory leak issue

I am loading images to UITableViewCell using the following function. But when I run my app with Debugger its getting crashed at [pool release] whenever I scroll my UITableView. What might I do to solve this? Thanks in advance.
- (void) loadImage{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
imageURL = [imageURL stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
//NSLog(#"img url cell**** %#",imageURL);
self.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
//self.image = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]];
[pool release];
}
By the fact that you are using a NSAutoreleasePool I guess that load image is running in a thread that is not the main thread. Is this Correct? If that is the case you are making a UIKit invocation (self.image = ...) in this non-main thread and this is a possible source of the crash you are experiencing. All UIKit updates must be made in the main thread, since UIKit is not thread safe. Try replacing:
self.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
by
[self performSelectorOnMainThread:#selector(setImage:) withObject:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]] waitUntilDone:YES];
Notice I'm guessing the name of the setter is setImage:, you may need to correct if the setter selector has a different name that.
First of all, it is recommended to use [pool drain] instead of [pool release].
But I see another potential issue here: according to your code, you defined initially imageURL outside of your newly created autorelease pool (I don't see any alloc/init for imageURL, where is coming from? is it a table cell instance?) then you reassign imageURL with a newly allocated and autoreleased string: [imageURL stringByAddingPercentEscapesUsingEncoding:...]. Finally when you release the pool the new imageURL is release, so at the end what you had is a leak of the previously allocated imageURL. I can expect at this point that when exiting from the inner autorelease pool the run loop autorelease tries to release imageURL again (for example, but I need to see your code to fully understand) which has been deallocated.
Can you try to assign the result of stringByAddingPercentEscapesUsingEncoding: to a different name (e.g. myImageURL)?
Quite a few things wrong here.
Firstly, your need of an autorelease pool quite clearly suggests that you're not doing this work on the main thread. UIKit is not thread-safe. Any UI work needs to be done on the main thread or undefined behaviour will occur, such as crashes.
Secondly, you're using what looks like a synchronous URL download of an image. Correct me if you're loading it from a file URL on disk. A synchronous image download is probably the reason why you've moved this into a separate thread, because it was blocking your UI right? One of the first things you should learn about network access it to never use a synchronous API on the main thread. Have you considered looking at the NSURLConnection class in order to do an asynchronous download?
Thirdly, this needs to be re-architected so that the image download isn't directly linked to the display of the cell.
Displaying the table cell should be done on the main thread with the rest of the UI. When the cell is displayed, it should make a call to download the image asynchronously. The image download should be cached, and the cell should be informed via a delegate callback when the image download is complete. It should then display the image.
You can wrap these kind of things up in NSOperations and use NSOperationQueue to manage them. Please, look at the documentation for these classes and checkout the example code for them to learn how to use them.

Execute a method that takes a second to complete without temporarily halting code

Basically, i have a method that takes a few seconds to complete as it copies some files using NSFileManager. This is invoked on the touchesMoved event when the user picks up a draggable UIView icon. However, there's a slight delay before the icon's position is updated. I'm guessing it's waiting for that method to copy it's files before continuing. The method HAS to be triggered on touchesMoved, so please don't suggest moving it.
How can i execute a method that takes about a second to complete, without holding up the code?
(..and don't worry the copy method doesn't get repeatedly called from the touchesMoved event)
You could perform the task in the background using performSelectorInBackground:...:
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html
This prevent that selector from blocking the main thread.
Example:
[self performSelectorInBackground:#selector(myMethod) withObject:nil];
Do it in a background thread. Leave the main thread to deal with UI stuff only.
Technically you could divide the copying of files into very small chunks, and tell the current NSRunLoop to dispatch between each file copy.
But practically just say no to any IO access on the main thread, all IO access should be done in the background. Even the slightest block on the main thread will make the UI stutter and be unresponsive, Android user might accept that, iOS user do not.
Your options are numerous, and easy to implement. You could do a simple performSelector–:
-(void)backgroundWorker {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Do your stuff
[pool release];
}
-(void)startDoingIOStuff {
[self performSelectorInBackground:#selector(backgroundWorker)
withObject:nil];
}
You could do it practically inline using a block and GCD:
-(void)startDoingIOStuff {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL),
^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Do your stuff
[pool release];
});
}
Or you could use an NSOperation on a NSOperationQueue. I have written a longer blog post on this topic, including source code that is available here: http://blog.jayway.com/2010/08/19/future-cocoa-operation/
Before immediately resorting to a secondary thread, it would certainly be worth a try to use a plain old performSelector on self. For example:
[self peformSelector:#selector(copyFiles) withObject:nil afterDelay:0.0];
Note that this is different from doing:
[self copyFiles];
The peformSelector version basically says "do copyFiles ASAP, OK?", but doesn't block everything while waiting for it to be done. In other words, it's possible that the perform selector version would allow the main event loop to update the UI (thereby preventing the apparent visual lag) before the file copying is actually done.