I'm working on a bible iPhone app. Here are the basics of what I'm doing so far:
My XMLParser parses an xml file and creates a Bible object
Specifically, the xml is stored in each Chapter object.
Once parsing is done, the viewController grabs a Chapter from the Bible and displays its innerHtml in a UIWebview.
This works, but whenever I try to access the Bible object outside of -(void)viewDidLoad; it either gives me a BAD_ACCESS error, or the results for what I'm asking for is unreadable. I think this is a memory management problem..
Here's what I'm doing in the viewController
- (void)viewDidLoad
{
[super viewDidLoad];
//Create parser and XML data object.
//Then, parse that data
finalBible = [[Bible alloc]init];
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"KJV" ofType:#"html"];
NSData *myData = [NSData dataWithContentsOfFile:filePath];
//INT I WANTED TO USE TO ITERATE WHEN BUTTON WAS PRESSED TO ADVANCE TO NXT CHAPTER
chapToShow = 2;
parser = [[XMLParser alloc] init];
nsParser = [[NSXMLParser alloc] initWithData:myData];
//set delegate for NSXMLParser
[nsParser setDelegate:parser];
//PARSE THE XML FILE AND BUILD BIBLE OBJECT - PARSED OK!!
if ([nsParser parse]) {
NSLog(#"Parsed with no errors!! :)");
}else{
NSLog(#"Error parsing document");
}
//IT SEEMS MY PROBLEMS MAY BE IN THIS TRADE OFF.
//I WANT TO STORE THE PARSER'S BIBLE IN THIS NEW BIBLE OBJECT.
finalBible = [parser getBible];
//Test querying bible via pullVerse method - IT WORKS!!
NSLog(#"%#",[finalBible pullVerse:#"65.021.000"]);
NSString *firstChap = [[[[finalBible getTestament:0]getBook:#"Genesis"]getChapterWithInt:3]getInnerHtml];
//Try and load Genesis 1 - THIS WORKS!!
NSLog(#"...Loading Genesis 1...");
[bibleView loadHTMLString:firstChap baseURL:nil];
//LOADING THE VERSION WORKS HERE!!
NSLog(#"Version = %#", [finalBible getVersion]);
}
- (IBAction)buttonPressed:(id)sender {
NSLog(#"Now reading chapter %d", chapToShow);
//HERE I'M TRYING TO GET THE BIBLE VERSION BUT THE APP CRASHES AS A RESULT
NSLog(#"Testing the bible: Version = %# \n OK", [finalBible getVersion]);
//NOTE: I've even tried [[parser getBible] getVersion] and it still doesn't work.
// I don't release the parser till the view's dealloc method, so I'm not sure why I
// can't access it here...
}
Of course, I'd be happy to post any other code. I just didn't want to over-stuff the page with code, so I pasted only where I think the problem lies, or at least where it's occurring.
You allocate finalBible, but then you set finalBible = [parser getBible] so the previous allocation is pointless. Also, it appears as though [parser getBible] returns an autoreleased object, so you should call [[parser getBible] retain] to make sure it does not leave memory.
This is exactly what you need to do in your .h file add this line:
#property(nonatomic, retain) Bible finalBible;
Then in your .m file add this line at the top:
//This generates the methods (get, set) for your instance variable
#synthesize finalBible;
Then drop this line:
finalBible = [[Bible] alloc]init];
This is because if you keep it, there will be a memory leak right here:
finalBible = [parser getBible];
Because now you are pointing to a new memory location, and the previous memory location had an object with a retain count of 1 provided by alloc init, and since there will no longer be any reference to this object it will never be released causing a memory leak.
Although if you use this line:
self.finalBible = [parser getBible];
Because that uses the setter, a setter releases the previous value and retains the new one, so there would not be a memory leak, but it would still be pointless to allocate that object.
Since getBible does not have the new, alloc or init keyword it should return an autoreleased object, which is why the object is released in the next run loop (not guaranteed but most likely), which is why you cannot access it outside of the viewDidload() method.
You can resolve this problem by making "finalBible" variable a property of the class using
#property(nonatomic, retain) Bible finalBible; //this is in the .h file
#synthesis finalBible; // at the top of .m file
All the reference to "finalBible" should be made as "self.finalBible"
Related
I have these two buttons hooked up to these two methods (they're nearly identical)
-(void)moveOneImageNewer{
int num = [numberOfImage intValue];
num--;
numberOfImage = [[NSString stringWithFormat:#"%i",num] retain];
//Load the image
[self loadImage];
}
-(void)moveOneImageOlder{
int num = [numberOfImage intValue];
num++;
numberOfImage = [NSString stringWithFormat:#"%i",num];
//Load the image
[self loadImage];
}
If I hit either of them twice (or once each, basically if they get called a total of two times) I get an EXC_BAD_ACCESS. If I throw a retain on: numberOfImage = [[NSString stringWithFormat:#"%i",num]retain] it's fine though. Can someone explain why this is? I did an NSZombie on the instruments and traced it back to this stringWithFormat call. Thanks in advance!
+stringWithFormat: doesn't contain 'new', 'alloc', 'copy', or 'retain', so it should be expected that you have to retain the return value of it if you want the new NSString it creates to stick around.
Edited to include this handy link duskwuff kindly dug up: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-BAJHFBGH
If numberOfImage is a properly declared property, e.g.
#property (copy) NSString *numberOfImage;
and it was properly synthesized (in the #implementation section for the class):
#synthesize numberOfImage;
then you can do:
- (void) moveOneImageNewer
{
self.numberOfImage = [NSString stringWithFormat: #"%i", [self.numberOfImage intValue] - 1];
// Load the image
[self loadImage];
}
The property setter will take care of retaining the string and, if necessary, releasing the previous string.
FWIW, why on earth is numberOfImage a string? Why not a simple int?
numberOfImage is an instance variable or property of your class, right?
You are setting it to a stringWithFormat (which returns an auto-released NSString) without claiming ownership of that object (by calling retain).
If you do not retain it, it will get auto-released before the method is called again (and then the first line will fail, as it tries to access the previously set, now auto-released value).
Consider using properties, they have auto-generated memory management code (including releasing the old NSString when you set the new one).
You haven't retained the string object in "moveOneImageOlder", so that object gets autoreleased at the end of the event cycle and points to nothing. That's why you get the EXC_BAD_ACCESS next time you try to use it.
Use a retain to claim ownership of the NSString. Remember to release when you're done though (you can use properties to help you with this)
-(void)moveOneImageNewer{
int num = [numberOfImage intValue];
num--;
[numberOfImage release];
numberOfImage = [[NSString stringWithFormat:#"%i",num] retain];
//Load the image
[self loadImage];
}
-(void)moveOneImageOlder{
int num = [numberOfImage intValue];
num++;
[numberOfImage release];
numberOfImage = [[NSString stringWithFormat:#"%i",num] retain];
//Load the image
[self loadImage];
}
Add this in dealloc:
- (void)dealloc {
[numberOfImage release];
[super dealloc];
}
Well, the NSString class method "stringWithFormat" returns an autorelease NSString object if I'm right.
So the second call to your method would have numberOfImage pointing to nothing, as the autorelease NSString object it used to be pointing to has already been released and deallocated since you didn't retain it.
The part that is directly causing the crash is [numberOfImage intValue] when you call the method a second time, as you sending a message to an object (pointed to by numberOfImage) that no longer exist.
After a ASIFormDataRequest , i create a temporary NSMutableArray *resultArray from the JSON then add it to a defined NSMutablearray *myData
-(void)viewDidLoad{
myData = [[NSMutableArray alloc] init];
//request that calls gotInfo method
}
-(void)gotInfo:(ASIFormDataRequest *)request{
NSString *responseString = [request responseString];
NSMutableArray *resultArray = [responseString yajl_JSON];
[myData addObject:resultArray];
}
-(IBAction)doSomethingWithData:(id)sender{
//something with myData
}
but when i try to call myData from outside of the gotInfo: method, i get bad access errors and when i inspect myData outside of the method, it shows a kern_protection_failure. So i'm guessing that outside of the method, the resultArray is obviously released, but it's also released from myData since the object inside myData is sharing the same memory location?
I also tried
-(void)gotInfo:(ASIFormDataRequest *)request{
NSString *responseString = [request responseString];
[myData addObject:[responseString yajl_JSON]];
}
How do I preserve myData??
in my header file:
#import <UIKit/UIKit.h>
#class ASIFormDataRequest;
#interface EventsTableController : UITableViewController <UITableViewDataSource>{
NSMutableArray *myData;
}
-(void)gotInfo:(ASIFormDataRequest *)request;
UPDATE:
so in the gbd, the myData is allocated as 0x5e96560 so i did
po 0x5e96560
and then i get the EXC_BAD_ACCESS with the reason being KERN_PROTECTION_FAILURE at address: 0x00000009
but if i do
po [[0x5e96560 objectAtIndex:0] objectForKey:#"key"]
then i get the value! whyyyyyy?
#property(nonatomic,retain) NSMutableArray *myData
and create the object
self.myData = [[NSMutableArray alloc] init];
and
// and i assume your resultArray is a mature NSMutableArray object
[self.myData addObject:resultArray];
The best way of using copy I can think of, is to always set NSString properties to "copy" instead of retain. That way you get more accurate readings from the Leaks instrument if you mess up and forget to release a string an object is holding onto. Other uses of copy need to be more carefully thought out.
NOTE : You are responsible to release myData after no use of that variable.
You dont really have any way to correctly access myData as you declare it as a member inside of EventsTableController, but you dont set the #property for it, and do not synthesize it either. By synthesizing it in your EventsTableController.m file you are telling xcode to generate the getter/setters you need to correctly touch myData, which is where your program seems to be failing. If you do this, this should solve your problem.
-Karoly
Except for the different name of your ivar (mienVar vs. myVar), I don't see a problem. Some other code must be releasing your ivar, or you are accessing it before viewDidLoad has the opportunity to actually create the array (I bet it is the latter).
I think you should put the code in viewDidLoad in your initialization method instead. Don't forget to release the array in dealloc.
You could, of course, also write your own myData getter method, doing lazy initialization, instead of creating it in the init method:
- (NSMutableArray *) myData
{
if (!myData)
myData = [[NSMutableArray alloc] init];
return myData;
}
Note that now, you should access self.myData if you want to use it.
I think the NSString yajl_JSON category can return an array or a dictionary - you might need to inspect the type of the result array on the line below as it may be an NSDictionary:
NSMutableArray *resultArray = [responseString yajl_JSON];
IF you are treating it as an array when its a dictionary that might be causing your problems.
(relevant code from the NSObject+YAJL category below)
YAJLDocument *document = [[YAJLDocument alloc] initWithData:data parserOptions:options error:error];
id root = [document.root retain];
[document release];
return [root autorelease];
(and in YAJLDocument object)
#interface YAJLDocument : NSObject <YAJLParserDelegate> {
(id root_; // NSArray or NSDictionary
When I construct a dynamic URL using NSString stringWithFormat and then use that value in my XML parser I get random crashes. However if I test it with a constant string it works fine...
This is my code for generating the string,
loginURL = [NSString stringWithFormat:#"%#%#",ScriptURLString,#"authenticate"];
Which results in,
http://edms.digistorm.com.au/test/index.php?s=&sc=D41D8CD98F00B204E9800998ECF8427E&m=authenticate
Then I use it in my XML parser,
XMLReturnData = [[NSMutableArray alloc] init];
xml = [[XMLParser alloc]
initWithXMLPath:loginURL
lookForElement:#"Authenticate"
setCallbackObject:self
withSelector:#selector(dataReady)
data:XMLReturnData
];
For some reason this is causing my app to crash. If I use a constant string like,
loginURL = #"http://edms.digistorm.com.au/test/index.php?s=&sc=D41D8CD98F00B204E9800998ECF8427E&m=authenticate";
it works fine...
loginURL is defined as NSString *loginURL; inside my header file for this view.
Any help or guidance would be much appreciated!
Thanks,
Tim
The method you are using to allocate the string is important.
You have two basic ways to allocate your string:
NSString *loginURL = [[NSString alloc] initWithFormat:#"%#authenticate", ScriptURLString];
Compared to:
NSString *loginURL = [NSString stringWithFormat:#"%#authenticate", ScriptURLString];
For the first, Cocoa conventions say that because you caused the object to be created via an alloc message you "own" it and are responsible for releasing it.
For the latter, the convention is that because you caused the object to be created by a class "convenience" method, you do NOT own it and are not responsible for releasing it. The class (here, NSString) has that responsibility which it will discharge through an autorelease pool.
To summarise, when you explicitly create something with an alloc/init, you must release it. When you use a [NSThing thingWithXXX] style method you must not.
This shows your string is get released and when you calls it in XML parser it crashes the app.
actually stringWithFormat gives a autorelease object for string.
So what you need,make your string as retain property inside .h then synthesize it in .m and release it in dealloc method.
and also do this,
in viewDidLoad
NSString *tempString=[[NSString alloc] init]; //using this because your string is retain type so it prevent increment in retain count.
self.loginURL=tempString;
[tempString release];
Now when you use stringWithFormat use like this
self.loginURL = [[NSString stringWithFormat:#"%#%#",ScriptURLString,#"authenticate"] retain];
It solves your problem.
Not to call release on loginURL, because you have'nt alloced it and only iOS have right to destroy it...
Use below code
loginURL = [[NSString alloc] initWithFormat:#"%#%#",ScriptURLString,#"authenticate"];
Once you used loginURL Do'nt forget to call release on it ...
[loginURL release];
Try this, it might help you.
loginURL = [NSString stringWithFormat:#"%#authenticate",ScriptURLString];
I am using two objects i.e., pagesUrl and pagesXmlParser in the code and after their use, im releasing those two objects. Its running well but its showing them in memory leaks. when i tried to verify it, i checked the reference count of those objects and its showing '1' even after the objects are released. can any one help me out how to remove that leak and how to release those particular objects in the following code.
-(void)loadPagesForChapter:(NSString *)path{
// have to parse the pages xml for this chapter
NSURL *pagesUrl = [[NSURL alloc] initFileURLWithPath:[self pagesXmlPath:path]];
NSXMLParser *pagesXmlParser = [[NSXMLParser alloc] initWithContentsOfURL:pagesUrl];
PagesAccumulator *pageLoader = [[PagesAccumulator alloc] init];
pagesXmlParser.delegate = pageLoader;
[pagesXmlParser parse];
// parsing pages done
// get the pages array
self.arrayOfPages = pageLoader.arrayOfPages;
[pageLoader release];
[pagesXmlParser release];
[pagesUrl release];
NSLog(#"pagesurl retain count is:%d",[pagesUrl retainCount]);
NSLog(#"pagesxmlparser retain count is:%d",[pagesXmlParser retainCount]);
}
Thanks in advance for any assistance.
Ignore the retain counts
Just because an object has a retain count > 0 doesn't mean that it's not also autoreleased :)
Your code looks fine to me.
If it's really a leak I would look at your implementation of PageLoader to see if there is a retain in one of the NSXMLParserDelegate methods that's preventing the parser being released (which would cause the URL to leak as well).
Thanks for your help on this one.
I am pulling a NSDictionary from a plist in my main bundle and am having troubles. Here is the code:
- (void)viewDidLoad {
// Pull in FAQ from Plist
NSString *strFAQPlist = [[NSBundle mainBundle] pathForResource:#"FAQs" ofType:#"plist"];
dictFAQList = [[NSDictionary alloc] initWithContentsOfFile: strFAQPlist];
// Create indexed array to hold the keys
arrFAQKeys = [[dictFAQList allKeys] retain];
// Release local vars
[strFAQPlist release];
[super viewDidLoad];
}
I feel like I should release NSString as I have already. The problem is, when I do so, I get an EXC_BAD_ACCESS error. When I comment that release out, everything works fine. Can someone explain to me why this is ocurring?
Thanks in advance!
pathForResource returns an autoreleased NSString.
Only call release if you've called an alloc/init method, copy method or retained it explicitly.
If you didn't create an object directly (or retained it) don't release it.