All,
Disclaimer: I'm aware that there are other questions that ask similar questions, but none provide an answer that I understand or an answer that applies to my situation.
I have two classes, classA and classB. In classA there is a void instance method that creates a NSMutableDictionary (if you want specifics, it's a XMLparser). After classA's XMLParser is run and the NSMutableDictionary has been created and filled, classB is called which needs to do some other things with that dictionary. For some reason, classB cannot access the NSMutableDictionary in classA (actually, it can access it, but for some reason it shows up as "NULL"). What should I do?
Thanks in advance.
EDIT: You asked for the source code, you got it. ClassA below, dictionary in question is called "response."
#import "XMLParser.h"
#import "CardSetupViewController.h"
#implementation XMLParser
#synthesize response;
- (XMLParser *) initXMLParser
{
self = [super init];
// init dictionary of response data
response = [[NSMutableDictionary alloc] init];
return self;
}
//Gets Start Element of SessionData
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:#"SessionData"])
{
NSLog(#"Found SessionData in the return XML! Continuing...");
//response is a NSMutableArray instance variable
//THIS SHOULD NEVER NEED TO BE USED
if (!response)//if array is empty, it makes it!
{
NSLog(#"Dictionary is empty for some reason, creating...");
response = [[NSMutableDictionary alloc] init];
}
//END: THIS SHOULD NEVER BE USED
return;
}
else
{
currentElementName = elementName;
NSLog(#"Current Element Name = %#", currentElementName);
return;
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if (!currentElementValue) {
// init the ad hoc string with the value
currentElementValue = [[NSMutableString alloc] initWithString:string];
} else {
[currentElementValue setString:string];
NSLog(#"Processing value for : %#", string);
}
}
//Gets End Element of SessionData
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"SessionData"])
{
// We reached the end of the XML document
// dumps dictionary into log
NSLog(#"Dump:%#", [response description]);
return;
}
else
{
//Adds key and object to dictionary
[response setObject:currentElementValue forKey:currentElementName];
NSLog(#"Set values, going around again... brb.");
}
currentElementValue = nil;
currentElementName = nil;
}
#end
You probably want to look into using singletons.
http://www.galloway.me.uk/tutorials/singleton-classes/
http://pixeleap.com/?p=19
http://www.iphonedevsdk.com/forum/iphone-sdk-development/5302-how-make-global-variables-objectivec.html
Those should get you started, if you need clarification just ask.
Are currentElementName and currentElementValue ivars ? If so, every time you catch either an element or a value, you must then release your ivar and retain the new value you catch.
Also, as the comments are saying you didn't follow any rules about writing a correct initializer.
What about classB ? Isn't there some logic that makes your dictionary be released ?
Try managing a bit more the memory.
you have several options... so many options actually
Option A:(set)
#interface ClassB{
NSDictionary * someDict;
}
-(void)setSomeDict:(NSDictionary *)aDict;
#end
#implementation ClassB
-(void)setSomeDict:(NSDictionary *)aDict
{
someDict = [aDict retain];//or copy depending on your needs;
}
#end
somewhere in ClassA...
ClassB * b = [ClassB new];
[b setSomeDict: someOtherDict];
Option B:(push)
#interface ClassB{
NSDictionary * someDict;
}
-(void)doSomethingWithDict:(NSDictionary *)aDict;
#end
#implementation ClassB
-(void)doSomethingWithDict:(NSDictionary *)aDict
{
NSLog(#"did something with aDict: %#",aDict);
}
#end
somewhere in ClassA...
ClassB * b = [ClassB new];
[b doSomethingWithDict: someOtherDict];
Option c:(init)
#interface ClassB{
NSDictionary * someDict;
}
-(id)initWithDict:(NSDictionary *)aDict;
#end
#implementation ClassB
-(id)initWithDict:(NSDictionary *)aDict
{
self = [super init];
if(self)
{
someDict = [someDict retain]; //or copy depending on your needs
}
NSLog(#"did something with aDict: %#",aDict);
}
#end
somewhere in ClassA...
ClassB * b = [[ClassB alloc] initWithDict:someOtherDict];
[b doSomethingelse];
you can also use properties etc... there are many many options, but you should understand object ownership as well so you don't end up leaking the dictionary.
-(void)dealloc
{
[someDict release];
}
should be added to the classB.
First of all check if u get the response in this delegate method of NSXMLParser
- (void)parserDidEndDocument:(NSXMLParser *)parser;
and send the response via a delegate (which u should implement in this class) and the delegate method is to implemented in the other class ...Simple !!!
Related
I was just reading an answer to a different question on nsxmlparsing.. and in it the guy was saying you should use self. notation for better memory management... what dose this mean?
I have left this out of initializing my dictionary should I have it in? how dose it help or not help?
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:#"Row"])
{
ICRow = [[NSMutableDictionary alloc] initWithDictionary:attributeDict];
}
if ([elementName isEqualToString:#"Rows"]) {
self.ICRows = [NSMutableArray arrayWithCapacity:8];
}
}
bar = self.foo;
is just a shorthand notation for
bar = [self foo];
and
self.foo = bar;
is shorthand for
[self setFoo:bar];
In terms of memory management this just means that the specific setters and getters are invoked, whereas
foo = bar
would simply assign the value (i.e. mostly just copy the pointer).
Say you have a retained property
#property (nonatomic, retain) FooType *foo;
//...
#synthesize foo;
then the setter might look like
- (void) setFoo:(FooType*)x
{
[x retain];
[foo release];
foo = x;
}
Now if you do a
FooType *newFoo = [[FooType alloc] init];
[self setFoo:newFoo];
// or self.foo = newFoo
Everything would be allright, since your previously allocated object is released and the new object is retained properly. If you instead just did a
foo = newFoo;
without thinking about to release the old and retain the new object, you would be leaking memory or worse...
Ok, my problem is that whenever i collect data from the parser into an array where the string contains Swedish ÅÄÖ characters. In my example the
[schemaInfoArray objectAtIndex:3]
is supposed to be #"Lördag" but is saved as #"L" and the
[schemaInfoArray objectAtIndex:4]
contains the rest of the string that gets presented as
#"ördag"
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
tempStrang = string;
[schemaInfoArray insertObject:tempStrang atIndex:uppraknare];
uppraknare++;
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ( [elementName isEqualToString:#"schemaInfo"] )
{
}
if ( [elementName isEqualToString:#"modfromtid"] )
{
frommodarbtid = [schemaInfoArray objectAtIndex:0];
}
if ([elementName isEqualToString:#"modtomtid"] )
{
tommodarbtid = [schemaInfoArray objectAtIndex:1];
}
if ([elementName isEqualToString:#"modrast"] )
{
modrast = [schemaInfoArray objectAtIndex:2];
}
if ([elementName isEqualToString:#"benamning"] )
{
benamning = [schemaInfoArray objectAtIndex:3];
}
if ([elementName isEqualToString:#"fromnormarb"] )
{
fromnormarbtid = [schemaInfoArray objectAtIndex:4];
}
if ([elementName isEqualToString:#"tomnormarb"] )
{
tomnormarbtid = [schemaInfoArray objectAtIndex:5];
}
if ([elementName isEqualToString:#"rast"] )
{
normrast = [schemaInfoArray objectAtIndex:6];
}
}
Does anyone have any thoughts about how to actually get #"Lördag" to be saved into ONE index instead of getting split into several indexes? This really destroys the structure of things that is supposed to be presented.
This is a documented design choice from Apple, and has nothing to do with Swedish characters:
Because string may be only part of
the total character content for the
current element, you should append it
to the current accumulation of
characters until the element changes.
So you should do just as they say: use a NSMutableString to accumulate the results, and when the element changes, save the buffer to a permanent, (preferrably) immutable NSString.
As requested, here's an example. It was written without any kind of IDE, so chances are that it'll work, but there's no guarantee that it will either compile or work.
#interface Foo : NSObject<NSXMLParserDelegate> {
NSMutableString* accumulator;
NSMutableArray* schemaInfoArray;
int uppraknare; // whatever 'uppraknare' means
}
/* snip */
#end
#implementation Foo
-(void)parser:(NSXMLParser*)parser foundCharacters:(NSString*)string
{
// only accumulate characters, until we get notified that we went through
// the whole XML element
[accumulator appendString:string];
}
-(void)parser:(NSXMLParser*)parser didEndElement:(NSString*)elementName namespaceURI:(NSString*)nsuri qualifiedName:(NSString*)qName
{
// we went through the whole element! time to save!
NSString* immutableResult = [accumulator copy];
[schemaInfoArray insertObject:immutableResult atIndex:uppraknare];
uppraknare++;
[immutableResult release];
// clear the accumulator for the next element
[accumulator deleteCharactersInRange:NSMakeRange(0, [accumulator length])];
/* the rest of your code here */
}
#end
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string is not guaranteed to contain the complete contents of the string. You need to have a class instance variable that is a NSMutableString that can append all of foundCharacters between the calls to didStartElement and didEndElement. Inside of didEndElement add the the string to the schemaInfoArray.
OK this should be an easy one but still im breaking my head here:
In my root view controller I have a NSString called "entry" and is working perfectly. I NSLogged it and it works.
I have another class called ´ParseOperation´ and in it i have a NSStringcalled "localEntry" and im trying to send to "ParseOperation" the variable "entry" from "RootViewController" this is my RootViewController code for that:
RootViewController.m
ParseOperation *parseOperation = [[ParseOperation alloc] init];
parseOperation.localEntry = entry;
It just doesn't work. If I NSLog in my ParseOperation.m it returns "null", but if i do it on my RootViewController it returns the correct variable. and yes i did imported the ParseOperation.h
Here is the ParseOperation code (only the part that uses localEntry):
ParseOperation.h
#interface ParseOperation : NSOperation <NSXMLParserDelegate>
{
NSString *localEntry;
}
#property (nonatomic, retain) NSString *localEntry;
#end
ParseOperation.m
#synthesize localEntry;
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
//NSLog(#"entrada %#",localEntry);
if ([elementName isEqualToString:localEntry])
{
self.workingEntry = [[[AppRecord alloc] init] autorelease];
}
storingCharacterData = [elementsToParse containsObject:elementName];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if (self.workingEntry)
{
if (storingCharacterData)
{
NSString *trimmedString = [workingPropertyString stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[workingPropertyString setString:#""]; // clear the string for next time
if ([elementName isEqualToString:kIDStr])
{
self.workingEntry.appURLString = trimmedString;
}
else if ([elementName isEqualToString:kNameStr])
{
self.workingEntry.appName = trimmedString;
}
else if ([elementName isEqualToString:kImageStr])
{
self.workingEntry.imageURLString = trimmedString;
}
else if ([elementName isEqualToString:kArtistStr])
{
self.workingEntry.artist = trimmedString;
}
}
else if ([elementName isEqualToString:localEntry])
{
[self.workingArray addObject:self.workingEntry];
self.workingEntry = nil;
}
}
}
THANKS!
In all likelihood, rootViewController is nil. When you declare and synthesize a property, it only creates the getter/setter methods for you. It does not initialize the variable to anything.
Since objective-c allows you to message nil, you won't crash when you write:
NSString *localentry = rootViewController.entry;
Messaging nil just returns nil. So if rootViewController is nil, then localentry will be nil as well.
Make sure you're actually setting rootViewController for this class. For example,
ParseOperation *myOperation = [[ParseOperation alloc] init];
[myOperation setRootViewController:rootViewController];
Or, make sure you've established the outlet connection in Interface Builder. In any case, I'd suspect rootViewController is nil. (You can test this with NSLog statements).
Are you sure, since it is an IBOutlet, that you connected to it in interface builder?
To answer my own question I just had to connect the viewController and the ParseOperation programmatically by adding the following to my header in the parseOperation:
#class RootViewController;
RootViewController *rootViewController;
#property (nonatomic, retain) IBOutlet RootViewController *rootViewController;
And the following on the m file of the ParseOperation:
#import "RootViewController.h"
rootViewController = [[RootViewController alloc]init];
After that in the parse operation I just declared:
localEntry= rootViewContoller.entry;
Hey,
I have to parse XML in my iOS app. I took Apple's SeismicXML Sample as my base, but I'm experiencing a really strange behaviour.
These are my parser methodes:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:kEntryElementName]) {
Photo *photo = [[Photo alloc] init];
self.currentPhotoObject = photo;
[photo release];
} else if ([elementName isEqualToString:kTitleElementName] ||
[elementName isEqualToString:kLocationElementName] ||
[elementName isEqualToString:kAuthorElementName]) {
accumulatingParsedCharacterData = YES;
[currentParsedCharacterData setString:#""];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:kEntryElementName]) {
NSLog(#"Did End - Titel:%#", self.currentPhotoObject.titleText);
NSLog(#"Did End - Location:%#", self.currentPhotoObject.locationText);
NSLog(#"Did End - Author:%#", self.currentPhotoObject.author);
[self.currentParseBatch addObject:self.currentPhotoObject];
parsedPhotosCounter++;
if ([self.currentParseBatch count] >= kMaximumNumberOfPhotosToParse) {
[self performSelectorOnMainThread:#selector(addPhotosToList:)
withObject:self.currentParseBatch
waitUntilDone:NO];
self.currentParseBatch = [NSMutableArray array];
}
}
else if ([elementName isEqualToString:kTitleElementName]) {
self.currentPhotoObject.titleText = self.currentParsedCharacterData;
}
else if ([elementName isEqualToString:kAuthorElementName]) {
self.currentPhotoObject.author = self.currentParsedCharacterData;
}
else if ([elementName isEqualToString:kLocationElementName]) {
self.currentPhotoObject.locationText = self.currentParsedCharacterData;
}
accumulatingParsedCharacterData = NO;
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (accumulatingParsedCharacterData) {
// If the current element is one whose content we care about, append 'string'
// to the property that holds the content of the current element.
//
[self.currentParsedCharacterData appendString:string];
}
}
Everything works great, the XML Data comes correctly. The parser parses everything as it should.
The problem is in the parser didEndElement methode.
else if ([elementName isEqualToString:kTitleElementName]) {
self.currentPhotoObject.titleText = self.currentParsedCharacterData;
}
When I get "self.currentPhotoObject.titleText" via NSLog, I get the right parsed Data. But then:
else if ([elementName isEqualToString:kAuthorElementName]) {
self.currentPhotoObject.author = self.currentParsedCharacterData;
}
When I get the NSLog of "self.currentPhotoObject.titleText" and from "self.currentPhotoObject.author" here, both give me the author.
In the third parsed methode it is the same. All three properties (titleText, author and locationText) are the locationText.
I have no idea why .titleText e.g. is changed when the parser sets .author.
I have doublechecked everything at least 10 times and compared it to the SeismicXML sample but I can't find the problem.
Please help me. I'm thankfull for every hint !
Greets Sebastian
ps: My properties in the .m file:
#interface ParseOperation () <NSXMLParserDelegate>
#property (nonatomic, retain) Photo *currentPhotoObject;
#property (nonatomic, retain) NSMutableArray *currentParseBatch;
#property (nonatomic, retain) NSMutableString *currentParsedCharacterData;
#end
#implementation ParseOperation
#synthesize photoData, currentPhotoObject, currentParsedCharacterData, currentParseBatch;
It's because you assign same NSMutableString instance to all this properties.
1) Declare author, titleText, locationText properties as copy to avoid this in future.
2) Make a copy each time you want to return value of NSMutableString or assign it to something
self.currentPhotoObject.titleText = [[self.currentParsedCharacterData copy] autorelease];
I am playing around with Dave DeLong's excellent CHCSVParser for Objective-C with an extremely long .CSV file and am running into some trouble using it. I would use the arrayWithContentsOfCSVFile method, but I'm running the code on an iPhone and parsing the whole file into memory would take more memory than is available.
In my code below, the parser opens the document and calls the delegate methods perfectly, but where in the delegate do I stop after each line and access the data (to create and save a Core Data object to the data store)? I assume that would be in - (void) parser:(CHCSVParser *)parser didEndLine:(NSUInteger)lineNumber, but how do I get an NSArray (or whatever) of the data from the parser when it's done with the line?
Here is my code so far:
//
// The code from a method in my view controller:
//
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSFileManager *manager = [NSFileManager defaultManager];
NSError *err = nil;
NSArray *fileList = [manager contentsOfDirectoryAtPath:documentsDirectory error:&err];
NSString *fileName = [fileList objectAtIndex:1];
NSURL *inputFileURL = [NSURL fileURLWithPath: [documentsDirectory stringByAppendingPathComponent:fileName]];
NSStringEncoding encoding = 0;
CHCSVParser *p = [[CHCSVParser alloc] initWithContentsOfCSVFile:[inputFileURL path] usedEncoding:&encoding error:nil];
[p setParserDelegate:self];
[p parse];
[p release];
...
#pragma mark -
#pragma mark CHCSVParserDelegate methods
- (void) parser:(CHCSVParser *)parser didStartDocument:(NSString *)csvFile {
NSLog(#"Parser started!");
}
- (void) parser:(CHCSVParser *)parser didStartLine:(NSUInteger)lineNumber {
//NSLog(#"Parser started line: %i", lineNumber);
}
- (void) parser:(CHCSVParser *)parser didEndLine:(NSUInteger)lineNumber {
NSLog(#"Parser ended line: %i", lineNumber);
}
- (void) parser:(CHCSVParser *)parser didReadField:(NSString *)field {
//NSLog(#"Parser didReadField: %#", field);
}
- (void) parser:(CHCSVParser *)parser didEndDocument:(NSString *)csvFile {
NSLog(#"Parser ended document: %#", csvFile);
}
- (void) parser:(CHCSVParser *)parser didFailWithError:(NSError *)error {
NSLog(#"Parser failed with error: %# %#", [error localizedDescription], [error userInfo]);
}
Thanks!
I'm glad to see that my code is proving useful! :)
CHCSVParser is similar in behavior to an NSXMLParser, in that every time it finds something interesting, it's going to let you know via one of the delegate callbacks. However, if you choose to ignore the data that it gives you in the callback, then it's gone. These parsers (CHCSVParser and NSXMLParser) are pretty stupid. They just know the format of the stuff they're trying to parse, but don't really do much beyond that.
So the answer, in a nutshell, is "you have to save it yourself". If you look at the code for the NSArray category, you'll see in the .m file that it's using a simple NSObject subclass as the parser delegate, and that subclass is what's aggregating the fields into an array, and then adding that array to the overall array. You'll need to do something similar.
Example delegate:
#interface CSVParserDelegate : NSObject <CHCSVParserDelegate> {
NSMutableArray * currentRow;
}
#end
#implementation CSVParserDelegate
- (void) parser:(CHCSVParser *)parser didStartLine:(NSUInteger)lineNumber {
currentRow = [[NSMutableArray alloc] init];
}
- (void) parser:(CHCSVParser *)parser didReadField:(NSString *)field {
[currentRow addObject:field];
}
- (void) parser:(CHCSVParser *)parser didEndLine:(NSUInteger)lineNumber {
NSLog(#"finished line! %#", currentRow);
[self doSomethingWithLine:currentRow];
[currentRow release], currentRow = nil;
}
#end
However, I could be convinced to modify the behavior of the parser to aggregate the row itself, but if I go down that route, why not just have the parser aggregate the entire file? (Answer: it shouldn't)
I tried using this today, based on #DaveDeLong's excellent answer and code, but I think the software has been revised since his (2010) answer. At the time of writing, I found I had to use this:
#interface CSVParserDelegate : NSObject <CHCSVParserDelegate> {
NSMutableArray * currentRow;
}
#end
#implementation CSVParserDelegate
- (void) parser:(CHCSVParser *)parser didBeginLine:(NSUInteger)lineNumber {
currentRow = [[NSMutableArray alloc] init];
}
- (void) parser:(CHCSVParser *)parser didReadField:(NSString *)field atIndex:(NSInteger)fieldIndex {
[currentRow addObject:field];
}
- (void) parser:(CHCSVParser *)parser didEndLine:(NSUInteger)lineNumber {
NSLog(#"finished line! %#", currentRow);
[self doSomethingWithLine:currentRow];
[currentRow release], currentRow = nil;
}
#end
i.e., parser:didStartLine:lineNumber: has become parser:didBeginLine:lineNumber: and parser:didReadField: has become parser:didReadField:atIndex:.
To use CHCSVParser with Swift you can use a swift wrapper for basic needs