I am working on an iOS ARC app (more code available on request), and earlier I was producing and discarding large numbers of images. I thought that I somewhere still had a reference to images, even if I called removeFromSuperview and tried to remove all references to no-longer-used images. I tried Leaks, and Leaks reported roughly linear increase in memory usage over time, starting around 17M.
I went through and replaced all references to images to be instance variables, so they would take a small, finite, and fixed amount of memory, and transformed, instead of getting rid of, the images used for clock hands. This, unfortunately, resulted in slowly increasing memory usage over time, starting at 5M rather than 17M, but otherwise the same problem, simply translated to a better starting point.
A trimmed version of my code is below. Could you tell me what is leaky (or "pseudo-leaky" as Leaks did not indicate a leak) about this and how I can stay close to the memory bounds the code uses when it starts up?
Thanks,
- (void) renderScreen
{
int height = floor([[UIScreen mainScreen] bounds].size.height + .4);
int width = floor([[UIScreen mainScreen] bounds].size.width + .4);
if (height == 2048 || height == 2008 || height == 1024 || height == 1004 || height == 984)
{
if (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
{
_backgroundImage = [UIImage imageNamed:@"Background-Default-Portrait.png"];
}
else
{
_backgroundImage = [UIImage imageNamed:@"Background-Default-Landscape.png"];
}
}
else if (height == 1536 || height == 768 || height == 748 || height == 728)
{
_backgroundImage = [UIImage imageNamed:@"Background-Default-Landscape.png"];
}
else if (height == 1136 || height == 1116 || height == 1096)
{
if (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
{
_backgroundImage = [UIImage imageNamed:@"Background-Default-568.png"];
}
else
{
_backgroundImage = [UIImage imageNamed:@"Background-Default-Rotated-568.png"];
}
}
else if (height == 960 || height == 940 || height == 920 || height == 480 || height == 460 || height == 440)
{
if (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
{
_backgroundImage = [UIImage imageNamed:@"Background-Default.png"];
}
else
{
_backgroundImage = [UIImage imageNamed:@"Background-Default-Rotated.png"];
}
}
else if ((height == 640 || height == 620 || height == 600) && (width == 1136 || width == 1116 || width == 1096))
{
_backgroundImage = [UIImage imageNamed:@"Background-Rotated-568.png"];
}
else if ((height == 640 || height == 620 || height == 600 || height == 320 || height == 300 || height == 280) && (width == 960 || width == 940 || width == 920 || width == 480 || width == 470 || width == 410))
{
if (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
{
_backgroundImage = [UIImage imageNamed:@"Background-Default-Portrait.png"];
}
else
{
_backgroundImage = [UIImage imageNamed:@"Background-Default-Rotated.png"];
}
}
else
{
_backgroundImage = [UIImage imageNamed:@"Background-Default-Portrait.png"];
}
if (!UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
{
int juggle = height;
height = width;
width = juggle;
}
NSUInteger centerX = width * .5;
NSUInteger centerY = height * .5;
_containerRect = CGRectZero;
_containerRect.size = [[UIScreen mainScreen] bounds].size;
self.view.backgroundColor = [UIColor colorWithPatternImage:_backgroundImage];
_backgroundView = [[UIImageView alloc] initWithImage:_backgroundImage];
[self.view addSubview:_backgroundView];
if (_changed)
{
_containerView = [[UIView alloc] initWithFrame:_containerRect];
}
double timeStampSeconds = [[NSDate date] timeIntervalSince1970];
double hours = fmod(timeStampSeconds / 86400, 24);
double minutes = fmod(timeStampSeconds / 3600, 60);
double seconds = fmod(timeStampSeconds, 60);
NSLog(@"Milliseconds: %lf, Hours: %.0f, minutes: %.0f, seconds: %.0f", timeStampSeconds * 1000.0, hours, minutes, seconds);
[_containerView removeFromSuperview];
_containerView = [[UIView alloc] initWithFrame:_containerRect];
_hourHandImage = [UIImage imageNamed:@"hour-hand.png"];
_hourHandView = [[UIImageView alloc] initWithImage:_hourHandImage];
_hourHandImage = [UIImage imageNamed:@"hour-hand.png"];
_hourHandView = [[UIImageView alloc] initWithImage:_hourHandImage];
[self.view addSubview:_hourHandView];
_hourHandView.layer.anchorPoint = CGPointMake(0.5f, 0.5f);
_hourTransform = CGAffineTransformMakeTranslation(centerX, centerY);
_hourTransform = CGAffineTransformTranslate(_hourTransform, -17, -127);
_hourTransform = CGAffineTransformRotate(_hourTransform, hours / 12.0 * M_PI * 2.0);
_minuteTransform = CGAffineTransformMakeTranslation(centerX, centerY);
_minuteTransform = CGAffineTransformTranslate(_minuteTransform, -10, -182);
_minuteTransform = CGAffineTransformRotate(_minuteTransform, minutes / 60.0 * M_PI * 2.0);
_hourHandView.transform = _hourTransform;
_minuteHandImage = [UIImage imageNamed:@"minute-hand.png"];
_minuteHandView = [[UIImageView alloc] initWithImage:_minuteHandImage];
_minuteHandView.transform = _minuteTransform;
[self.view addSubview:_minuteHandView];
_minuteTransform = CGAffineTransformRotate(_minuteTransform, minutes / 60.0 * M_PI * 2.0);
_secondHandImage = [UIImage imageNamed:@"second-hand.png"];
_secondTransform = CGAffineTransformMakeTranslation(centerX, centerY);
_secondTransform = CGAffineTransformTranslate(_secondTransform, -10, -189);
_secondTransform = CGAffineTransformRotate(_secondTransform, seconds / 60.0 * M_PI * 2.0);
_secondHandView = [[UIImageView alloc] initWithImage:_secondHandImage];
_secondHandView.transform = _secondTransform;
[self.view addSubview:_secondHandView];
}
--EDIT--
I have refactored the one method into two methods, one for initial display, and one for incremental updates. It appears that the incremental update method is only called once, as the clock is frozen and the signature logging statement is called exactly once.
I now have, for the update portion:
- (void)render
{
[self renderScreenInitial];
[NSTimer scheduledTimerWithTimeInterval:0.002
target:self
selector:@selector(renderingTimer:)
userInfo:nil
repeats:YES];
}
- (void) renderScreenIncremental
{
int height = floor([[UIScreen mainScreen] bounds].size.height + .4);
int width = floor([[UIScreen mainScreen] bounds].size.width + .4);
if (!UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
{
int juggle = height;
height = width;
width = juggle;
}
double decibelAngle = M_PI / 4 + (_decibel / 60) * M_PI / 2;
double decibelAngleDifference = decibelAngle - _previousDecibelAngle;
_previousDecibelAngle = decibelAngle;
NSLog(@"%lf %lf", _decibel, decibelAngle);
_decibelNeedleView = [[UIImageView alloc] initWithImage:_decibelNeedle];
// CGAffineTransform decibelTransform = CGAffineTransformMakeTranslation(centerX, centerY);
CGAffineTransform decibelTransform = CGAffineTransformMakeRotation(decibelAngleDifference);
decibelTransform = CGAffineTransformTranslate(decibelTransform, sin(decibelAngle - .06) * -298, -cos(decibelAngle - .06) * 298);
_decibelNeedleView.transform = decibelTransform;
[self.view addSubview:_decibelNeedleView];
double timestampSeconds = [[NSDate date] timeIntervalSince1970];
double timestampSecondsDifference = timestampSeconds - _previousTimestampSeconds;
_previousTimestampSeconds = timestampSeconds;
double hoursDifference = fmod(timestampSecondsDifference / 86400, 24);
double minutesDifference = fmod(timestampSecondsDifference / 3600, 60);
double secondsDifference = fmod(timestampSecondsDifference, 60);
NSLog(@"Milliseconds: %lf, Hours: %.0f, minutes: %.0f, seconds: %.0f", timestampSecondsDifference * 1000.0, hoursDifference, minutesDifference, secondsDifference);
_hourHandView.transform = CGAffineTransformMakeRotation(hoursDifference);
_minuteHandView.transform = CGAffineTransformMakeRotation(minutesDifference);
_secondHandView.transform = CGAffineTransformMakeRotation(secondsDifference);
}
-(void)renderingTimer:(NSTimer *)timer {
[self renderScreenIncremental];
}
I appreciate the help. Do you see why it should update the display once and then not continue to keep it updated?
Thanks,