property loses its value (long but easy to understand) - iphone

In my app, I have:
car.h
#interface car : NSObject
{
NSString *model;
NSString *price;
// others atributes
}
#property(nonatomic, retain) NSString *model;
#property(nonatomic, retain) NSString *price;
...
myshop.h
#import "car.h"
#interface myshop : UIViewController...
{
car *mycar;
}
#property(nonatomic, retain) car *mycar;
...
myshop.m
...
-(void) viewDidLoad
{
...
mycar = [[car alloc] init];
}
so, I have a method that shows a popover, where I can select a car from a tableview. This popover callback a method in the myshop.m, using delegate, where I assign a value to mycar.model, and call the method doA above, all of this works fine, and shows the value of mycar.model in Output:
-(void) doA
{
NSLog(#"car = %#", mycar.model );
...
}
But... now it is the problem: I have a buttom in the myshop view. When I press this button, the action shows an alert view (there is the delegate in .h). The return of this alert calls:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
[self doA];
...
}
And the program crash when doA tries to write mycar.model to Output. No error is showing in Output View of Xcode. In the code, it shows: Thread 1: Program received signal: EXEC_BAD_ACCESS.
I can show as many cars I want from popover view, but when press the button, and the AlertView closes, the program crashes.
Just for test, I call [self doA] in other methods that runs from another class via delegate and always the app craches.
Any idea, what is wrong?
Completing the code
Is there any error here? (this is a method of car.m)
- (void) setValues: (NSDictionary *) data
{
model = [data objectForKey:#"model"];
price = [data objectForKey:#"price"];
...
I don't initialize the properties (model, price, ...) anywhere in code.
SOLVED !!!
It was a memory management problem!
I change
model = [data objectForKey:#"model"];
by
model = [[NSString alloc] initWithFormat:#"%#", [data objectForKey:#"model"]];
I hope that I am right now! At least the app seens to work well!
Thanks a lot friends !!!

You have got a memory management bug somewhere in your code. I don't know where the bug is, it almost certainly isn't in the code you posted (it all looks perfect to me).
Unless you post more code, we can't help you fix this one.
I strongly recommend you enable ARC. It's fairly new, but nowadays it's old enough everyone should start using it.
There's a very good chance your bug will simply go away if you turn ARC on.

The code you posted looks fine, but somewhere else you must be over-releasing mycar. Profile it with the "zombies" instrument, and it will tell you where it is being released.

Related

Sharing data/string with a singleton between views

I'm trying to share a string between two views on my iPhone project. It currently works if I use the actual #"something here" for the string, but if I want to use something like label.text, it doesn't even though it is still a string.
I'll show you what I have to make it clearer.
First View: Info_ViewController.h
#import <UIKit/UIKit.h>
#interface Info_ViewController : UIViewController {
IBOutlet UITextField *locationField;
}
#property (nonatomic, retain) NSString *locationString;
+ (id)sharedInfoVC;
#end
First View: Info_ViewController.m
#import "Info_ViewController.h"
static Info_ViewController *sharedInfoVC = nil;
#implementation Info_ViewController
#synthesize locationString;
#pragma mark Singleton Methods
+ (id)sharedInfoVC {
#synchronized(self) {
if (sharedInfoVC == nil)
sharedInfoVC = [[self alloc] init];
}
return sharedInfoVC;
}
- (id)init {
if (self = [super init]) {
locationString = [[NSString alloc] initWithString:locationField.text]; //This is there part I mentioned earlier, when using #"something" instead of locationField.text works.
}
return self;
}
Second View: Confirm_ViewController.m
#import "Confirm_ViewController.h"
#import "Info_ViewController.h"
#implementation Confirm_ViewController
- (IBAction)buttonZ:(id)sender
{
Info_ViewController *infoVCmanager = [Info_ViewController sharedInfoVC];
locationLabel.text = infoVCmanager.locationString;
}
I put it under a button for now, but it will eventually be under viewDidLoad.
If you replace locationField.text with a string (#"blahblahblah") it won't crash and works.
When it crashes I get the error: Program received signal: "SIGABRT"
EDIT: I tried changing
initWithString:locationField.text
to
initWithFormat:#"%#",locationField.text
and now it my label in the second view prints "(NULL)"
Thanks for taking the time to give advice, I really appreciate it.
It is an error to pass nil as the format string to -[NSString initWithString].
So how are you passing nil? You actually have two instances of Info_ViewController. You have the one instance which is the normal part of your app, and then you also have a second instance which is your "singleton" (which really isn't a singleton any more).
So in your "singleton" instance, the UITextField is nil (and will always be nil) and so locationField.text is nil and you are passing that to initWithString:, which is a crash. In fact the "singleton" isn't even fully baked as view controller's go.
If you want a singleton to share data elsewhere in your app, it really should not be a Info_ViewController or any type of view controller. It should be of some other class that you use to manage your data. I would create another class and implement that as a singleton.
Hope that helps you understand what's happening here.
Pre-pend "self." to your location string.
self.locationString = [[NSString alloc] initWithString:locationField.text];
From what I understand of your code, you have got the value for locationString when you from the textfield when you initialize the viewController. At this point of time, your textfield would not be visible. After it becomes visible and you enter something, you don't have the code to store it to locationString.
What you should do is wait for Info_ViewController object to be initialized and displayed. Then on the press of some button or some other event, assign locationLabel.text from the locationString or even directly from locationField.text.
I would provide code, but I have no clue as to how you are structuring this. If you still need help, please provide the details.

Object-oriented design question, iPhone

Sorry I'm still a noob and just learning to program as I go and want to start out on the right foot by learning good design up front. I am using the CLLocationManager and MKReverseGecoder to get my location. In my MKReverseGecoderDelegate method, I create my annotation to show on the MKMapView. In my callout, I use a detail disclosure indicator to bring up another UITableView that displays your current address nicely as opposed to looking at the little black callout bubble.
What is a good way for my DetailViewController (the UITableView) to get the data? Do I have my first class have ivars for address, state, zipcode. In my MKReverseGecoderDelegate, set those ivars when I get that information. (The reason I would think I would need ivars is because my method to get that information in the MKReverseGeocoderDelegate is separate from the displayDetailViewController). And then do I have my DetailViewController have those same values, and when I go to display the DetailViewController, set those same variables? It seems redundant.
Any help would be greatly appreciated. Thanks!
One option
Declare custom class inheriting NSObject like
#interface YourClassName : NSObject
{
NSString *address;
NSString *state;
NSString *zipcode;
}
#property(nonatomic, retain) NSString *address;
#property(nonatomic, retain) NSString *state;
#property(nonatomic, retain) NSString *zipcode;
#end
#implementation YourClassName
#synthesize address,state,zipcode;
-(void)dealloc
{
[super dealloc];
[address release];
[state release];
[zipcode release];
}
#end
//Create object of YourClassName and set values
YourClassName *objYourClassName = [[YourClassName alloc] init];
objYourClassName.address = #"YourValue";
objYourClassName.state = #"YourValue";
objYourClassName.zipcode = #"YourValue";
Pass this object to your DetailViewController by one method after creating method like
-(void)setDetailsForDetailViewController:(YourClassName*)pObjYourClassName
{
//self.objOfYourClassName is property declared in your detailviewcontroller.
self.objOfYourClassName = pObjYourClassName; //You can use self.objOfYourClassName to set values in TableViewController.
}
If you stuck any where let me know I would be glad to help you fix that.
If you are doing the reverse geocoding on demand, initialize the DetailViewController with the coordinate of the annotation. Something like this:
- (id)initWithCoordinate:(CLLocation*)location {
if (self = [super initWithNibName:#"DetailController" bundle:nil]) {
self.location = location;
}
return self;
}
This is a common pattern to create the controllers, because it makes it clear for the controller's user that the controller depends on a location parameter. The other alternatives (global variables, or a singleton) are not so clean because they hide information and make the controller harder to understand and unit test.
Then let the controller launch an asynchronous task to do the geocoding, set itself as delegate, and present the information when it's done.

iphone development controls null after ViewDidLoad

I am a newbie to both Iphone dev and obj c. I am trying to call a web service when the view is loaded and display a activity indicator during the time it takes to retrieve result from the web service.
My problem is that once the ViewDidLoad is completed, my activityindicator and labels are getting null. Hence after the web service call I am not able to manipulate any of the controls.
I am not able to understand why this is happening; obviously it is my lack of understanding of the basics. I done a bit of homework on this but I have not reached anywhere. It would very helpful if some could please explain why the controls get null once the viewdidload is complete.
Update: thank you for the answers. On viewdidload, I am doing the following, I am animating an activity indicator and assigning some values to my textview. Then I call a web service. Debugging the code, I realized that once viewdidload is complete and -(void) connection: (NSURLConnection *) connection didReceiveResponse:(NSURLResponse *) is called, my text view/activityindicator gets null.
(void)viewDidLoad {
[activityindicator startAnimating];
if(myarray != NULL) {
myuitextview.text = #""; // displaying some values from array
#try{
// call web service
} #catch (NSException *ns) {
}
}
}
Welcome to Objective-C! :) The start can be a bit rough, but once you get used to it, it's great..
It's hard to answer your question without code, cause variables can "turn null" for many reasons. However since you're a newbie, it probably has to do with properties, retaining, etc.
There are obviously many related posts on the subject. Here's one: Objective-C 101 (retain vs assign) NSString
Basically in your .h file you need:
#import <UIKit/UIKit.h>
#interface Car:NSObject{
NSString *name;
}
#property(nonatomic, retain) NSString *name;
And then in your .m file in viewDidLoad:
self.name = [[[NSString alloc] init] autorelease];
// For strings, can use convinient function:
// self.name = [NSString string];
It's very important to use self.name and not just name as the former calls the property setter method and the latter does not (important for retaining the property correctly).
HTH, It's obviously general, but so is your question :)
Oded.

Passing array between view controllers?

I really need some more help!
I am trying to pass an array from one view controller to another. I think the latter being a 'child' view controller?
My code is as follows:
MainViewController.h:
#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#interface HelloWorldIOS4ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, AVAudioPlayerDelegate> {
NSMutableArray *countProductCode;
UIPopoverController *detailViewPopover;
}
#property (nonatomic, retain) NSMutableArray *countProductCode;
#property (nonatomic, retain) UIPopoverController *detailViewPopover;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
...
#end
MainViewController.m
#import "HelloWorldIOS4ViewController.h"
#import "JSON.h"
#import "PopoverContentViewController.h"
#implementation HelloWorldIOS4ViewController
#synthesize detailViewPopover;
#synthesize countProductCode;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *results = [jsonString JSONValue];
NSLog(#"RETURN: %#", results);
[countProductCode removeAllObjects];
NSArray *products = [results objectForKey:#"items"];
for (NSDictionary *row in products)
{
NSString *code = [row objectForKey:#"ic"];
[countProductCode addObject:code];
}
PopoverContentViewController.countProductCodes = countProductCode;
}
PopoverViewController.h:
#interface PopoverContentViewController : UITableViewController {
NSMutableArray *countProductCodes;
}
#property (nonatomic, retain) NSMutableArray *countProductCodes;
#end
PopoverViewController.m:
#import "PopoverContentViewController.h"
#import "HelloWorldIOS4ViewController.h"
#implementation PopoverContentViewController
#synthesize countProductCodes;
...
I have cut a lot out, but I know from a load of NSLog's dotted around that I get the data back etc, but I cannot pass the array countProductCode to the PopoverViewController's countProductCodes array.
I keep getting
"Accessing unknown 'setCountProductCodes:' class method"
errors.
This may be something really silly that I'm doing but it's driving me crazy!
Can anyone help please?
Thanks
James
Dear James,
I think you would like to have a closer look at the Model-View-Controller paradigm. In your app you are trying to implement some kind of a "super class". Let me explain what that means:
In your MainViewController class which is clearly a controller there is also some part of the model implemented. This is a bad idea, but a very common one to make in the beginning. Maybe I misunderstood your design, but here is how I would implement it:
The Model I would implement a proper model object, which could be in your case as easy as a custom NSObject subclass with a NSMutableArray as a property. In addition this model would have the methods for retrieving data off the internet implemented. That is right: do the networking in the model. You would have to have methods like - (void) refreshProductCode that you would call from your controller. If you want to get really fancy, use an NSOperation to encapsulate the download (then you would use the a synchronous variant of NSURLConnection, because the operation itself is already executed asynchronously) The nice thing would be then if your parsing of the JSON string takes longer, also this is performed in the background and your UI will stay responsive.
So now the model is downloading your stuff - great, but how do I know when it is done? Well you would post a Notification from the model once it is done. What if the download fails? You guessed it right: post a notification that it failed.
The Controller The controller which needs to display data from the model would first to get the model object. In this case the model object is a property on your AppController. The controller then has a property for this model object and retains it, so that the model object does not go away while the controller lives. The controller then also registers for notifications of the model. So how would a typical download work then?
Get an instance of the model object
call -(void) refreshProductCode on the model object
display network activity spinner in status bar and wait for notifications
when the notification came in, on success refresh the UI and on failure restart download or display a note to the user. Also disable the network activity spinner.
How do you shuffle data between view controllers? View controllers should operate a bit like the mafia: every view controller is working on a need-to-know basis. For example if you want a view controller to display the details of your product, you would not pass the model with all your products to the controller. Instead you would have an instance variable on the detail view controller holding only one produce model object, which has all the information like description text, photo etc. The cool thing then is if you ever want to display product information again in you app, you can reuse that view controller, as all it needs is a product model object to do its job.
In your code:
PopoverContentViewController.countProductCodes = countProductCode;
should be:
popoverContentViewController.countProductCodes = countProductCode;
Your instance name should be different from the class name.
In the mainViewController class, in the following method
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
you are accessing the "countProductCodes" using class name. You should access using its object.
like
PopoverContentViewController *obj = [[PopoverContentViewController alloc] init];
obj.countProductCodes = countProductCodes;
In MainViewController.h:
+(NSMutableArray)arrayRes;
In MainViewController.m
+(NSMutableArray)arrayRes{
return countProductCode;
}
perform any of the code changes on countProductCode array as usually
In PopoverViewController.m
declare #class MainViewController; and in viewDidLoad
NSMutableArray *newArray;
newArray = [MainViewController arrayRes];

Odd Core Data error caused by over releasing?

Occasional reader and first time question asker, so please be gentle :)
I am creating a Managed Object (Account), that is being passed into a child view controller where its being set in a property that is retained.
Account * account = [[Account alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
AddAccountViewController *childController = [[AddAccountViewController alloc] init];
childController.title = #"Account Details";
childController.anAccount = account;
childController.delegate = self;
[self.navigationController pushViewController:childController animated:YES];
[childController release];
[account release];
The view controller interface:
#interface AddAccountViewController : UIViewController {
}
#property (nonatomic, retain) IBOutlet UITextField * usernameTextField;
#property (nonatomic, retain) IBOutlet UITextField * passwordTextField;
#property (nonatomic, retain) Account * anAccount;
#property (nonatomic, assign) id <AddAccountDelegate> delegate;
- (IBAction)cancel:(id)sender;
- (IBAction)add:(id)sender;
- (IBAction)textFieldDone:(id)sender;
#end
So in code sample 1 I've released the account object because I am no longer interested in it in that method. As it is retained by the AddAccountViewController I have an entry in AddAccountViewController's dealloc that releases it.
However when I go to delete the object from the ManagedObjectContext the app crashes with the following (rather unclear) error:
Detected an attempt to call a symbol in system libraries that is not present on the iPhone:
_Unwind_Resume called from function _PFFaultHandlerLookupRow in image CoreData.
After much debugging & hair pulling I discovered that if I don't release account in AddAccountViewController's dealloc method the app works properly continually and doesn't appear to leak according to Instruments.
Can anyone shed any light as to whats going on? I understand from the docs on properties that those retained need to be released. What have I missed?
Update to answer Kevin's question
The code to delete the object from the ManagedObjectContext is in the RootViewController (that holding the child controller)
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object for the given index path
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
Firstly: It sounds like a bug on Apple's part. Core Data is calling _Unwind_Resume, which is (probably) some sort of exception unwind. Exception-unwinding exists on the phone, but (I think) uses the ARM ABI, which uses function names beginning with __cxa_. Are you running on the simulator? Which version of the SDK?
There might be an extra release floating around somewhere which is "balanced" when you remove the call to [account release];.
"Instruments doesn't show any leaks" doesn't mean there aren't any; last I checked it got confused by cycles (i.e. it wouldn't show a leak if you forgot to un-set IBOutlets in dealloc). I tested with NSMutableData * d = [NSMutableData dataWithLength:1<<20]; memcpy(d.mutableBytes, &d, 4);, but an easier test is just [[UIView alloc] initWithFrame:CGRectZero].
If you think it's a retain/release issue, I once debugged these by overriding retain/release/autorelease to call NSLog. I then added breakpoints on all of them, set them to run the command "bt", and clicked the autocontinue. Then run the thing that breaks (in my case I think it was just an extra retain), print out the log output,stick it on a whiteboard, and spend half an hour matching retains and releases.
I had a similar issue ending in a
"Detected an attempt to call a symbol in system libraries that is not present on the iPhone:
_Unwind_Resume called from function _PFFaultHandlerLookupRow in image CoreData."
error message.
My problem was a wrong "cascading" deletion-rule on a relation in the model. With this rule, my top managed object got deleted but still referenced in the code.
After setting the "delete rule" on this relation to "nulify", everything worked as designed.
--> no core data issue...design issue!
Johnny
When ever you delete any managedobject, system will automatically release all reference related to that object. So there is no need to realese object programatically. Once you delete object there you can not access that object in parent class.