Using instruments I found a strange problem regarding memory leaks. My app has a log mechanism which record events through the app and the whole communication with the server(request-response). Each event object that is been wrote has a timestamp. This timestamp is get as follows:
[NSDate descriptionWithLocale:[NSLocale systemLocale]]
Using instruments, I saw that descriptionWithLocale cause leaks.
Below is the code:
-(id)initEvent:(NSString*)eventStr details:(NSString*)detailsStr{
eventString = [[NSMutableString alloc] initWithString:eventStr];
detailsString = [[NSString alloc] initWithString:detailsStr];
date =[[NSMutableString alloc] init];
NSDate* currentDate = [NSDate date];
NSString *dateDescStr = [currentDate descriptionWithLocale:[NSLocale systemLocale]];
[date appendString:dateDescStr];
return [super init];
Thanks,
Alex.
Are you sure it's descriptionWithLocale that's causing your memory leak? You're not using the property accessor/mutator to set your class properties and you're releasing them in a method other than your dealloc method, which is going to cause issues. At the least, you're violating the basic memory management rules by allocating eventString, detailsString, and date in initEvent (which is then the owner, because you don't use a property accessor/mutator) and releasing them in the eventDictionary method, which is not the owner.
I would start by trying the following (assuming all your properties have the retain attribute):
-(id)initEvent:(NSString*)eventStr details:(NSString*)detailsStr{
this.eventString = [[NSMutableString alloc] initWithString:eventStr];
this.detailsString = [[NSString alloc] initWithString:detailsStr];
this.date =[[NSMutableString alloc] init];
NSDate* currentDate = [NSDate date];
NSString *dateDescStr = [currentDate descriptionWithLocale:[NSLocale systemLocale]];
[date appendString:dateDescStr];
[eventString release];
[detailsString release];
[date release];
return [super init];
}
Also remove your release calls from eventDictionary and add them to the dealloc method, as per the standard memory management pattern.
Give that a try and see if it helps. Let me know if I can clarify anything.
Thanks for your solution. It works great. Indeed not descriptionWithLocale was the cause of the leak, but my own memory rule implementation (was 1 year ago :( ). Strange that instruments was pointing out to that method call.... See how my class looks now:
#implementation EventItem
#synthesize eventString, detailsString, date;
-(id)initEvent:(NSString*)eventStr details:(NSString*)detailsStr{
if((self = [super init])){
self.eventString = [[NSMutableString alloc] initWithString:eventStr];
self.detailsString = [[NSString alloc] initWithString:detailsStr];
self.date =[[NSMutableString alloc] init];
NSDate* currentDate = [NSDate date];
NSString *dateDescStr = [currentDate descriptionWithLocale:[NSLocale systemLocale]];
[date appendString:dateDescStr];
[eventString release];
[detailsString release];
[date release];
}
return self;
}
-(NSMutableDictionary*)eventDictionary{
if(!dictionary)
dictionary = [[NSMutableDictionary alloc] init];
else
return dictionary;
[dictionary setObject:self.date forKey:#"Event"];
[dictionary setObject:self.date forKey:#"Date"];
[dictionary setObject:self.detailsString forKey:#"Details"];
[dictionary setObject:self.eventString forKey:#"EventDescription"];
return [dictionary autorelease];
}
-(void)dealloc{
// NSLog(#"dealloc called in EventItem");
[eventString release];
[detailsString release];
[date release];
[super dealloc];
}
#end
Now, another problem that i have is regarding to some image loaded via "loadWithContentsOfFile:"
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.rowHeight = OC_CELL_HEIGHT;
self.tableView.backgroundColor = [UIColor clearColor];
UIImage* backgroundImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"TableBg" ofType:#"png"]];
UIImageView* background = [[UIImageView alloc] initWithFrame:self.tableView.frame];
background.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
background.image = backgroundImage;
[background sizeToFit];
[backgroundImage release];
[self.tableView setBackgroundView:background];
[background release];
[self.tableView setAutoresizesSubviews:YES];
selectedOCData = nil;
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin;
}
The line :
UIImage* backgroundImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"TableBg" ofType:#"png"]];
leaks 98% and the haviest backtrace is 240 kb. I attached a screenshot with instruments for this. Is there a problem not with the actual call of initWithContentsOfFile, but with the tableView which is not deallocated correctly?(My class is a tableViewController).
Thanks,
Alex.
Related
I have a UITableView for my iPad app which when you tap on a row, will display a popOver with another table in it with one section. The popOver table section header is dependant on values from the fetchedResultsController on the main UITableView controller. When the popover first loads the information is correct. However, after the user taps on a cell in the popover, it goes to another view inside the popover that will allow you to edit data. Once you save, I call a delegate method to refresh the main UITableView while the popover stays shown. Then the view where the user edited the data is dismissed so it goes back to the popover UITableView. At this point the fetchedResultsController that was passed in as slot is now nil. I cannot figure out why it is doing this.
Why is the slot now nil in SlotViewController and how do I prevent it?
#interface
NSFetchedResultsController *fetchedResultsController;
Main UITableView
#synthesize fetchResultsController = _fetchedResultsController;
- (NSFetchedResultsController *)fetchedResultsController {
/*
Set up the fetched results controller.
*/
if (_fetchedResultsController != nil) {
NSLog(#"RETURNING FETCHEDRESULTS");
return _fetchedResultsController;
}
NSLog(#"Should only be called once");
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = nil;
if ([[dataObj.client objectForKey:#"appt_method"] integerValue] == 2) {
entity = [NSEntityDescription entityForName:#"Slots" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
} else {
entity = [NSEntityDescription entityForName:#"Appointments" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
}
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:nil];
//[fetchRequest setIncludesPendingChanges:YES];
// Set the batch size to a suitable number.
//[fetchRequest setFetchBatchSize:20];
// Sort using the date / then time property.
NSSortDescriptor *sortDescriptorDate = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
NSSortDescriptor *sortDescriptorTime = [[NSSortDescriptor alloc] initWithKey:#"start_time" ascending:YES selector:#selector(localizedStandardCompare:)];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptorDate, sortDescriptorTime, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Use the sectionIdentifier property to group into sections.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext] sectionNameKeyPath:#"date" cacheName:nil];
// aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Slots *selectedSlot = [_fetchedResultsController objectAtIndexPath:indexPath];
SlotViewController *slotView = [parentNav.storyboard instantiateViewControllerWithIdentifier:#"SlotViewController"];
[slotView setSlot:selectedSlot];
CGRect rect = [self rectForRowAtIndexPath:indexPath];
rect.size.width = rect.size.width / 3;
UINavigationController *navBar = [[UINavigationController alloc] initWithRootViewController:slotView];
popOver = [[UIPopoverController alloc] initWithContentViewController:navBar];
popOver.delegate = self;
//[popOver setPopoverContentSize:CGSizeMake(320, 460) animated:YES];
[popOver presentPopoverFromRect:rect inView:self permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
SlotViewController / popover UITableView
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSLog(#"slot: %#", _slot.date);
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyyMMdd"];
NSDate *currDate = [dateFormatter dateFromString:_slot.date];
[dateFormatter setDateFormat:#"h:mma"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"GMT"]];
NSDate *midnight = [NSDate dateWithTimeIntervalSince1970:0];
NSDate *startTimeDate = [midnight dateByAddingTimeInterval:[_slot.start_time integerValue] * 60];
NSDate *endTimeDate = [midnight dateByAddingTimeInterval:[_slot.end_time integerValue] * 60];
NSString *startTime = [dateFormatter stringFromDate:startTimeDate];
NSString *endTime = [dateFormatter stringFromDate:endTimeDate];
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
[dateFormatter setDateFormat:#"E, MMM dd, yyyy"];
return [NSString stringWithFormat:#"%# %#-%#",[dateFormatter stringFromDate:currDate], startTime, endTime];
}
I'm able to get it working properly by changing the didSelectRowAtIndexPath code from
Slots *selectedSlot = [_fetchedResultsController objectAtIndexPath:indexPath];
to
Slots *selectedSlot = [Slots disconnectedEntity];
Slots *tmpSlot = [_fetchedResultsController objectAtIndexPath:indexPath];
for (id key in tmpSlot.entity.attributesByName) {
[selectedSlot setValue:[tmpSlot valueForKey:key] forKey:key];
}
disconnectedEntity
is a Core Data managed context that is not tied to an object.
+ (id)disconnectedEntity {
NSManagedObjectContext *context = [[CoreDataHelper sharedInstance] managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Slots" inManagedObjectContext:context];
return [[self alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:nil];
}
However I am wondering why the original code will not work. I am assuming that it is because the fetchedResultsController is passing the reference to selectedSlots so when fetchedResultsController updates, the reference no longer exists?
In my Code showing 2 places memory leakage please see and Help me.
1.FIRST
UIButton *push = (UIButton *)sender;
NSString *string = [NSString stringWithFormat:#"%#",[push currentTitle]];
NSArray *chunks = [[NSArray alloc]initWithArray:[stringcomponentsSeparatedByString:#"-"]];
list = [[NSMutableArray alloc]initWithArray:chunks];
(NSMutableArray *list;)
[chunks release];
2.SECOND
Here is The Source Code First
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSURL *url = [[NSURL alloc]initWithString:#"http://www/absdf.com/myXML.xml"];
self.parser = [[NSXMLParser alloc]initWithContentsOfURL:url];
[parser setDelegate:self];
[parser parse];
[parser release];
[url release];
[pool drain];
while i am excuting this code and with this Instruments Tools on line number : 2 show memory leakage with heaviest backtraces.
So please let know the Reason .?
In addition to the other answers your pool should be released
NSAutoreleasePool *pool = [NSAutoreleasePool new];
//... then
[pool release];
Obviously, list = [[NSMutableArray alloc]initWithArray:chunks]; is never released.
self.parser = [[NSXMLParser alloc]initWithContentsOfURL:url];
alloc/init returns a retained object, and if you your parser property is declared with retain attribute, then you are over retaining it. It should be:
self.parser = [[[NSXMLParser alloc]initWithContentsOfURL:url] autorelease];
P.S. Make sure you are doing proper memory management to your list instance variable. Just to be sure you better use properties.
list should be release somewhere, maybe in dealloc
- (void)dealloc {
[list release];
[super dealloc];
}
parser = [[NSXMLParser alloc]initWithContentsOfURL:url];
or
NSXMLParser *tempParser = [[NSXMLParser alloc]initWithContentsOfURL:url];
self.parser = tempParser;
[tempParser release];
I've been at this issue for two days, and no matter what I do, I cannot get it to stop leaking strings.
The class is a XML parser (using TouchXML), that is designed to run repeatedly throughout the life time of the application. The first time it runs, there are no leaks, everything cleans up perfectly. On the second run, it begins to leaks, almost always where ever strings are.
Some images from Instruments:
http://www.producerstudio.net/1.png
http://www.producerstudio.net/2.png
.h
#import <Foundation/Foundation.h>
#import "TouchXML.h"
#protocol RSSParsingComplete
-(void)parsingFinished;
#end
#interface RSS : NSObject<NSXMLParserDelegate>{
NSArray *rssURLArray;
NSMutableData *xmlData;
NSMutableArray *articles;
NSMutableArray *arrayOfArticles;
int numberOfFeeds;
NSDateFormatter *inputFormatter;
NSDateFormatter *outputFormatter;
id<RSSParsingComplete> delegate;
}
#property (nonatomic, retain) NSArray *rssURLArray;
#property (nonatomic, retain) NSMutableData *xmlData;
#property (nonatomic, retain) id<RSSParsingComplete> delegate;
#property (nonatomic, retain) NSMutableArray *articles;
#property (nonatomic, retain) NSMutableArray *arrayOfArticles;
#property (nonatomic, retain) NSDateFormatter *inputFormatter;
#property (nonatomic, retain) NSDateFormatter *outputFormatter;
-(id)initWithRSSArray:(NSArray *)inputURLArray;
-(void)connect;
-(NSArray *)feedArticles;
#end
.m
#import "RSS.h"
#implementation RSS
#synthesize xmlData, rssURLArray, articles, arrayOfArticles, delegate, inputFormatter, outputFormatter;
-(void)connect{
self.xmlData = [[[NSMutableData alloc] init] autorelease];
NSURL *rssURL = [[NSURL alloc] initWithString:[self.rssURLArray objectAtIndex:numberOfFeeds-1]];
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:rssURL] delegate:self];
[urlConnection release];
[rssURL release];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
[self.xmlData setLength:0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.xmlData appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
[xmlData release];
[connection release];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
CXMLDocument *xmlDoc = [[[CXMLDocument alloc] initWithData:xmlData options:0 error:nil] autorelease];
self.articles = [[[NSMutableArray alloc] init] autorelease];
self.inputFormatter = [[[NSDateFormatter alloc] init] autorelease];
self.outputFormatter = [[[NSDateFormatter alloc] init] autorelease];
[self.inputFormatter setDateFormat:#"EEE, dd MMM yyyy HH:mm:ss zzz"];
[self.inputFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"] autorelease]];
[self.inputFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
[self.outputFormatter setDateFormat:#"dd.MM.yyyy HH:mm:ss"];
[self.outputFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"] autorelease]];
[self.outputFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSArray *itemNodes = [xmlDoc nodesForXPath:#"//item" error:nil];
for(CXMLElement *node in itemNodes){
NSMutableDictionary *article = [[NSMutableDictionary alloc] init];
for(int counter = 0; counter < [node childCount]; counter++){
if([[[node childAtIndex:counter] name] isEqualToString:#"title"]){
[article setObject:[[node childAtIndex:counter] stringValue] forKey:#"title"];
}
if([[[node childAtIndex:counter] name] isEqualToString:#"link"]){
[article setObject:[[node childAtIndex:counter] stringValue] forKey:#"url"];
}
if([[[node childAtIndex:counter] name] isEqualToString:#"description"]){
[article setObject:[[node childAtIndex:counter] stringValue] forKey:#"description"];
}
if([[[node childAtIndex:counter] name] isEqualToString:#"pubDate"]){
NSDate *tempDate = [self.inputFormatter dateFromString:[[node childAtIndex:counter] stringValue]];
[article setObject:[self.outputFormatter stringFromDate:tempDate] forKey:#"name"];
}
}
[self.articles addObject:article];
[article release];
}
NSArray *feedTitleNode = [xmlDoc nodesForXPath:#"//title" error:nil];
NSString *feedTitle = [[NSString alloc] initWithString:[[[feedTitleNode objectAtIndex:0] childAtIndex:0] stringValue]];
[self.articles addObject:feedTitle];
[feedTitle release];
[self.arrayOfArticles addObject:[articles copy]];
[self.articles removeAllObjects];
[inputFormatter release];
[outputFormatter release];
numberOfFeeds--;
if(numberOfFeeds > 0){
[self connect];
}else{
[delegate parsingFinished];
}
}
-(NSArray *)feedArticles{
NSLog(#"Array of Articles: %#", self.arrayOfArticles);
return self.arrayOfArticles;
}
-(id)initWithRSSArray:(NSArray *)inputURLArray{
self = [super init];
if (self) {
self.arrayOfArticles = [[[NSMutableArray alloc] init] autorelease];
self.rssURLArray = [[[NSArray alloc] initWithArray:inputURLArray] autorelease];
numberOfFeeds = [self.rssURLArray count];
[self connect];
}
return self;
}
-(void)dealloc{
[rssURLArray release];
[xmlData release];
[articles release];
[arrayOfArticles release];
[super dealloc];
}
- (id)init
{
self = [super init];
return self;
}
#end
I've done everything I can think of to resolve the leaks. I've read the Apple Memory Management guides, as well as the excellent guide on iPhoneDevSDK and that has helped me cut down on 90% of the leaks I originally had (the class doesn't leak so long as you call it once). Maybe i've been staring at this for too long, or maybe i'm missing something obvious.
I appreciate it!
First, should delegate be retained? I'm not sure why you need it as an instance variable at all. But since it's retained (and you don't seem to release it), your RSS object will retain a circular reference to itself and won't ever be released.
Second, do you need to keep the date formatters in an instance variable? It looks like you're allocating them and releasing them in the same method. Note that they're retained in the RSS instance and never released.
This is probably a very straight forward application, but I am new to Objective-C (coming from Java) and the whole memory management and "EXC_BAD_ACCESS" errors are breaking my heart.
I have a normal NavigationController iPhone App, with Core Data. in the AppDelegate the NSManagedObjectContext is created and passed to the RootViewController. A view things are looked up directly from the main thread to populate the table, and that seems to work fine.
The App is somekind of RSS-type reader, so as soon as the App starts I fire a thread to fetch new data and update the view:
-(void)updateData:(id)sender {
UIActivityIndicatorView *activityIndicator =
[[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
[activityIndicator startAnimating];
UIBarButtonItem *activityItem =
[[UIBarButtonItem alloc] initWithCustomView:activityIndicator];
[activityIndicator release];
self.navigationItem.leftBarButtonItem = activityItem;
[activityItem release];
// Start thread to update the data
[NSThread detachNewThreadSelector:#selector(doUpdateData) toTarget:self withObject:nil];
}
-(void)doUpdateData{
NSLog(#"Update data Thread (in 5 sec.)");
[NSThread sleepForTimeInterval:5];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
DataManager *data = [[DataManager alloc] initWithContext:managedObjectContext];
[data updateData];
[data release];
data=nil;
[self performSelectorOnMainThread:#selector(finishUpdateData) withObject:nil waitUntilDone:NO];
[pool release];
}
-(void)finishUpdateData{
self.navigationItem.leftBarButtonItem = updateBttn;
DataManager *data = [[DataManager alloc] initWithContext:managedObjectContext];
objects = [data getArticles];
[data release];
data=nil;
NSLog(#"Amount of records after update: %d", [objects count]);
[self.tableView reloadData];
}
The problem is that this doesn't work. In the DataManager, first settings need to be retrieved, and as soon as the NSEntityDescription is created I get the "EXC_BAD_ACCESS":
- (NSFetchedResultsController *)fetchedResultsController {
// Set up the fetched results controller if needed.
if (fetchedResultsController == nil) {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Setting" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"key" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchLimit:1];
.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
}
return fetchedResultsController;
}
I guess the pointer to the ManagedObjectContext is wrong, as a result from running in a different thread and memory-pool. So how do you create such an application if that is the issue), how do I get a reference to the original ManagedObjectContext format he thread?
[EDIT]
I also tried to use
iDomsAppDelegate *appDelegate = (iDomsAppDelegate *)[[UIApplication sharedApplication] delegate];
DataManager *data = [[DataManager alloc] initWithContext:appDelegate.managedObjectContext];
in doUpdateData (as hinted by other posts), but that gives the same result
Managed Object Contexts are not thread safe. Apple's guidelines indicate that you must have a separate instance of NSManagedObjectContext per thread.
I have looked on the web but can't find anything that might help. My question is can I write the tires[x] array as an NSArray, if so what would be the syntax to both declare and allocate the tire Class instance objects?
#interface CarClass : NSObject {
EngineClass *engine;
TireClass *tires[4];
}
.
#implementation CarClass
- (id) init {
[super init];
NSLog(#"_init: %#", NSStringFromClass([self class]));
engine= [[EngineClass alloc] init];
tires[0]= [[TireClass alloc] init];
tires[1]= [[TireClass alloc] init];
tires[2]= [[TireClass alloc] init];
tires[3]= [[TireClass alloc] init];
return self;
}
EDIT:
this is my dealloc method for the CarClass
- (void) dealloc {
NSLog(#"_deal: %#", NSStringFromClass([self class]));
[engine release];
[tires release];
[super dealloc];
}
Still a bit confused about the retain in the NSArray below, if I add an extra [tires retain] to the CarClass:init then the tires do not get released. However as the code is now the tires release with or without the end retain (i.e.)
tires = [[NSArray arrayWithObjects:
[[[TireClass alloc] init] autorelease],
[[[TireClass alloc] init] autorelease],
[[[TireClass alloc] init] autorelease],
[[[TireClass alloc] init] autorelease],
nil] retain];
tires = [NSArray arrayWithObjects:
[[[TireClass alloc] init] autorelease],
[[[TireClass alloc] init] autorelease],
[[[TireClass alloc] init] autorelease],
[[[TireClass alloc] init] autorelease],
nil];
FINAL EDIT: I also thought that without the autorelease on the individual tires that finally releasing the NSArray in the dealloc would release both the array and the objects it points to, this does not seem to be the case.
cheers -gary-
#interface CarClass : NSObject {
EngineClass *engine;
NSArray *tires;
}
.
#implementation CarClass
- (id) init {
self = [super init];
if (self == nil)
return nil;
NSLog(#"_init: %#", NSStringFromClass([self class]));
engine= [[EngineClass alloc] init];
tires = [[NSArray arrayWithObjects:
[[[TireClass alloc] init] autorelease],
[[[TireClass alloc] init] autorelease],
[[[TireClass alloc] init] autorelease],
[[[TireClass alloc] init] autorelease],
nil] retain];
return self;
}
You don't always need to use an NSArray for collections. How about this way which has a bit more information about each tyre:
#interface CarClass : NSObject {
EngineClass *engine;
NSDictionary *tiresDictionary;
}
#implementation CarClass
- (id) init {
self = [super init];
if (!self) {
return nil;
}
NSLog(#"_init: %#", NSStringFromClass([self class]));
engine= [[EngineClass alloc] init];
tiresDictionary = [[NSDictionary dictionaryWithObjectsAndKeys:
[[[TireClass alloc] init] autorelease], #"LeftFrontTyre",
[[[TireClass alloc] init] autorelease], #"RightFrontTyre",
[[[TireClass alloc] init] autorelease], #"LeftRearTyre",
[[[TireClass alloc] init] autorelease], #"RightRearTyre",
nil] retain];
return self;
}
This way you still have a collection class, but rather than try and remember which index of an array refers to which tyre, you have a dictionary with descriptive keys so you can refer to each tyre by a name.
I would change Nikolai's example a bit. Since I do not want to add autorelease and retain where none is needed, less code is always less bug-prone code.
#interface CarClass : NSObject {
EngineClass *engine;
NSArray *tires;
}
#implementation CarClass
- (id) init {
self = [super init];
if (self) {
NSLog(#"_init: %#", NSStringFromClass([self class]));
engine= [[EngineClass alloc] init];
tires = [[NSArray alloc] initWithObjects:
[TireClass new], [TireClass new], [TireClass new], [TireClass new], nil];
[tires makeObjectsPerormSelector:#selector(release)];
}
return self;
}
The [tires makeObjectsPerormSelector:#selector(release)]; can be scary, but is useful and also very performant. A slightly less performant way, but maybe more clear would be to let the TireClass implement a factory method, and create autoreleased objects from that one. Like so:
+(TireClass)tire;
{
return [[self new] autorelease];
}
Also please stop naming your classes "Something"Class, a kitten dies if you do :)