I have a problem with an existing application for iPad. It is quite sophisticated application written partly native (downloading, local caching and so on), partly using Sencha Touch for UI (without PhoneGap).
Application runs fine on both iPad 1 and iPad 2 under iOS4. But with public release of iOS 5 aproblem appears for iPad 1 users. Application crashes after several seconds of work. Some guys write that instead of 78 there are only 30 megs of memory available for iPad 1 under iOS 5. And the other blackbox is the UIWebView. Nobody knows its internal limitations.
I have a few ideas how to optimize JavaScript and HTML aspect. For example I've optimized structure and reduce memory allocations in scripts as I could. I've also replaced IMG tags with DIV + background-image. But crashes remains.
Below is the insane solution to the problem.
Solution
There are memory warnings but I can release neither something native nor anything inside JavaScript, I thought. So I decided to do ridiculous thing - to allocate the dummy array of several megabytes and release it on memory warning. This is nuts but it works for me: I could allocate up to 100 megs of RAM on start with simple malloc()
// Member variable
void* dummy;
// - (void)init
dummy = malloc(100000000L);
and free it with free() when system asks.
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if (dummy) {
free(dummy);
dummy = nil;
}
}
This time app works longer. So far so good... Next step - make possible repeated memory freeing.
// Member variable
NSMutableArray* _memoryWorkaround;
// - (void)init
int chunkCount = 100;
int chunkSize = 1L * 1024L * 1024L; // 1 megabyte
_memoryWorkaround = [[NSMutableArray alloc] initWithCapacity:chunkCount];
for (int i = 0; i < chunkCount; i++)
[_memoryWorkaround addObject:[NSValue valueWithPointer:malloc(chunkSize)]];
Here it is - 100 chunks of memory by 1 megabyte allocated. This parameters is subject to revise.
Now we can free up as much memory as needed:
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if ([_memoryWorkaround count]) {
NSValue* val = [_memoryWorkaround objectAtIndex:0];
free([val pointerValue]);
[_memoryWorkaround removeObject:val];
}
}
On exit free remaining:
- (void)dealloc {
if ([_memoryWorkaround count]) {
for (NSValue* val in _memoryWorkaround)
free([val pointerValue]);
[_memoryWorkaround removeAllObjects];
}
[_memoryWorkaround release];
[super dealloc];
}
The last thing to do - is to fill buffer back to chunkCount with NSTimer one by one block.
Seems crazy I know. Is better solution exists?
Related
I'm trying to simulate the behaviour of a component under heavy memory load.
The main idea is to allocate a bunch of memory, keep it resident, then work with my component, however no matter what I do, the memory I allocate on purpose seem do disappear somehow.
After a first test using a strong NSArray of NSData chunks (1Mb each), I tried with a rougher but lower level approach (to prevent some ARC magic to happen):
// somewhere after the includes
static char *foo[MAX_CHUNKS];
[...]
// this function is actually called, I checked :)
- (void) allocateMemoryChunks:(int) chunks ofSize:(long) bytes {
for(int i = 0; i < chunks; ++i) {
foo[i] = (char *) malloc(bytes);
}
}
No matter what I set for chunks and bytes, as long as the memory can be allocated, it 'disappears' from the memory count in Xcode and leaves me with the usual meager 2Mb consumed by the application (I'm talking of allocations in the range of 400 chunks of 1024 * 1024 bytes, or 1 chunk of 400 * 1024 * 1024 bytes, for what's worth...).
The memory is clearly outside the reach of ARC (I'm using plain C mallocs!) yet, I can't see it (note that the same happened when I used NSData and NSArray to hold the data).
Where does that memory go? What am I forgetting?
Thank you!
Addendum
For the records, the ARC version of the allocateMemoryChunks is:
- (void) allocateMemoryChunks2:(int) nchunks ofSize:(long) bytes {
self.allocated = [NSMutableArray arrayWithCapacity:nchunks];
for(int i = 0; i < nchunks; ++i) {
char *tmpraw = (char *) malloc(bytes);
NSData *tmp = [NSData dataWithBytes:tmpraw length:bytes];
[self.allocated addObject:tmp];
free(tmpraw);
}
}
with allocated declared as:
#property (nonatomic, strong) NSMutableArray *allocated;
So I'm relative new to objC programming. But not to C. In a more complicated app I think I have a memory leaks. I've programmed this just for make some tests. The app is very simple: it store in a MutableArray a series of integer that rappresent timers scheduled. The app has one NSTimer in the current runloop that check every second if it is the right time to ring comparing a counter with the right element of the MutableArray. Everything works, but memory in debug panel grow up, grow up, grow up…
I've try some variants but something still missing for me about ARC. I simply don't understand, since ARC is NOT a garbage collector, why memory grow and what I do wrong.
Here is the code:
-(id)initWithLabel:(UILabel *)label {
self = [super init];
self.list = [[mtAllarmList alloc]init];
self.label = label;
return self;
}
My class init function. I pass a label reference (weak beacause it is own by viewcontroller) to my class. I also allocate and init the class mtAllarmList that contain the MutableArray and other information (in the original app, file to play, volumes, eccetera).
-(void)ClockRun {
NSMethodSignature * signature = [mtClockController instanceMethodSignatureForSelector:#selector(check)];
NSInvocation * selector = [NSInvocation invocationWithMethodSignature: signature];
[selector setTarget:self];
[selector setSelector:#selector(check)];
[[NSRunLoop currentRunLoop] addTimer: self.time = [NSTimer scheduledTimerWithTimeInterval:1
invocation:selector
repeats:YES]
forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runUntilDate:[[NSDate alloc]initWithTimeIntervalSinceNow: 30]];
}
ClockRun: is the method the app call to start everything. It simply start the timer that fires every second to check:
-(void)check {
self.counter++;
int i = [self.list check:self.counter];
if(i == 1) {
[self writeAllarmToLabel:self.label isPlayingAllarmNumber:self.counter];
}
else if (i == 2) {
[self writeAllarmToLabel:self.label theString: #"Stop"];
[self.time invalidate];
self.counter = 0;
}
else {
[self writeAllarmToLabel:self.label theString:[[NSString alloc]initWithFormat:#"controllo, %d", self.counter]];
}
NSLog(#"controllo %d", self.counter);
}
Check: simply reacts to the return value of [list check: int] methods of mtAllarmList. It returns 1 if timer must ring, 0 if not, and 2 if the sequence ends. In that case self.counter will be set to 0 and the NSTimer will be invalidate.
-(id)init {
self = [super init];
self.arrayOfAllarms = [[NSMutableArray alloc]initWithCapacity:0];
int i;
for(i=1;i<=30;++i) {
[self.arrayOfAllarms addObject: [[NSNumber alloc]initWithInt:i*1]];
}
for(NSNumber * elemento in self.arrayOfAllarms)
NSLog(#"ho creato un array con elemento %d", [elemento intValue]);
return self;
}
In mtAllarmList init method simulates the costruction an array (I've try a variety of patterns) and log all the elements.
-(int)check:(int)second {
int maxValue = [[self.arrayOfAllarms lastObject] intValue];
if(maxValue == second){
self.index = 0;
return 2;
} else {
if ([[self.arrayOfAllarms objectAtIndex:self.index] intValue] == second) {
self.index++;
return 1;
} else {
return 0;
}
}
}
Check methods instead is very elementary and I don't think needs explanations.
So, why this simple very stupid app leaks?
Since you're doing this on the main run loop, you can (and should) simplify the ClockRun method:
- (void)ClockRun {
self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(check) userInfo:nil repeats:YES];
}
That NSInvocation code was unnecessary and the NSRunLoop code could only introduce problems.
Having said that, this is unlikely to be the source of your memory consumption. And nothing else in the provided code snippets looks like an obvious memory problem. If you're 100% confident that the timer is getting invalidated, then the timer is not the problem. I wonder about the object graph between the view controller at this mtClockController. Or perhaps some circular reference in view controllers (e.g. pushing from A to B and to A again). It's hard to say on the basis of what's been provided thus far.
Sadly, there's not much else we can suggest other than the routine diagnostics. First, I'd run the the app through the static analyzer (by pressing shift+command+B in Xcode, or choosing "Profile" from the Xcode "Product" menu).
Second, you should run your app through Leaks and Allocations tools to identify the what precisely is leaking on each iteration. Do you have extra instances of the view controllers? Or just the mtClockController?
Until you identify what's not being deallocated, it's hard to remedy it. And Instruments is the best tool for identifying what's not getting released. In WWDC 2012 video iOS App Performance: Memory the demonstration sections of the video give pragmatic demonstrations of using Instruments (as well as a wealth of good background info on memory management).
Third, when I've got a situation where I'm not sure if things are getting deallocated when they should, I sometimes include dealloc methods that tell me when the object is deallocated, e.g.:
- (void)dealloc {
NSLog(#"%s", __PRETTY_FUNCTION__);
}
I'd suggest this not only for your key model objects, but your view controller, too. (Sometimes we agonize over our model objects only to realize that it's the view controller, itself, which is be retained by something else.)
Clearly Instruments is a much richer tool, but this can be used to quickly identify failure to deallocate (and show you what's maintaining the strong references).
I ran you app through Instruments, watching your custom objects, and everything is being deallocated properly. Below, I marked generation A, hit the button, let the timer expire, marked generation B, hit the button again, etc. I did that four times, and I then simulated a memory warning, and did one final generation. Everything looks fine (this is a compilation of six screen snapshots in one, showing the total allocations at each of the six generations):
I inspected your Generations, as well as the Allocations themselves, and none of your objects are in there. Everything is getting released fine. The only things there are internal Cocoa objects associated with UIKit and NSString. Cocoa Touch does all sorts of caching of stuff behind the scenes that we have no control over. The reason I did that final "simulator memory warning" was to give Cocoa a chance to purge what it can (and you'll see that despite what Generations reports, the total allocations fell back down a bit).
Bottom line, your code is fine, and there is nothing to worry about here. In the future, don't worry about incidentally stuff showing up in the generations, but rather focus on (a) your classes; and (b) anything sizable. But neither of those apply here.
In fact, if you restrict Instruments to only record information for your classes with the mt prefix (you do this by stopping a recording of Instruments and tap on the "i" button on the Allocations graph and configure the "Recorded Types"), you'll see the sort of graph/generations that you were expecting:
A couple of observations:
Instead of using the invocation form of scheduledTimerWithInterval, try using the selector form directly, in this case it's a lot simpler and clearer to read.
Since you're call runUntilDate directly, I don't think you're getting any autorelease pools created/drained, which would lead to memory leakage, specifically in the check function. Either don't call runUntilDate and allow the normal run loop processing to handle things (the normal preferred mechanism) or wrap check in an #autoreleasepool block.
I have an image processing application that I am using to test potential speedup of blocks/threads on iOS. The algorithm works fine, uses a fair amount of memory and then dumps it after running.
I made a test suite that runs the algorithm 10 times sequentially and 10 times using my parallel implementation. If I try to run it on a phone it crashes due to memory pressure - it ends up needing about 432MB of memory. However, once the suite is done it all finally gets cleaned up :/
Each individual run is using around 25MB of memory. So I thought the solution would be to reset all of my objects after each run and they would get cleaned up. I basically have 2 processing objects that do all of my work. So after each run I thought setting them to nil would cause them to be recreated and the old versions to be destroyed and that memory freed. However, it had no effect on my memory usage.
Is there something more I need to do to free up the memory in-between calls? I thought Objective-C was now using reference counting, and once I eliminate the only reference - in my viewController - that it would be freed. Any suggestions would be greatly appreciated.
This is the test suite algorithm with my attempted memory-freeing:
- (void)runTestSuite
{
// Sequential
NSLog(#"Sequential Run: ");
for (int i = 0; i < 10; i++) {
self.imageView.image = self.startImage;
self.imageManipulator = nil;
self.objectRecognizer = nil;
for (UIView *view in self.gameView.subviews) {
[view removeFromSuperview];
}
[self processImageInParallel:NO];
}
[self printResults];
[self clearResults];
// Parallel
NSLog(#"Parallel Run: ");
for (int i = 0; i < 10; i++) {
self.imageView.image = self.startImage;
self.imageManipulator = nil;
self.objectRecognizer = nil;
for (UIView *view in self.gameView.subviews) {
[view removeFromSuperview];
}
[self processImageInParallel:YES];
}
[self printResults];
[self clearResults];
}
Other than working on the algorithm to improve memory usage you could give #autoreleasepool a shot. This will free your freeable objects used in between every loop, without the need of the current loop cycle to end.
for (int i = 0; i < 10; i++) {
#autoreleasepool {
}
}
From the documentation:
In many situations, allowing temporary objects to accumulate until the
end of the current event-loop iteration does not result in excessive
overhead; in some situations, however, you may create a large number
of temporary objects that add substantially to memory footprint and
that you want to dispose of more quickly. In these latter cases, you
can create your own autorelease pool block. At the end of the block,
the temporary objects are released, which typically results in their
deallocation thereby reducing the program’s memory footprint
Some source code would help. In absence of that, a general suggestion: wrap the code that is doing the image process in an autoreleasepool (see this Apple document).
This will discard temporary objects as soon as possible, reducing memory spike.
Autorelease pool should be used here
Many programs create temporary objects that are autoreleased. These
objects add to the program’s memory footprint until the end of the
block. In many situations, allowing temporary objects to accumulate
until the end of the current event-loop iteration does not result in
excessive overhead; in some situations, however, you may create a
large number of temporary objects that add substantially to memory
footprint and that you want to dispose of more quickly. In these
latter cases, you can create your own autorelease pool block. At the
end of the block, the temporary objects are released, which typically
results in their deallocation thereby reducing the program’s memory
footprint
- (void)runTestSuite
{
// Sequential
NSLog(#"Sequential Run: ");
for (int i = 0; i < 10; i++) {
#autoreleasepool
{
self.imageView.image = self.startImage;
self.imageManipulator = nil;
self.objectRecognizer = nil;
for (UIView *view in self.gameView.subviews) {
[view removeFromSuperview];
}
[self processImageInParallel:NO];
}
}
[self printResults];
[self clearResults];
// Parallel
NSLog(#"Parallel Run: ");
for (int i = 0; i < 10; i++) {
#autoreleasepool
{
self.imageView.image = self.startImage;
self.imageManipulator = nil;
self.objectRecognizer = nil;
for (UIView *view in self.gameView.subviews) {
[view removeFromSuperview];
}
[self processImageInParallel:YES];
}
}
[self printResults];
[self clearResults];
}
I need to do this : alloc objects until memory warning is called and then release all objects. But I have some problems. How can I do this? I need code example because the problem is that : the code. I have a class that doesn't use ARC. This class has a method which alloc N objects that are saved into an array. I need the memory is filled until didReceiveMemoryWarning is called because this is the only method to "free" RAM memory on iOS. Then, I will release all. I think the cleaner memory apps for the iPhone on the App Store use this trick to "free" the memory.
Thanks in advance
You'll have to fill in the missing details but here is what I have used before. Credit goes to who/where ever I found it. This will work on ARC and non ARC projects. I have found that usually you get 2-3 warnings before you're completely dead. Good luck. Dinner length is how much of a chunk that gets allocated each time. if you want more fine grained memory control change the size.
-(IBAction)startEatingMemory:(id)sender
{
if(self.belly == nil){
self.belly = [NSMutableArray array];
}
self.paused = false;
[self eatMemory];
}
- (IBAction)pauseEat:(id)sender {
self.paused = true;
[[self class]cancelPreviousPerformRequestsWithTarget:self selector:#selector(eatMemory) object:nil];
}
- (IBAction)stopEatingMemory:(id)sender {
[self pauseEat:self];
[self.belly removeAllObjects];
[[self class] cancelPreviousPerformRequestsWithTarget:self selector:#selector(eatMemory) object:nil];
}
-(void)eatMemory
{
unsigned long dinnerLength = 1024 * 1024;
char *dinner = malloc(sizeof(char) * dinnerLength);
for (int i=0; i < dinnerLength; i++)
{
//write to each byte ensure that the memory pages are actually allocated
dinner[i] = '0';
}
NSData *plate = [NSData dataWithBytesNoCopy:dinner length:dinnerLength freeWhenDone:YES];
[self.belly addObject:plate];
[self performSelector:#selector(eatMemory) withObject:nil afterDelay:.1];
}
-(void)didReceiveMemoryWarning
{
[self pauseEat:self];
<#Could release all here#>
[super didReceiveMemoryWarning];
}
I would edit/subclass the class that doesn't use ARC to either use ARC, or add a method to it that releases the N objects.
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?