So here is the thing. I am trying to build no-ARC/no-Storyboard project without previous experience in manual memory-management.
Full source code available here.
So what I've got here is a
class, which helps me create Products object with custom initialiser
Products.h
#interface Products : NSObject
#property (nonatomic,retain)NSString *productName;
#property (nonatomic,retain)NSString *productDescription;
#property (nonatomic,retain)NSString *productImage;
-(id) initWithName: (NSString *) name
description: (NSString *) description
image: (NSString *) image;
#end
Products.m
#implementation Products
-(id) initWithName:(NSString *)name description:(NSString *)description image:(NSString *)image {
self = [super init];
if (self) {
self.productName = name;
self.productDescription = description;
self.productImage = image;
}
return self;
#end
There you can see ProductParser class, which one contains most of magic
ProductsParser.h
#interface ProductsParser : NSObject <NSXMLParserDelegate>
#property (nonatomic,retain) NSMutableArray *productArray;
-(id) initWithArray:(NSMutableArray *) productArray;
-(void) parseXMLFile;
#end
ProductParser.m
// Creating private properties
#interface ProductsParser()
#property NSXMLParser *parser;
#property NSString *element;
#property NSMutableString *currentProductName;
#property NSMutableString *currentProductDescription;
#property NSMutableString *currentProductImage;
#end
#implementation ProductsParser
-(id) initWithArray:(NSMutableArray *)productArray {
self = [super init];
if (self) {
self.productArray = productArray;
}
return self;
}
-(void) parseXMLFile {
// We will do it here instead of writing that in viewDidLoad
NSURL *xmlPath = [[NSBundle mainBundle]URLForResource:#"productsList" withExtension:#"xml" ];
self.parser = [[NSXMLParser alloc]initWithContentsOfURL:xmlPath];
self.parser.delegate = self;
[self.parser parse];
[self.parser release];
}
-(void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
self.element = elementName;
}
-(void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
string = [string stringByReplacingOccurrencesOfString:#"\n" withString:#""];
string = [string stringByReplacingOccurrencesOfString:#"\t" withString:#""];
string = [string stringByReplacingOccurrencesOfString:#" " withString:#""];
if ([self.element isEqualToString:#"Name"]) {
if (self.currentProductName == nil) {
self.currentProductName = [[NSMutableString alloc] initWithString:string];
} else {
[self.currentProductName appendString:string];
}
}
if ([self.element isEqualToString:#"Description"]) {
if (self.currentProductDescription == nil) {
self.currentProductDescription = [[NSMutableString alloc] initWithString:string];
} else {
[self.currentProductDescription appendString:string];
}
} if ([self.element isEqualToString:#"Image"]) {
if (self.currentProductImage == nil) {
self.currentProductImage = [[NSMutableString alloc] initWithString:string];
} else {
[self.currentProductImage appendString:string];
}
}
}
-(void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"Product"]) {
Products *thisProduct = [[Products alloc] initWithName:self.currentProductName description:self.currentProductDescription image:self.currentProductImage];
[self.productArray addObject:thisProduct];
[self.currentProductName release];
self.currentProductName = nil;
[self.currentProductDescription release];
self.currentProductDescription = nil;
[self.currentProductImage release];
self.currentProductImage = nil;
[thisProduct release];
thisProduct = nil;
[self.element release];
self.element = nil;
}
}
#end
Later on, in the class which is suppose to handle the UITableView I am creating:
#property (retain,nonatomic)NSMutableArray *productArray;
And In viewDidLoad we have this code
self.productArray = [[NSMutableArray alloc] init];
ProductsParser *menuParser = [[ProductsParser alloc] initWithArray:self.productArray];
[menuParser parseXMLFile];
[menuParser release];
All that results in EXC_BAD_ACCESS pointing to
#property (nonatomic,retain)NSString *productName;
I've done some research about that error. Users claims that they successfully getting rid of that error by simply calling properties with self. syntax.(which is suppose to increase retain count?) In my case it is not the issue, as you may see.(Or did I missed something?)
I am also can see in debugger that productArray which is suppose to be populated with my products from .xml file, is in fact populated by trash stuff like #"/n/t" #"/n " #"/n/t" and so on.(why why why?!)
So at least point it to me, where to start. Really appreciate your guys help.
UPDATE 1: Apparently there was some defect logic, I almost got rid of the \n\t stuff by changing code in foundCharacters section.
So now instead of just \n\t \n \n\t I got actual data in my array with those afterwards. Like so:
#"Box of chocolate \n\t\t"
#"Box of chocolate, very tasty \n"
#"ImageName \n\t"
I know that \n should be like new line, and \t is apparently tabulation. Still, don't have a clear idea on how to completely get rid of those and why are they popping up.
UPDATE 2: I manage to get rid of the trashy \n\t from the array by adding stringByReplacingOccurrencesOfString. (code updated)
Related
I have started a Master Detail application and left the generated code untouched. I created and added two additional classes: a book class(contains an NSString for a title, author, and summary) and also a data controller class(contains a mutable array to store the books).
My understanding of #property attributes after reading Apple doc and others is this:
strong - default, creates ownership of an object
weak - alternative to strong, used to avoid retain cycles
copy - creates a copy of the existing object and takes ownership of that
nonatomic - disregards any sort of thread safety
This code throws a segmentation fault in addBookToList when the #property AJKBook is declared with the copy attribute and I don't understand why.
#interface AJKBookDataController ()
// when current book uses the copy attribute code seg faults in addBookToList
#property (nonatomic) AJKBook *currentBook;
#property (nonatomic, copy) NSString *currentValue;
- (void)populateBookList;
- (void)addBookToBookList;
#end
#implementation AJKBookDataController
- (id)init
{
self = [super init];
if (self) {
_bookList = [[NSMutableArray alloc] init];
_currentBook = [[AJKBook alloc] init];
_currentValue = [[NSString alloc] init];
[self populateBookList];
return self;
}
return nil;
}
- (void)setBookList:(NSMutableArray *)bookList
{
// this bit of code ensures bookList stays mutable
if (_bookList != bookList) {
_bookList = [bookList mutableCopy];
}
}
- (void)populateBookList
{
NSURL *url = [NSURL URLWithString:#"https://sites.google.com/site/iphonesdktutorials/xml/Books.xml"];
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[parser setDelegate:self];
[parser parse];
NSLog(#"%#", [self.bookList description]);
}
- (void)addBookToBookList
{
[self.bookList addObject:self.currentBook];
self.currentBook = [[AJKBook alloc] init];
}
...
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:#"title"]) {
// [self.currentBook title:self.currentValue];
self.currentBook.title = self.currentValue;
} else if ([elementName isEqualToString:#"author"]) {
self.currentBook.author = self.currentValue;
} else if ([elementName isEqualToString:#"summary"]) {
self.currentBook.summary = self.currentValue;
} else if ([elementName isEqualToString:#"Book"]) {
[self addBookToBookList];
}
self.currentValue = [NSString stringWithFormat:#""];
}
#end
if you want to use copy for your custom classes you have to implement – copyWithZone: in those classes.
But you don't have to use copy. Often strong is good enough. copy is mostly used for NSString properties because you want to prevent that a NSMutableString is assigned and later changed from outside the class.
You have to think if you really need to copy the current book. If something is named current in my opinion that's a strong indication that you do NOT want to copy. If the only assignment is from [[AJKBook alloc] init]; copy does not make sense at all.
I am trying to grab a token from a website. Upon successful authentication, an XML document would be displayed.
I created a connection as shown below:
NSString *strURLQueryString = [NSString stringWithFormat:#"%#?username=%#&password=%#", kURL_LOGIN, nameString, passwordString];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:strURLQueryString]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
In order to display the output, I used this:
unsigned char byteBuffer[[receivedData length]];
[receivedData getBytes:byteBuffer];
NSLog(#"Output: %s", (char *)byteBuffer);
So some of the output of the returned document is as shown below:
<status>0</status><reason>User fetched.</reason><token>9cb7396dccabe68c067521db219afb83</token>
I have read many XML parsing implementation but I just could not implement it as it does not fulfil my need, and I just could not understand the complexity of its explanation.
Would appreciate if anyone could give me a good advice on how to go about.
- (XMLParserViewController *) initXMLParser {
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
return self;
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict
{
if([elementName isEqualToString:#"Books"]) {
appDelegate.books = [[NSMutableArray alloc] init];
}
else if([elementName isEqualToString:#"Book"])
{
aBook = [[Books alloc] init];
}
NSLog(#"Processing Element: %#", elementName);
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if(!currentElementValue)
currentElementValue = [[NSMutableString alloc] initWithString:string];
else
[currentElementValue appendString:string];
NSLog(#"Processing Value: %#", currentElementValue);
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if([elementName isEqualToString:#"Books"])
return;
if([elementName isEqualToString:#"Book"])
{
[appDelegate.books addObject:aBook];
aBook = nil;
}
else if([elementName isEqualToString:#"name"])
{
aBook.name=currentElementValue;
}
else if([elementName isEqualToString:#"address"])
{
aBook.address=currentElementValue;
}
else if([elementName isEqualToString:#"country"])
{
aBook.country=currentElementValue;
}
currentElementValue = nil;
NSLog(#"%#",aBook.name);
NSLog(#"%#",aBook.address);
NSLog(#"%#",aBook.country);
}
Try this ,, I hope this works....
The relevant NSXMLParserDelegate methods:
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName //etc
{ _inToken = [elementName isEqualToString:#"token"]; }
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName //etc
{ _inToken = NO; }
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (_inToken) {
_token = string;
[parser abortParsing];
}
}
You should use XML parser.
It is easy to use, go all below steps to get best result:
Create XMLParser class subclasses from NSXMLParser, so you have a NSXMLParser.h and NSXMLParser.m class
your .h class must be like this:
#import <Foundation/Foundation.h>
#interface XMLParser : NSXMLParser <NSXMLParserDelegate>{
NSUInteger parsedCounter;
BOOL accumulatingParsedCharacterData;
BOOL didAbortParsing;
}
#property (nonatomic, strong) NSMutableString *currentParsedCharacterData;
#property (nonatomic, strong) NSMutableArray *currentParsedCharacterArray;
#end
And .m class :
#import "XMLParser.h"
#implementation XMLParser
#pragma mark Parser constants
// Limit the number of parsed data to 100.
static const NSUInteger kMaximumNumberOfFilesToParse = 100;
static NSUInteger const kSizeOfFileBatch = 10;
// Reduce potential parsing errors by using string constants declared in a single place.
static NSString * const kEntryElementName = #"dlResult";
static NSString * const kStringElementName = #"string";
#pragma mark NSXMLParser delegate methods
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
// NSLog(#"didStart");
// If the number of parsed earthquakes is greater than kMaximumNumberOfEarthquakesToParse, abort the parse.
if (parsedCounter >= kMaximumNumberOfFilesToParse) {
// Use the flag didAbortParsing to distinguish between this deliberate stop and other parser errors.
didAbortParsing = YES;
[self abortParsing];
}
if ([elementName isEqualToString:kEntryElementName]) {
_currentParsedCharacterArray = [[NSMutableArray alloc]init];
} else if ([elementName isEqualToString:kStringElementName]) {
accumulatingParsedCharacterData = YES;
_currentParsedCharacterData = [[NSMutableString alloc]init];
}
}
// return string between tags
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
// NSLog(#"foundCh");
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.
[_currentParsedCharacterData appendString:string];
NSLog(#"currentParsedCharacterData:%#",_currentParsedCharacterData);
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
// NSLog(#"didEnd");
if ([elementName isEqualToString:kStringElementName]) {
[_currentParsedCharacterArray addObject:_currentParsedCharacterData];
}
accumulatingParsedCharacterData = NO;
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
if (didAbortParsing == NO) {
// Pass the error to the main thread for handling.
[self performSelectorOnMainThread:#selector(handleError:) withObject:parseError waitUntilDone:NO];
}
}
#end
In .m class there are two const string:
static NSString * const kEntryElementName = #"dlResult";
static NSString * const kStringElementName = #"string";
those are string tags,the tags that you should implement are "status", "reason" "token"
from connection class, send data connection to XMLParser class like below:
#autoreleasepool {
// It's also possible to have NSXMLParser download the data, by passing it a URL, but this is not desirable
// because it gives less control over the network, particularly in responding to connection errors.
//
XMLParser *parser = [[XMLParser alloc] initWithData:data];
parser.currentParsedCharacterArray = [NSMutableArray array];
parser.currentParsedCharacterData = [NSMutableString string];
[parser setDelegate:parser];
[parser parse];
// depending on the total number of earthquakes parsed, the last batch might not have been a "full" batch, and thus
// not been part of the regular batch transfer. So, we check the count of the array and, if necessary, send it to the main thread.
if ([parser.currentParsedCharacterArray count] > 0) {
// send parsed data to another class or ...
// parser.currentParsedCharacterArray is parsed data
}
parser.currentParsedCharacterArray = nil;
parser.currentParsedCharacterData = nil;
}
if you have any question, ask me!
i'm new in iOS development, and at this moment i have implemented the NSXMLparser , but i really don't know how to separate tags with the same name, but different content, like the <description>. In some feeds, this tag has only the summary and in other, contains the " img src ", which i want to extract too. (with or without CDATA)
Example of description tags wich i need to grab the images and then pass to my UIImageView:
<description><![CDATA[ <p>Roger Craig Smith and Troy Baker to play Batman and the Joker respectively in upcoming action game; Deathstroke confirmed as playable character. </p><p><img src="http://image.com.com/gamespot/images/2013/139/ArkhamOrigins_29971_thumb.jpg"
<description><img src="http://cdn.gsmarena.com/vv/newsimg/13/05/samsung-galaxy-s4-active-photos/thumb.jpg" width="70" height="92" hspace="3" alt="" border="0" align=left style="background:#333333;padding:0px;margin:0px 4px 0px 0px;border-style:solid;border-color:#aaaaaa;border-width:1px" /> <p>
I think that #Rob example solves my case but i don't know how to include in my NSXMLParser, described below, to separate data and images. I'm able to grab only the data (summary) on this parser.
My NSXMLParser:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
element = [elementName copy];
if ([elementName isEqualToString:#"item"])
{
elements = [[NSMutableDictionary alloc] init];
title = [[NSMutableString alloc] init];
date = [[NSMutableString alloc] init];
summary = [[NSMutableString alloc] init];
link = [[NSMutableString alloc] init];
img = [[NSMutableString alloc] init];
imageLink = [[NSMutableString alloc]init];
}
if([elementName isEqualToString:#"media:thumbnail"]) {
NSLog(#"thumbnails media:thumbnail: %#", attributeDict);
imageLink = [attributeDict objectForKey:#"url"];
}
if([elementName isEqualToString:#"media:content"]) {
NSLog(#"thumbnails media:content: %#", attributeDict);
imageLink = [attributeDict objectForKey:#"url"];
}
if([elementName isEqualToString:#"enclosure"]) {
NSLog(#"thumbnails Enclosure %#", attributeDict);
imageLink = [attributeDict objectForKey:#"url"];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if ([element isEqualToString:#"title"])
{
[title appendString:string];
}
else if ([element isEqualToString:#"pubDate"])
{
[date appendString:string];
}
else if ([element isEqualToString:#"description"])
{
[summary appendString:string];
}
else if ([element isEqualToString:#"media:description"])
{
[summary appendString:string];
}
else if ([element isEqualToString:#"link"])
{
[link appendString:string];
}
else if ([element isEqualToString:#"url"]) {
[imageLink appendString:string];
}
else if ([element isEqualToString:#"src"]) {
[imageLink appendString:string];
}
else if ([element isEqualToString:#"content:encoded"]){
NSString *imgString = [self getImage:string];
if (imgString != nil) {
[img appendString:imgString];
NSLog(#"Content of img:%#", img);
}
}
-(NSString *) getImage:(NSString *)htmlString {
NSString *url = nil;
NSScanner *theScanner = [NSScanner scannerWithString:htmlString];
[theScanner scanUpToString:#"<img" intoString:nil];
if (![theScanner isAtEnd]) {
[theScanner scanUpToString:#"src" intoString:nil];
NSCharacterSet *charset = [NSCharacterSet characterSetWithCharactersInString:#"\"'"];
[theScanner scanUpToCharactersFromSet:charset intoString:nil];
[theScanner scanCharactersFromSet:charset intoString:nil];
[theScanner scanUpToCharactersFromSet:charset intoString:&url];
}
return url;
}
#end
In your example you just have two description elements, each which has the img tag embedded within it. You just parse the description like normal, and then pull out the img tags (using regular expressions, using my retrieveImageSourceTagsViaRegex below, or a scanner).
Note, you do not have to handle the CDATA and non-CDATA renditions differently if you don't want. While NSXMLParserDelegate provides a foundCDATA routine, I'd actually be inclined to not implement that. In the absence of a foundCDATA, the standard foundCharacters routine of NSXMLParser will gracefully handle both renditions of your description tag (with and without CDATA) seamlessly.
Consider the following hypothetical XML:
<xml>
<descriptions>
<description><![CDATA[ <p>Roger Craig Smith and Troy Baker to play Batman and the Joker respectively in upcoming action game; Deathstroke confirmed as playable character. </p><p><img src="http://image.com.com/gamespot/images/2013/139/ArkhamOrigins_29971_thumb.jpg">]]></description>
<description><img src="http://cdn.gsmarena.com/vv/newsimg/13/05/samsung-galaxy-s4-active-photos/thumb.jpg" width="70" height="92" hspace="3" alt="" border="0" align=left style="background:#333333;padding:0px;margin:0px 4px 0px 0px;border-style:solid;border-color:#aaaaaa;border-width:1px" /> <p></description>
</descriptions>
</xml>
The following parser will parse both of those description entries, grabbing the image URLs out of them. And as you'll see, there is no special handling for CDATA needed:
#interface ViewController () <NSXMLParserDelegate>
#property (nonatomic, strong) NSMutableString *description;
#property (nonatomic, strong) NSMutableArray *results;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSURL *filename = [[NSBundle mainBundle] URLForResource:#"test" withExtension:#"xml"];
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:filename];
parser.delegate = self;
[parser parse];
// full array of dictionary entries
NSLog(#"results = %#", self.results);
}
- (NSMutableArray *)retrieveImageSourceTagsViaRegex:(NSString *)string
{
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"(<img\\s[\\s\\S]*?src\\s*?=\\s*?['\"](.*?)['\"][\\s\\S]*?>)+?"
options:NSRegularExpressionCaseInsensitive
error:&error];
NSMutableArray *results = [NSMutableArray array];
[regex enumerateMatchesInString:string
options:0
range:NSMakeRange(0, [string length])
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
[results addObject:[string substringWithRange:[result rangeAtIndex:2]]];
}];
return results;
}
#pragma mark - NSXMLParserDelegate
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
self.results = [NSMutableArray array];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:#"description"])
self.description = [NSMutableString string];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if (self.description)
[self.description appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:#"description"])
{
NSArray *imgTags = [self retrieveImageSourceTagsViaRegex:self.description];
NSDictionary *result = #{#"description": self.description, #"imgs" : imgTags};
[self.results addObject:result];
self.description = nil;
}
}
#end
That yields the following results (note, no CDATA):
results = (
{
description = " <p>Roger Craig Smith and Troy Baker to play Batman and the Joker respectively in upcoming action game; Deathstroke confirmed as playable character. </p><p><img src=\"http://image.com.com/gamespot/images/2013/139/ArkhamOrigins_29971_thumb.jpg\">";
imgs = (
"http://image.com.com/gamespot/images/2013/139/ArkhamOrigins_29971_thumb.jpg"
);
},
{
description = "<img src=\"http://cdn.gsmarena.com/vv/newsimg/13/05/samsung-galaxy-s4-active-photos/thumb.jpg\" width=\"70\" height=\"92\" hspace=\"3\" alt=\"\" border=\"0\" align=left style=\"background:#333333;padding:0px;margin:0px 4px 0px 0px;border-style:solid;border-color:#aaaaaa;border-width:1px\" /> <p>";
imgs = (
"http://cdn.gsmarena.com/vv/newsimg/13/05/samsung-galaxy-s4-active-photos/thumb.jpg"
);
}
)
So, bottom line, just parse the XML like normal, don't worry about CDATA, and just parse out the image URL using a NSScanner or NSRegularExpression as you see fit.
I am having trouble parsing an XML document to my project. I am getting the following error:
Terminating app due to uncaught exception 'NSUnknownKeyException',
reason: '[ setValue:forUndefinedKey:]: this class is
not key value coding-compliant for the key morph.'
Here is a snippet of my XML document:
<?xml version="1.0" encoding="utf-8"?>
<CAS version="2.1" avatar="anna">
<frames count="111" signCount="3">
<signStart index="0" gloss="mug" />
<frame index="0" isComplete="true" time="0" duration="20" boneCount="74"
morphCount="51">
<morph name="aaa" amount="0" />
<morph name="ooo" amount="0" />
<morph name="pout" amount="0" />
<morph name="eee" amount="0" />
<morph name="cgng" amount="0" />
Here is my code in my XMLParser.h file:
#import <Foundation/Foundation.h>
#class User;
#interface XMLParser : NSObject {
// an ad hoc string to hold element value
NSMutableString *currentElementValue;
// user object
User *user;
// array of user objects
NSMutableArray *times;
NSMutableArray *durations;
NSMutableArray *wheres;
}
#property (nonatomic, retain) User *user;
#property (nonatomic, retain) NSMutableArray *times;
#property (nonatomic, retain) NSMutableArray *durations;
#property (nonatomic, retain) NSMutableArray *wheres;
- (XMLParser *) initXMLParser;
#end
and XMLParser.m
#import "XMLParser.h"
#import "User.h"
#implementation XMLParser
#synthesize user, times, durations, wheres;
- (XMLParser *) initXMLParser {
self = [super init];
if(self){
// init array of user objects
times = [[NSMutableArray alloc] init];
durations = [[NSMutableArray alloc] init];
// wheres = [[NSMutableArray alloc] init];
}
return self;
}
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict {
if(![elementName isEqual:#"frame"])
return;
NSLog(#"user element found – create a new instance of User class...");
user = [[User alloc] init];
//We do not have any attributes in the user elements, but if
// you do, you can extract them here:
NSString *time = [attributeDict objectForKey:#"time"];
NSString *duration = [attributeDict objectForKey:#"duration"];
//NSString *where = [attributeDict objectForKey:#"where"];
[times addObject:time];
[durations addObject:duration];
// [wheres addObject:where];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (!currentElementValue) {
// init the ad hoc string with the value
currentElementValue = [[NSMutableString alloc] initWithString:string];
} else {
// append value to the ad hoc string
[currentElementValue appendString:string];
}
NSLog(#"Processing value for : %#", string);
}
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"CAS"]) {
// We reached the end of the XML document
NSLog(#"names array: %#", times);
NSLog(#"prices array: %#", durations);
// NSLog(#"wheres array: %#", wheres);
return;
}
if ([elementName isEqualToString:#"frame"]) {
// We are done with user entry – add the parsed user
// object to our user array
//[users addObject:user];
//user = nil;
} else {
// The parser hit one of the element values.
// This syntax is possible because User object
// property names match the XML user element names
[user setValue:currentElementValue forKey:elementName];
}
currentElementValue = nil;
}
I assume it has something to do with my xml file, but am not 100% on the exact problem.
Any help would be much appreciated.
Cheers,
Sam
Offhand it looks like your User object doesn't actually have a property called 'morph'. Double check that you spelled it properly, and it's actually the right class at runtime.
Does your user class implement KVO for the key "morph"?
What your User class needs to have to be considered KVC
Can you post the interface and implementation of your User class? That would be handy.
More about implementing KVO
I discovered the problem was due to this line of code:
[user setValue:currentElementValue forKey:elementName];
I have temporarily removed it from my code until I understand why it is make the program throw an exception
I have a couple of classes which functions are execute SQL statements against a WEB service in order to get or set data in the database.
Works fine, but the problem is when I'm testing in instruments/leaks the 90% of the leaks are because these classes.
Could you tell me what I'm loosing?
Thanks.
Here is the code:
Where the data is stored:
.h
#interface iSQLResult : NSObject {
NSMutableArray *Records;
}
#property (nonatomic, assign) int CountX;
#property (nonatomic, assign) int CountY;
#property (nonatomic, retain) NSMutableArray *Columns;
#property (nonatomic, retain) NSMutableArray *Records;
#property (nonatomic, retain) NSMutableArray *FieldsNames;
#property (nonatomic, assign) int ErrorCode;
#property (nonatomic, retain) NSString *ErrorDescription;
-(void)addField:(NSString*)fieldName;
-(void)addRecord:(NSMutableArray*)items;
-(NSMutableArray *)getRecord:(int)y;
-(NSString*)getValue:(int) x posY:(int) y;
-(NSString*)getValueByName:(NSString *) colName posY:(int) y;
-(void)setValueByName:(NSString *) colName posY:(int) y value:(NSString *)value;
-(id) copyWithZone: (NSZone *) zone;
#end
.m
#import "iSQLResult.h"
#import <stdarg.h>
#implementation iSQLResult
#synthesize CountX;
#synthesize CountY;
#synthesize Columns;
#synthesize Records;
#synthesize FieldsNames;
#synthesize ErrorCode;
#synthesize ErrorDescription;
-(id)init
{
self = [super init];
if (self)
{
self.CountX =0;
self.CountY =0;
self.ErrorCode = 0;
self.ErrorDescription = #"";
self.FieldsNames = [NSMutableArray array];
self.Columns = [NSMutableArray array];
self.Records = [NSMutableArray array];
}
return self;
}
-(void)removeRecord:(int)index
{
[self.Records removeObjectAtIndex:index];
self.CountY = self.CountY - 1;
}
-(void)addField:(NSString*)fieldName
{
[self.FieldsNames addObject:[NSString stringWithFormat:#"%#", fieldName]];
self.CountX = self.CountX +1;
}
-(void)addRecord:(NSMutableArray*)items
{
[self.Records addObject:items];
self.CountY = self.CountY +1;
}
-(NSMutableArray *)getRecord:(int)y
{
return [Records objectAtIndex:y];
}
-(NSString *)getValue:(int) x posY:(int)y
{
return [[NSString stringWithFormat:#"%#", [[Records objectAtIndex:y] objectAtIndex:x]] copy];
}
-(NSString*)getValueByName:(NSString *) colName posY:(int) y
{
int a=0;
for (a=0;a<CountX;a++)
{
if ([[colName uppercaseString] isEqualToString:[[FieldsNames objectAtIndex:a] uppercaseString]])
{
return [[NSString stringWithFormat:#"%#", [[Records objectAtIndex:y] objectAtIndex:a]] copy];
}
}
return #"";
}
-(void)setValueByName:(NSString *) colName posY:(int) y value:(NSString *)value
{
int a=0;
for (a=0;a<CountX;a++)
{
if ([[colName uppercaseString] isEqualToString:[[FieldsNames objectAtIndex:a] uppercaseString]])
{
[[Records objectAtIndex:y] replaceObjectAtIndex:a withObject:value];
}
}
}
-(void)dealloc
{
[Columns release];
[Records release];
[FieldsNames release];
[ErrorDescription release];
[super dealloc];
}
-(id) copyWithZone: (NSZone *) zone
{
iSQLResult *SQLRCopy = [[iSQLResult allocWithZone: zone] init];
[SQLRCopy setCountX:self.CountX];
[SQLRCopy setCountY:self.CountY];
[SQLRCopy setRecords:[self.Records mutableCopyWithZone:zone]];
[SQLRCopy setColumns:[self.Columns mutableCopyWithZone:zone]];
[SQLRCopy setFieldsNames:[self.FieldsNames mutableCopyWithZone:zone]];
[SQLRCopy setErrorCode:self.ErrorCode];
[SQLRCopy setErrorDescription:[self.ErrorDescription copyWithZone:zone]];
return SQLRCopy;
}
#end
The class who ask for the data to the web service comunication class and gets a xml structure:
.h
#import <Foundation/Foundation.h>
#import "iSQLResult.h"
#import "IM2_WebServiceComm.h"
#interface iSQL : NSObject <NSXMLParserDelegate> {
iSQLResult *SQLR;
IM2_WebServiceComm * WSC;
NSXMLParser *xmlParser;
BOOL Found;
BOOL FieldsReaded;
BOOL loadFieldsNow;
BOOL loadErrNumNow;
BOOL loadErrDesNow;
NSString *chunksString;
NSMutableArray *tempRecord;
}
#property (nonatomic, retain) NSString *URLConnection;
-(void)SQLReader:(NSString*)SQLString;
-(void)SQLExec:(NSString*)SQLString;
-(void)setURLConnection:(NSString *) WebSURL;
-(iSQLResult*) getSQLR;
-(void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName;
-(void) parser:(NSXMLParser *) parser foundCharacters:(NSString *)string;
-(void) parser:(NSXMLParser *) parser didStartElement:(NSString *) elementName namespaceURI:(NSString *) namespaceURI qualifiedName:(NSString *) qName attributes:(NSDictionary *) attributeDict;
#end
.m
#import "iSQL.h"
#implementation iSQL
#synthesize URLConnection;
- (iSQLResult*)getSQLR
{
return [SQLR copy];
}
-(void)SQLExec:(NSString*)SQLString
{
FieldsReaded = NO;
Found = NO;
loadFieldsNow = NO;
if (SQLR)
{
[SQLR release];
SQLR = nil;
}
SQLR = [[iSQLResult alloc] init];
WSC = [[IM2_WebServiceComm alloc] init];
[WSC setURL:URLConnection];
NSString *theXML = [WSC callMethod:#"ExecNonQuery" sendInstruction:SQLString];
#try
{
xmlParser = [[NSXMLParser alloc] initWithData:[theXML dataUsingEncoding:NSUTF8StringEncoding]];
[xmlParser setDelegate: self];
[xmlParser setShouldResolveExternalEntities:NO];
if(![xmlParser parse])
{
NSLog(#"ERROR PARSING");
}
[xmlParser release];
}
#catch(NSException * ex)
{
NSLog(#"%#",[[ex reason] UTF8String]);
}
[WSC release];
}
-(void)SQLReader:(NSString*)SQLString
{
FieldsReaded = NO;
Found = NO;
loadFieldsNow = NO;
if (SQLR)
{
[SQLR release];
SQLR = nil;
}
SQLR = [[iSQLResult alloc] init];
WSC = [[IM2_WebServiceComm alloc] init];
[WSC setURL:URLConnection];
NSString *theXML = [WSC callMethod:#"ExecSQL" sendInstruction:SQLString];
#try
{
xmlParser = [[NSXMLParser alloc] initWithData:[theXML dataUsingEncoding:NSUTF8StringEncoding]];
[xmlParser setDelegate: self];
[xmlParser setShouldResolveExternalEntities:NO];
if(![xmlParser parse])
{
NSLog(#"ERROR PARSING");
}
[xmlParser release];
}
#catch(NSException * ex)
{
NSLog([NSString stringWithFormat:#"%#",[[ex reason] UTF8String]]);
}
[WSC release];
}
-(iSQLResult *)getSingleSQLR:(iSQLResult *)SQLSource usingRow:(int)y
{
iSQLResult *SQLRAux = [[[iSQLResult alloc]init]retain];
[SQLRAux setCountX:SQLSource.CountX];
[SQLRAux addRecord:[SQLSource getRecord:y]];
[SQLRAux setFieldsNames:SQLSource.FieldsNames];
return SQLRAux;
}
-(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
NSLog(#"Error on XML Parse: %#", [parseError localizedDescription] );
}
//#pragma XML Parser Delegate Methods
-(void) parser:(NSXMLParser *) parser didStartElement:(NSString *) elementName namespaceURI:(NSString *) namespaceURI qualifiedName:(NSString *) qName attributes:(NSDictionary *) attributeDict
{
if (!chunksString)
{
chunksString = [[NSString alloc] init];
}
chunksString = #"";
if ([elementName isEqualToString:#"ErrCode"])
{
loadErrNumNow = YES;
}
if ([elementName isEqualToString:#"ErrDesc"])
{
loadErrDesNow = YES;
}
if ([elementName isEqualToString:#"Table"])
{
Found = YES;
loadFieldsNow = NO;
tempRecord = [[NSMutableArray alloc] init];
}
if (Found && ![elementName isEqualToString:#"Table"])
{
loadFieldsNow = YES;
if (!FieldsReaded)
{
[SQLR addField:elementName];
}
}
}
-(void) parser:(NSXMLParser *) parser foundCharacters:(NSString *)string
{
chunksString = [chunksString stringByAppendingString:string];
}
-(void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
NSString * finalString;
finalString = [NSString stringWithFormat:#"%#",chunksString];
if (loadErrNumNow)
{
[SQLR setErrorCode:[finalString intValue] ];
loadErrNumNow = NO;
}
if (loadErrDesNow)
{
[SQLR setErrorDescription:finalString];
loadErrDesNow = NO;
}
if (Found)
{
if (loadFieldsNow)
{
[tempRecord addObject:finalString];
}
}
if ([elementName isEqualToString:#"Table"])
{
[SQLR addRecord:tempRecord];
[tempRecord release];
Found = NO;
FieldsReaded = YES;
loadFieldsNow = NO;
}
}
-(void)dealloc
{
if (SQLR)
{
[SQLR release];
SQLR = nil;
}
[URLConnection release];
[super dealloc];
}
#end
This is an absolutely buggy class because the leaks, every access is a leak :(
Any help please?
Here are some things I notice:
-[iSQLResult copyWithZone:]
You're sending the result of mutableCopyWithZone: (which returns a retained object) to the setters, which retain the object again. One way to fix it is to autorelease the copy:
[SQLRCopy setRecords:[[self.Records mutableCopyWithZone:zone] autorelease]]
-[iSQL SQLExec:] and -[iSQL SQLReader:]
The ivars WSC and xmlPareser are alloced then released but not set to nil. This isn't a leak, but you're keeping a reference to a released object. If you dereference that, you'll crash.
-[iSQL getSingleSQLR:usingRow:]
iSQLResult *SQLRAux = [[[iSQLResult alloc]init]retain];
Did you mean autorelease instead of retain there? (And if you did, shouldn't you also autorelease the return in getSQLR, for consistency?)
-[iSQL parser:didStartElement:namespaceURI:qualifiedName:attributes:]
chunksString is alloced (+1 retain), but later in parser:foundCharacters: you're losing the reference (i.e., leaking the string) and replacing it with an autoreleased string.
That's all I notice off hand. It sounds like you're leaking more than just those objects, so I'm guessing you're leaking iSQL and/or iSQLResult objects, too. Remember, when you leak an object, you also leak everything those objects hold a reference to. Even if these classes were leak-free, if the code that's instantiating the classes isn't properly releasing the objects you'll see all the members in these classes leak, too. When fixing leaks, always look for "top-level" objects first.