views:

3734

answers:

4

Hi, I am new to Objective-C and I have no clue why this code is not working:

    NSMutableDictionary *bookmarks = [NSMutableDictionary dictionaryWithCapacity:(NSUInteger) 4];
[bookmarks setObject:@"Stanford University" forKey:[NSURL URLWithString:(NSString *) @"http://www.stanford.edu"]];
[bookmarks setObject:@"Apple" forKey:[NSURL URLWithString:(NSString *) @"http://www.apple.com"]];
[bookmarks setObject:@"Berkeley" forKey:[NSURL URLWithString:(NSString *) @"http://www.berkeley.edu"]];
[bookmarks setObject:@"CS193P" forKey:[NSURL URLWithString:(NSString *) @"http://cs193p.stanford.edu"]];

NSEnumerator *browser = [bookmarks keyEnumerator];
id each;
NSURL *url;
while ((each = [browser nextObject])) {
 url = [browser valueForKey:(NSString *)each];
 NSLog(@"%@", [url absoluteURL]);
}

The error I get is:

2009-06-29 11:25:22.844 WhatATool[2102:10b] *** -[NSURL length]: unrecognized selector sent to instance 0x1072c0
2009-06-29 11:25:22.845 WhatATool[2102:10b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSURL length]: unrecognized selector sent to instance 0x1072c0'

Any help would be appreciated. Thanks.

+3  A: 

You have the bookmarks dictionary intiialization reversed:

[bookmarks setObject:@"Stanford University" forKey:[NSURL URLWithString:(NSString *) @"http://www.stanford.edu"]];

Should be:

[bookmarks setObject:[NSURL URLWithString:(NSString *) @"http://www.stanford.edu"] forKey:@"Stanford University"];

Also, there's a simpler way to do enumeration:

NSURL *url;
for ( NSString *each in [bookmarks allKeys] ) 
{
        url = [browser objectForKey:(NSString *)each];
        NSLog(@"%@", [url absoluteURL]);
}

You could also enumerate through [bookmarks allValues] to get at the URL object directly instead of getting the keys out first (though you may well want the keys for some other reason).

Kendall Helmstetter Gelner
This isn't a bad answer, but there are other problems in the code, too. See my answer for details. Also, it turns out that doing fast enumeration directly on the dictionary object is faster. (Strangely, NSEnumerator objects return only one object per call. It's a long story, and has to do with compatibility, so you can use the same enumerator after fast enumeration without unintentionally skipping elements.)
Quinn Taylor
+1  A: 

I think you're referring to the wrong variable in your while loop when trying to get the URL (and I think you should be using the objectForKey: selector to get a value of an NSDictionary, not valueForKey:):

NSEnumerator *browser = [bookmarks keyEnumerator];
id each;
NSURL *url;
while ((each = [browser nextObject])) {
    url = [bookmarks objectForKey:(NSString *)each]; // <-- bookmarks, not browser
 NSLog(@"URL = %@", url);
}
drewh
Good catch on the -valueForKey: distinction. The truth is that -valueForKey: is intended for use by Cocoa Bindings. The docs say that unless the key starts with "@", it just calls -objectForKey: anyway. It's generally best to just call that method directly.
Quinn Taylor
+7  A: 

A key is what you use to access an object in the dictionary. You have your keys and objects reversed on every line you call -[bookmarks setObject:... forKey...] in the code. The problem in your case arises because you're trying to treat NSURL objects as NSString objects — [bookmarks objectForKey:(NSString *)each] — and the dictionary is trying to get the length of the supposed string by calling -length, which exists for NSString but not for NSURL.

If you're always constructing a dictionary with the same objects, consider using a more compact "varargs" constructor for NSDictionary. (Note that the last comma-separated argument must be nil — see the related documentation) You can also use -[NSDictionary dictionaryWithObjects:forKeys:] with two NSArray objects, containing the URLs and the bookmark names. (Incidentally, casting string literals as NSString* to create an NSURL is completely unnecessary.)

NSDictionary *bookmarks = [NSDictionary dictionaryWithObjectsAndKeys:
                           [NSURL URLWithString:@"http://www.stanford.edu"],
                           @"Stanford University",
                           [NSURL URLWithString:@"http://www.apple.com"],
                           @"Apple",
                           [NSURL URLWithString:@"http://www.berkeley.edu"],
                           @"Berkeley",
                           [NSURL URLWithString:@"http://cs193p.stanford.edu"],
                           @"CS193P",
                           nil];

In addition, you have a lurking error that hasn't been mentioned yet: browser is an NSEnumerator, but you're calling valueForKey: as if it were an NSDictionary. That will cause a similar crash for an unrecognized selector, too. (Even for dictionary objects, you should call -objectForKey: instead; -valueForKey: is used for/by Cocoa Bindings, and does extra work you don't need. I realize it's confusing since we think in terms of "key-value pairs", but there it is...)

Finally, you can also simplify the enumeration code somewhat. (A for-in loop on an NSDictionary enumerates its keys just like -keyEnumerator would.)

Here's how I'd suggest doing the last part:

NSURL *url
for (NSString *key in bookmarks) {
    url = [bookmarks objectForKey:key];
    NSLog(@"%@", [url absoluteURL]);
}
Quinn Taylor
+1  A: 

I'm working through the same online classes and found that none of the answers above seem to work either because NSEnumerator may not respond to objectForKey or because the particular type of enumeration is not supported (perhaps not in the iPhone SDK as compared to OSX i don't know). I got it to work like a charm as follows though...

NSString *aKey;
NSEnumerator *keyEnumerator = [bookmarks keyEnumerator];
NSURL *url;

while (aKey = [keyEnumerator nextObject])
{
    url = [bookmarks objectForKey:aKey];
    NSLog(@"value: %@", [url absoluteURL]);
}
tijs