I have a part in my project, where I have to fill many arrays and dictionaries with custom classes. During the process the memory usage starts to increase of course. But at the end when I remove items from everywhere using removeAllObjects, the memory usage remains on the same level instead of decreasing to the starting value.
I made a simplified code which does not make sense of course but reproduces the original issue I am getting:
Header:
#interface ViewController : UIViewController
#property (strong, nonatomic) NSMutableArray *imageArray;
#property (strong, nonatomic) NSTimer *timer;
- (IBAction)start:(id)sender;
- (IBAction)stop:(id)sender;
#end
Implementation:
#implementation ViewController
....
- (IBAction)start:(id)sender
{
[self.loadingIndicator startAnimating];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(addImages) userInfo:nil repeats:YES];
}
- (IBAction)stop:(id)sender
{
[self.loadingIndicator stopAnimating];
[self.timer invalidate];
[self.imageArray removeAllObjects];
}
- (void)addImages
{
int i = 0;
while (i < 500)
{
UIImage *image = [UIImage imageNamed:#"test.png"];
[self.imageArray addObject:image];
i++;
}
}
...
#end
Issue:
When I call start, after 1 min the memory usage increases from 3.9 MB to 58 MB. It is fine, but when I call stop, where removelAllObjects is called, it is still remains the same value, 58 MB.
What am I doing wrong? I am using ARC!
Images created with "imageNamed:" are cached, and if nobody else has a strong reference, then the operating system will release them when it feels it is a good idea to do it.
That avoids situations where you call imageNamed: repeatedly and each time a new image is created, which is rather expensive.
Related
Just starting out with Objective-C after spending years in Python.. still trying to wrap my head around some concepts.. I can't seem to figure this out but every time I either increment up or deduct from myCount it retains the older variable in memory. I am using ARC so shouldn't it autorelease? I have a feeling it has to do with self.varOfMyCount = [NSString stringWithFormat:#"%d", myCount];
Header:
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
{
IBOutlet NSMenu *statusMenu;
NSStatusItem *statusItem;
}
- (IBAction)itemOne:(id)sender;
- (IBAction)itemTwo:(id)sender;
- (IBAction)itemThree:(id)sender;
#property (assign) IBOutlet NSWindow *window;
#property (nonatomic, copy) NSString *varOfMyCount;
#end
int myCount = 0;
Implementation:
#import "AppDelegate.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength ];
[statusItem setMenu:statusMenu];
[statusItem setTitle:#"Status"];
[statusItem setHighlightMode:YES];
}
- (IBAction)itemOne:(id)sender {
myCount++;
self.varOfMyCount = [NSString stringWithFormat:#"%d", myCount];
NSLog(#"%#",self.varOfMyCount);
[statusItem setTitle:self.varOfMyCount];
}
- (IBAction)itemTwo:(id)sender {
myCount = myCount-1;
self.varOfMyCount = [NSString stringWithFormat:#"%d", myCount];
NSLog(#"%#",self.varOfMyCount);
[statusItem setTitle:self.varOfMyCount];
}
- (IBAction)itemThree:(id)sender {
NSLog(#"Quit.");
[[NSApplication sharedApplication] terminate:nil];
}
#end
From your images, it doesn't really look like there is a problem. In order for your app to run it does need to use memory. Depending on what you do it will use different amounts.
Using [NSString stringWithFormat:#"%d", myCount]; requires more than you might think because you are asking the system to parse your format string and inject parameters into it. Parsing and scanning a string isn't a trivial operation.
In a number of cases when memory is allocated for a task it isn't released. This is usually the case when it's expensive to create (like the scanning structure, or parts of it) or is likely to be used repeatedly.
You should be worried if the memory grows bigger each time you do the same activity and return to your 'transient' state. Consider running multiple iterations of your button push and, between each push, take a heap shot. If each heap shot (apart from the first and last) are empty (or very close to it) then everything's good. If not, it will show exactly what isn't being released.
there is a nib file and i am creating different window instances with different context, all controls works normally except timer and variables triggered by timers looks shared with all windows. this is how i am create window instances.
#import <Cocoa/Cocoa.h>
#import "MYWindow.h"
#interface AppDelegate : NSObject <NSApplicationDelegate>
{
}
#property (strong) MYWindow *pickerWindow;
--
#import "AppDelegate.h"
#implementation AppDelegate
-(IBAction)newWindow:(id)sender
{
myWindow = [[MYWindow alloc] initWithWindowNibName:#"MYWindowNIB"];
[myWindow showWindow:self];
}
Also i am having problem with ARC, when i open new instance of a window previous one releases immediately even i declare it is strong in property that is why compiling AppDelegate with a flag -fno-objc-arc. otherwise as i said what ever i did windows releases immediately.
XCode 4.6
edit
int i = 0;
-(void)windowDidLoad
{
timerMoveOutNavBar = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(countUP) userInfo:nil repeats:YES];
}
-(void)countUP
{
[text setStringValue:[NSString stringWithFormat:#"%d", i]];
i++;
}
i found the solution, you have to declare variables and all other objects as private.
#private
int i;
What do you mean by "share same variables"? You can have a superclass that all the window controllers inherit from, and they will all have the properties and methods you create in the superclass, if that's what you're talking about.
As far as the windows being released, do you have the "Release When Closed" box checked in IB? If so, uncheck that box.
After Edit
The problem has to do with the way you initialize the int variable "i". By putting it outside a method, you're declaring it as a global variable that all instance will see. You should create an ivar and set its value to zero where you create the timer:
#implementation MyWindowController {
IBOutlet NSTextField *text;
int i;
}
- (void)windowDidLoad {
[super windowDidLoad];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(countUP:) userInfo:nil repeats:YES];
i = 0;
}
-(void)countUP:(NSTimer *) timer {
[text setStringValue:[NSString stringWithFormat:#"%d", i]];
i++;
if (i== 50) [timer invalidate];
}
Notice that I added a colon to the selector name -- with a timer, the timer passes itself as the argument to its selector, so you can reference it like I do to invalidate it. But it's ok to do it the way you did by assigning the timer to an ivar or property (timerMoveOutNavBar in your case).
I have a very simple app. From the main screen I launch a new view ("learn view") that displays an image and a label. on the learn view there is a menu button and a next button. When the user taps the next button, the view updates the image and the label from char * array of C strings. However, when I do this, the instruments allocations shows a forever growing number of allocations that are not reduced when the view is destroyed by clicking the menu button. If I just display the learn view then click menu there is no problem, the allocations go up and then go back down to the prior level, but if I click next updating the label.text, then allocations are made that are not recovered. Instruments reports no leaks. here are relevant code snippets:
LearnVC.h
#interface LearnVC : UIViewController {
IBOutlet UIImageView *imageView;
IBOutlet UILabel *labelView1;
NSInteger page;
}
#property (retain, nonatomic) UIImageView *imageView;
#property (retain, nonatomic) UILabel *labelView1;
#property NSInteger page;
- (IBAction)handleNext;
- (IBAction) gotoMenu;
#end
LearnVC.m
#import ...
char *states[] { "Alabama", "Alaska", "Arizona", ... };
#define maxStates 50
#implementation LearnVC
#synthesize ...
- (void)viewDidLoad
{
NSString *tstring;
self.page = 0;
//test!!!!
tstring = [[NSString alloc] initWithCString:states[self.page] encoding:NSUTF8StringEncoding];
labelView1.text = tstring;
[tstring release];
[super viewDidLoad];
}
- (IBAction) handleNext {
NSString *tstring;
self.page++;
if (self.page > maxStates-1) {
self.page = 0;
}
//test!!!!
tstring = [[NSString alloc] initWithCString:states[self.page] encoding:NSUTF8StringEncoding];
labelView1.text = tstring;
[tstring release];
}
- (void)dealloc
{
[imageView release];
[labelView1 release];
[super dealloc];
}
This seems to occur anytime I update a view without removing (deallocating) it and re-adding it. Is there something with old C arrays that don't copy/release properly. Or some issue with UILabel.text properties that don't release memory? Any help is appreciated.
Thanks beforehand, Neal
I converted it to use NSArray -
I added this to the .h file
NSArray *statesArray;
and
#property (retain, nonatomic) NSArray *statesArray;
then in the .m file
in viewDidLoad
NSArray *objects = [NSArray arrayWithObjects:#"Alabama", #"Montgomery",
#"Alaska", #"Juneau" ,..., nil];
self.statesArray = objects;
//I assume that since I didn't init it, I don't need to release objects.
labelView1.text = [self.statesArray objectAtIndex:self.page];in dealloc
[statesArray release];
Then in my handleNext
labelView1.text = [self.statesArray objectAtIndex:self.page];
I run through the entire array, exit, reload, ect. and the allocations climb the first time through but stop climbing after I've been through the list once. This was not the case with the char * array, there must be something else going on, oh well, I'll just have to shed my old C ways and stick to NS/UI classes.
This seems to have solved it. Now I'll work on the image loads and see if the it works the same.
The initWithCString:encoding: method may make an internal copy of the C string you pass in, but is not guaranteed to free the copy during deallocation. Since the C strings you're passing are constant, and therefore can never be freed, you can instead use initWithBytesNoCopy:length:encoding:freeWhenDone: to avoid creating the extra copies. For example:
NSString *s = [[NSString alloc] initWithBytesNoCopy:someCString
length:strlen(someCString)
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
Also, the system does a lot of caching, it acts like each control may cache the text it has used. I found that if I ran through the series a couple of times, then then allocations stopped rising, as if it had cached all it was going to. Allocations did not go down to the starting point when exiting learn view, but subsequent runs did not add anymore.
I am trying to initialize a ViewController from another. Here is the code written in my first ViewController:
MediasViewController.h
#import "MediasVideosViewController.h"
#interface MediasViewController : UIViewController <UIWebViewDelegate>
{
NSArray* videosList;
MediasVideosViewController *mediasVideosViewController;
}
#property (nonatomic, retain) NSArray* videosList;
#property (nonatomic, retain) MediasVideosViewController* mediasVideosViewController;
MediasViewController.m :
if (self.mediasVideosViewController == nil)
{
MediasVideosViewController* mediasVideos = [[MediasVideosViewController alloc] initWithNibName:#"MediasVideosView" bundle:nil];
self.mediasVideosViewController = mediasVideos;
self.mediasVideosViewController.videosList = self.videosList;
[mediasVideos release];
}
NSDate *start = [NSDate date];
[mediasVideosViewController.view addSubview:nil];
NSLog(#"adding nil to mediasVideosViewController.view took %f seconds", [[NSDate date] timeIntervalSinceDate:start]);
Console result :
adding nil to
mediasVideosViewController.view took
4.261444 seconds
Seriously? More than 4s to add nil to mediasVideosController ? It's swings between 1s and 5s.
But if I remove this line :
self.mediasVideosViewController.videosList = self.videosList;
from MediasViewController.m, I get a really shorter loading time, like :
adding nil to
mediasVideosViewController.view took
0.007613 seconds
It drives me crazy...
Does anyone have a solution?
When you call mediasVideosViewController.view, you are basically calling loadView for the first time. You aren't just adding a subview, you are creating the entire view with that call.
Presumably, when you set the list of videos you are giving your loadView method a lot more work to do. This will result in the behavior you see.
In a nutshell, check out your loadView method for clues.
EDIT: I noticed you were loading the view controller from a NIB file, in that case you want to check viewDidLoad.
So I am trying to set up a basic timer but I am failing miserably. Basically all I want is to start a 60 second timer when the user clicks a button, and to update a label with the time remaining(like a countdown). I created my label and button and connected them in IB. Next I created a IBAction for the button. Now when I tried to update the label based on the timer, my app screws up. Here's my code:
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 1
target: self
selector:#selector(updateLabelDisplay)
userInfo: nil repeats:YES];
I also have an updateLabelDisplay function that determines how many times the timer has ran and then subtracted that number from 60 and displays that number in the countdown label. Can anyone tell me what I am doing wrong?
Ok, well for starters, check this out if you haven't already: Official Apple Docs about Using Timers
Based on your description, you probably want code that looks something like this. I've made some assumptions regarding behavior, but you can suit to taste.
This example assumes that you want to hold on to a reference to the timer so that you could pause it or something. If this is not the case, you could modify the handleTimerTick method so that it takes an NSTimer* as an argument and use this for invalidating the timer once it has expired.
#interface MyController : UIViewController
{
UILabel * theLabel;
#private
NSTimer * countdownTimer;
NSUInteger remainingTicks;
}
#property (nonatomic, retain) IBOutlet UILabel * theLabel;
-(IBAction)doCountdown: (id)sender;
-(void)handleTimerTick;
-(void)updateLabel;
#end
#implementation MyController
#synthesize theLabel;
// { your own lifecycle code here.... }
-(IBAction)doCountdown: (id)sender
{
if (countdownTimer)
return;
remainingTicks = 60;
[self updateLabel];
countdownTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: #selector(handleTimerTick) userInfo: nil repeats: YES];
}
-(void)handleTimerTick
{
remainingTicks--;
[self updateLabel];
if (remainingTicks <= 0) {
[countdownTimer invalidate];
countdownTimer = nil;
}
}
-(void)updateLabel
{
theLabel.text = [[NSNumber numberWithUnsignedInt: remainingTicks] stringValue];
}
#end
It may be a little late to post a second answer to this question but I've been looking for a good place to post my own solution to this problem. In case it is of use to anyone here it is. It fires 8 times but of course this can be customised as you please. The timer deallocates itself when time is up.
I like this approach because it keeps the counter integrated with the timer.
To create an instance call something like:
SpecialKTimer *timer = [[SpecialKTimer alloc] initWithTimeInterval:0.1
andTarget:myObject
andSelector:#selector(methodInMyObjectForTimer)];
Anyway, here are the header and method files.
//Header
#import <Foundation/Foundation.h>
#interface SpecialKTimer : NSObject {
#private
NSTimer *timer;
id target;
SEL selector;
unsigned int counter;
}
- (id)initWithTimeInterval:(NSTimeInterval)seconds
andTarget:(id)t
andSelector:(SEL)s;
- (void)dealloc;
#end
//Implementation
#import "SpecialKTimer.h"
#interface SpecialKTimer()
- (void)resetCounter;
- (void)incrementCounter;
- (void)targetMethod;
#end
#implementation SpecialKTimer
- (id)initWithTimeInterval:(NSTimeInterval)seconds
andTarget:(id)t
andSelector:(SEL)s {
if ( self == [super init] ) {
[self resetCounter];
target = t;
selector = s;
timer = [NSTimer scheduledTimerWithTimeInterval:seconds
target:self
selector:#selector(targetMethod)
userInfo:nil
repeats:YES];
}
return self;
}
- (void)resetCounter {
counter = 0;
}
- (void)incrementCounter {
counter++;
}
- (void)targetMethod {
if ( counter < 8 ) {
IMP methodPointer = [target methodForSelector:selector];
methodPointer(target, selector);
[self incrementCounter];
}
else {
[timer invalidate];
[self release];
}
}
- (void)dealloc {
[super dealloc];
}
#end