I'm trying to share data between various viewControllers, I'm using a property declared in the appDelegate. I set the property in my first viewController and when I print out the contents, everything looks fine, but after I call a custom class method, the property gets set to some random value that seems to change every time I run the app. See code below:
appDelegate .h
NSDate *lastUpdated;
#property (nonatomic, retain) NSDate *lastUpdated;
viewController .m
AppDelegateClassName *appDelegate = (AppDelegateClassName *)[[UIApplication sharedApplication] delegate];
[appDelegate setLastUpdated:[NSDate date]];
After I set the property, I then call the following custom class method with a reference to the viewController as a parameter:
viewController .m
ForceData *forceData = [[ForceData alloc] init];
[forceData queryServiceWithParent:self];
If I try and display the contents of the appDelegate property within my custom class, it returns a random value. Once the custom class returns to the viewController, the appDelegate property stays as the random value.
I can't see what's going wrong. Can anyone help?
Thanks.
UPDATE
Here is the code within the queryServiceWithParent method:
- (void)queryServiceWithParent:(UIViewController *)controller {
viewController = (ForcesTableViewController *)controller;
responseData = [[NSMutableData data] retain];
NSString *url = [NSString stringWithFormat:#"http://example.com"];
theURL = [[NSURL URLWithString:url] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:theURL];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
I'm still having this problem so any help is much appreciated.
UPDATE:
I've spent some more time looking at this and I can display the contents of lastUpdated anywhere within the queryServiceWithParent (just above) and it displays fine. But if I display the same property in the connectionDidFinishLoading, it's reset. Very stuck with this one!
The way this sounds to me is that your NSDate property is autoreleased at some point, even though your memory management sounds fine the way you describe it.
Can you try to add an extra retain to your NSDate, as in:
appDelegate.lastUpdated = [[NSDate date] retain]
If that helps than we need more information about your code to find out where your memory management has errors. i.e. the complete header and main file of your appDelegate and viewController.
Related
I have no idea where this code is wrong. Please help, it is supposed to read a value from a dictionary and I use the value to call an image. I've tried to read the value as label.text but I got no result.
The only one I can call is from the nslog.
for (id key1 in dictionary)
{
NSMutableString *textnamed = [dictionary objectForKey:key1];
NSMutableString *imageDisplay =[NSMutableString stringWithFormat:#"%#.png",[dictionary objectForKey:key1]];
eyeImageSaved.image = [UIImage imageNamed:imageDisplay];
labelSaved.text = textnamed;
NSLog(#"%#",textnamed);
}
There are a few issues with your code. First of all, you should consider using NSString instead of NSMutableString because it should be faster. Second, why are you putting the dictionary object in a string then calling the dictionary object again in the next line? That first line is unnecessary. It's entirely possible that the dictionary entry is not a string, and that is why you are having issues. You should write it like this NSString *textnamed = (NSString *)[dictionary objectForKey:key1]. Also, imageNamed can only be used for files in the file bundle. Are you sure those pictures are stored there? There are a few other issues you could be having. What exactly is going wrong here?
I solved my problem. There is nothing wrong with my code above, the mistake I made is that the object that is supposed to hold my variable and store in plist as the name of the image became null. So I added the object to appdelegate.h as seen below:
AppDelegate
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
NSString *imageNameHolder;
}
#property (retain, nonatomic) NSString *StorageDecider;
View Controller
- (IBAction)lips2:(id)sender {
imageNameHolder = #"lips_blue";
}
-(void)writeNewPlist
{
AppDelegate* ref = (AppDelegate*) [[UIApplication sharedApplication] delegate];
[dictionary setObject:imageNameHolder forKey:#"image1"];
[dictionary writeToFile:finalPath atomically:YES];
}
nameDataFormArray_ & imageDataFormArray_ are NSMutableArray. Both have string and find image as per string (name of image).
- (void)readInfoFromThePlist
{
NSString* PListPath=[[NSBundle mainBundle] pathForResource:#"dataList" ofType:#"plist"];
NSMutableArray* tempDataList=[[NSMutableArray alloc ] initWithContentsOfFile:PListPath];
for(NSDictionary *dataDict in tempDataList)
{
[nameDataFormArray_ addObject:[dataDict objectForKey:#"name"]];
[imageDataFormArray_ addObject:[dataDict objectForKey:#"imagepath"]];
}
[tempDataList release];
}
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
A brief over view of what I am trying to do.
I am using the tableView:didSelectRowAtIndexPath: method inside my UITableViewController subclass which is catching a row selection from that view like so...
//..... inside tableView:didSelectRowAtIndexPath:
if (indexPath.section == 0) {
//--- Get the subview ready for use
VehicleSearchResponseTableViewController *vehicleSearchResponseTableViewController = [[VehicleSearchResponseTableViewController alloc] initWithNibName:#"VehicleSearchResponseTableViewController" bundle:nil];
// ...
//--- Sets the back button for the new view that loads
self.navigationItem.backBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:#"Back" style: UIBarButtonItemStyleBordered target:nil action:nil] autorelease];
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:vehicleSearchResponseTableViewController animated:YES];
if(indexPath.row == 0) {
vehicleSearchResponseTableViewController.title = #"Mans";
EngineRequests *engineRequest = [[EngineRequests alloc] init];
[engineRequest getMans];
[engineRequest release];
}
if(indexPath.row == 1) {
//.... etc etc
As you can see in this method I set up a few things, pushing the new view onto the viewstack and changing the back buttons text, then I go into catching the different rows and then initiating a method in a subclass of nsobject where I want to have all my connection/request stuff going on.
Inside my NSObject I have several different methods for the different cells that you can select on the UITableViewController, basicly they specify different strings that will then initialize my ASIHTTPRequest wrapper to make a connection to the php script and catch all the data that will come back from the database.. NSObject looks like this.
//.... NSObject.m
- (IBAction) getMans
{
NSString *mansString = [[NSString alloc] initWithString:#"mans.php"];
[self grabURLInBackground:mansString];
[manusString release];
}
//....cont....
//--- Connect to server and send request ---------------->>
- (IBAction)grabURLInBackground:(NSString *)setUrlString
{
NSString *startURL = [NSString stringWithFormat:#"http://127.0.0.1:8888/CodeTest/%#", setUrlString];
NSURL *url = [NSURL URLWithString:startURL];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSString *responseString = [request responseString]; //Pass request text from server over to NSString
NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding]; //Create NSData object for Parser Delegate and load with responseString
NSLog(#"stuff %#",responseData);
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
NSLog(#"%#", error);
}
From here I would like to pass the data I am getting from the requestFinished method back over to the newly pushed UITableView.. However I have an error before I am able to get this far that I need to solve... if I run the simulator and click back and forth between the views (the main UITableViewController with the cells and then the newly popped view where I want to put the data) the application falls over and pops up an error in main.m Thread 1: program receive signal EXC_BAD_ACCESS.. I just don;t know whats causing because from what I can tell my code is not so bad.
Also when I debug my application I notice that once grabURLInBackground method has finished it bounces out back to the getMans method then goes back over to the UITableViewController and continues through the if statements, completely neglecting the requestFinished and requestFailed methods, and I just cannot figure out why.
I guess I am not sure if I am calling the methods and functions I need to use in the right places so if you have any suggestions or answers on how I can improve or if you know where my error is coming form that would be greatly appreciated.
There's a few issues with the code above but I'd guess that your bad access exception is due to the handling of your EngineRequests and use of AsiHttpRequest.
The code here
EngineRequests *engineRequest = [[EngineRequests alloc] init];
[engineRequest getMans];
[engineRequest release];
effectively creates an object then deallocates as soon as getMans has finished running.
Then inside the engineRequest object this code
[request setDelegate:self];
[request startAsynchronous];
requests that AsiHttpRequest notify the almost certainly released object once the request has completed.
There may be other issues at work here but I'd start by restructuring to try to keep this object around until at least after it's received the response from AsiHttpRequest.
Hard to tell from the brief overview, but generally when you bad_access and end up in the main application method, it's usually because you autoreleased something, then released it, and it craps out when the autorelease pool is drained. Might want to turn on NSZombiesEnabled and look for memory problems.
Who does receive your request?
The sender (and receiver) object is engineRequest.
But you release Engine Request in that very moment after you issued the async request (by mens of the getMans Method.
I would suggest that you
1. move the code
vehicleSearchResponseTableViewController.title = #"Mans";
EngineRequests *engineRequest = [[EngineRequests alloc] init];
[engineRequest getMans];
[engineRequest release];
from your UITableViewController's didSelectRowAtIndexPath method to your vehicleSearchResponseTableViewController's viewDidLoad method.
2. to retain your EngineRequests object and keep it in some instance variable within vehicleSearchResponseTableViewControllerand do not release it before the request is completely processed, either successfully or in error.
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"
I have an iPhone app (in XCode 4) that uses the standard Tab Bar Application project template (UITabBarController).
My first view has a UITableView that gets data from a JSON feed. Here's where the controller loads that data:
#import "FirstViewController.h"
#import "SBJson.h"
#import "WheelRecord.h"
#import "WheelTableCell.h"
#import "UIImageView+WebCache.h"
#implementation FirstViewController
#synthesize wheels, wheelsTable;
- (NSMutableArray *) wheels {
if (!wheels) {
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://wheelspotting.com/recent.json"]];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
NSArray *wheelData = [parser objectWithString:json_string error:nil];
self.wheels = [NSMutableArray array];
for (NSDictionary *wheel in wheelData)
{
WheelRecord *currentWheel = [[[WheelRecord alloc] init] autorelease];
currentWheel.title = [wheel objectForKey:#"title"];
currentWheel.smallImage = [wheel objectForKey:#"small_image"];
currentWheel.mediumImage = [wheel objectForKey:#"medium_image"];
currentWheel.largeImage = [wheel objectForKey:#"large_image"];
[self.wheels addObject:currentWheel];
}
}
return wheels;
}
I'm trying to reload that data any time the user selects a different tab and then goes back to the first tab.
I've tried setting wheels to nil in viewDidLoad and viewWillAppear but when I switch tabs and come back to the first tab, it never updates (I make changes to the data to test).
I don't believe that ViewDidLoad will be called again as all the view controllers will be present at all times. I would have thought that viewWillAppear should have been called though.
Silly question, but are you sure the data is not being updated? Perhaps it's just your table view that's not being updated with the new data? You are calling [yourTableView reloadData] somewhere from within your viewWillAppear or viewDidAppear method? Alternatively, pop a [yourTableView reloadData] just before the end of your if code, eg
[self.wheels addObject:currentWheel];
[yourTableView reloadData]; // where yourTableView is the pointer to your table view
}
Tab bar controllers can be awkward things to work with, I know I've personally spent many hours swearing at them. One thing to try, if you've subclassed UIViewController in any way, or are using a UITableViewController or UINavigationController make sure you've set the correct object type in the inspector for relevant tab - I've had no end of problems due to forgetting to do this in the past.
Have you tried [self reloadData]? Considering it's the UITableView FirstViewController you want to update.