Does core data do its own type casting in the background? - objective-c

I am working on a simple comparison of two lists to see which items in an "evaluation" list are contained in a larger "target" list. I am getting the data on-the-fly- by parsing two CSV files and storing everything as strings. I successfully import the data into the data store and I can get a list of entities no problem
The problem comes when I actually do a search. Essentially, I am looking for short ISBNs in the form of 1234 from the evaluation list in the target list, which are in the form of 1234-5. The predicate I am using is I am using the CONTAINS comparison in the form of [NSString stringWithFormat:#"%# CONTAINS %#", kOC_Target_PrintBookCode, evalIsbn]
The error I get is the following (grabbed by my NSLog)
NSInvalidArgumentException: Can't look for value (1494) in string (49885); value is not a string
I get the impression that even though the ISBN is being read from a NSString and the Core Data store has the data point spec'd as a String, that Core Data is still doing something in the background with the value for whatever reason it sees fit. Any ideas?
Here is the relevant process logic (though I use that term dubiously) code. Unless otherwise noted in the code, all values being manipulated and/or stored are NSString:
NSArray *evalBooks = [self getEntitiesByName:kOC_EntityName_EvalBook
usingPredicateValue:[NSString stringWithFormat:#"%# > \"\"", kOC_Eval_Bookcode]
withSubstitutionVariables:nil
inModel:[self managedObjectModel]
andContext:[self managedObjectContext]
sortByAttribute:nil];
if ( ( !evalBooks ) || ( [evalBooks count] == 0 ) ) {
// we have problem
NSLog(#"( !evalBooks ) || ( [evalBooks count] == 0 )");
return;
}
[evalBooks retain];
int firstEvalBook = 0;
int thisEvalBook = firstEvalBook;
int lastEvalBook = [evalBooks count]; NSLog(#"lastEvalBook: %i", lastEvalBook);
for (thisEvalBook = firstEvalBook; thisEvalBook < lastEvalBook; thisEvalBook++) {
NSManagedObject *evalBook = [[evalBooks objectAtIndex:thisEvalBook] retain];
NSString *rawIsbn = [[evalBook valueForKey:kOC_Eval_Bookcode] retain];
NSString *isbnRoot = [[self getIsbnRootFromIsbn:rawIsbn] retain];
// this is a custom method I created and use elsewhere without any issues.
NSArray *foundBooks = [self getEntitiesByName:kOC_EntityName_TargetBook
usingPredicateValue:[NSString stringWithFormat:#"%# CONTAINS %#", kOC_Target_PrintBookCode, isbnRoot]
withSubstitutionVariables:nil
inModel:[self managedObjectModel]
andContext:[self managedObjectContext]
sortByAttribute:kOC_Target_PrintBookCode];
if ( foundBooks != nil ) {
[foundBooks retain];
NSLog(#"foundBooks: %lu", [foundBooks count]);
} else {
}

If you're building your predicate as an NSString, I believe
[NSString stringWithFormat:#"%# CONTAINS %#", kOC_Target_PrintBookCode, isbnRoot]
should actually be
[NSString stringWithFormat:#"%# CONTAINS '%#'", kOC_Target_PrintBookCode, isbnRoot]
It seems that you're confusing the way predicateWithFormat: works with the way stringWithFormat: works.

Presumably either kOC_Target_PrintBookCode or isbnRoot is not an object that can be converted to a string. E.g. if either is an integer, the %# operator cannot convert the integer to a string value.

Related

removing null from arrays in Object-c

I have this snipped of code that results in an array with a whole bunch of "<null>" throughout and I need to figure out how to remove them. Obviously after smashing my head against the keyboard I'm asking for some help.
In my .h I have declared:
NSArray *sortedContacts;
NSArray *rawContacts;
And then in .m:
-(void) buildContacts {
ABAddressBook *addressBook = [ABAddressBook sharedAddressBook];
NSArray *contacts = [addressBook people];
rawContacts=contacts;
NSArray *firstNames = [rawContacts valueForKey:#"First"];
NSArray *lastNames = [rawContacts valueForKey:#"Last"];
NSArray *organization = [rawContacts valueForKey:#"Organization"];
NSMutableArray *fullNames = [NSMutableArray array];
for(int i = 0; i < [firstNames count]; i++)
{
NSString *fullName = [NSString stringWithFormat:#"%# %# %#",
[firstNames objectAtIndex:i],
[lastNames objectAtIndex:i],
[organization objectAtIndex:i]];
[fullNames addObject:fullName];
}
NSMutableArray *fullList = [[NSMutableArray alloc]initWithArray:fullNames];
[fullList removeObjectIdenticalTo: #"<null>"];
sortedContacts = [fullList sortedArrayUsingSelector:#selector(compare:)];
NSLog(#"%#",sortedContacts);
}
I've tried so many things that I just can't see the forest for the trees anymore.
The text <null> is how the singleton instance of NSNull describes itself. That is, it's what -[NSNull description] returns.
In turn, these NSNull objects are getting into your firstNames, lastNames, and organization arrays because that's what Key-Value Coding does when you call -valueForKey: on an array and some of the elements return nil when that message is forwarded on to them with the same key. That is, calling [rawContacts valueForKey:#"First"] causes NSArray to call [element valueForKey:#"First"] for each element in rawContacts and to put the result in the array it builds. But, since an array can't contain nil, if one of those elements returns nil from [element valueForKey:#"First"], an NSNull object is added in its place.
Then, you are formatting the string fullName from the corresponding elements of firstNames, lastNames, and organization. You need to check if any of those elements are NSNull using if ([value isKindOfClass:[NSNull class]]) and handling that. For instance, you might just skip that record. Or you might combine the available fields and leave out any unavailable ones.
In any case, none of the elements of fullList will be #"<null>" because formatting values into #"%# %# %#" can never result in that string. (It might be #"<null> <null> <null>" or something like that, but never just #"<null>".)
A quick look at your code suggests you cannot get any empty strings added to your array, (a) you add elements using:
[fullNames addObject:fullName];
and fullName is created using:
[NSString stringWithFormat:#"%# %# %#" ...
so even if the %#'s get replaced by nothing you'll still have 2 spaces...
Maybe this is why all the things you've tried fail, if you're looking for empty strings you won't find them.
(Addendum: Question now says you're looking for #"<null>", you won't get that either for the same reason - there is at least two spaces in your string.)
The simple answer to removing invalid entries in fullNames is not to add them in the first place. You are adding elements in a loop (for), and conditional logic (e.g. if) inside the loop to determine whether you have something valid to add - however you define "something valid" - and only add an item to fullNames if so.
HTH
I'm not really familiar with the AddressBook framework, however this might be what's causing the confusion:
The values you collect in your arrays firstNames, lastNames and organization can be of type NSString or NSNull. You have to do any null-checking within the for-loop, before the fullName-string is constructed.
Remove this useless line:
[fullList removeObjectIdenticalTo: #"<null>"];
And replace the contents of your for-loop with the following code:
for(int i = 0; i < [firstNames count]; i++)
{
NSString *firstName = [firstNames objectAtIndex:i];
NSString *lastName = [lastNames objectAtIndex:i];
NSString *org = [organization objectAtIndex:i];
NSMutableArray *namesArray = [NSMutableArray array];
if ([firstName isKindOfClass:[NSString class]])
[namesArray addObject:firstName];
if ([lastName isKindOfClass:[NSString class]])
[namesArray addObject:lastName];
if ([org isKindOfClass:[NSString class]])
[namesArray addObject:org];
if (namesArray.count > 0)
[fullNames addObject:[namesArray componentsJoinedByString:#" "]];
}

Search String in NSDictionary store in NSMutableArray

I am trying to search a String in NSDictionary stored in NSMutableArray
for (int k = 0; k < [onlyActiveArr count]; k++) {
NSString *localID = [onlyActiveArr objectAtIndex:k];
NSLog(#"%#",localID);
int localIndex = [onlyActiveArr indexOfObject:localActiveCallID];
NSLog(#"%d",localIndex);
for (NSDictionary *dict in self.SummaryArr) {
NSLog(#"%#",[dict objectForKey:#"ActiveID"]);
if (![[dict objectForKey:#"ActiveID"] isEqualToString:localID]) {
NSLog(#"found such a key, e.g. %#",localID);
}
}
}
But I am getting
NSLog(#"found such a key, e.g. %#",localActiveCallID);
when the ID is still there in SummaryArr, I am checking if localID retrieved from onlyActiveArr is not present in dictionary.
Please suggest me how to overcome my problem.
You cannot make a decision that a key is not present until you finish processing the entire dictionary. Make a boolean variable initially set to NO, and change it to YES if you find an item in the dictionary, like this:
BOOL found = NO;
for (NSDictionary *dict in self.SummaryArr) {
NSLog(#"%#",[dict objectForKey:#"ActiveID"]);
found = [[dict objectForKey:#"ActiveID"] isEqualToString:localID];
if (found) break;
}
if (!found) {
NSLog(#"found such a key, e.g. %#",localID);
}
If you like predicates, then you can use the fact that accessing an inexistent key in a dictionary produces a nil value and make a predicate that filters out those dictionaries that have nil for your key.
If the count of the result is larger than zero, your key is somewhere in the array. It won't tell you where, though.
A snippet to show the idea:
NSPredicate *keyPred = [NSPredicate predicateWithFormat: #"NOT ActiveID == nil"];
BOOL found = [[self.SummaryArr filteredArrayUsingPredicate: keyPred] count] > 0;
I don't know how the performance stacks up, so if your data set is large you may want to check that the execution time is within your limits.

How to get count of a NSDictionary-stored NSArray?

I have a NSDictionary (parsed from JSON using JSONObjectWithData, if that's relevant) that looks like:
{
ids = (
49939999,
44754859,
14424892,
16311801,
16045487,
31247745,
5982852
);
"next_cursor" = 0;
"next_cursor_str" = 0;
"previous_cursor" = 0;
"previous_cursor_str" = 0;
}
when logged using NSLog(#"%#", jsonResult);.
I'm accessing ids with friends = [jsonResult objectForKey:#"ids"];, and would expect friends to be of type NSArray, but apparently it's of type __NSCFArray. Why?
I then try to get friends' size using [friends count] but this creates an exception when run.
How to get count of a NSDictionary-stored "NSArray"?
UPDATE: Code
NSError *jsonError = nil;
id jsonResult = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&jsonError];
if (jsonResult != nil) {
self.friends = [jsonResult objectForKey:#"ids"];
NSLog(#"%#", self.friends);
NSLog(#"%#", [self.friends class]);
NSLog(#"%#", [self.friends count]);
dispatch_sync(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
NSCFArray is a subclass of NSArray. Most of the time when you deal with an NSArray, that's the concrete class you're dealing with. This is what it means in the documentation when it says NSArray is a class cluster.
Your crash is because when you try to print [friends count], you use the format string #"%#". %# tells NSLog to expect an object, but this is an NSUInteger. Instead, you should do NSLog(#"%lu", (unsigned long)[friends count]). (If you're not entirely clear on the idea of format specifiers, Apple has a handy guide.)
__NSCFArray are just the underlying structure that both NSArray and CFArray use to support so-called toll-free bridging. Use use this:
NSArray *friends = (NSArray*)[jsonResult objectForKey:#"ids"];
Wow, that was stupid of myself. I just found out that NSLog(#"%#") only takes an object. You've got to specifically use NSLog(#"%d") to input an integer.
I'm not sure why this is not detected at compile-time, though.
Source: http://cocoadev.com/wiki/NSLog

best way to load/save disparate UITableView data for cellForRowAtIndexPath?

I have a multi-sectioin UITableView with different kinds of controls throughout various rows (multi-select checkboxes, single-select checkboxes, text inputs, text areas etc.). Each row could have a different data type (string, integer, date etc) and the number of rows and location are dynamic so you can't always depend on section X row Y being a certain control.
My question is what is the best way to save the data input into these fields for use in the view, grabbing the right data to show what was entered into that field when calling cellForRowAtIndexPath.
Note that I am NOT asking how to save this data persistently, I'm using CoreData for that, the question is just how to temporarily save the data while interacting with the view, so that you have it in an NSMutableArray or NSMutableDictionary ready to be saved with CoreData when the user touches the "Save" button, or completely discarded if they press "Cancel".
Currently I'm trying to implement a dictionary but it seems somewhat kludgy and I often get one row's data showing up in another row.
Here is my current method for saving the form data. It's using a name from the arguments along with a counter variable used for the view as a whole. The counter variable is also used as the tag integer for the control.
-(id)documentField:(UIView *)view withKey:(NSString *)key andValue:(id)value{
NSInteger foundTag = -1;
NSLog(#"searching dictionary for key: %#", key);
for(NSString *existingKey in fieldValues){
NSArray *keyParts = [existingKey componentsSeparatedByString:#"~"];
if( [[keyParts objectAtIndex:0] isEqualToString:key] )
{
foundTag = [[keyParts objectAtIndex:1] intValue];
NSLog(#"found key: %#, it's tag is: %d", [keyParts objectAtIndex:0], foundTag);
break;
}//end if
else{
//NSLog(#"no match: %# != %#", (NSString *)[keyParts objectAtIndex:0], key);
}
}//end for
//if we haven't tagged this element yet
//set the tag
if (foundTag == -1) {
view.tag = fieldValueCounter;
foundTag = fieldValueCounter;
fieldValueCounter++;
}//end if
NSString *fieldKey = [NSString stringWithFormat:#"%#~%d", key, foundTag];
if( ! [fieldValues objectForKey:fieldKey] ){
[fieldValues setObject:((value)? value : #"") forKey:fieldKey];
}
NSLog(#"returning fieldValue: %# = %#", fieldKey, [fieldValues objectForKey:fieldKey]);
return [fieldValues objectForKey:fieldKey];
}//end documentField:withKey:andValue:
And here is how it is being used.
((UTVCellTextField *)cell).textLabel.text = #"Door Location:";
((UTVCellTextField *)cell).textField.text = [self documentField:((UTVCellTextField *)cell).textField withKey:#"door.door_location" andValue:door.door_location];
((UTVCellTextField *)cell).textField.delegate = self;

Sorting NSStrings of Numbers

So I have an NSDictionary where the keys are years as NSString's and the value for each key is also an NSString which is sort of a description for the year. So for example, one key is "943 B.C.", another "1886". The problem I am encountering is that I want to sort them, naturally, in ascending order.
The thing is that the data source of these years is already in order, it's just that when I go ahead and call setValue:forKey the order is lost, naturally. I imagine figuring out a way to sort these NSString's might be a pain and instead I should look for a method of preserving the order at the insertion phase. What should I do? Should I instead make this an NSMutableArray in which every object is actually an NSDictionary consisting of the key being the year and the value being the description?
I guess I just answered my own question, but to avoid having wasted this time I'll leave this up in case anyone can recommend a better way of doing this.
Thanks!
EDIT: I went ahead with my own idea of NSMutableArray with NSDictionary entries to hold the key/value pairs. This is how I am accessing the information later on, hopefully I'm doing this correctly:
// parsedData is the NSMutableArray which holdes the NSDictionary entries
for (id entry in parsedData) {
NSString *year = [[entry allKeys] objectAtIndex:0];
NSString *text = [entry objectForKey:year];
NSLog(#"Year: %#, Text: %#", year, text);
}
Maintain a NSMutableArray to store the keys in order, in addition to the NSDictionary which holds all key-value pairs.
Here is a similar question.
You could either do it as an array of dictionaries, as you suggest, or as an array of strings where the strings are the keys to your original dictionary. The latter is probably a simpler way of going about it. NSDictionary does not, as I understand it, maintain any particular ordering of its keys, so attempting to sort the values there may be unwise.
I needed to solve a similar problem to sort strings of operating system names, such as "Ubuntu 10.04 (lucid)".
In my case, the string could have any value, so I sort by tokenizing and testing to see if a token is a number. I'm also accounting for a string like "8.04.2" being considered a number, so I have a nested level of tokenizing. Luckily, the nested loop is typically only one iteration.
This is from the upcoming OpenStack iPhone app.
- (NSComparisonResult)compare:(ComputeModel *)aComputeModel {
NSComparisonResult result = NSOrderedSame;
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSArray *tokensA = [self.name componentsSeparatedByString:#" "];
NSArray *tokensB = [aComputeModel.name componentsSeparatedByString:#" "];
for (int i = 0; (i < [tokensA count] || i < [tokensB count]) && result == NSOrderedSame; i++) {
NSString *tokenA = [tokensA objectAtIndex:i];
NSString *tokenB = [tokensB objectAtIndex:i];
// problem: 8.04.2 is not a number, so we need to tokenize again on .
NSArray *versionTokensA = [tokenA componentsSeparatedByString:#"."];
NSArray *versionTokensB = [tokenB componentsSeparatedByString:#"."];
for (int j = 0; (j < [versionTokensA count] || j < [versionTokensB count]) && result == NSOrderedSame; j++) {
NSString *versionTokenA = [versionTokensA objectAtIndex:j];
NSString *versionTokenB = [versionTokensB objectAtIndex:j];
NSNumber *numberA = [formatter numberFromString:versionTokenA];
NSNumber *numberB = [formatter numberFromString:versionTokenB];
if (numberA && numberB) {
result = [numberA compare:numberB];
} else {
result = [versionTokenA compare:versionTokenB];
}
}
}
[formatter release];
return result;
}