Why won't this simple 'if' statement work (inside fast enumeration)? - iphone

I am enumerating through the ChecklistItem entities in my table to see which ones have a priority (NSNumber attribute) of 1. checklistItems are in a to-many relationship with Checklist.
In this simple code, the first NSLog works fine, and reports that several of my ChecklistItems have a priority of 1. But the second NSLog never gets called. Why is this? I assume I'm framing the "if" statement wrong, but I don't know how.
for (ChecklistItem *eachItem in checklist.checklistItems){
NSLog(#"Going through loop. Item %# has priority %#.", eachItem.name, eachItem.priority);
if (eachItem.priority == [NSNumber numberWithInt:1]) {
NSLog(#"Item %# has priority 1", eachItem.name);
}
}

You're comparing the pointers of the return values of eachItem.priority and [NSNumber numberWithInt:1]. You should use NSNumber's equality method.

You can not compare objects as you did above. Use the following code.
for (ChecklistItem *eachItem in checklist.checklistItems){
NSLog(#"Going through loop. Item %# has priority %#.", eachItem.name, eachItem.priority);
if ([eachItem.priority intValue]== 1) {
NSLog(#"Item %# has priority 1", eachItem.name);
}
}
Thanks,

Well, you should be checking for value equality something like this:
if ( [eachItem.priority intValue] == 1 ) { ... }
However, I'm kind of surprised it doesn't accidentally work as it is, because I thought NSNumber pooled a few base instances and I'd expect 1 to be one of them. Relying on that would be very bad form, though, even if it happened to work in this case.

Related

Accessing value from array of objects

I am having two arrays, Namely
NMutableArray* first;
NMutableArray* second;
Now I am copying first object to the second array like
for (int i=0;i<first.count; i++)
{
[second addObject:[first objectAtIndex:i];
}
This is ok. I don't know how to access the value of the First Array. I tried like this ,
[second addObject:[[first objectAtIndex:i]name]];
I want to get the name value which is in the first object of first array. I tried using the above line, it is showing some warning. Please help me
Assuming you started with an array like this:
NSArray *array1 = #[#{#name : #"Fred"},
#{#name : #"Bill"}];
You could create a second array that contains the value of a given property of each element of the first array as follows:
NSArray *array2 = [array1 valueForKey:#"name"];
If you then logged the second array...
NSLog(#"%#", array2);
...the resulting output would be
2012-04-18 16:26:11.226 ExampleRunner[23320:707] (
Fred,
Bill
)
EDIT
Note that this will work regardless of whether the objects in the first array are instances of NSDictionary as shown in the example above, or instances of a class or classes that have a name property or instance variable (or an _name instance variable, for that matter). For more information on how and why this works, see the documentation for the NSKeyValueCoding informal protocol:
http://developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Reference/Foundation/Protocols/NSKeyValueCoding_Protocol/Reference/Reference.html
The brackets are currently in the wrong place:
[second addObject:[[first objectAtIndex:i] name]];
Updated Answer:
Again, I think you should split stuff out into easy to parse lines of code:
for (id theObject in first)
{
// without an actual type, I still think the compiler might
// throw a warning on this next line of code;
// but maybe RJR III is correct and it won't warn.
// I didn't check.
NSString * nameOfObject = [theObject name];
if(nameOfObject)
{
[second addObject:nameOfObject];
}
}
Notice that I do some error checking in here as well (i.e. making sure the name is not nil).
Original Answer:
You're getting a warning because the compiler doesn't know what kind of custom object is being fetched from your call to "[first objectAtIndex: i]". In other words, it doesn't know what kind of object you're trying to get the "name" of.
Cast it to the right type and you'll get rid of the warning.
Or even better, split that one line of multiple things happening at once into two or three lines of code and make your code more readable in the process.

While-Loop works but not the if statement?

I have this while loop in my code. The loop seems to work fine since I printed my i++ in the console. But for some reason it only checks my if statement the first time around. I can only add one title into the NSMutableArray called sectionZeroTitleArray. I have many arrays in this loop so it might get confusing. I will try my best to explain.
Here is what I am trying to do:
Loop through the length of an array(topicArray). If the array's(topicArray)is the same as this other array's(anotherArray) first object then add an object that has the same index(titleArray) as topicArray to a new MutableArray(sectionZeroTitleArray).
I'm sure I did something stupid, maybe someone that hasn't stared at this all day can fix me up? Please and thank you.
while (i < (topicArray.count)) {
if ([topicArray objectAtIndex:i] == [anotherArray objectAtIndex:0]) {
[sectionZeroTitleArray addObject:[titleArray objectAtIndex:i]];
}
NSLog(#"sectionZeroLoopCount: %d", i);
i++;
}
You are checking for pointer equality when you use ==. Are you sure you want to do this? What is the type that you're expecting? If it's a NSString, use isEqualToString:, otherwise use NSObject's isEqual: method:
If the expected type is an NSString:
if([[topicArray objectAtIndex:i] isEqualToString:[anotherArray objectAtIndex:0]]) {
//...
}
Otherwise, you should probably do this:
if([[topicArray objectAtIndex:i] isEqual:[anotherArray objectAtIndex:0]]) {
//...
}
Yeah, you're comparing the pointers and not the values. Look at the documentation of NSString, particular the isEqualToString: method for comparing strings.

iPhone OS: making a switch statement that uses string literals as comparators instead of integers

So I'd like to do this:
switch (keyPath) {
case #"refreshCount":
//do stuff
case #"timesLaunched":
//do other stuff
}
but apparently you can only use integers as the switch quantity. Is the only way to do this parse the string into an integer identifier and then run the switch statement?
like this:
nsinteger num = nil;
if (keyPath isEqual:#"refreshCount") {
num = 0
}
if (keyPath isEqual:#"timesLaunched") {
num = 1
}
I'm trying to optimize this code to be as quick as possible because its going to get called quite often.
thanks,
Nick
NOTE: Yes I'm using KVO so I am recieving a string in the "callback."
NOTE #2: So what made me first consider the switch statement is my original code implementation was like this:
if ([keyPath isEqual:#"refreshCount"] && ([[change valueForKey:#"new"] intValue] == 10)) { //
NSLog(#"achievemnt hit inside");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"title" message:#"Achievement Unlocked!" delegate:self cancelButtonTitle:#"cancel" otherButtonTitles:nil];
[alert show];
I want to do a bunch of these with different XX values all in the same method:
if ([keyPath isEqual:#"refreshCount"] && ([[change valueForKey:#"new"] intValue] == 10)) {
//unlock small achievement
}
if ([keyPath isEqual:#"refreshCount"] && ([[change valueForKey:#"new"] intValue] == 50)) {
//unlock bigger achievement
}
//etc
This just seemed very inefficient to me but maybe I'm wrong.
That is, for a cast statement to work, it would just have to call isEqualToString: anyway and would be just as slow, but probably not anywhere near as slow as you think.
The first question, of course, is have you measured performance and do you have evidence that the code is causing a performance issue?
If not, go and finish your app. A shipping app always outperforms an app still in development!
I would bet you don't have a performance issue; if all of your strings really are inline string constants -- #"refreshCount" and the like -- related to key-value observing, then it is quite likely that all of them are constant strings compiled into the app and, thus, comparison will be really fast because every time you mention "#refreshCount" it really is the same string at the same address (which compares very fast).
If you do have a quantifiable performance issue, post the details in a different question and someone can answer specifically. As it is, there isn't enough architectural or quantitative information to do anything more than speculate.
Why not just use an enum?
typedef enum _KeyPath
{
KeyPathNone,
KeyPathRefreshCount,
KeyPathTimesLaunched,
KeyPathCount
} KeyPath;
If you must use strings that you should be comparing with isEqualToString:
From the NSString docs:
Special Considerations
When you know both objects are strings, this method is a faster way to check equality than isEqual:.
The short answer is to not use strings in the first place. Barring that, you can have the strings be keys in a dictionary with integer (NSNumber) values. Or you can use the hashes of the strings.
switch ( [keyPath myQuickHash] ) {
case kHashRefreshCount:
case kHashTimesLaunched:
}
If there are just a few distinct strings, you can use the first (or last) 4 characters as a string literal and consider that the hash.
switch ( [keyPath stringLiteral] ) {
case 'refr':
case 'time':
}
Edit:
A switch statement is essentially a sparse array of code snippets. A hash table is a means of looking up an index in a sparse array given an arbitrary value. Given a known set of string inputs, a switch statement can operate like a hash table, which is to say, have constant lookup time. All you have to do is choose a hash algorithm with no collisions for the known inputs. If the set of inputs is not known, this is not an option, but in this question it is possible that all inputs are known.
This has absolutely nothing to do with how Apple implements their hash algorithm because you have to implement your own hash algorithm. The algorithm chosen could probably be as simple as adding up the length and letters in the string.

how to write an If statement, to compare core data attribute value?

i'm an objective-c newcomer.
im trying to compare a core data entity attribute value and having trouble with the syntax.
i just want to know the best way way to write the if statement to compare values.
in this example, the someAttribute attribute is a boolean, and its default is NO.
NSString *myValue = [NSString stringWithFormat:#"%#", myObject.someAttribute];
if ([myValue isEqualToString:#"1"]) {
// do something
} else {
// do something else
}
is this the right way? i've tried other flavors, like below, but the results aren't accurate:
if (myObject.someAttribute == 1) {
if (myObject.someAttribute) {
If you look in the generated header for this entity, there's a good chance that the actual type of the property is not BOOL, but NSNumber, which is how Cocoa boxes numeric types into objects. Assuming I'm right, you might try:
if ([myObject.someAttribute boolValue]) { ... }
If your attribute is of BOOL type, this code will work fine
if(myObject.someAttribyte){
//so smth if someAttribute is YES
}
You can't convert a BOOL directly to a string.
Predicates are the preferred method of comparing CoreData values. It's more complicated to start but works better in the long run. See NSPredicate programming guide

Detect if one position in Array is already initiated

I need to check specific positions in an NSArray to see if they have already been initialized, but I am having trouble. I tried to do the following, but it causes my application to crash!
if ((NSMutableArray *)[arrAllBlocks objectAtIndex:iLine] == nil)
{
[arrAllBlocks insertObject:[[NSMutableArray alloc] init] atIndex:iLine];
}
NSMutableArray *columArray = (NSMutableArray *)[arrAllBlocks
objectAtIndex:iLine];
[columArray insertObject:newBlock atIndex:iColumn];
What is the best to do this? I already tried some methods like isValid, and things like that!
You have a few options here:
Option 1: Pre-fill the array with instances of NSNull, and then use the code given by Dave DeLong in his answer.
Option 2: (Similar to #1) pre-fill the array with instances of NSMutableArray, and then have no extra code at all. (If you're going to pre-fill, you may as well do this).
Option 3: Do not pre-fill the array, but insert items dynamically as required. This will be almost identical to a pre-fill if the first iLine is near the maximum:
while([arrAllBlocks count] <= iLine)
{
[arrAllBlocks addObject:[NSMutableArray arrayWithCapacity:0]];
}
NSMutableArray *columArray = (NSMutableArray *)[arrAllBlocks
objectAtIndex:iLine];
[columArray insertObject:newBlock atIndex:iColumn];
Option 4: Use a dictionary to maintain the list of NSMutableArrays:
NSString *key = [NSString stringWithFormat:#"%d", iLine];
NSMutableArray *columnArray = [dictAllBlocks objectForKey:key];
if (columnArray == nil)
{
columnArray = [NSMutableArray arrayWithCapacity:0];
[dictAllBlocks setObject:columnArray forKey:key];
}
[columArray insertObject:newBlock atIndex:iColumn];
How to choose:
If the maximum value for iLine is not enormous, I would go with option #2. A handful of NSMutableArrays initialized to zero capacity will take up very little memory.
If the maximum value for iLine is enormous, but you expect it to be accessed sparsely (i.e., only a few values of iLine will ever be accessed), then you should go with Option #4. This will save you from having to fill an NSMutableArray with objects that never get used. The overhead of converting the string-value key for the dictionary will be less than the overhead for creating all of those blanks.
If you're not sure, try out each option and profile them: measure your memory usage and the time required to execute. If neither of these options work, you may have to explore more complex solutions, but only do that if it turns out to be necessary.
A note of caution:
The original code that you posted has a memory leak in the following line:
[arrAllBlocks insertObject:[[NSMutableArray alloc] init] atIndex:iLine];
The NSMutableArray objects that you initialize here are never released. When you call [[NSMutableArray init] alloc], a brand new object is created (with a reference count of one). The insertObject method then adds that new object to arrAllBlocks, and retains it (increasing its retain count to 2). Later, when you release arrAllBlocks, the new array will be sent a release message, but that will only reduce its retain count to one again. At that point, it will stick around in RAM until your program exits.
The best thing to do here is to use [NSMutableArray arrayWithCapacity:0] instead (as I have done in my examples). This returns a new NSMutableArray, just the same as your code did, but this instance has already been autoreleased. That way, arrAllBlocks can take ownership of the new object and you can be sure that it will be released when appropriate.
You can't. NSArray (and its subclass NSMutableArray) do not allow you to insert nil into the array. That's clearly outlined in the documentation.
If, for some reason, you need to have "empty" values in an array, then you should insert [NSNull null] instead and test for that. From the docs: "The NSNull class defines a singleton object used to represent null values in collection objects (which don’t allow nil values)."
UPDATE:
This means you could change your code very simply to this:
if ([[arrAllBlocks objectAtIndex:iLine] isEqual:[NSNull null]]) {
[(NSMutableArray *)arrAllBlocks insertObject:[NSMutableArray array] atIndex:iLine];
}
NSMutableArray *columnArray = (NSMutableArray *)[arrAllBlocks objectAtIndex:iLine];
[columnArray insertObject:newBlock atIndex:iColumn];
To check for NSNull you can simply compare against the pointer, since it's a Singleton:
if ([NSNull null] == [arrAllBlocks objectAtIndex:iLine]) {
[arrAllBlocks insertObject:[NSMutableArray array] atIndex:iLine];
}
NSMutableArray *columnArray = [arrAllBlocks objectAtIndex:iLine];
[columnArray insertObject:newBlock atIndex:iColumn];
I also removed the unsightly casts. Casting is rarely necessary in Objective-C. It usually just adds noise, and can hide real bugs. Since you're experiencing crashes, it's worth removing the casts from this code and listen to what the compiler has to tell you about it.
Telling the compiler to ignore warnings for a piece of code does not make the underlying problem with it go away!