Cocoa Newbie Thread/Memory Problems
Cocoa Newbie Thread/Memory Problems
- Subject: Cocoa Newbie Thread/Memory Problems
- From: Dave <email@hidden>
- Date: Tue, 16 Mar 2010 16:57:00 +0000
Hi All,
I adapted the sample code of "XMLPerformance" as a basis to start my
own app. This is for the iPhone but I think it's a general problem
with my understanding of Cocoa Memory Management and/or Threading.
Basically I have a VewController with a button on it and when the
user clicks the button, it should read some XML data from a URL and
display it in a Table View. The main thread spawns a secondary thread
to handle the downloading and parsing of the data. The main view
controller is called via a delegate when a batch worth of array
objects (in this case NSMutableDictionary objects) has been parsed or
if there are items objects left in the array when parsing operation
has completed. At present I am not doing anything with the result,
except assigning the values so I can look at them in the debugger.
The problem is that when I look at the items in the array passed to
the view controller the data retrieved is not as it should be
(contains "invalid" or "out of scope" in the debugger). Sometimes the
values are correct, but most of the time they are garbage.
The method called:
- (void)parser:(ParserBase*)theParser didParseItems:(NSArray*)
theItemDictionaryArray
{
NSEnumerator* myArraytEnumerator = [theItemDictionaryArray
objectEnumerator];
NSDictionary* myDictionary;
NSString* myObjectNameString;
NSString* myFactYearString;
NSString* mtFactMonthString;
NSString* myFactDayString;
NSString* myFactSourceDatabaseString;
NSString* myFactTextString;
static int myObjectCount=0;
while (myDictionary = [myArraytEnumerator nextObject])
{
myObjectNameString = [myDictionary objectForKey:[ParserXML
parserObjectFieldName]];
myFactYearString = [myDictionary objectForKey:kField_FactYear];
mtFactMonthString = [myDictionary objectForKey:kField_FactMonth];
myFactDayString = [myDictionary objectForKey:kField_FactDay];
myFactSourceDatabaseString = [myDictionary
objectForKey:kField_FactSourceDatabase];
myFactTextString = [myDictionary objectForKey:kField_FactText];
myObjectCount++;
}
}
myObjectNameString, myFactYearString, mtFactMonthString,
myFactDayString, myFactSourceDatabaseString, myFactTextString are
gargage most of the time.
I've checked and the data is downloaded and parsed ok. I also changed
the code so the batch size was set to 1, which means that parser:
didParseItems is called for every completed object instead of
accumulating the objects into a batch. If I do this the data seems ok
when I look at it in the debugger.
I'm sure the problem must be related to Memory Management but AFAICT
I'm doing everything I should! I am aware that there maybe some leaks
in the code (and if you spot them please point them out). I was going
to do a memory profile on it once I had got everything working ok and
get rid of any leaks then.
I have copied a cut-down version of my app below, if anyone can spot
the problem I'd be really grateful. I've been programming for many
years on Mac's but have not worked with Cocoa for a long time.
Thanks in advance or any help.
All the Best
Dave
---------------------------------------------------------------
//MainViewController.h
@interface MainViewController : UIViewController <ParserDelegate>
{
ParserBase* mParserBase;
}
@property (nonatomic, retain) ParserBase* mParserBase;
- (IBAction)getData:(id) sender;
- (void)didEndParsingItems:(ParserBase*)theParser;
- (void)parser:(ParserBase*)theParser didFailWithError:(NSError*)
theError;
- (void)parser:(ParserBase*)theParser didParseItems:(NSArray*)
theItemDictionaryArray;
@end
---------------------------------------------------------------
//MainViewController.m
@implementation MainViewController
@synthesize mParserBase;
static NSString* kDefaultURL = @"http://www.looktowindward.com/dp/
rs_applet2.php?month=Mar&day=11&dbase=main";
static NSString* kField_List = @"Container";
static NSString* kField_Detail = @"Detail";
static NSString* kField_Year = @"FactYear";
static NSString* kField_Month = @"FactMonth";
static NSString* kField_Day = @"FactDay";
static NSString* kField_SourceDatabase = @"FactSourceDatabase";
static NSString* kField_Text = @"FactText";
- (IBAction)getData:(id) sender
{
Class myParserClass = nil;
myParserClass = [ParserXML class];
self.mParserBase = [[[myParserClass alloc] init] autorelease];
mParserBase.mParserDelegate = self;
[mParserBase startWithURL:kDefaultURL ForStructure:kField_List];
}
- (void)dealloc
{
[mParserBase release];
[super dealloc];
}
- (void)didEndParsingItems:(ParserBase*)theParser
{
static int myCount=0;
myCount++;
}
- (void)parser:(ParserBase*)theParser didParseItems:(NSArray*)
theItemDictionaryArray
{
NSEnumerator* myArraytEnumerator = [theItemDictionaryArray
objectEnumerator];
NSDictionary* myDictionary;
NSString* myObjectNameString;
NSString* myFactYearString;
NSString* mtFactMonthString;
NSString* myFactDayString;
NSString* myFactSourceDatabaseString;
NSString* myFactTextString;
static int myObjectCount=0;
while (myDictionary = [myArraytEnumerator nextObject])
{
myObjectNameString = [myDictionary objectForKey:[ParserXML
parserObjectFieldName]];
myFactYearString = [myDictionary objectForKey:kField_FactYear];
mtFactMonthString = [myDictionary objectForKey:kField_FactMonth];
myFactDayString = [myDictionary objectForKey:kField_FactDay];
myFactSourceDatabaseString = [myDictionary
objectForKey:kField_FactSourceDatabase];
myFactTextString = [myDictionary objectForKey:kField_FactText];
myObjectCount++;
}
}
- (void)parser:(ParserBase*)theParser didFailWithError:(NSError *)
theError
{
}
//ParserXML.h
---------------------------------------------------------------------
@interface ParserXML : ParserBase
{
NSURLConnection* mURLConnection;
NSAutoreleasePool* mDownloadAndParsePool;
NSMutableData* mXMLSourceData;
BOOL mParserDoneFlag;
int mParsedCount;
BOOL mContainerStructureFoundFlag;
BOOL mDetailStructureFoundFlag;
BOOL mGatherCharactersFlag;
BOOL mIgnoreCharactersFlag;
NSMutableString* mCurrentValueString;
NSMutableDictionary* mParsedDataDictionary;
}
@property (nonatomic, retain) NSMutableString* mCurrentValueString;
@property (nonatomic, retain) NSMutableData* mXMLSourceData;
@property (nonatomic, retain) NSURLConnection* mURLConnection;
@property (nonatomic, retain) NSMutableDictionary*
mParsedDataDictionary;
@property (nonatomic, assign) NSAutoreleasePool* mDownloadAndParsePool;
- (void)downloadAndParse:(void*)theObject;
@end
------------------------------------------------------------------------
-----------
//ParserXML.m
@implementation ParserXML
static const NSUInteger kAutoreleasePoolPurgeFrequency = 20;
static const NSString* kXMLObjectName = @"_XMLObjectName";
@synthesize mParsedDataDictionary;
@synthesize mCurrentValueString;
@synthesize mXMLSourceData;
@synthesize mURLConnection;
@synthesize mDownloadAndParsePool;
- (void)downloadAndParse:(void*)theObject;
{
mParserDoneFlag = NO;
mContainerStructureFoundFlag = NO;
mDetailStructureFoundFlag = NO;
self.mParsedDataDictionary = nil;
self.mDownloadAndParsePool = [[NSAutoreleasePool alloc] init];
self.mXMLSourceData = [NSMutableData data];
[[NSURLCache sharedURLCache] removeAllCachedResponses];
NSURLRequest *theRequest = [NSURLRequest
requestWithURL:self.mParserSourceURL];
mURLConnection = [[NSURLConnection alloc] initWithRequest:theRequest
delegate:self];
[self performSelectorOnMainThread:@selector(downloadStarted)
withObject:nil waitUntilDone:NO];
if (mURLConnection != nil)
{
do
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
while(mParserDoneFlag == NO);
}
self.mURLConnection = nil;
[mDownloadAndParsePool release];
self.mDownloadAndParsePool = nil;
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return nil;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:
(NSError *)error
{
mParserDoneFlag = YES;
[self performSelectorOnMainThread:@selector(parseError:)
withObject:error waitUntilDone:NO];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:
(NSData *)data
{
[mXMLSourceData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self performSelectorOnMainThread:@selector(downloadEnded)
withObject:nil waitUntilDone:NO];
self.mCurrentValueString = [NSMutableString string];
NSXMLParser* myParser = [[NSXMLParser alloc]
initWithData:mXMLSourceData];
myParser.delegate = self;
[myParser setShouldProcessNamespaces:NO];
[myParser setShouldReportNamespacePrefixes:NO];
[myParser parse];
[self performSelectorOnMainThread:@selector(parseEnded)
withObject:nil waitUntilDone:NO];
[myParser release];
self.mCurrentValueString = nil;
self.mXMLSourceData = nil;
mParserDoneFlag = YES;
}
- (void) finishedCurrentItem
{
mParsedCount++;
[self performSelectorOnMainThread:@selector(parsedItem:)
withObject:mParsedDataDictionary waitUntilDone:NO];
self.mParsedDataDictionary = nil;
if ((mParsedCount % kAutoreleasePoolPurgeFrequency) == 0)
{
[mDownloadAndParsePool release];
self.mDownloadAndParsePool = [[NSAutoreleasePool alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)
elementName namespaceURI:(NSString *)namespaceURI qualifiedName:
(NSString *) qualifiedName attributes:(NSDictionary *)attributeDict
{
NSString* myFieldValue = nil;
if ([elementName isEqualToString:mContainerStructureName])
{
mContainerStructureFoundFlag = YES;
mDetailStructureFoundFlag = NO;
mGatherCharactersFlag = NO;
mIgnoreCharactersFlag = NO;
}
else if (mContainerStructureFoundFlag == YES)
{
if (mDetailStructureFoundFlag == NO)
{
self.mParsedDataDictionary = [[[NSMutableDictionary alloc] init]
autorelease];
myFieldValue = [[NSString alloc] initWithString:elementName];
[self.mParsedDataDictionary setObject:myFieldValue
forKey:kXMLObjectName];
mDetailStructureFoundFlag = YES;
}
//**
//** We are Interested in All Fields in a Detail Structure
//**
else
{
[mCurrentValueString setString:@""];
mGatherCharactersFlag = YES;
}
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)
elementName namespaceURI:(NSString *)namespaceURI qualifiedName:
(NSString *)qName
{
NSString* myFieldValue = nil;
if ([elementName isEqualToString:mContainerStructureName])
{
mContainerStructureFoundFlag = NO;
}
else if ([elementName isEqualToString:[mParsedDataDictionary
objectForKey:kXMLObjectName]])
{
mDetailStructureFoundFlag = NO;
[self finishedCurrentItem];
}
else
{
myFieldValue = [[NSString alloc] initWithString:mCurrentValueString ];
[self.mParsedDataDictionary setObject:myFieldValue
forKey:elementName];
}
mGatherCharactersFlag = NO;
mIgnoreCharactersFlag = NO;
}
- (void)parser:(NSXMLParser *)theParser foundCharacters:(NSString *)
theString
{
if (mGatherCharactersFlag == NO)
return;
if (mIgnoreCharactersFlag == NO)
{
if ([theString isEqualToString:@"<"] == YES)
{
mIgnoreCharactersFlag = YES;
return;
}
}
else
{
if ([theString isEqualToString:@">"] == YES)
{
mIgnoreCharactersFlag = NO;
}
return;
}
[mCurrentValueString appendString:theString];
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)
parseError
{
NSString* myErrorString;
NSInteger myLineNumber;
NSInteger myColumnNumber;
myLineNumber = [parser lineNumber];
myColumnNumber = [parser columnNumber];
myErrorString = [NSString stringWithFormat:@"Error %i,Description: %
@, Line: %i, Column: %i",[parseError code],[[parser parserError]
localizedDescription],[parser lineNumber],[parser columnNumber]];
}
------------------------------------------------------------------------
------------------------
//ParserBase.h
typedef enum
{
ParserTypeAbstract = -1,
ParserTypeNSXMLParser = 0,
} ParserType;
@class ParserBase;
@protocol ParserDelegate <NSObject>
@optional
- (void)didEndParsingItems:(ParserBase*)theParser;
- (void)parser:(ParserBase*) theParser didFailWithError:(NSError *)
theError;
- (void)parser:(ParserBase*)theParser didParseItems:(NSArray*)
theItemDictionaryArray;
@end
@interface ParserBase : NSObject
{
id <ParserDelegate> mParserDelegate;
NSURL* mParserSourceURL;
NSString* mContainerStructureName;
NSMutableArray* mParsedItemsArray;
}
@property (nonatomic, assign) id <ParserDelegate> mParserDelegate;
@property (nonatomic, retain) NSURL* mParserSourceURL;
@property (nonatomic, retain) NSString* mContainerStructureName;
@property (nonatomic, retain) NSMutableArray* mParsedItemsArray;
+ (NSString *)parserName;
+ (ParserType)parserType;
+ (const NSString*)parserObjectFieldName;
- (void)startWithURL:(NSString*) theURLString ForStructure:
(NSString*) theContainerStructureName;
- (void)downloadAndParse:(void*)theObject;
- (void)downloadStarted;
- (void)downloadEnded;
- (void)parseEnded;
- (void)parsedItem:(NSMutableDictionary*) theItemDictionary;
- (void)parseError:(NSError *)error;
@end
------------------------------------------------------------------------
--------------------------
//ParserBase.m
@implementation ParserBase
@synthesize mContainerStructureName;
@synthesize mParserDelegate;
@synthesize mParserSourceURL;
@synthesize mParsedItemsArray;
static NSUInteger kCountForNotification = 10;
- (void)startWithURL:(NSString*) theURLString ForStructure:
(NSString*) theContainerStructureName;
{
[[NSURLCache sharedURLCache] removeAllCachedResponses];
self.mParsedItemsArray = [NSMutableArray array];
self.mParserSourceURL = [NSURL URLWithString:theURLString];
self.mContainerStructureName = theContainerStructureName;
[NSThread detachNewThreadSelector:@selector(downloadAndParse:)
toTarget:self withObject:nil];
}
- (void)dealloc
{
[mParsedItemsArray release];
//[mParserSourceURL release];
//[mContainerStructureName release];
[super dealloc];
}
- (void)downloadAndParse:(void*)theObject;
{
NSAssert([self isMemberOfClass:[ParserBase class]] == NO,
@"downloadAndParse - Object is of abstract base class ParserBase");
}
- (void)downloadStarted
{
NSAssert2([NSThread isMainThread], @"%s at line %d called on
secondary thread", __FUNCTION__, __LINE__);
[UIApplication sharedApplication].networkActivityIndicatorVisible =
YES;
}
- (void)downloadEnded
{
NSAssert2([NSThread isMainThread], @"%s at line %d called on
secondary thread", __FUNCTION__, __LINE__);
[UIApplication sharedApplication].networkActivityIndicatorVisible =
NO;
}
- (void)parseEnded
{
NSAssert2([NSThread isMainThread], @"%s at line %d called on
secondary thread", __FUNCTION__, __LINE__);
if (self.mParserDelegate != nil)
{
if (self.mParsedItemsArray.count > 0)
{
if ([self.mParserDelegate respondsToSelector:@selector
(parser:didParseItems:)])
{
[self.mParserDelegate parser:self didParseItems:mParsedItemsArray];
}
}
[self.mParsedItemsArray removeAllObjects];
if ([self.mParserDelegate respondsToSelector:@selector
(didEndParsingItems:)])
{
[self.mParserDelegate didEndParsingItems:self];
}
}
}
- (void)parsedItem:(NSMutableDictionary*) theItemDictionary
{
NSAssert2([NSThread isMainThread], @"%s at line %d called on
secondary thread", __FUNCTION__, __LINE__);
[self.mParsedItemsArray addObject:theItemDictionary];
if (self.mParsedItemsArray.count > kCountForNotification)
{
if (self.mParserDelegate != nil)
{
if ([self.mParserDelegate respondsToSelector:@selector
(parser:didParseItems:)])
{
[self.mParserDelegate parser:self didParseItems:mParsedItemsArray];
}
[self.mParsedItemsArray removeAllObjects];
}
}
}
- (void)parseError:(NSError *)error
{
NSAssert2([NSThread isMainThread], @"%s at line %d called on
secondary thread", __FUNCTION__, __LINE__);
if (self.mParserDelegate != nil && [self.mParserDelegate
respondsToSelector:@selector(parser:didFailWithError:)])
{
[self.mParserDelegate parser:self didFailWithError:error];
}
}
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden