I have a very simple test app with ARC. One of the view controllers contains UITableView. After making row animations (insertRowsAtIndexPaths or deleteRowsAtIndexPaths) UITableView (and all cells) never deallocated. If I use reloadData, it works fine. No problems on iOS 6, only iOS 7.0.
Any ideas how to fix this memory leak?
-(void)expand {
expanded = !expanded;
NSArray* paths = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:0 inSection:0], [NSIndexPath indexPathForRow:1 inSection:0],nil];
if (expanded) {
//[table_view reloadData];
[table_view insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationMiddle];
} else {
//[table_view reloadData];
[table_view deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationMiddle];
}
}
-(int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return expanded ? 2 : 0;
}
table_view is kind of class TableView (subclass of UITableView):
#implementation TableView
static int totalTableView;
- (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
{
if (self = [super initWithFrame:frame style:style]) {
totalTableView++;
NSLog(#"init tableView (%d)", totalTableView);
}
return self;
}
-(void)dealloc {
totalTableView--;
NSLog(#"dealloc tableView (%d)", totalTableView);
}
#end
Well, if you dig a little bit deeper (disable ARC, subclass tableview, override retain/release/dealloc methods then put logs/breakpoints on them), you'll find that something bad happens in an animation completion block which possibly causes the leak.
It looks like the tableview receives too many retains from a completion block after cell inserting/deleting on iOS 7, but not on iOS 6 (on iOS 6 UITableView has not yet been use block animations - you can check it too on the stack trace).
So I try to take over the tableview's animation completion block lifecycle from UIView in a dirty way: method swizzling. And this actually solves the problem.
But it does a lot more so I still looking for a more sophisticated solution.
So extend UIView:
#interface UIView (iOS7UITableViewLeak)
+ (void)fixed_animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;
+ (void)swizzleStaticSelector:(SEL)selOrig withSelector:(SEL)selNew;
#end
#import <objc/runtime.h>
typedef void (^CompletionBlock)(BOOL finished);
#implementation UIView (iOS7UITableViewLeak)
+ (void)fixed_animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion {
__block CompletionBlock completionBlock = [completion copy];
[UIView fixed_animateWithDuration:duration delay:delay options:options animations:animations completion:^(BOOL finished) {
if (completionBlock) completionBlock(finished);
[completionBlock autorelease];
}];
}
+ (void)swizzleStaticSelector:(SEL)selOrig withSelector:(SEL)selNew {
Method origMethod = class_getClassMethod([self class], selOrig);
Method newMethod = class_getClassMethod([self class], selNew);
method_exchangeImplementations(origMethod, newMethod);
}
#end
As you can see the original completion block is not passed directly to the animateWithDuration: method and it is released correctly from the wrapper block (the lack of this causes leaks in tableviews). I know it looks a little bit strange but it solves the problem.
Now replace the original animation implementation with the new one in your App Delegate's didFinishLaunchingWithOptions: or wherever you want:
[UIView swizzleStaticSelector:#selector(animateWithDuration:delay:options:animations:completion:) withSelector:#selector(fixed_animateWithDuration:delay:options:animations:completion:)];
After that, all of the calls to [UIView animateWithDuration:...] leads to this modified implementation.
I was debugging a memory leak in my application, which turned out to be this same leak, and eventually came to the exact same conclusion as #gabbayabb -- the completion block of the animation used by UITableView never gets freed, and it has a strong reference to the table view, meaning that never gets freed either. Mine happened with a simple [tableView beginUpdates]; [tableView endUpdates]; pair of calls, with nothing in between. I did discover that disabling animations ([UIView setAnimationsEnabled:NO]...[UIView setAnimationsEnabled:YES]) around the calls avoided the leak -- the block in that case is invoked directly by UIView, and it never gets copied to the heap, and therefore never creates a strong reference to the table view in the first place. If you don't really need the animation, that approach should work. If you need the animation though... either wait for Apple to fix it and live with the leak, or attempt to solve or mitigate the leak via swizzling some methods, such as the approach by #gabbayabb above.
That approach works by wrapping the completion block with a very small one, and managing the references to the original completion block manually. I did confirm this works, and the original completion block gets freed up (and releases all of its strong references appropriately). The small wrapper block will still leak until Apple fixes their bug, but that does not retain any other objects so it will be a relatively small leak in comparison. The fact this approach works indicates that the problem is actually in the UIView code rather than the UITableView, but in testing I have not yet found that any of the other calls to this method leak their completion blocks -- it only seems to be the UITableView ones. Also, it appears that the UITableView animation has a bunch of nested animations (one for each section or row maybe), and each one has a reference to the table view. With my more involved fix below, I found we were forcibly disposing of about twelve leaked completion blocks (for a small table) for each call to begin/endUpdates.
A version of #gabbayabb's solution (but for ARC) would be:
#import <objc/runtime.h>
typedef void (^CompletionBlock)(BOOL finished);
#implementation UIView (iOS7UITableViewLeak)
+ (void)load
{
if ([UIDevice currentDevice].systemVersion.intValue >= 7)
{
Method animateMethod = class_getClassMethod(self, #selector(animateWithDuration:delay:options:animations:completion:));
Method replacement = class_getClassMethod(self, #selector(_leakbugfix_animateWithDuration:delay:options:animations:completion:));
method_exchangeImplementations(animateMethod, replacement);
}
}
+ (void)_leakbugfix_animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay
options:(UIViewAnimationOptions)options
animations:(void (^)(void))animations
completion:(void (^)(BOOL finished))completion
{
CompletionBlock realBlock = completion;
/* If animations are off, the block is never copied to the heap and the leak does not occur, so ignore that case. */
if (completion != nil && [UIView areAnimationsEnabled])
{
/* Copy to ensure we have a handle to a heap block */
__block CompletionBlock completionBlock = [completion copy];
CompletionBlock wrapperBlock = ^(BOOL finished)
{
/* Call the original block */
if (completionBlock) completionBlock(finished);
/* Nil the last reference so the original block gets dealloced */
completionBlock = nil;
};
realBlock = [wrapperBlock copy];
}
/* Call the original method (name changed due to swizzle) with the wrapper block (or the original, if no wrap needed) */
[self _leakbugfix_animateWithDuration:duration delay:delay options:options animations:animations completion:realBlock];
}
#end
This is basically identical to #gabbayabb 's solution, except it is done with ARC in mind, and avoids doing any extra work if the passed-in completion is nil to begin with or if animations are disabled. That should be safe, and while it does not completely solve the leak, it drastically reduces the impact.
If you want to try to eliminate the leak of the wrapper blocks, something like the following should work:
#import <objc/runtime.h>
typedef void (^CompletionBlock)(BOOL finished);
/* Time to wait to ensure the wrapper block is really leaked */
static const NSTimeInterval BlockCheckTime = 10.0;
#interface _IOS7LeakFixCompletionBlockHolder : NSObject
#property (nonatomic, weak) CompletionBlock block;
- (void)processAfterCompletion;
#end
#implementation _IOS7LeakFixCompletionBlockHolder
- (void)processAfterCompletion
{
/* If the block reference is nil, it dealloced correctly on its own, so we do nothing. If it's still here,
* we assume it was leaked, and needs an extra release.
*/
if (self.block != nil)
{
/* Call an extra autorelease, avoiding ARC's attempts to foil it */
SEL autoSelector = sel_getUid("autorelease");
CompletionBlock block = self.block;
IMP autoImp = [block methodForSelector:autoSelector];
if (autoImp)
{
autoImp(block, autoSelector);
}
}
}
#end
#implementation UIView (iOS7UITableViewLeak)
+ (void)load
{
if ([UIDevice currentDevice].systemVersion.intValue >= 7)
{
Method animateMethod = class_getClassMethod(self, #selector(animateWithDuration:delay:options:animations:completion:));
Method replacement = class_getClassMethod(self, #selector(_leakbugfix_animateWithDuration:delay:options:animations:completion:));
method_exchangeImplementations(animateMethod, replacement);
}
}
+ (void)_leakbugfix_animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay
options:(UIViewAnimationOptions)options
animations:(void (^)(void))animations
completion:(void (^)(BOOL finished))completion
{
CompletionBlock realBlock = completion;
/* If animations are off, the block is never copied to the heap and the leak does not occur, so ignore that case. */
if (completion != nil && [UIView areAnimationsEnabled])
{
/* Copy to ensure we have a handle to a heap block */
__block CompletionBlock completionBlock = [completion copy];
/* Create a special object to hold the wrapper block, which we can do a delayed perform on */
__block _IOS7LeakFixCompletionBlockHolder *holder = [_IOS7LeakFixCompletionBlockHolder new];
CompletionBlock wrapperBlock = ^(BOOL finished)
{
/* Call the original block */
if (completionBlock) completionBlock(finished);
/* Nil the last reference so the original block gets dealloced */
completionBlock = nil;
/* Fire off a delayed perform to make sure the wrapper block goes away */
[holder performSelector:#selector(processAfterCompletion) withObject:nil afterDelay:BlockCheckTime];
/* And release our reference to the holder, so it goes away after the delayed perform */
holder = nil;
};
realBlock = [wrapperBlock copy];
holder.block = realBlock; // this needs to be a reference to the heap block
}
/* Call the original method (name changed due to swizzle) with the wrapper block (or the original, if no wrap needed */
[self _leakbugfix_animateWithDuration:duration delay:delay options:options animations:animations completion:realBlock];
}
#end
This approach is a little bit more dangerous. It is the same as the previous solution, except it adds a small object which holds a weak reference to the wrapper block, waits 10 seconds after the animation finishes, and if that wrapper block has not been dealloced yet (which it normally should), assumes it is leaked and forces an additional autorelease call on it. The main danger is if that assumption is incorrect, and the completion block somehow really does have a valid reference elsewhere, we could be causing a crash. It seems very unlikely though, since we won't start the timer until after the original completion block has been called (meaning the animation is done), and the completion blocks really should not survive much longer than that (and nothing other than the UIView mechanism should have a reference to it). There is a slight risk, but it seems low, and this does completely get rid of the leak.
With some additional testing, I looked at the UIViewAnimationOptions value for each of the calls. When called by UITableView, the options value is 0x404, and for all of the nested animations it is 0x44. 0x44 is basically UIViewAnimationOptionBeginFromCurrentState| UIViewAnimationOptionOverrideInheritedCurve and seems OK -- I see lots of other animations go through with that same options value and not leak their completion blocks. 0x404 however... also has UIViewAnimationOptionBeginFromCurrentState set, but the 0x400 value is equivalent to (1 << 10), and the documented options only go up to (1 << 9) in the UIView.h header. So UITableView appears to be using an undocumented UIViewAnimationOption, and the handling of that option in UIView causes the completion block (plus the completion block of all nested animations) to be leaked. That leads itself to another possible solution:
#import <objc/runtime.h>
enum {
UndocumentedUITableViewAnimationOption = 1 << 10
};
#implementation UIView (iOS7UITableViewLeak)
+ (void)load
{
if ([UIDevice currentDevice].systemVersion.intValue >= 7)
{
Method animateMethod = class_getClassMethod(self, #selector(animateWithDuration:delay:options:animations:completion:));
Method replacement = class_getClassMethod(self, #selector(_leakbugfix_animateWithDuration:delay:options:animations:completion:));
method_exchangeImplementations(animateMethod, replacement);
}
}
+ (void)_leakbugfix_animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay
options:(UIViewAnimationOptions)options
animations:(void (^)(void))animations
completion:(void (^)(BOOL finished))completion
{
/*
* Whatever option this is, UIView leaks the completion block, plus completion blocks in all
* nested animations. So... we will just remove it and risk the consequences of not having it.
*/
options &= ~UndocumentedUITableViewAnimationOption;
[self _leakbugfix_animateWithDuration:duration delay:delay options:options animations:animations completion:completion];
}
#end
This approach simply eliminates the undocumented option bit and forwards on to the real UIView method. And this does seem to work -- the UITableView does go away, meaning the completion block is dealloced, including all nested animation completion blocks. I have no idea what the option does, but in light testing things seem to work OK without it. It's always possible that option value is vitally important in a way that's not immediately obvious, which is the risk with this approach. This fix is also not "safe" in the sense that if Apple fixes their bug, it will take an application update to get the undocumented option restored to table view animations. But it does avoid the leak.
Basically though... let's hope Apple fixes this bug sooner rather than later.
(Small update: Made one edit to explicitly call [wrapperBlock copy] in the first example -- seems like ARC did not do that for us in a Release build and so it crashed, while it worked in a Debug build.)
Good news! Apple has fixed this bug as of iOS 7.0.3 (released today, Oct 22 2013).
I tested and can no longer reproduce the issue using the sample project #Joachim provided here when running iOS 7.0.3: https://github.com/jschuster/RadarSamples/tree/master/TableViewCellAnimationBug
I also cannot reproduce the issue under iOS 7.0.3 on one of the other apps I am developing, where the bug was causing problems.
It still may be wise to continue shipping any workarounds for a while, until the majority of users on iOS 7 have updated their devices to at least 7.0.3 (which may take a couple weeks). Well, that is assuming your workarounds are safe and tested!
Related
I just finished debugging a very nasty UIViewController leak, such that the UIViewController was not dealloc'd even after calling dismissViewControllerAnimated.
I tracked down the issue to the following block of code:
self.dataSource.doNotAllowUpdates = YES;
[self.collectionView performBatchUpdates:^{
[self.collectionView reloadItemsAtIndexPaths:#[indexPath]];
} completion:^(BOOL finished) {
self.dataSource.doNotAllowUpdates = NO;
}];
Basically, if I make a call to performBatchUpdates and then immediately call dismissViewControllerAnimated, the UIViewController gets leaked and the dealloc method of that UIViewController never gets called. The UIViewController hangs around forever.
Can someone explain this behavior? I assume performBatchUpdates runs over some time interval, say, 500 ms, so I would assume that after said interval, it would call these methods and then trigger the dealloc.
The fix appears to be this:
self.dataSource.doNotAllowUpdates = YES;
__weak __typeof(self)weakSelf = self;
[self.collectionView performBatchUpdates:^{
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf) {
[strongSelf.collectionView reloadItemsAtIndexPaths:#[indexPath]];
}
} completion:^(BOOL finished) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf) {
strongSelf.dataSource.doNotAllowUpdates = NO;
}
}];
Note that the BOOL member variable, doNotAllowUpdates, is a variable I added that prevents any kind of dataSource / collectionView updates while a call to performBatchUpdates is running.
I searched around for discussion online about whether or not we should use the weakSelf/strongSelf pattern in performBatchUpdates, but did not find anything specifically on this question.
I am happy that I was able to get to the bottom of this bug, but I would love a smarter iOS developer to explain to me this behavior I am seeing.
This seems like a bug with UICollectionView. API users should not expect single-run block parameters to be retained beyond the execution of the task, so preventing reference cycles should not be an issue.
UICollectionView should be clearing up any references to blocks once it has finished the batch update process, or if the batch update process is interrupted (for example, by the collection view being removed from the screen).
You've seen for yourself that the completion block is called even if the collection view is taken off-screen during the update process, so the collection view should then be nilling out any reference it has to that completion block - it will never be called again, regardless of the current state of the collection view.
As you figured out, when weak is not used a retain cycle is created.
The retain cycle is caused by self having a strong reference to collectionView and collectionView now has a strong reference to self.
One must always assume that self could have been deallocated before an asynchronous block is executed. To handle this safely two things must be done:
Always use a weak reference to self (or the ivar itself)
Always confirm weakSelf exists before passing it as a nunnull
param
UPDATE:
Putting a little bit of logging around performBatchUpdates confirms a lot:
- (void)logPerformBatchUpdates {
[self.collectionView performBatchUpdates:^{
NSLog(#"starting reload");
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
NSLog(#"finishing reload");
} completion:^(BOOL finished) {
NSLog(#"completed");
}];
NSLog(#"exiting");
}
prints:
starting reload
finishing reload
exiting
completed
This shows that the completion block is fired AFTER leaving the current scope, which means it is dispatched asynchronously back to the main thread.
You mention that you immediately dismiss the view controller after doing the batch update. I think this is the root of your issue:
After some testing, the only way I was able recreate the memory leak was by dispatching the work before dismissing. It's a long shot, but does your code look like this by chance?:
- (void)breakIt {
// dispatch causes the view controller to get dismissed before the enclosed block is executed
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView performBatchUpdates:^{
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
} completion:^(BOOL finished) {
NSLog(#"completed: %#", self);
}];
});
[self.presentationController.presentingViewController dismissViewControllerAnimated:NO completion:nil];
}
The code above leads to dealloc not being called on the view controller.
If you take your existing code and simply dispatch (or performSelector:after:) the dismissViewController call you will likely fix the issue as well.
I have this very strange error happening when I'm changing view controllers in my iOs app.
First some background info.
I am retrieving an image from the web in a background NSOperation. This operation is started from another (background) operation that is instantiated in an collection view cell. The way this works is that the cell creates an object , then sets itself as an observer for that object, then creates an NSOperation with that object as a parameter. This first level operation will start the second operation that will get the image from the web and another NSOperation that will try to get the data from a file (if possible) and report it via delegation to the first operation. That first level operation will change a property on the observed object thus triggering the KVO. The collection/tableView cell will update from the - observeValueChange method.
Here is the problem:
Sometime the cell disappears (reused or deallocated) and when the background thread tries to set the value on the observed object it triggers an EXC_BREAKPOINT exception ([collectionViewCell message retain sent to deallocated instance]).
In order to prevent this I tried implementing -prepareForReuse and -dealloc on the cells. But the error keeps happening.
The flow seem like this:
-User loads VC that has collectionViewWithCells
-cell creates object and NSOperation 1
NSoperation 1 creates NSOperation2 (this is of two types get from web or get from file)
NSOpeartion 2 get image from internet or from a local file
NSoperation 2 sends data to NSOperation1
User has left this screen
NSOperation 1 tries to set data on observed object
-- CRASH
Here is the code inside the cell:
#interface CustomCollectionViewCell ()
#property (strong, nonatomic) NSOperationQueue *imagesOperationQueue;
#property (strong, nonatomic) ImageObject *imgObj;
#end
#implementation CustomCollectionViewCell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)prepareForReuse{
[self clearDelegatesAndObservers];
[super prepareForReuse];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
- (void) getImage {
self.imgObj = [ImageObject newRefrenceWithId:obj_ref];
[self.imgObj addObserver:self forKeyPath:#"data" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];
TaskImageReqCache *imgReq = [[TaskImageReqCache alloc] initWithUrl:imgUrl andImageObject:self.imgObj];
[self.imagesOperationQueue addOperation:imgReq];
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == self.imgObj) {
UIImage *img = [UIImage imageWithData:self.imgObj.data];
self.thumbnailImage.image = img;
}
}
- (void)dealloc
{
[self clearDelegatesAndObservers];
}
- (void)clearDelegatesAndObservers
{
[self.imagesOperationQueue cancelAllOperations];
self.thumbnailImage.image = nil;
[self.imgObj removeObserver:self forKeyPath:#"data"];
[self.pageListAdapter removeDelegateAtIndex:self.myIndexInCollection];
self.imgObj = nil;
}
In the first Level NSOperation this is where the exception breakpoint shows the crash happening:
- (void)didLoadDataFromFile:(NSData *)data
{
if (self.isCancelled) {
[self.opQueue cancelAllOperations];
[self completeOperation];
return;
}
if (!fileDownloadedFromWeb) {
self.observedObject.data = data; // CRASH
}
dataFromDisk = data;
fileReadDone = YES;
if (debugLog) {
NSLog(#"image loaded from local cache (%#)",self.sUrl);
}
}
Any suggestion on how to prevent this crash?
Thanks.
Edited to add:
what I am trying to achieve is: When a tableView cell is displayed a nsoperation is activated to get an image from the net. If the user scrolls quickly and the operation has not finished I need to end it and deallocate any data, and when the cell is reused start a new operation to get the appropriate image from the internet...
Based on comments below, we know that:
- (void)didLoadDataFromFile:(NSData *)data
is called on a different thread to dealloc, so there is a race condition. You need to access self.observedObject on the same thread as the thread it is deallocated on. I'm presuming "observedObject" is a weak reference?
dispatch_sync(dispatch_get_main_queue(), ^{
if (!fileDownloadedFromWeb) {
// Get a strong reference. This will retain observedObject - we must do this
// on the same thread as observedObject:dealloc is called, to prevent retaining
// an object during (or after) dealloc.
ObservedObject *strongRef = self.observedObject;
// This will do nothing if strongRef is nil.
strongRef.data = data;
}
});
A more structured approach would be to have the cell fetch all its images from a singleton cache (it looks as though at the moment there is no caching). The cell would obviously need to register itself as an observer for a particular URL in the cache, and the cache would notify the cell when the URL had downloaded. The cache should post that notification on the main thread.
The cache itself would manage all downloads, and there would be no background deallocation problem because it would be a singleton.
If you don't want to cache, that's fine. Use the same architecture, but call the cache an image fetcher instead. You can always add caching later if you want to.
EDIT - if your objects may be reused, rather than deallocated, as is the case for UITableViewCells, then the cell needs to be careful to ignore notifications about images that relate to a previous fetch. Either of these models will work
a) The cell retains a reference to the NSOperation until the NSOperation calls it back, or until prepareForReuse is called. Any callback from an unrecognised NSOperation must be a previous fetch (that we tried to cancel), and should be ignored. I don't really recommend this model, having the cell know about the operation AND vice versa seems silly.
b) The NSOperation sends a notification when it completes (on the main thread), and in the user info specifies the url/path that was requested. The UITableViewCell remembers what url/path it was trying to fetch, and ignores notifications that relate to other images. It unobserved that path in dealloc/prepareForReuse.
This was getting to long to be a comment so I'll make it an answer.
The reason why it's crashing has to do with the fact that UICollectionViewCells get recycled and deallocated. ARC is has put a [cvcell retain] in the wrong place. So, there are a few options:
One way to fix this is to just not create a NSOperation from a UICollectionViewCell.
Force the users to stay on the UICollectionViewController / UICollectionView so that it stays in memory.
Keep a property / pointer to the UICollectionViewController / UICollectionView so that it stays in memory even when the user has left it. (Make sure you retain it as strong or retain).
NOTE: All of these solutions do the same thing, force ARC to put the retain call somewhere else or to remove it entirely.
Cells get reused and reassigned frequently without your control so you should avoid assigning pending requests or operations to them.
Instead handle operations in your collection view data source (the view controller), and keep track of the operations not per cell but per indexPath's in a dictionary.
Even better keep this as a good experience and use something trusted and tested such as SDwebImage.
When to copy a block? The document says, blocks are "deleted when execution returns from the scope in which they are defined.This means you can’t return them directly from a function. If blocks could only be used while their defining scope was still on the call stack, they wouldn’t be nearly as useful as they actually are"
So, here is code which I tried, hoping the block will be deleted once execution is completed in viewDidLoad.
MyReaderController.h
#interface MyReaderController : UIViewController
{
myBlockVar aBlockVar;
}
-(myBlockVar) getABlock;
#end
MyReaderController.m
#implementation MyReaderController
- (void)viewDidLoad
{
[super viewDidLoad];
aBlockVar=[self getABlock];
NSLog(#"Block Result = %f",aBlockVar(1));
}
-(void) viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
NSLog(#"Block Exists even after the execution completes=%# %f",aBlockVar,aBlockVar(5));
}
-(myBlockVar) getABlock{
return ^(int var){return 4.0f;};
}
#end
So, does this code require viewDidLoad to be changed to as coded below, if not then when should I use it.
- (void) viewDidLoad{
[super viewDidLoad];
aBlockVar=Block_copy([self getABlock]);
NSLog(#"Block Result = %f",aBlockVar(1));
}
PART 2
Later on I tried with this following code, hoping now it will return aBlockVar as nil obj in
viewDidDisappear.
- (void)viewDidLoad
{
[super viewDidLoad];
Blocker *blocker=[[Blocker alloc] init];
myBlockVar myVar=[blocker getABlock];
aBlockVar=myVar;
NSLog(#"Block Result = %f",aBlockVar(1));
blocker=nil;
myVar=nil;
}
Blocker.m
#import "Blocker.h"
#implementation Blocker
-(myBlockVar) getABlock{
return ^(int var){return 4.0f;};
}
#end
Are you using ARC? If so, you don't need to use Block_copy or Block_release.
If you are, then you are correct with your revised code, as Block_copy takes it off the stack and into the heap where it is has an effective retain count of 1. You would also need to call Block_release where appropriate, when finally finished with the block, to bring its balance the copy, effectively bringing the retain count back to 0.
use #property (nonatomic, copy) (int)(^myBlock)(void);
let the system do all right memory management for you!
initialize:
self.myBlock = ^int(void){
return 4.0;
};
if you want to destroy your block somewhere do self.myBlock = NULL;
An addendum to the existing answers:
Even if you're using ARC, there are certain situations where you still need Block_copy.
For example, extracting a block argument from an NSInvocation and using it after the function returns.
- (void)interceptInvocation:(NSInvocation *)call {
BlockType block;
[call getArgument:&block atIndex:2]; // ARC cannot see this happen
block = (__bridge BlockType)Block_copy((__bridge void *)block);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block();
});
}
Without the copy, the block will have been deallocated by the time it is run.
It appears a matching Block_release is not necessary, as when I added one it crashed due to too many releases.
with arc never, without arc:
: when you have a STACK block and want to keep it as a HEAP block (e.g. when you have a block in a function and want it to live after you exited the function!)
You need to block_copy it then.
also you need retain/release it as you would a NSString so (using block_copy/block_release)
I am developing an iOS app with the iOS 5 SDK, Automatic Reference Counting is enabled. But I have a specific object that is being created in large numbers and must be released after a second because otherwise the device will become very slow. It looks like they are not released, as the device is very slow. Is there a way to manually release an object when ARC is enabled?
EDIT: My code, this is called 200 times a second to generate sparkles. They fade out after 0.8 seconds so they are useless after then.
int xanimationdiff = arc4random() % 30;
int yanimationdiff = arc4random() % 30;
if (arc4random()%2 == 0) {
xanimationdiff = xanimationdiff * -1;
}
if (arc4random()%2 == 0) {
yanimationdiff = yanimationdiff * -1;
}
Sparkle *newSparkle = [[Sparkle alloc] initWithFrame:CGRectMake(20 + arc4random() % 280, 20, 10, 10)];
//[newSparkle setTransform:CGAffineTransformMakeRotation(arc4random() * (M_PI * 360 / 180))]; //Rotatie instellen (was niet mooi, net sneeuw)
[self.view addSubview:newSparkle];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.8];
[newSparkle setFrame:CGRectMake(newSparkle.frame.origin.x - xanimationdiff, newSparkle.frame.origin.y - yanimationdiff, newSparkle.frame.size.width, newSparkle.frame.size.height)];
newSparkle.alpha = 0;
[UIView commitAnimations];
The sparkle object code:
#import "Sparkle.h"
#implementation Sparkle
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"sparkle.png"]]];
}
return self;
}
#end
Object* myObject = [[Object alloc] init];
myObject = nil; // poof...
EDIT: You cannot directly control when an object is released BUT you can indirectly cause it to happen. How? Remember what ARC does EXACTLY. Unlike human coding convention, ARC parses your code and inserts release statements AS SOON AS OBJECTS CAN be released. This frees up the memory for new allocations straight away, which is awesome/necessary.
Meaning, setting an object to nil, or simply allowing a variable to go out of scope ... something that CAUSES A 0 RETAIN COUNT forces ARC to place its release calls there.
It must ... because it would leak otherwise.
Just surround the section of code that is going to be called 200 times with an #autoreleasepool { ... } statement. This will cause the memory to be deallocated immediately as opposed to waiting for the control to go all the way back up the event chain to the top level autorelease pool.
I found the answer, it was actually really stupid. I didn't remove the sparkles from the superview. Now I remove them after 0.8 seconds with a timer and it performs great again :)
With ARC you cannot call dealloc, release, or retain, although you can still retain and release CoreFoundation objects (NB: you can implement dealloc methods for your own custom subclasses, but you can't call super dealloc). So the simple answer is 'no', you unfortunately cannot manually release an object when using ARC.
I'd double check you're sure they're not being released, because in theory if you no longer reference an object it should be released. What do you do with these objects once you create them? You simply create them then immediately destroy them?
Perhaps you could post the code you're using / the property declarations - are these weak or strong referenced objects?
If you are an advanced user of drawRect, you will know that of course drawRect will not actually run until "all processing is finished."
setNeedsDisplay flags a view as invalidated and the OS, and basically waits until all processing is done. This can be infuriating in the common situation where you want to have:
a view controller 1
starts some function 2
which incrementally 3
creates a more and more complicated artwork and 4
at each step, you setNeedsDisplay (wrong!) 5
until all the work is done 6
Of course, when you do the above 1-6, all that happens is that drawRect is run once only after step 6.
Your goal is for the view to be refreshed at point 5. What to do?
If I understand your question correctly, there is a simple solution to this. During your long-running routine you need to tell the current runloop to process for a single iteration (or more, of the runloop) at certain points in your own processing. e.g, when you want to update the display. Any views with dirty update regions will have their drawRect: methods called when you run the runloop.
To tell the current runloop to process for one iteration (and then return to you...):
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
Here's an example of an (inefficient) long running routine with a corresponding drawRect - each in the context of a custom UIView:
- (void) longRunningRoutine:(id)sender
{
srand( time( NULL ) );
CGFloat x = 0;
CGFloat y = 0;
[_path moveToPoint: CGPointMake(0, 0)];
for ( int j = 0 ; j < 1000 ; j++ )
{
x = 0;
y = (CGFloat)(rand() % (int)self.bounds.size.height);
[_path addLineToPoint: CGPointMake( x, y)];
y = 0;
x = (CGFloat)(rand() % (int)self.bounds.size.width);
[_path addLineToPoint: CGPointMake( x, y)];
x = self.bounds.size.width;
y = (CGFloat)(rand() % (int)self.bounds.size.height);
[_path addLineToPoint: CGPointMake( x, y)];
y = self.bounds.size.height;
x = (CGFloat)(rand() % (int)self.bounds.size.width);
[_path addLineToPoint: CGPointMake( x, y)];
[self setNeedsDisplay];
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
}
[_path removeAllPoints];
}
- (void) drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor( ctx, [UIColor blueColor].CGColor );
CGContextFillRect( ctx, rect);
CGContextSetStrokeColorWithColor( ctx, [UIColor whiteColor].CGColor );
[_path stroke];
}
And here is a fully working sample demonstrating this technique.
With some tweaking you can probably adjust this to make the rest of the UI (i.e. user-input) responsive as well.
Update (caveat for using this technique)
I just want to say that I agree with much of the feedback from others here saying this solution (calling runMode: to force a call to drawRect:) isn't necessarily a great idea. I've answered this question with what I feel is a factual "here's how" answer to the stated question, and I am not intending to promote this as "correct" architecture. Also, I'm not saying there might not be other (better?) ways to achieve the same effect - certainly there may be other approaches that I wasn't aware of.
Update (response to the Joe's sample code and performance question)
The performance slowdown you're seeing is the overhead of running the runloop on each iteration of your drawing code, which includes rendering the layer to the screen as well as all of the other processing the runloop does such as input gathering and processing.
One option might be to invoke the runloop less frequently.
Another option might be to optimize your drawing code. As it stands (and I don't know if this is your actual app, or just your sample...) there are a handful of things you could do to make it faster. The first thing I would do is move all the UIGraphicsGet/Save/Restore code outside the loop.
From an architectural standpoint however, I would highly recommend considering some of the other approaches mentioned here. I see no reason why you can't structure your drawing to happen on a background thread (algorithm unchanged), and use a timer or other mechanism to signal the main thread to update it's UI on some frequency until the drawing is complete. I think most of the folks who've participated in the discussion would agree that this would be the "correct" approach.
Updates to the user interface happen at the end of the current pass through the run loop. These updates are performed on the main thread, so anything that runs for a long time in the main thread (lengthy calculations, etc.) will prevent the interface updates from being started. Additionally, anything that runs for a while on the main thread will also cause your touch handling to be unresponsive.
This means that there is no way to "force" a UI refresh to occur from some other point in a process running on the main thread. The previous statement is not entirely correct, as Tom's answer shows. You can allow the run loop to come to completion in the middle of operations performed on the main thread. However, this still may reduce the responsiveness of your application.
In general, it is recommended that you move anything that takes a while to perform to a background thread so that the user interface can remain responsive. However, any updates you wish to perform to the UI need to be done back on the main thread.
Perhaps the easiest way to do this under Snow Leopard and iOS 4.0+ is to use blocks, like in the following rudimentary sample:
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// Do some work
dispatch_async(main_queue, ^{
// Update the UI
});
});
The Do some work part of the above could be a lengthy calculation, or an operation that loops over multiple values. In this example, the UI is only updated at the end of the operation, but if you wanted continuous progress tracking in your UI, you could place the dispatch to the main queue where ever you needed a UI update to be performed.
For older OS versions, you can break off a background thread manually or through an NSOperation. For manual background threading, you can use
[NSThread detachNewThreadSelector:#selector(doWork) toTarget:self withObject:nil];
or
[self performSelectorInBackground:#selector(doWork) withObject:nil];
and then to update the UI you can use
[self performSelectorOnMainThread:#selector(updateProgress) withObject:nil waitUntilDone:NO];
Note that I've found the NO argument in the previous method to be needed to get constant UI updates while dealing with a continuous progress bar.
This sample application I created for my class illustrates how to use both NSOperations and queues for performing background work and then updating the UI when done. Also, my Molecules application uses background threads for processing new structures, with a status bar that is updated as this progresses. You can download the source code to see how I achieved this.
You can do this repeatedly in a loop and it'll work fine, no threads, no messing with the runloop, etc.
[CATransaction begin];
// modify view or views
[view setNeedsDisplay];
[CATransaction commit];
If there is an implicit transaction already in place prior to the loop you need to commit that with [CATransaction commit] before this will work.
In order to get drawRect called the soonest (which is not necessarily immediately, as the OS may still wait until, for instance, the next hardware display refresh, etc.), an app should idle it's UI run loop as soon as possible, by exiting any and all methods in the UI thread, and for a non-zero amount of time.
You can either do this in the main thread by chopping any processing that takes more than an animation frame time into shorter chunks and scheduling continuing work only after a short delay (so drawRect might run in the gaps), or by doing the processing in a background thread, with a periodic call to performSelectorOnMainThread to do a setNeedsDisplay at some reasonable animation frame rate.
A non-OpenGL method to update the display near immediately (which means at the very next hardware display refresh or three) is by swapping visible CALayer contents with an image or CGBitmap that you have drawn into. An app can do Quartz drawing into a Core Graphics bitmap at pretty much at any time.
New added answer:
Please see Brad Larson's comments below and Christopher Lloyd's comment on another answer here as the hint leading towards this solution.
[ CATransaction flush ];
will cause drawRect to be called on views on which a setNeedsDisplay request has been done, even if the flush is done from inside a method that is blocking the UI run loop.
Note that, when blocking the UI thread, a Core Animation flush is required to update changing CALayer contents as well. So, for animating graphic content to show progress, these may both end up being forms of the same thing.
New added note to new added answer above:
Do not flush faster than your drawRect or animation drawing can complete, as this might queue up flushes, causing weird animation effects.
Without questioning the wisdom of this (which you ought to do), you can do:
[myView setNeedsDisplay];
[[myView layer] displayIfNeeded];
-setNeedsDisplay will mark the view as needing to be redrawn.
-displayIfNeeded will force the view's backing layer to redraw, but only if it has been marked as needing to be displayed.
I will emphasize, however, that your question is indicative of an architecture that could use some re-working. In all but exceptionally rare cases, you should never need to or want to force a view to redraw immediately. UIKit with not built with that use-case in mind, and if it works, consider yourself lucky.
Have you tried doing the heavy processing on a secondary thread and calling back to the main thread to schedule view updates? NSOperationQueue makes this sort of thing pretty easy.
Sample code that takes an array of NSURLs as input and asynchronously downloads them all, notifying the main thread as each of them is finished and saved.
- (void)fetchImageWithURLs:(NSArray *)urlArray {
[self.retriveAvatarQueue cancelAllOperations];
self.retriveAvatarQueue = nil;
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
for (NSUInteger i=0; i<[urlArray count]; i++) {
NSURL *url = [urlArray objectAtIndex:i];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:#selector(cacheImageWithIndex:andURL:)]];
[inv setTarget:self];
[inv setSelector:#selector(cacheImageWithIndex:andURL:)];
[inv setArgument:&i atIndex:2];
[inv setArgument:&url atIndex:3];
NSInvocationOperation *invOp = [[NSInvocationOperation alloc] initWithInvocation:inv];
[opQueue addOperation:invOp];
[invOp release];
}
self.retriveAvatarQueue = opQueue;
[opQueue release];
}
- (void)cacheImageWithIndex:(NSUInteger)index andURL:(NSURL *)url {
NSData *imageData = [NSData dataWithContentsOfURL:url];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *filePath = PATH_FOR_IMG_AT_INDEX(index);
NSError *error = nil;
// Save the file
if (![fileManager createFileAtPath:filePath contents:imageData attributes:nil]) {
DLog(#"Error saving file at %#", filePath);
}
// Notifiy the main thread that our file is saved.
[self performSelectorOnMainThread:#selector(imageLoadedAtPath:) withObject:filePath waitUntilDone:NO];
}
I think, the most complete answer comes from the Jeffrey Sambell's blog post 'Asynchronous Operations in iOS with Grand Central Dispatch' and it worked for me!
It's basically the same solution as proposed by Brad above but fully explained in terms of OSX/IOS concurrency model.
The dispatch_get_current_queue function will return the current queue
from which the block is dispatched and the dispatch_get_main_queue
function will return the main queue where your UI is running.
The dispatch_get_main_queue function is very useful for updating the
iOS app’s UI as UIKit methods are not thread safe (with a few
exceptions) so any calls you make to update UI elements must always be
done from the main queue.
A typical GCD call would look something like this:
// Doing something on the main thread
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
// Perform long running process
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
});
});
// Continue doing other stuff on the
// main thread while process is running.
And here goes my working example (iOS 6+). It displays frames of a stored video using the AVAssetReader class:
//...prepare the AVAssetReader* asset_reader earlier and start reading frames now:
[asset_reader startReading];
dispatch_queue_t readerQueue = dispatch_queue_create("Reader Queue", NULL);
dispatch_async(readerQueue, ^{
CMSampleBufferRef buffer;
while ( [asset_reader status]==AVAssetReaderStatusReading )
{
buffer = [asset_reader_output copyNextSampleBuffer];
if (buffer!=nil)
{
//The point is here: to use the main queue for actual UI operations
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI using the AVCaptureVideoDataOutputSampleBufferDelegate style function
[self captureOutput:nil didOutputSampleBuffer:buffer fromConnection:nil];
CFRelease (buffer);
});
}
}
});
The first part of this sample may be found here in Damian's answer.
I'd like to offer a clean solution to the given problem.
I agree with other posters that in an ideal situation all the heavy lifting should be done in a background thread, however there are times when this simply isn't possible because the time consuming part requires lots of accessing to non thread-safe methods such as those offered by UIKit. In my case, initialising my UI is time consuming and there's nothing I can run in the background, so my best option is to update a progress bar during the init.
However, once we think in terms of the ideal GCD approach, the solution is actually a simple. We do all the work in a background thread, dividing it into chucks that are called synchronously on the main thread. The run loop will be run for each chuck, updating the UI and any progress bars etc.
- (void)myInit
{
// Start the work in a background thread.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// Back to the main thread for a chunk of code
dispatch_sync(dispatch_get_main_queue(), ^{
...
// Update progress bar
self.progressIndicator.progress = ...:
});
// Next chunk
dispatch_sync(dispatch_get_main_queue(), ^{
...
// Update progress bar
self.progressIndicator.progress = ...:
});
...
});
}
Of course, this is essentially the same as Brad's technique, but his answer doesn't quite address the issue at hand - that of running a lot of non thread safe code while updating the UI periodically.
Joe -- if you are willing to set it up so that your lengthy processing all happens inside of drawRect, you can make it work. I just wrote a test project. It works. See code below.
LengthyComputationTestAppDelegate.h:
#import <UIKit/UIKit.h>
#interface LengthyComputationTestAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#end
LengthComputationTestAppDelegate.m:
#import "LengthyComputationTestAppDelegate.h"
#import "Incrementer.h"
#import "IncrementerProgressView.h"
#implementation LengthyComputationTestAppDelegate
#synthesize window;
#pragma mark -
#pragma mark Application lifecycle
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
IncrementerProgressView *ipv = [[IncrementerProgressView alloc]initWithFrame:self.window.bounds];
[self.window addSubview:ipv];
[ipv release];
[self.window makeKeyAndVisible];
return YES;
}
Incrementer.h:
#import <Foundation/Foundation.h>
//singleton object
#interface Incrementer : NSObject {
NSUInteger theInteger_;
}
#property (nonatomic) NSUInteger theInteger;
+(Incrementer *) sharedIncrementer;
-(NSUInteger) incrementForTimeInterval: (NSTimeInterval) timeInterval;
-(BOOL) finishedIncrementing;
incrementer.m:
#import "Incrementer.h"
#implementation Incrementer
#synthesize theInteger = theInteger_;
static Incrementer *inc = nil;
-(void) increment {
theInteger_++;
}
-(BOOL) finishedIncrementing {
return (theInteger_>=100000000);
}
-(NSUInteger) incrementForTimeInterval: (NSTimeInterval) timeInterval {
NSTimeInterval negativeTimeInterval = -1*timeInterval;
NSDate *startDate = [NSDate date];
while (!([self finishedIncrementing]) && [startDate timeIntervalSinceNow] > negativeTimeInterval)
[self increment];
return self.theInteger;
}
-(id) init {
if (self = [super init]) {
self.theInteger = 0;
}
return self;
}
#pragma mark --
#pragma mark singleton object methods
+ (Incrementer *) sharedIncrementer {
#synchronized(self) {
if (inc == nil) {
inc = [[Incrementer alloc]init];
}
}
return inc;
}
+ (id)allocWithZone:(NSZone *)zone {
#synchronized(self) {
if (inc == nil) {
inc = [super allocWithZone:zone];
return inc; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; // denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
#end
IncrementerProgressView.m:
#import "IncrementerProgressView.h"
#implementation IncrementerProgressView
#synthesize progressLabel = progressLabel_;
#synthesize nextUpdateTimer = nextUpdateTimer_;
-(id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame: frame]) {
progressLabel_ = [[UILabel alloc]initWithFrame:CGRectMake(20, 40, 300, 30)];
progressLabel_.font = [UIFont systemFontOfSize:26];
progressLabel_.adjustsFontSizeToFitWidth = YES;
progressLabel_.textColor = [UIColor blackColor];
[self addSubview:progressLabel_];
}
return self;
}
-(void) drawRect:(CGRect)rect {
[self.nextUpdateTimer invalidate];
Incrementer *shared = [Incrementer sharedIncrementer];
NSUInteger progress = [shared incrementForTimeInterval: 0.1];
self.progressLabel.text = [NSString stringWithFormat:#"Increments performed: %d", progress];
if (![shared finishedIncrementing])
self.nextUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:0. target:self selector:(#selector(setNeedsDisplay)) userInfo:nil repeats:NO];
}
- (void)dealloc {
[super dealloc];
}
#end
Regarding the original issue:
In a word, you can (A) background the large painting, and call to the foreground for UI updates or (B) arguably controversially there are four 'immediate' methods suggested that do not use a background process. For the result of what works, run the demo program. It has #defines for all five methods.
Alternately per Tom Swift
Tom Swift has explained the amazing idea of quite simply manipulating the run loop. Here's how you trigger the run loop:
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
This is a truly amazing piece of engineering. Of course one should be extremely careful when manipulating the run loop and as many pointed out this approach is strictly for experts.
However, a bizarre problem arises ...
Even though a number of the methods work, they don't actually "work" because there is a bizarre progressive-slow-down artifact you will see clearly in the demo.
Scroll to the 'answer' I pasted in below, showing the console output - you can see how it progressively slows.
Here's the new SO question:
Mysterious "progressive slowing" problem in run loop / drawRect
Here is V2 of the demo app...
http://www.fileswap.com/dl/p8lU3gAi/stepwiseDrawingV2.zip.html
You will see it tests all five methods,
#ifdef TOMSWIFTMETHOD
[self setNeedsDisplay];
[[NSRunLoop currentRunLoop]
runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
#endif
#ifdef HOTPAW
[self setNeedsDisplay];
[CATransaction flush];
#endif
#ifdef LLOYDMETHOD
[CATransaction begin];
[self setNeedsDisplay];
[CATransaction commit];
#endif
#ifdef DDLONG
[self setNeedsDisplay];
[[self layer] displayIfNeeded];
#endif
#ifdef BACKGROUNDMETHOD
// here, the painting is being done in the bg, we have been
// called here in the foreground to inval
[self setNeedsDisplay];
#endif
You can see for yourself which methods work and which do not.
you can see the bizarre "progressive-slow-down". Why does it happen?
you can see with the controversial TOMSWIFT method, there is actually no problem at all with responsiveness. tap for response at any time (but still the bizarre "progressive-slow-down" problem)
So the overwhelming thing is this weird "progressive-slow-down": on each iteration, for unknown reasons, the time taken for a loop decreases. Note that this applies to both doing it "properly" (background look) or using one of the 'immediate' methods.
Practical solutions?
For anyone reading in the future, if you are actually unable to get this to work in production code because of the "mystery progressive slowdown", Felz and Void have each presented astounding solutions in the other specific question.