Renaming keys in NSMutableDictionary - cocoa

Given an NSMutableDictionary *dict, is this a bad way to replace keys with a new name? Is there an easier way?
NSArray *originalField = [NSArray arrayWithObjects:#"oldkey", #"oldkey2", nil];
NSArray *replacedField = [NSArray arrayWithObjects:#"newkey", #"newkey2", nil];
for (int i=0; i<[originalField count]; ++i)
{
if ([dict objectForKey:[originalField objectAtIndex:i]] != nil) {
[dict setObject:[dict objectForKey:[originalField objectAtIndex:i]] forKey:[replacedField objectAtIndex:i]];
[dict removeObjectForKey:[originalField objectAtIndex:i]];
}
}
Thanks!

Nope, that's pretty much it. In general, you'd use fast enumeration and/or NSEnumerator to walk the arrays instead of going index-by-index, but since you're walking two parallel arrays, indexes are the clearest way to do it.

That's not a bad way per se, but you could certainly make it more elegant (and in my opinion, easier) by cleaning up the code a bit and eliminating a few redundant method calls. As #Peter suggested, fast enumeration (you can use it on Leopard+ or iPhone) would be much quicker and cleaner, and therefore generally preferable. Here's an example:
NSArray *originalField = [NSArray arrayWithObjects:#"oldkey", #"oldkey2", nil];
NSArray *replacedField = [NSArray arrayWithObjects:#"newkey", #"newkey2", nil];
id anObject;
NSEnumerator *replacementKeys = [replacedField objectEnumerator];
for (id originalKey in originalField) {
if ((anObject = [dict objectForKey:originalKey]) != nil) {
[dict removeObjectForKey:originalKey];
[dict setObject:anObject forKey:[replacementKeys nextObject]];
}
}
One note of warning: you'll want to make sure that the arrays originalField and replacedField are the same length. If the latter is shorter, you'll get an exception, either from -[NSEnumerator nextObject] or -[NSArray objectAtIndex:]. If the latter is longer, you may wonder why some of the replacement keys are never used. You could use an NSAssert macro to verify that during debugging, and it will be disabled automatically in release builds.
Alternatively, if there is truly a one-to-one relationship between the keys, perhaps you could use a dictionary to map from old key to new key, and enumerate over the result of -[NSDictionary allKeys].

Related

addObject to NSArray

I am a newbe and have been working all day readying/watching/searching for a solution to this. The following code works, but now how do I add an integer to the array?
//this works
NSArray *array = [NSArray arrayWithObjects:[NSNumber numberWithInt:10],
[NSNumber numberWithFloat:25.96], #"Hello World", nil];
for (id obj in array)
NSLog(#"%#", obj);
The following returns error: Use of undeclared identifier 'obj'
NSArray *array = [NSArray arrayWithObjects:[NSNumber numberWithInt:10],
[NSNumber numberWithFloat:25.96], #"Hello World", nil];
for (id obj in array)
[array addObject:7];
NSLog(#"%#", obj);
you must use a NSMutabbleArray not an NSArray to add objects dynamically and don't forget [] and arrayWithObjects is only for nsarray. try this:
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:[NSNumber numberWithInt:10]];
[array addObject:[NSNumber numberWithFloat:25.96]];
[array addObject:#"Hello World"];
[array addObject:[NSNumber numberWithInt:7]];
for (id obj in array) {
NSLog(#"%#", obj);
}
Just Copy paste this code and it will initiate the array with object you want and then show them.
There are several issues here:
You are attempting to mutate (change) an immutable array. You would need an NSMutableArray instead of NSArray to do what you want. (Incidentally, your actual NSArray creation code is fine.)
Your for() loop is blowing up mainly because you don't have braces to delimit your code. Absent braces, the compiler is going to use only the first line following the for as the loop contents. Hence your NSLog() is outside the loop, and obj out of scope.
I don't know why you don't have brackets around your addObject: line, as in [array addObject:...]. Did that even compile? Is that a copy and paste error?
Finally, you can't add a naked integer to a Cocoa collection such as NSArray. You would need to follow what you properly did earlier in your code: [array addObject:[NSNumber numberWithInt:7]].
Given that you were using indentation as syntax, I'm guessing maybe you have more of a Python (or similar) background? If so, you are going to have to quickly adjust some of your modes of thinking to program effectively in C or Objective-C. I urge you to get a beginners book on Cocoa programming and start at the beginning or you are going to get very frustrated very quickly.
Edit: I notice that after I posted you added brackets back in, so I struck #3 above. This leads to another important note: make sure that if you ask a question that you copy and paste the exact code that failed. Never re-type!
It's because your using indentation in your for loop which only considers the following line. Try this instead
    for (id obj in array) {
        [array addObject:[NSNumber numberWithInt:7]];
        NSLog(#"%#", obj);
}
Note
I think you need to use a NSMutableArray if you want to add objects after initialising it. Also it's not a good idea to modify a collection your looping through (in this case the loop will never end as you keep adding an object into an array your looping through). You can make a mutable version of the array by using this line.
NSMutableArray * mutArray = [NSMutableArray arrayWithArray:array];

CoreData predicate latest stored date

I need to get the latest date from coredata
i found a way
NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[fetchRequest setFetchLimit:1];
so sort them by date and then pick the first
however can this not be done more optimal? this approach looks like a brute force
sort is nlogn, but simple search for the max is n
You can actually ask SQL for just that value, not the object with that value:
NSExpression *date = [NSExpression expressionForKeyPath:#"date"];
NSExpression *maxDate = [NSExpression expressionForFunction:#"max:"
arguments:[NSArray arrayWithObject:maxDate]];
NSExpressionDescription *d = [[[NSExpressionDescription alloc] init] autorelease];
[d setName:#"maxDate"];
[d setExpression:maxSalaryExpression];
[d setExpressionResultType:NSDateAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:d]];
NSError *error = nil;
NSArray *objects = [managedObjectContext executeFetchRequest:request error:&error];
if (objects == nil) {
// Handle the error.
} else {
if (0 < [objects count]) {
NSLog(#"Maximum date: %#", [[objects objectAtIndex:0] valueForKey:#"maxDate"]);
}
}
This is described in more detail under Fetching Managed Objects -> Fetching Specific Values in the CoreData documentation.
On line 2 maxDate expression refers to itself (maxDate).
I assume this must be the "date" variable from the first line.
I guess that's pretty much the best way. I'm not sure whether there is a more efficient way since it has to compare every date anyway to figure out which the oldest is.
Here are 2 other ways:
1) You could work with BOOLs as an attribute of the managed object. (like oldest = 1)
However, you'd have to find a new "oldest" managed object every time you delete one.
2) You could just save the oldest one until it changes. This might save a lot of work if you have to find the oldest managedObject often.
It depends on your application (how many times you insert/remove managed objects and how many times you need the oldest object).

Is there a more memory efficient way to search through a Core Data database?

I need to see if an object that I have obtained from a CSV file with a unique identifier exists in my Core Data Database, and this is the code I deemed suitable for this task:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity;
entity =
[NSEntityDescription entityForName:#"ICD9"
inManagedObjectContext:passedContext];
[fetchRequest setEntity:entity];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"uniqueID like %#", uniqueIdentifier];
[fetchRequest setPredicate:pred];
NSError *err;
NSArray* icd9s = [passedContext executeFetchRequest:fetchRequest error:&err];
[fetchRequest release];
if ([icd9s count] > 0) {
for (int i = 0; i < [icd9s count]; i++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSString *name = [[icd9s objectAtIndex:i] valueForKey:#"uniqueID"];
if ([name caseInsensitiveCompare:uniqueIdentifier] == NSOrderedSame && name != nil)
{
[pool release];
return [icd9s objectAtIndex:i];
}
[pool release];
}
}
return nil;
After more thorough testing it appears that this code is responsible for a huge amount of leaking in the app I'm writing (it crashes on a 3GS before making it 20 percent through the 1459 items). I feel like this isn't the most efficient way to do this, any suggestions for a more memory efficient way? Thanks in advance!
Don't use the like operator in your request predicate. Use =. That should be much faster.
You can specify the case insensitivity of the search via the predicate, using the [c] modifier.
It's not necessary to create and destroy an NSAutoreleasePool on each iteration of your loop. In fact, it's probably not needed at all.
You don't need to do any of the checking inside the for() loop. You're duplicating the work of your predicate.
So I would change your code to be:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:...];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"uniqueID =[c] %#", uniqueIdentifier]];
NSError *err = nil;
NSArray *icd9s = [passedContext executeFetchRequest:fetchRequest error:&err];
[fetchRequest release];
if (error == nil && [icd9s count] > 0) {
return [icd9s objectAtIndex:0]; //we know the uniqueID matches, because of the predicate
}
return nil;
Use the Leaks template in Instruments to hunt down the leak(s). Your current code may be just fine once you fix them. The leak(s) may even be somewhere other than code.
Other problems:
Using fast enumeration will make the loop over the array (1) faster and (2) much easier to read.
Don't send release to an autorelease pool. If you ever port the code to garbage-collected Cocoa, the pool will not do anything. Instead, send it drain; in retain-release Cocoa and in Cocoa Touch, this works the same as release, and in garbage-collected Cocoa, it pokes the garbage collector, which is the closest equivalent in GC-land to draining the pool.
Don't repeat yourself. You currently have two [pool release]; lines for one pool, which gets every experienced Cocoa and Cocoa Touch programmer really worried. Store the result of your tests upon the name in a Boolean variable, then drain the pool before the condition, then conditionally return the object.
Be careful with variable types. -[NSArray count] returns and -[NSArray objectAtIndex:] takes an NSUInteger, not an int. Try to keep all your types matching at all times. (Switching to fast enumeration will, of course, solve this instance of this problem in a different way.)
Don't hide releases. I almost accused you of leaking the fetch request, then noticed that you'd buried it in the middle of the code. Make your releases prominent so that you're less likely to accidentally add redundant (i.e., crash-inducing) ones.

What is the better way of handling temporary strings?

I have a situation where I need to use some strings temporarily but I've read so many conflicting things that I'm a bit confused as to what the best way to proceed is.
I need to assign some strings inside an if structure but use them outside the if structure so they need to be created outside the if, I was thinking something like:
NSString *arbString = [[NSString alloc] init];
if(whatever)
{
arbString = #"Whatever"
}
else
{
arbString = #"SomethingElse"
}
myLabel.text = arbString;
[arbString release];
I have seen examples where people just used:
NSString *arbString;
to create the string variable
Google's Objective C guide says it's preferred to autorelease at creation time:
"When creating new temporary objects, autorelease them on the same line as you create them rather than a separate release later in the same method":
// AVOID (unless you have a compelling performance reason)
MyController* controller = [[MyController alloc] init];
// ... code here that might return ...
[controller release];
// BETTER
MyController* controller = [[[MyController alloc] init] autorelease];
So I have no idea, which is the best practice?
In the example you posted, you actually lose the reference to the NSString you created when you assign it in arbString = #"Whatever". You then release the string constant (which is unreleasable, by the way).
So there's a memory leak, since you never release the NSString you created.
Remember that all these types are pointers, so = only reassigns them.
As for the question, in this example, you don't need the [[NSString alloc] init]. You don't need to copy the string into a local variable anyway, you can just set myLabel.text to the string constant #"Whatever".
(edit: that's not to say that you can't use your pointer arbString, arbString = #"Whatever"; myLabel.text = arbString is fine. But this is just pointer assignment, not copying)
If you needed to manipulate the string before you returned it, you would create an NSMutableString, and either release or auto-release it. Personally, create autoreleased objects using class methods, so in this example, I'd use [NSString string], or [NSString stringWithString:], which return autoreleased objects.

NSArray to Core Data items

I have an method that reads an xml file and stores the xml nodes at a certain XPath-path in an NSArray called *nodes. What I want to do is take each one of the items in the array and add it to a core data entity called Category with the attribute of "name".
I have tried a number of different ways of creating the entity but I'm not sure about the correct way to do this effectively. This is the code used to create the NSArray, any ideas on how to implement this? (ignore the NSError, I will fix this in the final version)
- (IBAction)readCategories:(id)sender
{
NSString *xmlString = [resultView string];
NSData *xmlData = [xmlString dataUsingEncoding: NSASCIIStringEncoding];
NSXMLDocument *xmlDoc = [[NSXMLDocument alloc] initWithData:xmlData options:nil error:nil];
//XPath
NSError *err=nil;
NSArray *nodes = [xmlDoc nodesForXPath:#"//member[name='description']/value/string" error:&err];
}
EDIT - My loop code
NSArray *nodes = [xmlDoc nodesForXPath:#"//member[name='description']/value/string" error:&err];
int arrayCount = [nodes count];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSXMLElement *categoryEl;
NSString *new = [catArrayController newObject];
int i;
for (i = 0; i < arrayCount; i++)
{
[categoryEl = [nodes objectAtIndex:i]];
[new setValue:[categoryEl stringValue] forKey:#"name"];
[catArrayController addObject:new];
}
[pool release];
Here's how I'd write it:
for (NSXMLElement *categoryElement in nodes) {
NSManagedObject *newObject = [catArrayController newObject];
[newObject setValue:[categoryElement stringValue] forKey:#"name"];
[catArrayController addObject:newObject];
[newObject release];
}
First, I'm using the Objective-C 2.0 for-each syntax. This is simpler than using index variables. I eliminated i and arrayCount.
Next, I took out your NSAutoreleasePool. None of the objects in the loop are autoreleased, so it had no effect. (The newObject method returns a retained object which is, by convention, what methods with the word new in their name do) This is also why I release newObject after adding it to the array controller. Since I'm not going to be using it any more in this method, I need to release it.
Also, you had defined new (which I renamed newObject) as an NSString. Core Data objects are always either an instance of NSManagedObject or a subclass of NSManagedObject.
Your line [categoryEl = [nodes objectAtIndex:i]] won't compile. That's because the bracket syntax is used to send a message to an object. This is an assignment statement, so the bracket syntax is not needed here. (This line is also not necessary any more because of I've changed the loop to use the for-each syntax) But, for future reference, categoryEl = [nodes objectAtIndex:i]; would have worked.
What part are you having trouble with? There shouldn't be much more to it than looping through the array, creating a new managed object for each entry, and setting the correct attributes. You can create the managed object with NSEntityDescription's -insertNewObjectForEntityForName:inManagedObjectContext: method.

Resources