I have a memory leak that seems to be coming from a retain cycle. The memory allocation size is increasing every time this code runs:
- (void)nextPhoto {
self.photoIndex++;
if (self.photoIndex >= [self.photos count]) {
self.photoIndex = 0;
}
__weak Photo *photo = [self.photos objectAtIndex:self.photoIndex];
[[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:photo.thumbnailURLString] options:SDWebImageRetryFailed progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
}];
}
The code is looping on a 2 second timer:
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(nextPhoto) userInfo:nil repeats:YES];
The total memory use increases without bounds until I get a memory overuse event.
Why is this code causing a retain cycle? Is there a special way I need to handle self in this situation?
self.photos is an NSMutableArray
self.photoIndex is an NSInteger
SDWebImageManager is a well maintained library: https://github.com/rs/SDWebImage and I use it in numerous other locations with no issues
I don't see any problem involving a retain cycle here, even if you use self in the completion block. the block owner is SDWebImageManager so no problems here. a retain cycle could occur if you store your block in a property of your viewController, cause it then would own a block that retains it... It's not what is happening here imho.
Now your problem, i presume, comes from the UIImage. I depends of what you do in the block of course but if your storing the images then, yes every 2 seconds a new one is created and then it will fail eventually. You should keep a cache of images that has already been downloaded and try to download them only if needed... Add a NSDictionary with url as key and UIImage as value for example, this way you will only download your images once.
Ok I should have slept on this one... The function is actually working exactly as it should and it was self.photos that was increasing without bounds. Putting a limit on the size of that array fixed the "leak".
Related
I'm having a problem where I'm unable to update UI when performing synchronous downloads. I would expect that using synchronous APIs would ensure that code executes in order (which it doesn't seem to be doing), which is really confusing me.
The following code is in a UICollectionView's didSelectItemAtIndexPath and is not wrapped in any asynchronous block or anything.
Any ideas on what I can do to be able to update the UI (most importantly a progress indicator) as these tasks occur? I think that the way it is currently laid out should work, but for some reason it's not able to update until the code has all 'executed'.
if ([internetReachable isReachable]) {
//does not become visible until after
self.circleProgress.alpha = 1.0;
//lots of downloading and saving with NSData dataWithContentsOfURL followed by this:
for (int i = 1; i < pages.count; i++) {
NSString *number;
if (i < 10) {
number = [NSString stringWithFormat:#"00%d", i];
}
else if (i < 100) {
number = [NSString stringWithFormat:#"0%d", i];
}
else {
number = [NSString stringWithFormat:#"%d", i];
}
NSURL *imageURL = [NSURL URLWithString:[NSString stringWithFormat:#"http://books.hardbound.co/%#/%#-%#.png", slug, slug, number]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
[df setObject:imageData forKey:[NSString stringWithFormat:#"%#-%#", slug, number]];
CGFloat progress = ((CGFloat)i / pages.count);
//only runs for the last iteration, rather than calling the method to update the progress indicator each iteration and allowing it to update before going back to the next iteration as I would expect
[self updateProgressBarWithAmount:[NSNumber numberWithFloat:progress]];
NSLog(#"progress after: %f", self.circleProgress.progress);
}
}
UI can only be executed on the main thread. Since the main thread is busy doing the downloading, it can't update the UI. It's almost never a good idea to perform any long running operations on the main thread. You should make the download asynchronous, and update the UI on the main thread.
The loop in the code you posted will only be executed after lots of downloading and saving with NSData dataWithContentsOfURL is performed, all the while the application will be unresponsive, and that's very poor UX. Take a look at this question for a much better implementation of a progress bar.
I am not by any means qualified to explain what exactly happens during each render loop and why updateProgress doesn't actually let a screen render occur before you block the main thread again, but I am able to provide a solution.
After you update the progress of the progress view, you want the changes to get rendered "right now". This means you have to tell the current run loop to run one iteration, and then return to you so you can do another long running task.
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
Call that whoever you want the progress view to update, and it will do a screen render and then return to you.
I got this from this answer
However, you really should be doing this asynchronously.
(Apologies for any typos, as this is being typed on my phone)
Importing multiple photos from album, one of the delegate method is
// Here info is array of dictionary containing FileName/AssetURL etc
- (void)somePicker(SomePicker*)somePicker didFinishPickingMediaWithInfo:(NSArray *)info {
_importStatusView.center = self.view.center;
[self.view addSubview:_importStatusView];
[self dismissViewControllerAnimated:YES completion:^{
NSNumber *total = [NSNumber numberWithInteger:info.count];
[info enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSDictionary *imageInfo = (NSDictionary*)obj;
NSString *fileName = [imageInfo objectForKey:#"UIImagePickerControllerFileName"];
NSURL *imageURL = [imageInfo objectForKey:UIImagePickerControllerReferenceURL];
ALAssetsLibrary *assetLibrary=[[ALAssetsLibrary alloc] init];
[assetLibrary assetForURL:imageURL resultBlock:^(ALAsset *asset) {
NSLog(#"start");
ALAssetRepresentation *rep = [asset defaultRepresentation];
Byte *buffer = (Byte*)malloc(rep.size);
NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:rep.size error:nil];
NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
NSString *filePath = [_currentPath stringByAppendingPathComponent:fileName];
[data writeToFile:filePath atomically:YES];
//This also has no effect
//dispatch_async(dispatch_get_main_queue(), ^{
//_lblImportCountStatus.text = [NSString stringWithFormat:#"%d of %d",idx+1,[total integerValue]];
//NSLog(#"Label value->%#",_lblImportCountStatus.text); //This prints values but after everything is finished it prints all line at once i.e. at the end of the enumeration of all items
//});
//Update UI
NSNumber *current = [NSNumber numberWithInteger:idx+1];
NSDictionary *status = [NSDictionary dictionaryWithObjectsAndKeys:current,#"current", total,#"totalCount", nil];
[self performSelectorOnMainThread:#selector(updateImportCount:) withObject:status waitUntilDone:YES];
//_lblImportCountStatus.text = [NSString stringWithFormat:#"%d of %d",idx+1,[total integerValue]];
if(idx==info.count-1){
[_importStatusView removeFromSuperview];
}
NSLog(#"Finish");
} failureBlock:^(NSError *error) {
NSLog(#"Error: %#",[error localizedDescription]);
}];
}];
}];
}
My declaration for status view and label is
#property (strong, nonatomic) IBOutlet UIView *importStatusView; //View containing label
#property (weak, nonatomic) IBOutlet UILabel *lblImportCountStatus; //Label
Everything in above code is working fine and as expected, but the problem is a importStatusView is being added to the screen but lblImportCountStatus value is not displaying, though If I log the values it shows updated.
When enumeration is finished at the end all the NSLog gets printed for e.g. If I have imported 10 photos than at last it prints, i.e. dispatch_async(dispatch_get_main_queue() this function has no effect at all while enumeration is in progress.
Label value->1 of 10
Label value->2 of 10
Label value->3 of 10
Label value->4 of 10
Label value->5 of 10
Label value->6 of 10
Label value->7 of 10
Label value->8 of 10
Label value->9 of 10
Label value->10 of 10
What could be the issue ?
Update:
-(void)updateImportCount:(NSDictionary*)info{ //(NSNumber*)current forTotalItems:(NSNumber*)totalCount{
NSNumber *current = [info objectForKey:#"current"];
NSNumber *totalCount = [info objectForKey:#"totalCount"];
_lblImportCountStatus.text = [NSString stringWithFormat:#"%d of %d",[current integerValue],[totalCount integerValue]];
[_lblImportCountStatus setNeedsDisplay];
NSLog(#"Updating ui->%#",_lblImportCountStatus.text);
}
Above function works on main thread and updates but stil label is not shown it prints following NSLog
start
Updating ui->1 of 10
Finish
start
Updating ui->2 of 10
Finish
start
Updating ui->3 of 10
Finish
start
Updating ui->4 of 10
Finish
start
Updating ui->5 of 10
Finish
start
Updating ui->6 of 10
Finish
start
Updating ui->7 of 10
Finish
start
Updating ui->8 of 10
Finish
start
Updating ui->9 of 10
Finish
start
Updating ui->10 of 10
Finish
I have uploaded project at this location, please feel free to help.
All those blocks are executing on the main thread (the easiest way to verify this is using NSThread's +currentThread to get the current thread, and -isMainThread to check if it's the main thread. Anywhere you have code that you want to see what thread it's on, do something like this:
NSLog( #"enumeration block on main thread: %#",
[[NSThread currentThread] isMainThread] ? #"YES" : #"NO" );
I think the problem is, since this is all executing on the main thread, you're locking up the runloop, not giving the UI a chance to update.
The right way to fix this is probably to really do this processing on a separate thread (calling the code to update the UI via performSelectorOnMainThread:, as you're doing now). But, a quick hack to make it work would be to allow the runloop to run. At the end of updateImportCount:, do something like this:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
It ain't pretty, but it will work.
Update:
In Cocoa (Mac OS and iOS) there a concept of a runloop. The main thread of your application drives an NSRunLoop which serves as the event loop. User actions -- taps, etc. -- are processed through this loop, as are things like timers, network connections, and other things. See the NSRunLoop Reference for more information on that.
In iOS, drawing also happens on the runloop. So when you call setNeedsDisplay on a view, that view is not redrawn immediately. Rather, it's simply flagged as needing redraw, and then on the next drawing cycle (the next trip through the runloop) the actual drawing takes place. The UIView reference has a brief description of this (see the section "The View Drawing Cycle". Quoting from that section:
When the actual content of your view changes, it is your
responsibility to notify the system that your view needs to be
redrawn. You do this by calling your view’s setNeedsDisplay or
setNeedsDisplayInRect: method of the view. These methods let the
system know that it should update the view during the next drawing
cycle. Because it waits until the next drawing cycle to update the
view, you can call these methods on multiple views to update them at
the same time.
When you return from whatever method was called in response to some user action (in this case, somePicker:didFinishPickingMediaWithInfo:, control returns to the runloop, and any views that need redrawing, are. However, if you do a bunch of processing on the main thread without returning, the runloop is basically stalled, and drawing won't take place. The code above basically gives the runloop some time to process, so drawing can take place. [NSDate date] returns the current date and time, right now, so that basically tells the runlooop "run until right now", which ends up giving it one cycle through the loop, giving you one drawing cycle, which is an opportunity for your label to be redrawn.
I'm using Multipeer-Connectivity.
When the session ends, the app comes to the main menu and all network stuff is released then deallocated.
But my dealloc method is called in main thread and the MCSession object takes a very long time to release itself, I don't know why, and consequently the main menu screen freezes.
If somebody know why MCSession could be so long, I'm interested. But if it comes from the MCSession itself, is it a good solution to do this?
-(void) dealloc
{
//... other release
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_session release];
_session = nil;
});
[super dealloc];
}
EDIT: nope, it's definitely not a good solution, because it makes my app crashing. Anyway, other ideas?
When you call [_session release] since _session is an Ivar, the compiler will replace this line by [self->_session release] and the block will retain self instead of the iVar _session.
Here you have 2 problems:
Trying to retain an object(self) which are deallocating.
When the queue will be executed, it'll call self which is already deallocated.
The following solution create a local variable which point to the same address as the iVar and release it inside the block, the block will not capture self.
-(void) dealloc
{
//... other release
MCSession* localSession = _session;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[localSession release];
});
[super dealloc];
}
bsarr007's solution will work for non-ARC projects. If you are using ARC, you can try this:
__block MCSession *localSession = _session;
_session = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
oldSession = nil;
});
It works for me. What I am doing here is increasing reference count of MCSession object by creating new local variable that points to that object, so it won't be deallocated immediately when setting _session = nil. After that I am running asynchronously the code that decreasing reference counter of my MCSession object using background queue.
I have a problem with my network activity indicator in that sometimes it will continue to be displayed when it should not be.
I wrote my own manager for it and swapped it out for one that uses an NSAssert statement like this...
- (void)setNetworkActivityIndicatorVisible:(BOOL)setVisible {
static NSInteger NumberOfCallsToSetVisible = 0;
if (setVisible)
NumberOfCallsToSetVisible++;
else
NumberOfCallsToSetVisible--;
// The assertion helps to find programmer errors in activity indicator management.
// Since a negative NumberOfCallsToSetVisible is not a fatal error,
// it should probably be removed from production code.
NSAssert(NumberOfCallsToSetVisible >= 0, #"Network Activity Indicator was asked to hide more often than shown");
// Display the indicator as long as our static counter is > 0.
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:(NumberOfCallsToSetVisible > 0)];
}
I found it on SO and it has immediately pointed out that something is going wrong with my use of this function.
All of my network activity is exclusively run through a single NSOperationQueue which is managed by a singleton class. Every operation is a subclass of NSOperation (actually a subclass of a TemplateOperation which is a subclass of NSOperation).
Anyway, all the downloads and uploads are working fine and I'm doing them all like this...
- (void)sendRequest:(NSURLRequest *)request
{
NSError *error = nil;
NSURLResponse *response = nil;
[[NetworkManager sharedInstance] setNetworkActivityIndicatorVisible:YES];
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
[[NetworkManager sharedInstance] setNetworkActivityIndicatorVisible:NO];
// other stuff...
[self processData:data];
}
The important lines are immediately before and after I send the NSURLConnection synchronously.
Immediately before I send the request I set the network activity indicator to visible (using my manager class) and then immediately after I set it back to invisible.
Except the NSAssert has pointed out that somewhere this is not happening properly.
Could it be that running this function from multiple threads could be causing an issue? How could I solve this?
Integer increment or decrement is not thread-safe (as far as I know), so if two threads call your method "simultaneously", the count might not get updated properly.
One solution would be to add some synchronization directive (such as #synchronized)
to your method. Or you use the atomic increment/decrement functions:
#include <libkern/OSAtomic.h>
- (void)setNetworkActivityIndicatorVisible:(BOOL)setVisible {
static volatile int32_t NumberOfCallsToSetVisible = 0;
int32_t newValue = OSAtomicAdd32((setVisible ? +1 : -1), &NumberOfCallsToSetVisible);
NSAssert(newValue >= 0, #"Network Activity Indicator was asked to hide more often than shown");
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:(newValue > 0)];
}
Hi I'm working through the Stanford iOS development class. I have a question regarding threading. I understand UIKit calls should be handled by the main thread. I was wondering if something like this is legal?
- (UIImage *)mapViewController:(MapViewController *)sender imageForAnnotation:(id<MKAnnotation>)annotation {
FlickrPhotoAnnotation *fpa = (FlickrPhotoAnnotation *) annotation;
NSURL *url = [FlickrFetcher urlForPhoto:fpa.photo format:FlickrPhotoFormatSquare];
__block UIImage *image;
dispatch_queue_t downloadQ = dispatch_queue_create("download queue", NULL);
dispatch_async(downloadQ, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
if (data) {
dispatch_async(dispatch_get_main_queue(), ^{
image = [UIImage imageWithData:data];
});
}
});
dispatch_release(downloadQ);
return image;
}
or I should just return NSData and handle all the threading in the calling method?
Thanks
Your code won't do what you want.
You are putting an asynchronous block into a queue, and then immediately returning from the method. You are not guaranteed that the block will actually run before you return -- in fact, the odds are that it won't.
So you'll return the current value of image. Since you didn't initialize it, it's probably garbage. Whoever calls this method will try to use a garbage pointer to an image, and (if you're lucky) crash. If you had initialized it to nil:
__block UIImage *image = nil;
that would be a little more polite.
The problem here is: your method must return a UIImage, so you must wait for the time it takes to make a fully constructed UIImage before you return. There is zero benefit to doing this on some other thread -- you're still waiting the same amount of time, and switching threads just adds overhead.
In order to load the image in a usefully asynchronous way, you need some way to asynchronously tell the caller when the image is done loading, via a callback to a delegate method or block. For example, look at NSURLConnection in Foundation. It has an older method that calls back via a delegate, and a newer method (+sendAsynchronousRequest:queue:completionHandler:) that calls back via a block.
I concur with CodaFi's comment. Actually, images can be created off the main thread. UIView creation and manipulation must be done on the main thread, but UIImage is not a UIView though. Furthermore, the runtime is likely to give you a warning or error if you try to manipulate the displaying UI on another thread (it did for me when I accidentally updated a UITableView on another thread).