What does forKey mean? What does it do? - objective-c

I'm guessing that it gives the object that is being added to the NSMutableDictionary or NSDictionary a name to access it. But, I have to confirm it. Can anybody tell me?

Dictionaries are data structures that contain key-value pairs. They're also known as hash tables. So yes, you use a key to refer to its corresponding value.
For the following dictionary:
// Pseudo-code, not actual Objective-C code, merely for illustration
// (This {} syntax would be really nice to have though...)
NSDictionary *dict = {
#"one" => NSNumber (1),
#"two" => NSNumber (2)
};
The following code yields 1:
NSNumber *one = [dict objectForKey:#"one"];
NSLog(#"%d", [one intValue]);

Related

EXC_BAD_ACCESS while filling in the dictionary (?)

void CountlyRecordEventSegmentationCountSum(const char * key, const char * segmentation, int count, double sum)
{
NSString * seg = CreateNSString(segmentation);
NSArray * entries = [seg componentsSeparatedByString:#"`"];
NSDictionary * dict = [NSDictionary dictionary];
for (id entry in entries)
{
NSArray * keyValue = [entry componentsSeparatedByString:#"|"];
[dict setValue:[keyValue objectAtIndex:1] forKey:[keyValue objectAtIndex:0]];
}
[[Countly sharedInstance] recordEvent:CreateNSString(key) segmentation:dict count:count sum:sum];
}
I put "?" in the title because I'm not entirely sure if the problem is in the code above but that's my best guess. I'm integrating Countly iOS plugin with Unity and one of Countly plugin's methods take NSDictionary * as argument. As I don't know how to send a dictionary from C# to Objective-C I'm storing my dict in a string, sending it to Objective-C and then recreating the dictionary (the code above).
But that's probably even not relevant. I know EXC_BAD_ACCESS usually has something to do with unfreed resources or sth so maybe you can see what I'm doing wrong (I don't know Objective-C at all, just writing a few lines needed by the plugin).
Edit:
From Unity sample:
// Converts C style string to NSString
NSString * CreateNSString (const char * string)
{
if (string)
return [NSString stringWithUTF8String: string];
else
return [NSString stringWithUTF8String: ""];
}
The error you've made is that you are trying to modify immutable version of NSDictionary.
One cannot modify contents of the NSDictionary after it's initialization. You should use NSMutableDictionary instead.
Here is a documentation on NSMutableDictionary.
And here is an example of how to create mutable version of an immutable object that conforms to NSMutableCopying protocol.
You need to be using NSMutableDictionary, you can't modify an NSDictionary.
Also, you should use setObject:forKey: because setValue:forKey: is a KVC method. It happens to do the same thing on an NSMutableDictionary for most keys, but it is marginally slower.
Finally, you should check that [keyValue count] >= 2 before trying to access the objects at indexes 0 and 1.
Edit Also, CreateNSString() looks suspicious. It might be either leaking or prematurely releasing the string. But you need to post the code. In any case, I'd use
seg = [NSString stringWithUTF8String: segment];
or, other appropriate method if segment is not encoded in UTF-8.

NSDictionary case insensitive objectForKey:

NSDictionary has objectForKey but it's case-sentive for keys. There is No function available like
- (id)objectForKey:(id)aKey options:(id) options;
where in options you can pass "NSCaseInsensitiveSearch"
To get key's from NSDictionary which is case-insesitive one can use the following code written below.
You need to add Category of NSDictionary Class with this functionality
- (id)objectForCaseInsensitiveKey:(NSString *)key {
NSArray *allKeys = [self allKeys];
for (NSString *str in allKeys) {
if ([key caseInsensitiveCompare:str] == NSOrderedSame) {
return [self objectForKey:str];
}
}
return nil;
}
This isn't included for a couple of reasons:
NSDictionary uses hash equality, and for pretty much any good hashing algorithm, any variation in the source string results in a different hash.
More importantly, NSDictionary keys are not strings. Any object that conforms to NSCopying can be a dictionary key, and that includes a whole lot more than strings. What would a case-insensitive comparison of an NSNumber with an NSBezierPath look like?
Many of the answers here offer solutions that amount to transforming the dictionary into an array and iterating over it. That works, and if you just need this as a one-off, that's fine. But that solution is kinda ugly and has bad performance characteristics. If this were something I needed a lot (say, enough to create an NSDictionary category), I would want to solve it properly, at the data structure level.
What you want is a class that wraps an NSDictionary, only allows strings for keys and automatically lowercases keys as they are given (and possibly also remembers the original key if you need a two-way mapping). This would be fairly simple to implement and is a much cleaner design. It's too heavy for a one-off, but if this is something you're doing a lot, I think it's worth doing cleanly.
The correct answer is that you should use case-folded keys as dictionary keys. This is not the same as converting them to upper or lower case and it won't destroy the O(1) average case search/insert complexity.
Unfortunately, Cocoa doesn't seem to have an appropriate NSString method to case-fold a string, but Core Foundation has CFStringFold() which you can use for that purpose. Let's write a short function to do the necessary work:
NSString *foldedString(NSString *s, NSLocale *locale)
{
CFMutableStringRef ret = CFStringCreateMutableCopy(kCFAllocatorDefault, 0,
(__bridge CFStringRef)s);
CFStringNormalize(ret, kCFStringNormalizationFormD);
CFStringFold(ret, kCFCompareCaseInsensitive, (__bridge CFLocaleRef)locale);
return (__bridge_transfer NSString *)ret;
}
Note that the locale argument is important. If you specify NULL, you will get the current system locale. This will be fine in most cases, but Turkish users might be surprised that "I" matches "i" rather than "ı". You might therefore want to pass [NSLocale currentLocale], and if you're saving the results you might also want to save the locale identifier and create the locale from that.
So, when adding to the dictionary, you now need to do
[dict setObject:obj forKey:foldedString(myKey, locale)];
and to look up again
[dict objectForKey:foldedString(myKey, locale)];
One final observation is that you might wish to store the case-folded keys alongside the original values, then you don't have to fold them on every access to the dictionary.
In the code written below, I search for a actual key for a input key. So , if input key=#"naMe" then the actual key=#"name".
NSDictionary *dic=[NSDictionary dictionaryWithObjectsAndKeys:#"John",#"Name",#"123456",#"empId", nil];
NSString *key=#"naMe";
NSString *name=[dic objectForKey:key];
if(name==nil){
NSPredicate *searchPred=[NSPredicate predicateWithFormat:#"self LIKE[cd] %#",key];
NSArray *searchedKeys=[[dic allKeys] filteredArrayUsingPredicate:searchPred];
if(searchedKeys.count>0){
name=[dic objectForKey:[searchedKeys objectAtIndex:0]];
}
}
NSLog(#"Name = %#",name);
Many answers are correct, but here's a more example:
NSDictionary* dict= #{ #"hello" : #"Hey" };
NSArray* keys= [dict allKeys];
NSUInteger index=[keys indexOfObjectPassingTest: ^BOOL (id obj, NSUInteger index, BOOL* stop)
{
if( [obj caseInsensitiveCompare: #"Hello"]==NSOrderedSame)
{
*stop= YES;
return YES;
}
else
{
return NO;
}
}];
Personally I find this method easier, but everyone has his programming style.
EDIT
A less readable but shorter solution:
NSDictionary* dict= #{ #"hello" : #"Hey" };
NSArray* keys= [dict allKeys];
NSUInteger index=[keys indexOfObjectPassingTest: ^BOOL (id obj, NSUInteger index, BOOL* stop)
{
return *stop= [obj caseInsensitiveCompare: #"Hello"]==NSOrderedSame ;
}];
If you are only storing into, and retrieving from, the NSDictionary in one place (maybe two or three), you could use
[myString lowercaseString]
in both. The more rigorous answers are useful if the dictionary object is used all over your code.

Get all keys of an NSDictionary as an NSArray

Is it possible to get all the keys from a specific NSDictionary as a seperate NSArray?
Just use
NSArray*keys=[dict allKeys];
In general, if you wonder if a specific class has a specific method, look up Apple's own documentation. In this case, see NSDictionary class reference. Go through all the methods. You'll discover many useful methods that way.
Yes it's possible. Use allKeys method:
NSDictionary *yourDictionary;
NSArray * yourKeys
yourKeys = [yourDictionary allKeys];
And if you want to get all keys and values, here's what you do:
for (NSString *key in dictionary) {
id value = dictionary[key];
NSLog(#"Value: %# for key: %#", value, key);
}

How does NSDictionary handle NIL objects?

Consider the code below. In essence, we get 2 strings, then we add these values to the NSDictionary.
However, i hit a weird bug. When fbAccessTokenKey is 0x0 (or nil), then twitterToken would not be added as well.
NSString *fbAccessTokenKey=[[UserStockInfo sharedUserStockInfo] getFBAccessTokenKey];
NSString *twitterToken=[[UserStockInfo sharedUserStockInfo] getTwitterAccessTokenKey];
NSDictionary *params= [[NSDictionary alloc] initWithObjectsAndKeys:
fbAccessTokenKey, #"fb_access_token",
twitterToken, #"twitter_access_token",
nil
];
Why is this happening, and what is a good way of resolving this?
nil is used as a 'sentinel' for marking the "end of arguments" list. If twitterToken was nil, the runtime would go through your arguments, and once it got to twitterToken, it would think that it was up to the end of your list of objects and keys. This is due to the way that C/Obj-C is implemented when it comes to list arguments.
The alternative safe way to do it is to use an NSMutableDictionary, and check to see if your values are non-nil, then add them to the mutable dictionary like this:
NSString *fbAccessTokenKey = [[UserStockInfo sharedUserStockInfo] getFBAccessTokenKey];
NSString *twitterToken = [[UserStockInfo sharedUserStockInfo] getTwitterAccessTokenKey];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
if (fbAccessTokenKey) [params setObject:fbAccessTokenKey forKey:#"fb_access_token"];
if (twitterToken) [params setObject:twitterToken forKey:#"twitter_access_token"];
For more technical info, there's a good article on Cocoa with Love: http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html
You can use the NSNull object.
Documentation here:
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSNull_Class/Reference/Reference.html
Rather than initializing with initWithObjectAndKeys. Why not instantiate an NSMutableDictionary and then add the key value pairs (or not if the key is null)?
NSMutableDictionary * params = [[NSMutableDictionary alloc] init];
if (fbAccessTokenKey)
[params setObject:fbAccessTokenKey forKey:#"fb_access_token];
// ... etc
You could cast it back to an NSDictionary later if you want to keep it immutable from that point.
Update
Just a note in response to Josh's comment, I should clarify that of course the cast will not magically convert the params NSMutableDictionary to an NSDictionary. But if you are passing it to code which requires an NSDictionary, the cast will let you treat it as such.
Josh's comment includes this code:
NSMutableDictionary * md = [[NSMutableDictionary alloc] init];
NSDictionary * d = (NSDictionary *)md;
[d setObject:#"Yes I am" forKey:#"Still mutable?"];
NSLog(#"%#", d); // Prints { "Still mutable?" = Yes I am; }
This will generate the following compiler warning (and for me, with warnings generating errors, a compile error):
file:blah.m: error: Semantic Issue: 'NSDictionary' may not respond to 'setObject:forKey:'

How to implement Perl hashes in objective-c?

I have been looking through many sites and tutorials, and the Apple documentation, and still haven't found a solution: it seems to me that NSArray, NSDictionary and their mutable counterparts are not at all resembling the simple functionalities of a Perl hash. I hope I am wrong of course.
What I need: a mutable structure of dynamic keys and values (1 key - 1 value, as simple as that)! I mean, I don't know keys in advance and I need to easily check whether a key exists and if it exists retrieve a value or update it, if it does not exist enter the new key with the new value. And I need the values to be floats, not objects nor arrays. After I finished populating the structure I need to be able to retrieve the keys and finally looping through the values by the keys I retrieved.
All of this is easily accomplished in Perl with the following:
my %expenses;
if (exists $expenses{$key}) {
$expenses{$key} += $amount;
} else {
$expenses{$key} = $amount;
}
[...]
Is there someone who could tell me how to implement something similar in objective-c without using primitive types?
Thank you so much for any help.
Fabrizio
The Cocoa and Core Foundation collection classes are generally oriented towards storing objects rather than primitive values; the usual solution to a problem like yours is to wrap the floats in NSNumber objects. And unfortunately, the syntax for getting and setting the objects is more verbose than Perl's
Other than that, an NSMutableDictionary should do exactly what you want. The keys can be any string, or any other object that can be copied (i.e. it conforms to the NSCopying protocol), you can get a list of all keys, and you can check if a key "exists" in the dictionary by simply trying to fetch the corresponding value. The code corresponding to your example could look something like this:
// NB: there no autovivification in Objective-C. Be sure to initialize this somewhere
// before using it!
NSMutableDictionary *expenses;
if ([expenses objectForKey:key]) {
// This *could* be done in one statement. But it would be very long, so I split
// it in two for clarity.
float currentValue = [[expenses objectForKey:key] floatValue];
[expenses setObject:[NSNumber numberWithFloat:currentValue + amount] forKey:key];
} else {
[expenses setObject:[NSNumber numberWithFloat:amount] forKey:key];
}
This works:
NSString *key=#"fish";
NSMutableDictionary *expenses = [NSMutableDictionary dictionary];
float amount=22.0;
[expenses setObject:[NSNumber numberWithFloat:amount/2] forKey:key];
if ([expenses objectForKey:key]) {
[expenses setObject:
[NSNumber numberWithFloat:
[[expenses objectForKey:key] floatValue] + amount] forKey:key];
} else {
[expenses setObject:[NSNumber numberWithFloat:amount] forKey:key];
}
NSLog(#"expenses: %#",expenses);