Memory management issues when passing items between arrays - iphone

I'm attempting to move a group of strings between three different places by calling this function getNextRandomItem. This works the first time I call it, but then I get an access error the second time. Can you explain what I'm doing wrong?
New Items:binNew (NSMutable Array) -> Current Item (NS String) -> Old Items:binOld (NSMutable Array)
-(NSString *) getNextRandomItem {
if (binNew.count > 0){
if (currentItem) {
[binUsed addObject:currentItem];
}
int r = floor(arc4random() % binNew.count);
currentItem = [binNew objectAtIndex:r];
[binNew removeObjectAtIndex:r];
return currentItem;
}
return #"No more items!";
}

You have to retain r when it is removed from the array. Now you remove it and set its pointer to currentItem. The retain count should become 0 so that probably will cause the error. You have two options.
1) Add a property in your interface:
#property (retain) NSString *currentItem;
and add this to your implementation
#synthesize currentItem;
then use
self.currentItem = ...
instead of
currentItem = ...
2) Use correct memory management. See altered code:
-(NSString *) getNextRandomItem {
if (binNew.count > 0){
if (currentItem) {
[binUsed addObject:currentItem];
[currentItem release]; //See here
currentItem = nil; //See here
}
int r = floor(arc4random() % binNew.count);
currentItem = [[binNew objectAtIndex:r] retain]; //See here
[binNew removeObjectAtIndex:r];
return currentItem;
} else {
[currentItem release];
currentItem = nil;
}
return #"No more items!";
}
Note currently you never set your currentItem to nil. You should do that.

This line
currentItem = [binNew objectAtIndex:r];
should be
currentItem = [[[binNew objectAtIndex:r] retain] autorelease];
When you remove object from container with [binNew removeObjectAtIndex:r], it receives release message and without retain-ing it, you get an invalid pointer (because it points to released object).

-(NSString *) getNextRandomItem {
if (binNew.count > 0){
if (currentItem) {
[binUsed addObject: currentItem];
[currentItem release]; //here
currentItem = nil;//not really necessary
}
int r = floor(arc4random() % binNew.count);
currentItem = [[binNew objectAtIndex:r] retain]; //and here
[binNew removeObjectAtIndex:r];
return currentItem;
}
return #"No more items!";
}

Related

Populating NSDictionary and NSArrays for Model data

I'm trying to create an NSDictionary full of arrays in the implementation file of my model but my code hasn't worked yet. I want to create arrays that are lists of types of dogs and cats and then add those arrays to a dictionary with keys called DOG and CAT. Here is my code:
#implementation wordDictionary
#synthesize catList = _catList;
#synthesize dogList = _dogList;
#synthesize standardDictionary =_standardDictionary;
- (void)setCatList:(NSMutableArray *)catList
{
self.catList = [NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil];
}
- (void)setDogList:(NSMutableArray *)dogList
{
self.dogList = [NSMutableArray arrayWithObjects:#"pit bull", #"pug", #"chihuahua", nil];
}
-(void)setStandardDictionary:(NSMutableDictionary *)standardDictionary
{
[self.standardDictionary setObject: _catList forKey:#"CAT"];
[self.standardDictionary setObject: _dogList forKey:#"DOG"];
}
- (NSString*)selectKey
{
NSInteger keyCount = [[self.standardDictionary allKeys] count];
NSInteger randomKeyIndex = arc4random() % keyCount;
NSString *randomKey = [[self.standardDictionary allKeys] objectAtIndex:randomKeyIndex];
return randomKey;
}
#end
This code is the model. The model is hooked up to my view controller such that when a user taps a button, the NSString returned from randomKey is displayed in a label on the screen. So the text will read either CAT or DOG. Here's the code for that:
- (IBAction)changeGreeting:(UIButton*)sender {
NSString *chosenKey = [self.dictionary selectKey];
NSString *labelText = [[NSString alloc] initWithFormat:#"%#", chosenKey];
self.label.text = labelText;
}
Unfortunately when I tap the button on the simulator I get an error message saying: Thread 1:EXC_ARITHMETIC (code=EXC_1386_DIV, subcode=0x0) at NSInteger randomKeyIndex = arc4random() % keyCount; and it appears that I'm getting it because neither my NSArray nor my NSDictionary have any objects inside of them.
Does anyone have any idea why my NSArray and NSDictionary haven't been populated?
Thanks very much.
The simple answer is that there isn't any code here that calls the methods to set the arrays or dictionary.
But the real underlying issue is that there are a couple of bad 'patterns' going on here that you should fix:
In your setter methods (setCatList:, setDogList:, setStandardDictionary:) you're not setting the properties in question to the values that are passed in. For example, you should be setting catList to the passed in "catList" variable.
- (void)setCatList:(NSMutableArray *)catList
{
if (_catList != catList) {
[_catList release];
_catList = [catList retain];
}
}
Then you should have some kind of "setup" happening, usually in a method in the view controller like viewDidLoad:
[wordDictionary setCatList:[NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil]];
// and more for the other two setters
Alternately, you can set these default values in the init for the wordDictionary class:
- (id)init {
self = [super init];
if (self) {
[self setCatList:[NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil]];
}
return self;
}
The former is better in most cases, but you may have a good reason to pre-populate your model for all instances of the class.
Assuming you called setCatList:, setDogList: and setStandardDictionary: before. Probably that causing is this :
NSString *chosenKey = [self.dictionary selectKey];
change into this :
NSString *chosenKey = [self selectKey];
UPDATE
I'm trying to make your life easier. no need to create your object if you don't need the most.
- (NSMutableArray*)getCatList
{
return [NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil];
}
- (NSMutableArray*)getDogList
{
return [NSMutableArray arrayWithObjects:#"pit bull", #"pug", #"chihuahua", nil];
}
-(NSMutableDictionary*)getStandardDictionary
{
NSMutableDictionary *standardDictionary = [NSMutableDictionary new];
[standardDictionary setObject:[self getCatList] forKey:#"CAT"];
[standardDictionary setObject:[self getDogList] forKey:#"DOG"];
return [standardDictionary autorelease];
}
- (NSString*)selectKey
{
NSMutableDictionary *standardDictionary = [self getStandardDictionary];
NSInteger keyCount = [[standardDictionary allKeys] count];
NSInteger randomKeyIndex = arc4random() % keyCount;
NSString *randomKey = [[standardDictionary allKeys] objectAtIndex:randomKeyIndex];
return randomKey;
}
- (IBAction)changeGreeting:(UIButton*)sender {
// NSString *chosenKey = [self selectKey];
//NSString *labelText = [[NSString alloc] initWithFormat:#"%#", chosenKey];
self.label.text = [self selectKey]; //no need to convert it to NSString again
}
Two things to consider:
I don't see you calling these:
setCatList:(NSMutableArray*)catList;
setDogList:(NSMutableArray*)dogList;
You use self.catList and self.dogList, but neither of those are synthesized, instead you have beatList and meList synthesized
Change the synthesizes to the catList and dogList, and make sure you call the set list methods, and then you should make some progress.

ABRecordCopyValue and ABPropertyID crash

I'm trying to retrieve data from the iPhone address book and I have some problems.
First of all, I have an array of all contacts (self.allContacts):
ABAddressBookRef abRef = ABAddressBookCreate();
self.allContacts = (NSMutableArray*)ABAddressBookCopyArrayOfAllPeople(abRef);
I also have an array of all properties (each property as string) called self.allKeys.
The crash occurs when I try to get the properties using a property from self.allKeys:
NSString *currentRecord = [[NSString alloc] init];
ABRecordRef currentRecordRef;
ABPropertyID currentFieldProperty;
currentRecordRef = (ABRecordRef)[self.allContacts objectAtIndex:i];
currentFieldProperty = (ABPropertyID)[self.allKeys objectAtIndex:j];
currentRecord = (NSString*)ABRecordCopyValue(currentRecordRef, currentFieldProperty);
The problem is that passing currentFieldProperty to ABRecordCopyValue causes a crash.
self.allContacts is an array of all contacts
self.allKeys is an array of all properties (each property as string)
When trying to retrieve a single property from ABRecordCopyValue it causes an EXC_BAD_ACCESS crash.
Thanks!
Set NSZombieEnabled, MallocStackLogging, and guard malloc in the debugger. Then, when your App crashes, type this in the gdb console:
(gdb) info malloc-history 0x543216
Replace 0x543216 with the address of the object that caused the crash, and you will get a much more useful stack trace and it should help you pinpoint the exact line in your code that is causing the problem.
- More thoughts -
If self.allKeys is indeed
"an array of all properties (each property as string)"
then you should probably get the intValue of the array object (property) since an ABPropertyID is just a typedef int32_t. Something like:
currentFieldProperty = (ABPropertyID)[[self.allKeys objectAtIndex:j] intValue];
ABRecordCopyValue(currentRecordRef, currentFieldProperty)
But we would need to see the values in self.allKeys or how it is populated to be sure.
From ABRecordRef Reference and CFTypeRef Reference
ABRecordCopyValue - Returns the value of a record property.
CFTypeRef ABRecordCopyValue (
ABRecordRef record,
ABPropertyID property
);
Parameters
record - The record containing the property in question.
property - The property of record whose value is being returned.
Return Value
The value of property in record.
And:
ABPropertyID - Integer that identifies a record property.
typedef int32_t ABPropertyID;
- And more troubleshooting ideas -
If the above is not the case, then your crash may be caused when you cast CFTypeRef to NSString * in (NSString*)ABRecordCopyValue(currentRecordRef, currentFieldProperty) so here is a little helper function that might solve that:
- (NSString*)stringValueForCFType:(CFTypeRef)cfValue {
NSString *stringValue = nil;
if (!cfValue) return nil;
CFTypeID cfType = CFGetTypeID(cfValue);
if (cfType == CFStringGetTypeID()) {
stringValue = [[(id)CFMakeCollectable(cfValue) retain] autorelease];
} else if (cfType == CFURLGetTypeID()) {
stringValue = [(NSURL*)cfValue absoluteString];
} else if (cfType == CFNumberGetTypeID()) {
stringValue = [(NSNumber*)cfValue stringValue];
} else if (cfType == CFNullGetTypeID()) {
stringValue = [NSString string];
} else if (cfType == AXUIElementGetTypeID()) {
stringValue = [[GTMAXUIElement elementWithElement:cfValue] description];
} else if (cfType == AXValueGetTypeID()) {
stringValue = [self stringValueForAXValue:cfValue];
} else if (cfType == CFArrayGetTypeID()) {
stringValue = [self stringValueForCFArray:cfValue];
} else if (cfType == CFBooleanGetTypeID()) {
stringValue = CFBooleanGetValue(cfValue) ? #"YES" : #"NO";
} else {
CFStringRef description = CFCopyDescription(cfValue);
stringValue = [(id)CFMakeCollectable(description) autorelease];
}
return stringValue;
}
Then do currentRecord = [self stringValueForCFType:ABRecordCopyValue(currentRecordRef, currentFieldProperty)]; and check to make sure self.allKeys has an object at index j and self.allContacts has an object at index i:
NSString *currentRecord = [[NSString alloc] init];
ABRecordRef currentRecordRef;
ABPropertyID currentFieldProperty;
if (self.allContacts.count > i) {
currentRecordRef = (ABRecordRef)[self.allContacts objectAtIndex:i];
if (self.allKeys.count > j) {
currentFieldProperty = (ABPropertyID)[self.allKeys objectAtIndex:j];
currentRecord = [self stringValueForCFType:ABRecordCopyValue(currentRecordRef, currentFieldProperty)];
} else {
NSLog(#"self.allKeys has no value at index (%d): %#", j, [allKeys description]);
}
} else {
NSLog(#"self.allContacts has no value at index (%d): %#", i, [allContacts description]);
}
Edit (regarding the comments):
To convert string of property name to its int value, you need to create the following (this probably is not be the correct order that they need to be in, so NSLog them first to see what order they need to be in):
NSString * const ABPropertyID_toString[] = {
#"kABPersonFirstNameProperty",
#"kABPersonLastNameProperty",
#"kABPersonMiddleNameProperty",
#"kABPersonPrefixProperty",
#"kABPersonSuffixProperty",
#"kABPersonNicknameProperty",
#"kABPersonFirstNamePhoneticProperty",
#"kABPersonLastNamePhoneticProperty",
#"kABPersonMiddleNamePhoneticProperty",
#"kABPersonOrganizationProperty",
#"kABPersonJobTitleProperty",
#"kABPersonDepartmentProperty",
#"kABPersonEmailProperty",
#"kABPersonBirthdayProperty",
#"kABPersonNoteProperty",
#"kABPersonCreationDateProperty",
#"kABPersonModificationDateProperty",
#"kABPersonAddressProperty",
// ... etc
};
- (NSString *)ABPropertyIDToString:(ABPropertyID)propertyVal {
return ABPropertyID_toString[propertyVal];
}
- (ABPropertyID)stringToABPropertyID:(NSString *)propertyString {
int retVal;
for(int i=0; i < sizeof(ABPropertyID_toString) - 1; ++i) {
if([(NSString *)ABPropertyID_toString[i] isEqual:propertyString]) {
retVal = i;
break;
}
}
return retVal;
}
Then pass stringToABPropertyID: the value from the array [self.allKeys objectAtIndex:j] and you will be returned an ABPropertyID:
currentFieldProperty = [self stringToABPropertyID:[self.allKeys objectAtIndex:j]];
I'm posting my earlier comment as an answer:
I would especially give thought to the value of currentFieldProperty - because it is taken out of a dictionary, it is an object but the function is supposed to receive an enumerated value. Maybe try
currentFieldProperty = (ABPropertyID)[[self.allKeys objectAtIndex:j] intValue];
Several problems:
ABPropertyID is not an object type. (ABPropertyID)[self.allKeys objectAtIndex:j] is clearly wrong. I'm not sure how it's even possible to be "an array of all properties (each property as string)". What string are you talking about? Where did you get this string from? If you are talking about the string of the name of the property e.g. #"kABPersonEmailProperty" then it would be pretty much impossible to get the value of it from that.
ABPropertyID values like kABPersonEmailProperty are not constants. They are variables, and they only seem to be initialized after you initialize an address book for the first time.

Why can't a Double value stored as Object in NSUserDefault is not reflected in UITableViewCell?

I am trying to store Double Value in NSUserDefault But though i am able to store it (as my NSLog value shows true value), when I tried to reload UITableView, its Cell value is not updated with current value in userdefault.
This weird behavior happens only when i call my setUserDefaults method from delegate method of UIAlertView
Why such weird behavior happens??
Here is my code:
- (void)setUserDefaults
{
NSLog(#"setUserDefault : empArray: %d, empCount: %d",empArray.count, empCount);
NSMutableDictionary *empData = [[NSMutableDictionary alloc] init];
if (empArray.count>1)
empData = [empArray objectAtIndex:(empCount-1)]; // because empCount starts from 1. and empArray[0] = empCount 1
else
empData = [empArray objectAtIndex:0];
[userDefault setObject:[NSString stringWithFormat:#"%.2f",[empData objectForKey:#"salary"]] forKey:#"salary"];
NSLog(#"setUserDefaults: salary=%.2f",[[empData objectForKey:#"salary"] doubleValue]);
[empData release];
[self.tblView reloadData];
}
Delegate method of UIAlertView goes as below:
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger) buttonIndex
{
if (alertView.tag == 1) // btnRemoveEmpPressed.
{
if(buttonIndex==0)
{
NSLog(#"buttonIndex 0");
}
else
{
NSLog(#"empRemovedPressed OK, buttonIndex 1, empArray Count %d, empCount %d",empArray.count, empCount);
// [empArray removeObjectAtIndex:(empCount)];
empCount -= 1;
lblTitle.text = [NSString stringWithFormat:#"Employee %d",empCount];
[self setUserDefaults];
}
// [tblView reloadData];
}
else if (alertView.tag == 2)
{
if (buttonIndex==1)
{
[self resetUserDefaults];
empCount = 1;
[empArray removeAllObjects];
lblTitle.text = [NSString stringWithFormat:#"Employee %d",empCount];
}
}
}
You should use the setDouble:forKey: method of NSUserDefaults and let it manage the value for you. Also synchronize the NSUserDefaults in order to save the value for later usage.
You aren't using doubleValue when setting user defaults. Either do that or, as herz says, use the setDouble method.
You aren't calling synchronize on your defaults object after updating it
Don't release empData, you didn't retain it
I'm assuming userDefault is set up elsewhere and it is not nil when you are using it?

Change UILabel in loop

I want to change the UILabel after 2 sec in a loop.
But current code change it to last value after loop finished.
- (IBAction) start:(id)sender{
for (int i=0; i<3; i++) {
NSString *tempStr = [[NSString alloc] initWithFormat:#"%s", #" "];
int randomNumber = 1+ arc4random() %(3);
if (randomNumber == 1) {
tempStr = #"Red";
}else if (randomNumber == 2) {
tempStr = #"Blue";
} else {
tempStr = #"Green";
}
NSLog(#"log: %# ", tempStr);
labelsText.text = tempStr;
[tempStr release];
sleep(2);
}
}
Your code updates label to last value only as your function blocks main thread so UI cannot get updated. To solve that problem move your updating code to separate function and call it using performSelector:withObject:afterDelay: method. (or schedule calls using NSTimer)
Possible solution (you will also need to handle the case when user taps your button several times in a row, but that should not be too difficult):
- (IBAction) start:(id)sender{
[self updateLabel];
}
- (void) updateLabel{
static const NSString* allStrings[] = {#"Red", #"Blue", #"Green"};
static int count = 0;
int randomNumber = arc4random()%3;
NSString *tempStr = allStrings[randomNumber];
NSLog(#"log: %# ", tempStr);
labelsText.text = tempStr;
++count;
if (count)
[self performSelector:#selector(updateLabel) withObject:nil afterDelay:2.0];
}
- (IBAction) start:(id)sender{
for (int i=0; i<3; i++) {
int randomNumber = 1+ arc4random() %(3);
NSString *tempStr = #"";
if (randomNumber == 1) {
tempStr = #"Red";
}else if (randomNumber == 2) {
tempStr = #"Blue";
} else {
tempStr = #"Green";
}
[labelsText performSelector:#selector(setText:) withObject:tempStr afterDelay:i * 2]
NSLog(#"log: %# ", tempStr);
}
}
Don't use sleep() to perform actions after a delay, it blocks the whole thread. Use performSelector:withObject:afterDelay: instead. As you are probably running this on the main thread, sleep() will block any UI updates until after the whole loop has run, so the only thing you see is the last update. As a general rule of thumb, assume that the UI doesn't ever get updated until after the app has finished executing your method.
You shouldn't use %s as the format specifier for an NSString, that's the format specifier for a C string. You should use %# instead. In fact, if all you are doing is initialising the string with an NSString literal, there's no need to use initWithFormat at all, you can just use the literal itself.
You've also got big memory problems. At the beginning of the loop, you allocate memory for an instance of NSString that is a single space. You then overwrite the pointer to this memory when you assign to tempStr again, meaning you leak the original allocation of memory. Build and Analyze will find problems like this for you. Then you release tempStr, but as the second assignment to this pointer variable was to an autoreleased NSString, the instance will be released one time too many when the autorelease pool gets drained, which will probably manifest itself as a crash that's impossible to debug a little later in the app.
I'd do something like this:
- (void)showRandomColourAfterDelay {
static NSUInteger count = 0;
switch (arc4random()%3) {
case 0:
labelsText.text = #"Red";
case 1:
labelsText.text = #"Blue";
case 2:
labelsText.text = #"Green";
}
count++;
if (count >= 3) return;
[self performSelector:#selector(showRandomColourAfterDelay) withObject:nil afterDelay:3];
}
In fact, I'd probable use an NSArray to hold the colour strings, but that would involve changes outside of a single method, so I've stuck with your hard-coding approach.

Why doesn't this for loop execute?

I have a picker view controller to select a chemical source and possibly a concentration. If the source doesn't have concentrations, it just presents a single picker. It gets populated by an NSDictionary with source type names as keys and a custom model object I made called Chemical that has four properties, two NSString, one float and one BOOL.
When I trigger this with dictionary that has 2 components, I want to extract the four values from the Chemical that is represented. Note that I populate the picker with values from the first two properties, but not the float or BOOL. I run through the array for the key that's selected in the first component and check the string from the second component against the chemConcentration property from each of the Chemicals in the key/value array. When the chemConcentration matches, I know I have the right Chemical and I can get its properties to send back.
Whew!
The problem is that even though I know I get to the for loop, it seems to get skipped. The NSLog right before it prints, but the one inside doesn't. sourceConstant and sourceIsLiquid stay 0.0 and NO
- (IBAction)selectedSourceButton {
NSLog(#"selectedSourceButton pressed");
NSInteger sourceRow = [picker selectedRowInComponent:kSourceComponent];
NSString *selectedSource = [self.sources objectAtIndex:sourceRow];
NSArray *selectedChemicalGroup = [dictionaryOfSources objectForKey:selectedSource];
NSInteger concentrationRow = [picker selectedRowInComponent:kConcentrationComponent];
NSString *selectedConcentration = [[NSString alloc] init];
float selectedConstant = 0.0;
BOOL selectedIsLiquid = NO;
if (numberOfComponents == 2) {
NSLog(#"numberOfComponents = 2 if/then chosen"); // <-- This prints.
selectedConcentration = [self.concentrations objectAtIndex:concentrationRow];
NSLog(#"begin selectedConcentration for loop. Number of loops = %d", [selectedChemicalGroup count]); // <-- And so does this.
for (int i; i<[selectedChemicalGroup count]; i++) { // <-- But this doesn't seem to fire!
NSLog(#"selectedConcentration = %#, from selectedChemicalGroup = %#", selectedConcentration, [[selectedChemicalGroup objectAtIndex:i] chemConcentration]); // <-- Because this doesn't print.
if ([selectedConcentration isEqualToString:[[selectedChemicalGroup objectAtIndex:i] chemConcentration]]) {
selectedConstant = [[selectedChemicalGroup objectAtIndex:i] chemConstant];
selectedIsLiquid = [[selectedChemicalGroup objectAtIndex:i] chemIsLiquid];
}
}
}
else {
selectedConcentration = #"";
selectedConstant = [[selectedChemicalGroup objectAtIndex:0] chemConstant];
selectedIsLiquid = [[selectedChemicalGroup objectAtIndex:0] chemIsLiquid];
}
NSLog(#"selectedSourceButton source to return = %#, concentration = %#, sourceConstant = %1.7f, isLiquid = %d", selectedSource, selectedConcentration, selectedConstant, selectedIsLiquid);
if ([self.delegate respondsToSelector:#selector (sourcePickerViewController:didSelectSource:andConcentration:andConstant:andIsLiquid:)]) {
[self.delegate sourcePickerViewController:self didSelectSource:selectedSource andConcentration:selectedConcentration andConstant:selectedConstant andIsLiquid:selectedIsLiquid];
}
}
You need to initialize your variable i: for (int i = 0; ...
But there's a better way to do this, using "fast enumeration":
for (MyChemicalGroupClass *group in selectedChemicalGroup) {
if ([selectedConcentration isEqualToString:[group chemConcentration]]) {
...
}
}
Initialize loop count i
for (int i = 0; i<[selectedChemicalGroup count]; i++)
Do the following and you will understand why:
int i;
NSLog(#"%d", i);