Re: Cocoa Newbie Thread/Memory Problems
Re: Cocoa Newbie Thread/Memory Problems
- Subject: Re: Cocoa Newbie Thread/Memory Problems
- From: Dave <email@hidden>
- Date: Wed, 17 Mar 2010 18:12:09 +0000
Hi Quincy,
Sorry for the typeos and lack of clarity in my last message and
thanks a lot for your help. I was really tired when I sent the
message last night, I should have waited until today to send it.
Basically what I was trying to get at is that I have a handler that
gets called after the user clicks a button (in this case the
"getData" action/event). As I said before, I based this app on the
"XMLPerformance" sample code from Apple Developer. On that app, the
user clicks a button which fires a new view controller which does the
downloading and parsing work. Since the button that triggered the
event is on the previous view and that view is now hidden, there is
no chance that the user could click the button again before the
download and parse operation has completed. However, on my App there
is a button that is still visible while the download and parse
operation is still running and the user can click it. From this I
have two questions:
1. How can I disable the button to stop this happening?
2. If I want this to happen what is the best way to achieve it?
My App basically retrieves data from a URL based on a date, each URL
request can result in 0 or more Items being download/parsed. The date
is passed as a parameter in the URL string e.g. ?
month=mar&day=16&dbase=rs_main. When it is first launched, it
retreives the data for the current day, while this is downloading/
parsing the user could click on another button to get the data for
the whole of "next week". This would cause 7 requests to be
downloaded, one for each day, e.g. ?month=mar&day=17&dbase=rs_main
then &day=18, &day=19 etc. The data is to be put into a table view
and the user will be able to select a cell from the view and get the
details for the a particular item. In pseudo code:
User Launches App.
Download/Parse Process is Started on the URL for the Current Date.
The Table View begins to fill, the user can Scroll the Table View and
"home in" on items.
User Clicks "Next Week".
Download/Parse Process is Started on the URL for the Current Date + 1
Day. The new data gets added to the Table View, the user can Scroll
the Table View and "home in" on items.
Download/Parse Process is Started on the URL for the Current Date + 2
Days. The new data gets added to the Table View, the user can Scroll
the Table View and "home in" on items.
.......................... and so on for each of the 7 days.
Also, the user can select a different database or can select multiple
databases, in which case the request needs to be sent to each of the
databases, this is passed to the server via the &dbase= parameter. If
two databases were selected them clicking "Next Week" would result in
14 requests being sent to the Server.
The XML for this is as follows:
<Container>
<Detail>
<Detail_Item1>Some String Data 1</Detail_Item1>
<Detail_Item2> Some String Data 2 </Detail_Item2>
<Detail_Item3> Some String Data 3</Detail_Item3>
...........
...........
<Detail_ItemN> Some String Data N</Detail_ItemN>
</Detail>
</Container>
I am wondering if part of my problem is that I based this on the
"XMLPerformance" sample code (which I suppose was more focused on XML
parsing) rather than writing it all from scratch. The basic flow of
this is:
ViewController creates a "ParserXML" class and sends a "start" message.
ParserXML is a subclass of ParserBase and between them they handle
creating a new thread to handle the downloading and parsing of the
XML data. When parsing each "Detail" item (see above XML) is put into
a newly created NSDictionary with the keys and data set from the
"Detail" item. e.g. for the above XML it would result in a
NSDictionary item with key=<Detail_Item1>="Some String Data
1',key=<Detail_Item2>="Some String Data 2", etc. Each completed
NSDictionary item is put into an NSArray and when there are more than
N items in the NSArray, a Call back to the main thread is made
passing the NSArray as "theObject".
At present I am just logging the data in this call back as per my
first post, but in the real app I will add the array to a Table View.
There are also call backs to signal the parse has finished and for an
error condition.
Basically I need change the code so that the UI "action" method (e.g.
"getData" in this case), just adds a request to a queue and returns.
I am trying to figure out the best way to implement this given the
code I have so far and wondering if it might be better to start again
with a new model. If so, any suggestions on the best design model for
this? Specifically I am wondering how I should pass the parameters
(URLBaseString, Date, ContainerName, from the Main Thread View
Controller to the Download/Parse secondary thread. At present it just
assumes it is not already running so just creates a new one with
disastrous consequences!
My thoughts so far.....
I decided that the best way to handle this would be to add requests
from to a "ParserQueue" and have the background thread loop until all
requests have been finished, then close itself down. I created a new
Class "ParserParameterInfo| and use this to hold the parameters and
add this object to the queue.
I added the Queue handling code, but I can't get it to work! I have
copied the code as best I can below while trying to keep it short and
focused. The downloading and parsing seems to work (although there
may be leaks). In the View Controller Action method "getData " I
queue two requests for the 18th (given that it is the 17th today).
There are two facts defined for 18th March in database "rs_main". So
my logging method ("parser:didParseItems") should be called twice and
log four items, however it only seems to get called twice.
Any idea what is going wrong? Is there a better/easier way to do this?
I can zip the project and send it to you if this would help?
Thanks a lot for your help so far.
All the Best
Dave
---------------------------------------------------------------
ViewController.m
- (IBAction)getData:(id) sender
{
ParserParameterInfo* myParameterInfo;
static NSDate* myQueryDate = nil;
if (myQueryDate == nil)
{
myQueryDate = [NSDate date];
}
else
myQueryDate = [myQueryDate addTimeInterval:(24 * 60 * 60)];
myQueryDate = [myQueryDate addTimeInterval:(24 * 60 * 60)]; //18
myParameterInfo = [[ParserParameterInfo alloc] initForURL:kBaseURL
ForContainer:kField_Container On:myQueryDate WithDatabase:@"rs_main"];
[mParserBase QueueReqest:myParameterInfo];
//myQueryDate = [myQueryDate addTimeInterval:(24 * 60 * 60)]; //19
myParameterInfo = [[ParserParameterInfo alloc] initForURL:kBaseURL
ForContainer:kField_Container On:myQueryDate WithDatabase:@"rs_main"];
[mParserBase QueueReqest:myParameterInfo];
}
(void)viewDidLoad
{
[super viewDidLoad];
self.mParserBase = [[ParserXML alloc] init];
mParserBase.mParserDelegate = self;
}
- (void)viewDidUnload
{
[self.mParserBase release];
}
- (void)dealloc
{
[mParserBase release];
[super dealloc];
}
- (void)parser:(ParserBase*)theParser didParseItems:(NSArray*)
theItemDictionaryArray
{
NSArray* myArray;
NSEnumerator* myArrayEnumerator;
NSDictionary* myDictionary;
NSString* myObjectNameString;
NSString* myFactYearString;
NSString* myFactMonthString;
NSString* myFactDayString;
NSString* myFactSourceDatabaseString;
NSString* myFactTextString;
static int myObjectCount=0;
myArray = [[[NSArray alloc] initWithArray:theItemDictionaryArray]
autorelease];
myArrayEnumerator = [myArray objectEnumerator];
while (myDictionary = [myArrayEnumerator nextObject])
{
NSLog(@"********************** item:%d",myObjectCount);
myObjectNameString = [myDictionary objectForKey:[ParserXML
parserObjectFieldName]];
NSLog(myObjectNameString);
myFactYearString = [myDictionary objectForKey:kField_FactYear];
NSLog(myFactYearString);
myFactMonthString = [myDictionary objectForKey:kField_FactMonth];
NSLog(myFactMonthString);
myFactDayString = [myDictionary objectForKey:kField_FactDay];
NSLog(myFactDayString);
myFactSourceDatabaseString = [myDictionary
objectForKey:kField_FactSourceDatabase];
NSLog(myFactSourceDatabaseString);
myFactTextString = [myDictionary objectForKey:kField_FactText];
NSLog(myFactTextString);
myObjectCount++;
}
}
------------------------------------------------------------------------
---------
in ParserXML.m
/- (void)QueueReqest:(ParserParameterInfo*) theParameterInfo
{
[mParseRequestQueue addObject:theParameterInfo];
if ([mParseRequestQueue count] == 1)
{
//**
//** Call "startRequests" in ParserXML Subclass on New Thread
//**
[NSThread detachNewThreadSelector:@selector(startRequests:)
toTarget:self withObject:nil];
}
}
- (ParserBase*)init
{
[super init];
mParserDelegate = nil;
mContainerStructureName = nil;
mParsedItemsArray = nil;
mParseRequestQueue = [[NSMutableArray alloc] init];
mParsedItemsArray = [[NSMutableArray alloc] init];
return self;
}
- (void)dealloc
{
[mParsedItemsArray release];
//[mParserSourceURL release];
//[mContainerStructureName release];
[super dealloc];
}
------------------------------------------------------------------------
-----------------------------------------------------------------
In ParserXML.m
- (ParserXML*)init
{
[super init];
mURLConnection = nil;
mDownloadAndParsePool = nil;
mXMLSourceData = nil;
mParsedCount = 0;
mParserDoneFlag = NO;
mCurrentValueString = nil;
mParsedDataDictionary = nil;
mParserDoneFlag = NO;
mContainerStructureFoundFlag = NO;
mDetailStructureFoundFlag = NO;
mIgnoreCharactersFlag = NO;
return self;
}
- (void)startRequests:(void*)theObject
{
ParserParameterInfo* myParameterInfo;
NSURLRequest* myURLRequest;
NSString* myURLString = nil;
NSURL* myParserSourceURL = nil;
int myCount;
myCount = [mParseRequestQueue count];
//**
//** Initialize
//**
self.mDownloadAndParsePool = [[NSAutoreleasePool alloc] init];
self.mParsedDataDictionary = nil;
self.mXMLSourceData = [NSMutableData data];
while ([mParseRequestQueue count] > 0)
{
//**
//** Get the Next Item to Process
//**
myParameterInfo = [mParseRequestQueue objectAtIndex:0];
self.mContainerStructureName = myParameterInfo.mContainerStructureName;
mParserDoneFlag = NO;
mContainerStructureFoundFlag = NO;
mDetailStructureFoundFlag = NO;
//**
//** Form the URL
//**
myURLString = [self makeURLString:myParameterInfo.mBaseURLString
ForDate:myParameterInfo.mRequestDate
UsingDatabase:myParameterInfo.mRequestDatabaseType];
myParserSourceURL = [NSURL URLWithString:myURLString];
myURLRequest = [NSURLRequest requestWithURL:myParserSourceURL];
mURLConnection = [[NSURLConnection alloc]
initWithRequest:myURLRequest delegate:self];
NSAssert((mURLConnection != nil), @"NSURLConnection Failed");
//**
//** Signal Download Started on Main Thread
//**
[self performSelectorOnMainThread:@selector(downloadStarted)
withObject:myParameterInfo waitUntilDone:NO];
//**
//** Wait Until the Download and Parse Operation has Completed
//**
do
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:
[NSDate distantFuture]];
}
while(mParserDoneFlag == NO);
//**
//** Remove the Head Item from the Queue
//**
[mParseRequestQueue removeObjectAtIndex:0];
[self.mURLConnection release];
// [myParserSourceURL release];
// [myURLRequest release];
}
[mDownloadAndParsePool release];
self.mDownloadAndParsePool = nil;
}
_______________________________________________
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