ABRecordCopyValue and ABPropertyID crash - iphone

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.

Related

Int decimal count in iPhone

I need to know whatever an int64_t has decimals, and how many. This should be placed in if-else-statement. I tried this code, but it causes the app to crash.
NSNumber *numValue = [NSNumber numberWithInt:testAnswer];
NSString *string = [numValue stringValue];
NSArray *stringComps = [string componentsSeparatedByString:#"."];
int64_t numberOfDecimalPlaces = [[stringComps objectAtIndex:1] length];
if (numberOfDecimalPlaces == 0) {
[self doSomething];
} else {
[self doSomethingElse];
}
Your question doesn't make a lot of sense; you are creating the NSNumber object from an int so it will never have decimal places, as an int cannot store them. The reason your code is crashing is that it assumes that the array of components is always at least 2 elements long (as you use objectAtIndex:1).
This is better, though still not that good:
NSString *answer = ...; // From somewhere
NSArray *stringComps = [answer componentsSeparatedByString:#"."];
if ([stringComps count] == 0) {
[self doSomething];
} else if [stringComps count] == 1) {
[self doSomethingElse];
} else {
// Error! More than one period entered
}
This still isn't a very good test as it only tests if a period (.) has been entered, not a valid number.

NSDictionnary : objectForKey return nil, even if the key is in the dictionnary

I'm having a strange problem while using NSDictionnary ...
I'm trying to retrieve an object for a key that is present is the dictionnary with the method objectForKey, but it returns nil instead.
When I print out the whole dictionnary, I can see clearly the key and the value I'm looking for.
Here is the code :
- (MObject *)GetWithMProperty:(MProperty *)prop {
NSLog(#"We search an object for a property named %#", prop.Name);
NSArray *keyArray = [_dict allKeys];
int count = [keyArray count];
for (int i=0; i < count; i++) {
MObject *tmp = [_dict objectForKey:[ keyArray objectAtIndex:i]];
NSLog(#"Key = %# | Object = %d", ((MProperty*)[keyArray objectAtIndex:i]).Name, tmp.GetTypeId);
if (prop == [keyArray objectAtIndex:i])
NSLog(#"Wouhou !");
else
NSLog(#"Too bad :(");
}
return [_dict objectForKey:prop];
}
And the stack trace :
2012-10-29 11:24:07.730 IOS6[1451:11303] We search an object for a property named Value
2012-10-29 11:24:07.730 IOS6[1451:11303] Key = Name | Object = 4
2012-10-29 11:24:07.731 IOS6[1451:11303] Too bad :(
2012-10-29 11:24:07.731 IOS6[1451:11303] Key = Value | Object = 0
2012-10-29 11:24:07.732 IOS6[1451:11303] Too bad :(
It's a bit complicated, I'm using J2ObjC to compile a fully functional Engine, and thus I can't modify the classes MProperty and MObject (used by the Engine).
MProperty doesn't conforms to NSCopying protocol, so I created a classe called IPhoneMProperty that inherits from MProperty and conforms to the protocol.
Here is this class :
#implementation IPhoneMProperty
- (id)initWithMProperty:(MProperty *)prop {
self = [super initWithInt:prop.OwnerTypeId withNSString:prop.Name withInt:prop.TypeId withMBasicValue:prop.DefaultValue withInt:prop.Flags];
return self;
}
- (id)copyWithZone:(NSZone *)zone {
IPhoneMProperty *prop = [[IPhoneMProperty alloc] initWithMProperty:self];
return prop;
}
#end
And the method I use to add object and keys to the dictionnary :
- (void)SetWithMProperty:(MProperty *)prop withMObject:(MObject *)obj {
IPhoneMProperty *tempKey = [[IPhoneMProperty alloc] initWithMProperty:prop];
[_dict setObject:obj forKey:tempKey];
}
I hope it's clear enough, actually it's the only solution I found for the moment, but it doesn't work :(
Can anyone helps me with this ?
Thanks !
The problem exists in the line
if (prop == [keyArray objectAtIndex:i])
Instead, implement isEquals: method in your MProperty class.
-(BOOL)isEquals:(MProperty*)inProp {
if( [inProp.name isEqualToString:self.name] )return YES;
return NO;
}
And, here, instead of the line
if (prop == [keyArray objectAtIndex:i])
use the following line,
if ([prop isEquals [keyArray objectAtIndex:i]])
Have you allocated your _dict object before using?
modify your code as follows & check.
- (void)SetWithMProperty:(MProperty *)prop withMObject:(MObject *)obj {
IPhoneMProperty *tempKey = [[IPhoneMProperty alloc] initWithMProperty:prop];
if(!_dict)
_dict = [[NSMutableDictionay alloc]init];
[_dict setObject:obj forKey:tempKey];
}
Hope it works for you.
Can you try changing the if condition to compare betweenstring values ? Like this :
if ([prop.Name isEqualToString:((MProperty*)[keyArray objectAtIndex:i]).Name])
NSLog(#"Wouhou !");
else
NSLog(#"Too bad :(");
}

How do I get the index of an object in an NSArray using string value?

I want to get the index of an object within the NSMutableArray of categories.
The category object has an attribute "category_title" and I want to be able to get the index by passing the value of category_title.
I have looked through the docs and can't find a simple way to go about this.
NSArray does not guarantee that you can only store one copy of a given object, so you have to make sure that you handle that yourself (or use NSOrderedSet).
That said, there are a couple approaches here. If your category objects implement isEqual: to match category_title, then you can just use -indexOfObject:.
If you can't do that (because the category objects use a different definition of equality), use -indexOfObjectPassingTest:. It takes a block in which you can do whatever test you want to define your "test" - in this case, testing category_title string equality.
Note that these are all declared for NSArray, so you won't see them if you are only looking at the NSMutableArray header/documentation.
EDIT: Code sample. This assumes objects of class CASCategory with an NSString property categoryTitle (I can't bring myself to put underscores in an ivar name :-):
CASCategory *cat1 = [[CASCategory alloc] init];
[cat1 setCategoryTitle:#"foo"];
CASCategory *cat2 = [[CASCategory alloc] init];
[cat2 setCategoryTitle:#"bar"];
CASCategory *cat3 = [[CASCategory alloc] init];
[cat3 setCategoryTitle:#"baz"];
NSMutableArray *array = [NSMutableArray arrayWithObjects:cat1, cat2, cat3, nil];
[cat1 release];
[cat2 release];
[cat3 release];
NSUInteger barIndex = [array indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
if ([[(CASCategory *)obj categoryTitle] isEqualToString:#"bar"]) {
*stop = YES;
return YES;
}
return NO;
}];
if (barIndex != NSNotFound) {
NSLog(#"The title of category at index %lu is %#", barIndex, [[array objectAtIndex:barIndex] categoryTitle]);
}
else {
NSLog(#"Not found");
}
Not sure that I understand the question but something like this might work (assuming the Mutable Array contains objects of Class "Category"):
int indx;
bool chk;
for (Category *aCategory in theArray)
{
chk = ([[aCategory category_title] isEqualToString:#"valOfCategoryTitle"])
if ( chk )
indx = [theArray indexOfObject:aCategory];
}
Try this code much more simpler:-
int f = [yourArray indexOfObject:#"yourString"];

Memory management issues when passing items between arrays

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!";
}

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);