Browsing an object's fields - objective-c

In Objective C, I have an object e.g. Person with a lot of fields firstName, lastName, phoneNumber, address, city... and so on. These fields types are NSString and any of these could be nil.
Now I want to concatenate my field values in another NSString :
Person *p = ...
NSMutableString *s = [[NSMutableString alloc] init];
for (NSString *field in #[p.firstName, p.lastName, p.phoneNumber,
p.adress, p.city, ....more fields...]) {
if ([field length] > 0) {
[s appendFormat:#"%#\n", field];
}
}
Issue is that this code crash whenever one of the field is nil. I have the exception :
[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object
from objects[0]'
How could I handle simply the case of nil values within my for loop ?

I agree with #TomPace's post, for this small number I would do a simple if/else.
However, there may be times you do need to loop through a list of fields.
It's a bad idea to blindly pull the values into an array as you could be trying inserting nil values into the array. In this case, it would be better to place the field names into a key array as strings and loop through the list using valueForKey: to access the values. I would possibly store the keys list somewhere else where it can be used again.
Person *p = ...
NSMutableString *s = [[NSMutableString alloc] init];
NSArray *keys = #[#"firstName", #"lastName", #"phoneNumber", #"adress", #"city"];
for (NSString *key in keys)
{
NSString *value = [p valueForKey:key];
if ([value length] > 0) {
[s appendFormat:#"%#\n", value];
}
}

Person *person = [[Person alloc] init];
person.firstName = nil;
person.lastName = #"lastName";
NSMutableString *s = [[NSMutableString alloc] init];
[s appendFormat:#"%#\n", person.firstName == nil?#"":person.firstName];
[s appendFormat:#"%#\n", person.lastName == nil?#"":person.lastName];

For a selection of fields this small, don't use a for loop.
You may be saving a bit of code by attempting the for-loop structure, but it's really not the way to go if you're building the NSArray with only a few fields, and especially because you can't put nil items in it.
A better way to go is:
Person *p = ...
NSMutableString *s = [[NSMutableString alloc] init];
if ([p.firstName length] > 0) [s appendFormat:#"%#\n", p.firstName];
if ([p.lastName length] > 0) [s appendFormat:#"%#\n", p.lastName];
if ([p.phoneNumber length] > 0) [s appendFormat:#"%#\n", p.phoneNumber];
if ([p.adress length] > 0) [s appendFormat:#"%#\n", p.adress];
if ([p.city length] > 0) [s appendFormat:#"%#\n", p.city];
Edit, after original Question was updated with large amount of fields.
Like #BergQuester said, an approach to support a larger, arbitrary set of fields is using KVO-style inspection.
NSArray *fieldNames = #[#"firstName", #"lastName", #"phoneNumber", ....more fields...];
NSString *field;
for (NSString *fieldName in fieldNames) {
field = [p valueForKey:fieldName];
if ([field length] > 0 ) {
[s appendFormat: #"%#\n", field];
}
}

Try to create NSMutableString category
#import "NSMutableString+checkForNilObject.h"
#implementation NSMutableString (checkForNilObject)
-(void) appendNotNillObject:(NSString *) string
{
if(string)
{
[self appendString:string];
}
}
#end

You can override the getters of the class Person.
#implementation Person
- (NSString *)firstName{
if (_firseName == nil)
_firstName = #"";
return _firstName;
}
///....Other setters
#end
Like this you can define all your setters here.

Related

FMDB Query with dictionary

I need to run a query that looks would look like
INSERT INTO Appointments (field1, field2, field3, ..., field30) VALUES (value1, value2, value3, ..., value30)
I have my Appointments being stored inside a Dictionary and would like to loop through that dictionary to make the keys equal the fields and the values equal the values.
I'm trying to use the executeUpdate:... withParameterDictionary:... but can't figure out how to make that work with multiple fields if I don't know the field names. The field names are being sent via JSON and instead of manually typing out 30 fields I would just like to loop through the dictionary and get them that way.
I have even tried
NSMutableArray *keys = nil;
NSMutableArray *values = nil;
for (NSDictionary *dict in [json objectForKey:#"data"]) {
keys = [NSMutableArray array];
values = [NSMutableArray array];
for (id key in dict) {
[keys addObject:key];
[values addObject:[NSString stringWithFormat:#":%#", key]];
}
NSString *keyString = [keys componentsJoinedByString:#","];
NSString *valueString = [values componentsJoinedByString:#","];
[[dataObj db] executeUpdate:#"DELETE FROM Appointments"];
NSLog(#"INSERT INTO Appointments (%#) VALUES (%#)", keyString, valueString);
[[dataObj db] executeUpdate:#"INSERT INTO Appointments (?) VALUES (?)", keyString, valueString];
}
The code above prints the NSLog how the query should looks but nothing is being inserted into the database. I know this because I am opening the simulator database file after the queries run and it is still blank.
How can I get the above code to work or how can I get the executeQuery:... withParameterDictionary:... to work with multiple names.
I ran a couple of quick tests, and this works for me:
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:#"AAAA44", #"a", #"BBBB44", #"b", #"CCCC44", #"c", nil];
NSMutableArray* cols = [[NSMutableArray alloc] init];
NSMutableArray* vals = [[NSMutableArray alloc] init];
for (id key in dict) {
[cols addObject:key];
[vals addObject:[dict objectForKey:key]];
}
NSMutableArray* newCols = [[NSMutableArray alloc] init];
NSMutableArray* newVals = [[NSMutableArray alloc] init];
for (int i = 0; i<[cols count]; i++) {
[newCols addObject:[NSString stringWithFormat:#"'%#'", [cols objectAtIndex:i]]];
[newVals addObject:[NSString stringWithFormat:#"'%#'", [vals objectAtIndex:i]]];
}
NSString* sql = [NSString stringWithFormat:#"insert into test (%#) values (%#)", [newCols componentsJoinedByString:#", "], [newVals componentsJoinedByString:#", "]];
NSLog(#"%#", sql);
BOOL updateSuccess = [db executeUpdate:sql];
The trick is to add ' to the data in the arrays.
NSDictionary *argsDict
= [NSDictionary dictionaryWithObjectsAndKeys:#"My Name",
#"name", nil];
[db executeUpdate:#"INSERT INTO myTable (name) VALUES (:name)"
withParameterDictionary:argsDict];
Here is some sample code I just wrote to support optional values at insert time. Just briefly tested but I think it works.
NSMutableDictionary* fieldsandvalues = [NSMutableDictionary dictionary];
fieldsandvalues[#"word"] = userphrase.word;
fieldsandvalues[#"translation"] = userphrase.translation;
if (userphrase.samplesentence.length > 0) {
fieldsandvalues[#"samplesentence"] = userphrase.samplesentence;
}
if (userphrase.notes.length > 0) {
fieldsandvalues[#"notes"] = userphrase.notes;
}
NSMutableArray* keyswithcolon = [NSMutableArray array];
for (NSString* key in fieldsandvalues.allKeys) {
[keyswithcolon addObject:[NSString stringWithFormat:#":%#", key]];
}
NSString* sql = [NSString stringWithFormat:#"INSERT INTO userphrase (%#) VALUES (%#)", [fieldsandvalues.allKeys componentsJoinedByString:#","], [keyswithcolon componentsJoinedByString:#","]];
// DLog(#"sql: %#", sql);
if (![self.db executeUpdate:sql withParameterDictionary:fieldsandvalues]) {
NSAssert(NO, #"Failed inserting userphrase into database! Last error: %# - %#", self.db.lastError, self.db.lastErrorMessage);
return nil;
}

Different Keys Point to Same Object(s) in NSMutableDictionary

I have a custom object called Person that among other things contains an NSString field called descriptor, which stores what sort of person that Person object is (angry, sad, wild, happy, morose, etc). All of my Person objects are in an NSMutableArray, but I would like to store them in an NSMutableDictionary in such a manner:
Key: A, Object: An NSMutableArray where all Person objects have descriptor starting with 'A'
Key: B, Object: An NSMutableArray where all Person objects have descriptor starting with 'B'
Key: C, Object: An NSMutableArray where all Person objects have descriptor starting with 'C'
etc...
I've tried to do this in my code below, and at the comment //POINT 1, the keys and arrays seem to match up, but at //POINT 2, when I print out the complete dictionary, all the keys come up with the same values!
So I wanted to know why the NSMutableArray I seem to have is not being stored as I want it in the NSMutableDictionary?
- (void)buildDictionaryForIndexList {
NSMutableDictionary *tempDict = [[[NSMutableDictionary alloc] init] autorelease];
NSMutableArray *personsStartingWithLetter = [[NSMutableArray alloc] init];
NSMutableArray *indexList = [[[NSMutableArray alloc] init] autorelease];
NSInteger loopCounter = 1;
NSString *firstLetter = [[[NSString alloc] init] autorelease];
for (Person *v in persons) {
firstLetter = [[v descriptor] substringWithRange:NSMakeRange(0, 1)];
if ([indexList containsObject:firstLetter]) {
[personsStartingWithLetter addObject:v];
if (loopCounter == [persons count]) {
[tempDict setObject:personsStartingWithLetter forKey:firstLetter];
}
} else {
if (loopCounter > 1) {
//POINT 1
NSLog(#"%#",[indexList objectAtIndex:[indexList count]-1]);
for (Person *q in personsStartingWithLetter) {
NSLog(#"%#",[q descriptor]);
}
[tempDict setObject:personsStartingWithLetter forKey:[indexList objectAtIndex:([indexList count] - 1)]];
[personsStartingWithLetter removeAllObjects];
}
[indexList addObject:firstLetter];
[personsStartingWithLetter addObject:v];
} // else
loopCounter++;
} // for
//POINT 2
NSEnumerator *enumerator = [tempDict keyEnumerator];
for (NSString *str in enumerator) {
NSLog(#"%#",str);
for (Person *c in [tempDict objectForKey:str]) {
NSLog(#"%#",[c descriptor]);
}
}
self.dictionary = tempDict;
} // buildDictionaryForIndexList
So, for example, at POINT 1 my output is:
A
Angry
Amiable
B
Belligerent
C
Cool
...
W
Wild
but at POINT 2 my output is
T
Wild
J
Wild
A
Wild
...
W
Wild
Change [tempDict setObject:personsStartingWithLetter forKey:[indexList objectAtIndex:([indexList count] - 1)]]; (just after point 1) to [tempDict setObject:[[personsStartingWithLetter copy] autorelease] forKey:[indexList objectAtIndex:([indexList count] - 1)]];. The problem is that NSDictionary copies the key, but retains the value. Therefore, if you add a mutable array to the dictionary and then change it, the array in the dictionary also changes. You need to create a non-mutable copy of the array to put in the dictionary.
The whole method is a bit overcomplicated.
- (void)buildDictionaryForIndexList
{
NSMutableDictionary *tempDict = [[[NSMutableDictionary alloc] init] autorelease];
for (Person *v in persons)
{
NSString* firstLetter = [[v descriptor] substringWithRange:NSMakeRange(0, 1)];
NSMutableArray* personsStartingWithLetter = tempDict [firstLetter];
if (personsStartingWithLetter == nil)
{
personsStartingWithLetter = [NSMutableArray array];
tempDict [firstLetter] = personsStartingWithLetter;
}
[personsStartingWithLetter addObject:v];
} // for
self.dictionary = tempDict;
}
You start with an empty dictionary that will contain arrays. For every person, you check whether there is a suitable array or not, and if there isn't one, you create it. So now there is an array for the person, so you add it to the array. That's all.

How to insert NSString * between two NSString *

I want to ask an NSString * question. I have a NSString * object. The content is like the following example. (like the CSV file)
Example: (my file is longer than this one so much)
First Name,Last Name,Middle Name,Phone,Chan,Tim,Man,123-456-789,Tom,,,987-654-321
(if it is empty, no space between the ',')
How can I insert the "(null)" NSString * / or NSString * object between the two ',' by using the objective C?
// After convent
First Name,Last Name,Middle Name,Phone,Chan,Tim,Man,123-456-789,Tom,(null),(null),987-654-321
Thank you very much.
Have a look at the method stringByReplacingOccurrencesOfString:withString: (untested):
NSString *newString = [oldString stringByReplacingOccurrencesOfString:#",,"
withString:#",(null),"];
This might be sufficient for your needs.
I'm a beginner in Objective-C and I didn't test this answer, but I think it does the job.
//given your NSString called string
NSArray *pieces = [[NSArray alloc] initWithArray: [string componentsSeparatedByString:#","]];
NSMutableString *final = [[NSMutableString alloc] init];
for(int i; i < [pieces count]; i++)
{
if(i > 0)
[final appendString: [NSString stringWithFormat:#","]];
if([pieces objectAtIndex: [NSNumber numberWithInt: i]] == nil)
{
[final appendString: [NSString stringWithFormat:#"(null)"]];
}
else
{
[final appendString: [pieces objectAtIndex: [NSNumber numberWithInt: i]]];
}
}
I hope I didn't screw it up too bad :)

Generating an NSDictionary from an SQL statement

I am trying to generate an NSDictonary that can be used to populate a listview with data I retrieved from an SQL statement. when I go to create an array and add them it adds the arrays for ALL my keys and not just for the current key. I've tried a removeAllObjects on the array but for some reason that destroys ALL my data that I already put in the dictionary.
//open the database
if(sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK)
{
const char *sql = "select alphaID, word from words order by word";
sqlite3_stmt *selectStatement;
//prepare the select statement
int returnValue = sqlite3_prepare_v2(database, sql, -1, &selectStatement, NULL);
if(returnValue == SQLITE_OK)
{
NSMutableArray *NameArray = [[NSMutableArray alloc] init];
NSString *alphaTemp = [[NSString alloc] init];
//loop all the rows returned by the query.
while(sqlite3_step(selectStatement) == SQLITE_ROW)
{
NSString *currentAlpha = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectStatement, 1)];
NSString *definitionName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectStatement, 2)];
if (alphaTemp == nil){
alphaTemp = currentAlpha;
}
if ([alphaTemp isEqualToString:(NSString *)currentAlpha]) {
[NameArray addObject:definitionName];
}
else if (alphaTemp != (NSString *)currentAlpha) {
[self.words setObject:NameArray forKey:currentAlpha];
[NameArray removeAllObjects];
[NameArray addObject:definitionName];
}
}
}
The Statement above adds all the "keys" but then removes all the array elements for all keys. if I take out the removeAllKeys it adds ALL the array elements for ALL keys. I don't want this I want it to add the array elements FOR the specific key then move on to the next key.
in the end I want a NSDictonary with
A (array)
Alpha (string)
Apple (string)
B (array)
Beta (string)
Ball (string)
C (array)
Code (string)
...
Though I don't think it affects your problem, from the way I read your code, you should change
NSString *alphaTemp = [[NSString alloc] init];
to
NSString *alphaTemp = nil;
since alphaTemp is just used to point to an NSString that is generated initially as currentAlpha. You also should call [NameArray release] at some point below the code you've given, since you alloc'd it.
The real issue is that you are repeatedly adding pointers to the same NSMutableArray to your NSDictionary (self.words). I can see two ways to fix this:
Change
[self.words setObject:NameArray forKey:currentAlpha];
to
[self.words setObject:[NSArray arrayWithArray:NameArray] forKey:currentAlpha];
so that you are adding a newly-created (non-mutable) NSArray to your NSDictionary.
-- or --
Insert
[NameArray release];
NameArray = [[NSMutableArray alloc] init];
after
[self.words setObject:NameArray forKey:currentAlpha];
so that once you've inserted the NSMutableArray into the NSDictionary, you create a new NSMutableArray for the next pass.

Comparing string arrays

Sorry in advance if this is a stupid question. I'm working on a simple program that compares two arrays filled with strings. One is a list of 1309 proper names the other is a list of 235,877 english words. The point of the program is to compare the lists, and have any words that appear on both lists added to a mutable array. Then, the program will enumerate through the mutable array and print out the words that are on both lists. Here is my code:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSString *nameString = [NSString stringWithContentsOfFile:#"/usr/share/dict/propernames"
encoding:NSUTF8StringEncoding
error:NULL];
NSString *wordString = [NSString stringWithContentsOfFile:#"/usr/share/dict/words"
encoding:NSUTF8StringEncoding
error:NULL];
NSArray *names = [nameString componentsSeparatedByString:#"\n"];
NSArray *words = [wordString componentsSeparatedByString:#"\n"];
NSMutableArray *namesAndWords = [[NSMutableArray alloc]init];
for (NSString *w in words){
for (NSString *n in names){
if ([[n lowercaseString] compare:w] == NSEqualToComparison){
[namesAndWords addObject: w];}}}
for (NSString *item in namesAndWords){
NSLog(#"%#", item);}
NSLog(#"There are %lu items in the array",[namesAndWords count]);
NSLog(#"%lu", [names count]);
NSLog(#"%lu", [words count]);
}
return 0;
}
As of right now, I've got this program working exactly as it should (showing 294 matches). My real question is when I first tried comparing the strings I tried it like this:
for (NSString *w in words){
for (NSString *n in names){
if ([n caseInsensitiveCompare:w] == NSEqualToComparison){
[namesAndWords addObject: w];}}}
and like this:
for (NSString *w in words){
for (NSString *n in names){
if ([n compare:w options:NSCaseInsensitiveSearch] == NSOrderedSame){
[namesAndWords addObject: w];}}}
These two ways both gave me 1602 matches and for some reason adds some items from both arrays into the mutable array namesAndWords. So for example in the console I will see Woody and woody printed out.
The other way I tried was this:
for (NSString *w in words){
for (NSString *n in names){
if ([n compare:w] == NSOrderedSame){
[namesAndWords addObject: w];}}}
When doing it this way it added all 1309 strings from the names array. Before running this I actually thought I wouldn't get any matches since I didn't specify it to be case insensitive.
I'm trying to figure out why these methods that seem so similar have the different results that they do. I'm also trying to find out why if ([[n lowerCaseString] compare:w] == NSEqualToComparison) is the right way to go. Any help here is greatly appreciated.
Because the below line checks the word only converts lowercase string of the first array and not the second one. It gets only matching value like m->m including duplicates.
[[n lowercaseString] compare:w] == NSEqualToComparison
Below is my workout for your problem.
NSMutableArray *actualarray1=[[NSMutableArray alloc] init];
NSMutableArray *actualarray2=[[NSMutableArray alloc] init];
actualarray1=[#[#"Apple",#"Litchi",#"Plum",#"Litchi",#"Pineapple",#"mango",#"Apple",#"berry",#"Pineapple",#"berry",#"mango",#"Apple"]mutableCopy];
actualarray2=[#[#"guava",#"Orange",#"Litchi",#"Pineapples",#"mangoes",#"Orange",#"Strawberry",#"Pineapple",#"berry",#"mango",#"Apple"]mutableCopy];
NSMutableArray *namesAndWords = [[NSMutableArray alloc]init];
for (NSString *w in actualarray1){
for (NSString *n in actualarray2){
if ([[n lowercaseString] compare:w] == NSEqualToComparison){
[namesAndWords addObject: w];}}}
NSLog(#"Array without duplicates %d",(int)[namesAndWords count]);
namesAndWords=[[NSMutableArray alloc] init];
for (NSString *w in actualarray1){
for (NSString *n in actualarray2){
if ([n compare:w options:NSCaseInsensitiveSearch] == NSOrderedSame){
[namesAndWords addObject: w];}}}
NSLog(#"Array with duplicates %d",(int)[namesAndWords count]);
namesAndWords=[[NSMutableArray alloc] init];
for (NSString *w in actualarray1){
for (NSString *n in actualarray2){
if ( [n caseInsensitiveCompare:w] == NSOrderedSame ){
[namesAndWords addObject: w];}}}
NSLog(#"Array with duplicates %d",(int)[namesAndWords count]);
In the above code, array 1 has duplicates on itself and array 2 as well. Please try some manual iterations and it is just because of the last two comparison ends up with one-to-many mapping. Last two methods which produces duplicates on your case is just because, you're using for each loop and checking all the values in the array. What will be the result if you remove the duplicates in the array before comparing? Let's have a look at below code.
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:actualarray1];
NSArray *arrayWithoutDuplicates = [orderedSet array];
actualarray1=[arrayWithoutDuplicates mutableCopy];
orderedSet = [NSOrderedSet orderedSetWithArray:actualarray2];
arrayWithoutDuplicates = [orderedSet array];
actualarray2=[arrayWithoutDuplicates mutableCopy];
NSLog(#"%# %#",actualarray1,actualarray2);
namesAndWords=[[NSMutableArray alloc] init];
for (NSString *w in actualarray1){
for (NSString *n in actualarray2){
if ( [n caseInsensitiveCompare:w] == NSOrderedSame ){
[namesAndWords addObject: w];}}}
//Your code works like a charm!
NSLog(#"After removing duplicates %d",(int)[namesAndWords count]);
namesAndWords=[[NSMutableArray alloc] init];
for (NSString *s in actualarray1){
if([actualarray2 containsObject:s]){
[namesAndWords addObject: s];
}
}
//This is my code which eventually reduces time
NSLog(#"Count after unique %d",(int)[namesAndWords count]);
I'd suggest you to not to use comparison like [[n lowercaseString] compare:w] == NSEqualToComparison which has incorrect logic. Because, you're converting only one object from the array to lowercase and the logic is incorrect. Because, it gets only lowercase data which is matching in the above code. Instead, you can use [n caseInsensitiveCompare:w] == NSOrderedSame if you need values with unique or remove duplicates before comparing. Also, it is not advisable to use fast iteration in this scenario since performance may be degraded if the array is too large.
Hope it clears your doubt!
When you use:
[[n lowercaseString] compare:w]
You will only get a match if w is lowercase. In your case insensitive comparisons the case of both n and w are ignored. This will account for the different results. HTH