I'm trying to write an app that has two scenes in it. The first page is a UITableView that will contain a list of note entries. The second scene has 2 text fields (note summary and note description). I'm entering details on the second scene and then clicking a "Save" button which saves the data:
NoteListViewController.m
- (IBAction)saveAndGoBack:(id)sender {
NSLog(#"NoteDetailViewController.m: %#", NSStringFromSelector(_cmd));
NSString * desc = [[NSString alloc]init];
NSString * detail = [[NSString alloc]init];
desc = _noteTitle.text;
detail = _noteDesc.text;
[[NoteStore sharedStore]createNoteWithDesc:desc AndDetail:detail];
[self.navigationController popViewControllerAnimated:YES];
}
NoteStore is a static sharedStore that I am saving the data into.
NoteStore.m
-(Notes *)createNoteWithDesc:(NSString *)desc AndDetail:(NSString *)detail {
NSLog(#"NoteStore.m: %#", NSStringFromSelector(_cmd));
Notes * newNote = [[Notes alloc]initNoteWithDesc:desc AndDetail:detail];
[self.privateItems addObject:newNote];
return newNote;
}
So, the note is added to an NSMutableArray called "privateItems". I confirmed that the Note object gets added properly.
*****The problem happens when I try to retrieve the Note object (desc and detail) from the privateItems array later on using an accessor method which has a public property in the NoteStore.h file called allItems (it's an NSArray readonly, nonatomic and a copy):
NoteStore.m
-(NSArray *)allItems{
NSLog(#"NoteStore.m: %#", NSStringFromSelector(_cmd));
return [self.privateItems copy];
}
Everytime I try to retrieve it, the first property (desc) comes up as nil while the second property (detail) has the data I saved in the second text field of the second scene. Why is the first field constantly coming up as nil???
Just for clarity, a Note object is declared as follows
#interface Notes : NSObject
// What are the properties of a note?
#property (nonatomic, weak) NSString * noteDesc;
#property (nonatomic, weak) NSString * noteDetail;
#property (nonatomic, weak) NSString * test;
// Designated Initializer
-(instancetype)initNoteWithDesc:(NSString *)desc AndDetail:(NSString *)detail;
#end
Any help would be greatly appreciated.
When you call the designated initialiser you pass in two NSString objects. At this point they are owned by the method where they are created.
When they are assigned to the properties they only have a weak reference and therefor the retain count is not bumped up. Weak references are good for things like delegate objects. In this case you want your objects to stick around, so by declaring them as strong you're saying I want these properties to stick around in memory and take ownership of them.
Related
I create a NSMutableArray that I need as long as my app lives, lets call it suseranArray, just after the #implementation of my main class. This Array will hold several objects of a class called Vassal. A Vassal is simply:
1) A NSMutableString
2) Another NSMutableString
3) A NSMutableArray
4) Another NSMutable Array
Each Vassal created is also needed for the life of the app, and they never change.
These objects are made as (retain) properties in an .h file, synthesized in the .m file, and each given an alloc+init whenever the object Vassal is created during the init function. Each vassal has data filled in and stored in the suzerain Array. the 3rd item always has several elements, and after a bug appeared, I put a line to check if it is ever empty, but it never is, and life is good.
Now, later on when a certain Vassal object is needed, we try to access its 3rd property to fetch the data in there, and sometimes that array empty... I checked to see if it disappeared somehow, but it is always there on the debug, carrying a nice address like 0x2319f8a0 which makes sense since the NSMutableString just above it is at address 0x2319fb40 - (I was expecting 00000000 after a lot of headache). What is happening? I my head, I am creating an RETAINed objects, which retains data put in by default, and that object is put inside another, but somehow the data inside the array vanishes. What possible scenario could lead to this? Thank you for your time :)
Note: the last array currently just holds one item at this stage of development, and curiously enough, that one item is never missing, despite the two arrays being 'brothers'
Vassal.h
#interface Vassal : NSObject
#property (retain) NSMutableString *wordBody;
#property (retain) NSMutableString *wordCode;
#property (retain) NSMutableArray *wordRelations;
#property (retain) NSMutableArray *wordLinks;
#end
Vassal.m
#implementation Vassal:NSObject
#synthesize wordBody;
#synthesize wordCode;
#synthesize wordRelations;
#synthesize wordLinks;
-(NSObject*) init
{
if(self=[super init])
{
wordBody=[[NSMutableString alloc] init];
wordCode=[[NSMutableString alloc] init];
wordRelations=[[NSMutableArray alloc] init];
wordLinks=[[NSMutableArray alloc] init];
}
return self;
}
//Somewhere in Suseran:
-(void)fillStuff
{
...
Vassal *vassal=[Vassal new];
for (int i=0;i<[originalDataString length];i++)
{
...
[vassal.wordRelations addObject:anItem];
...
}
int errorTest=[vassal.wordRelations count];
if (errorTest==0)
{
//breakpoint here. Program NEVER comes here
}
[bigArrayOfVassals addObject:vassal];
}
//these arrays are never touched again but here:
-(void) getVassalstuff:(NSMutableString*)codeOfDesiredVassal
{
Vassal *aVassal;
for (int i=0;i<[bigArrayOfVassals count];i++)
{
aVassal=bigArrayOfVassals[i];
if ([codeOfDesiredVassal isEqualToString:aVassal.wordCode)
{
int errorTest=[aVassal.wordRelations count];
if (errorTest==0)
{
//yay! this breakpoint sometimes is hit, sometimes not,
//depending on code's mood. Why is this happening to me? :,(
}
}
}
}
I see that that you have properties that are mutable (which is itself a bad idea except for specific cases) and that you are retaining them.
Mutability means that if you have set the array as a property based on some other array, and if that original array is changed, the array in your property is also changed. It may be, and I don't know because you haven't shown any code, that you are emptying the original array, and thus changing the array you have as a property
Solutions:
My preferred solution is to use the immutable versions of these classes for your properties; NSString, NSArray and instead of retain use copy
A second solution is to leave the properties as mutable, but write a custom setter for each of them that stores a mutableCopy of the object that you pass in.
In both of these cases, your property will be a copy of the object used to set the property, so that if the object is changed outside of your class it will not affect your class's properties.
edited to add, after a comment
If you declare your property as
#property (copy) NSArray wordRelations;
Then simply writing
vassal wordArray = tempArray;
will do the same thing and is cleaner and more readable..
I tried initializing the array :
In .h file
#property (nonatomic, retain) NSArray *accounts;
In .m file :
#synthesize accounts;
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *arrList = [acAccountStore accountsWithAccountType:accountType];
// This returns array
self.accounts = [NSArray arrayWithArray:arrList]; // I tried debug after
// this and it gives me data in debugger.
// Note array List have 3 data in it.
}
Now On button click i call a method:
- (IBAction) ButtonClicked :(id) sender {
NSLog(#" data : %#",[self.accounts objectAtIndex:0]); // Breaks at this point.
// When i tried with debug it gives me (no Objective-C description available)
}
Is the initialization of array correct Or If the code is not right please let me know.
Main concern is when i do debug in viewDidLoad, the self.accounts show me proper values. But after doing the click event its empty and throws EXEC_BAD_ACCESS error.
Thanks for help in advance
hm looks fine. A couple of questions then:
Where are you calling the self.accounts = [NSArray arrayWithArray:arrList];
I assume that the array is being setup before your button is being pressed?
There's no real reason that arc should be clearing out the variable. Have you set a strong reference to it or a weak one? If you're using self. on a variable, you should have :
#property (nonatomic, strong) NSArray *accounts;
or similar to that in the .h file and then
#synthesize accounts;
in the .m file.
If you've got weak instead of strong then ARC may possibly clear the memory but it still shouldn't.
Update:
Create a property for your account store as well. I had this exact issue recently and this fixed it.
#property (nonatomic, strong) ACAccountStore *accountStore;
Original Answer
Because you're using ARC, you need to change your property declaration from
#property (nonatomic, retain) NSArray *accounts;
to:
#property (nonatomic, strong) NSArray *accounts;
With the latest LLVM compiler, you don't need to synthesize properties either. So you can remove #synthesize accounts.
You should always use defensive coding as well, so in your - buttonClicked: method, you should do:
- (IBAction)buttonClicked:(id)sender {
if (self.accounts) {
NSLog(#"data: %#", [self.accounts objectAtIndex:0]);
}
}
This makes sure that the pointer to the array is valid.
You can also check to make sure an item in an array exists before trying to read it by doing:
- (IBAction)buttonClicked:(id)sender {
if (self.accounts.count > 0)
NSLog(#"data: %#", [self.accounts objectAtIndex:0]);
}
}
This is the code from the class within which I am trying to reference the properties from the other class(BIDDoubleComponentPickerViewController):
BIDDoubleComponentPickerViewController *dummyObject = [BIDDoubleComponentPickerViewController new];
NSString *selectedValue = [dummyObject value];
NSString *selectedSuit = [dummyObject suit];
However, whenever I attempt to access the variables selectedValue and selectedSuit, they claim that they are null, when I know for a fact that value and suit are not null within the other class. Here is the code that sets the properties in BIDDoubleComponentPickerViewController:
NSInteger valueRow = [doublePicker selectedRowInComponent:
kValueComponent];
NSInteger suitRow = [doublePicker selectedRowInComponent:
kSuitComponent];
value=[[NSString alloc]initWithString:[values objectAtIndex:valueRow]];
suit=[[NSString alloc]initWithString:[suits objectAtIndex:suitRow]];
Here is the code for the declaration of the properties:
#property (strong, nonatomic) NSString *value;
#property (strong, nonatomic) NSString *suit;
Anyone have any ideas?
When you create a new view controller, it doesn't load its view immediately. So if the code where you initialize value and suit is located in the init method, then doublePicker is still nil. Otherwise, if that code is located in viewDidLoad or loadView, then it is still not executed when you try to access value and suit using [dummyObject value]; and [dummyObject suit];
Edit: (After OP's comment)
My answer is still correct because in the line:
BIDDoubleComponentPickerViewController *dummyObject = [BIDDoubleComponentPickerViewController new];
you create a new object dummyObject that is different from the view controller where the picker view was visible. So the values value and suit that you are looking for are properties of the other view controller and not the one you just initialized.
To solve your problem, you should not create a new instance of BIDDoubleComponentPickerViewController but retrieve the instance that holds the values you are looking for.
I want to store a list of data records in a NSMutableArray for use in a UITableView. In other languages I would have used a simple 'type' structure to define the record structure but I understand the way to do this in Obj-C is to define a new class. I've done this as follows :
#interface CustSuppListItem : NSObject
#property (nonatomic, copy, readwrite) NSString *acCode;
#property (nonatomic, copy, readwrite) NSString *acCompany;
#property (nonatomic, copy, readwrite) NSString *acContact;
#property (nonatomic, assign, readwrite) double osBalBase;
#property (nonatomic, assign, readwrite) unsigned int acAccStatus;
#end
#implementation CustSuppListItem
#synthesize acCode, acCompany, acContact, osBalBase, acAccStatus;
#end
In the viewDidLoad of my UITableViewController I instantiate the array :
tableListDataArray = [[NSMutableArray alloc] init];
Once I have retrieved my data, I add it to the array as follows :
CustSuppListItem *custSuppItem = [[CustSuppListItem alloc] init];
[custSuppItem setAcCode:[jsonCustSuppRecord getStringForKey:#"acCode"]];
[custSuppItem setAcCompany:[jsonCustSuppRecord getStringForKey:#"acCompany"]];
[custSuppItem setAcContact:[jsonCustSuppRecord getStringForKey:#"acContact"]];
[custSuppItem setOsBalBase:[jsonCustSuppRecord getDoubleForKey:#"osBalBase"]];
[custSuppItem setAcAccStatus:[jsonCustSuppRecord getIntForKey:#"acAccStatus"]];
[tableListDataArray addObject:custSuppItem];
[custSuppItem release];
In my table cellForRowAtIndexPath method, I retrieve the data for the current cell as follows:
CustSuppListItem *listDataRecord = [tableListDataArray objectAtIndex:indexPath.row];
[cell.lblCompanyName setText:listDataRecord.acCompany]; // EXC_BAD_ACCESS here
[cell.lblAcCodeContact setText:[NSString stringWithFormat:#"%#, %#",
listDataRecord.acCode, listDataRecord.acContact]];
[cell.lblBalance setText:[Utils fmtNumber:listDataRecord.osBalBase withDecPlaces:2]];
[cell.lblStatus setText:[Utils exchAccStatusDesc:listDataRecord.acAccStatus]];
return cell;
In the dealloc method for the view controller I release the NSMutableArray :
[tableListDataArray release];
I'm very new to Obj-C so it would be great if somebody could confirm everything I've done so far makes sense and is in order. I am getting an intermittent EXC_BAD_ACCESS error when trying to read the acCompany property (see comment next to line) so something must not be right.
Any help appreciated,
Jonathan
All your code looks reasonable and correct to me at first glance.
A few things that I would look at are:
Confirm that cell definitely has a property lblCompanyName. If you're trying to assign to a property that doesn't exist then you will get this type of error. Have you defined a custom cell object type?
Confirm that it is always the acCompany property that is causing the EXC_BAD_ACCESS, and not just any property on the object. One way to do this would be to change the ordering of the lines in the cellForRowAtIndexPath method.
Confirm that the listDataRecord that's causing the crash is getting populated correctly in the first place. In other words, confirm that your jsonCustSuppRecord is always valid. What does jsonCustSuppRecord getStringForKey: return if the key doesn't exist in the jsonCustSuppRecord?
Set a breakpoint at this line: [tableListDataArray addObject:custSuppItem]; and examine the contents of the custSuppItem each time (this is an extension of point 3. above)
I am new here, and already searched related articles like "Is “self” necessary?" and "Setting an Objective-C class property without using a self reference" However i still can't get a clear answer which can explain my case.
I have a simple class and my UI has two textfield and one button, here is the code:
#interface testViewController : UIViewController {
NSString *teststring_A;
NSString *teststring_B;
IBOutlet UITextField *textfield_1;
IBOutlet UITextField *textfield_2;
}
#property (nonatomic, retain) NSString *teststring_A;
#property (nonatomic, retain) NSString *teststring_B;
- (IBAction)action1:(id)sender;
- (IBAction)action2:(id)sender;
#end
#implementation testViewController
#synthesize teststring_A;
#synthesize teststring_B;
- (void)dealloc {
[super dealloc];
}
- (IBAction)action1:sender
{
teststring_A = textfield_1.text ;
NSLog(#"teststring_A in action 1 is : %#\n", teststring_A);
teststring_B = textfield_2.text ;
NSLog(#"teststring_B in action 1 is : %#\n", teststring_B);
}
- (IBAction)action2:(id)sender
{
NSLog(#"teststring_A in action 2 is : %#\n", teststring_A);
NSLog(#"teststring_B in action 2 is : %#\n", teststring_B);
}
the output is :
2010-11-19 15:32:14.827 test[419:207] teststring_A in action 1 is : 123
2010-11-19 15:32:14.829 test[419:207] teststring_B in action 1 is : 456
2010-11-19 15:32:14.927 test[419:207] teststring_A in action 2 is : 123
2010-11-19 15:32:14.929 test[419:207] teststring_B in action 2 is : {(
>
)}
And when click button, it triggers action1 first then action2. My problem is... in action2, the value of teststring_B becomes incorrect, sometimes the application even crashes. What confuses me is (1) why is the value of teststring_A correct??? (2) teststring_B is assigned by textfield_2.text which is not created with 'alloc', so suppose the pointer should exist all the time. then why teststring_B's value becomes incorrect in action2 ??? (3) in dealloc, I should release teststring_A and teststring_B, right? (i think so )
All I know is if I add 'self.', like 'self.teststring_B = textfield_2.text;' then there won't be problem. the value will be correct. So I would like to know the technical reason.
You are confusing variables and properties. Properties are backed by variables, but in reality they are methods.
Here, you define a property named teststring_B which retains anything that gets assigned to it (and releases the old value). The equivalent methods would look like this (simplified):
- (NSString *)teststring_B {
// Return the content of the variable teststring_B.
return teststring_B;
}
- (void)setTeststring_B:(NSString *)val {
// Retain new value.
[val retain];
// Release old value in variable teststring_B
[teststring_B release];
// Assign the new, retained value to variable teststring_B
teststring_B = val;
}
You can now use the property in two ways: either with [self setTeststring_B:foo]; or with self.teststring_B = foo;. The important point is that the later is just a convenient way of writing, the compiler will translate it into the first form, that is the compiler will turn the self.foo = bar; lines into [self setFoo:bar];.
Now that we have this explained, on to your crash: you've got a string value which is most likely autoreleased. Now you just plain assign it to the variable teststring_B, not the property. And you forgot to retain the value. The property would have retained that value for you.
Now the assigned value was autoreleased (it didn't know you've got a variable still pointing to it) and later a new object came to life at the exact same memory location (if you're lucky). In any case, the teststring_B variable is now not pointing to the text as you thought it would, but to some random object (or to garbage).
There are two ways to fix this:
// First retain, then release; it might be the same object
// and if you would release it first its retain count might
// drop to 0 and get cleaned up before you can retain it again.
NSString *tmp = [textfield_2.text retain];
[teststring_B release];
teststring_B = tmp;
// Better !
self.teststring_B = textfield_2.text;
Accessing the variable directly without using the self will not retain it. So when you accessing it later the variable got auto-released and makes your application crash.
so you can write
1) [self setTeststring_B:textfield_2.text]; or
2) the dot syntax self.teststring_B = textfield_2.text; or
3) teststring_b = [textfield_2.text retain]
What you are doing now is simple assignment. That can cause a crash if the object teststring_A or teststring_B point to is deallocated; this is also called a dangling reference.
The reason only simple assignment is happening is because you aren't accessing the setters through the #property semantics; you could get a retain on those NSString objects by doing self.teststring_A = textfield_1.text instead.
However, you should be using copy with NSString properties. See: NSString property: copy or retain?
In other words, you want this:
#property (nonatomic, copy) NSString *teststring_A;
#property (nonatomic, copy) NSString *teststring_B;
and this:
self.teststring_A = textfield_1.text ;
self.teststring_B = textfield_1.text ;