NSSet with NSStrings containstObject not return YES when it should - objective-c

I'm loading a dictionary (list of word, not the class) into a NSSet as NSStrings. I then repeatedly send this set the message -containsObject:someNSString. But it always returns false. I wrote some code to test it:
NSLog(#"Random from dictionary: %#", [dictionary anyObject]);
NSString *test = [NSString stringWithFormat:#"BEMIRED"];
NSLog(#"To match this word: %#", test);
if ([dictionary containsObject:test])
NSLog(#"YES!");
In the log I get the following:
Random from dictionary: BEMIRED
To match this word: BEMIRED
(I'm missing the "YES!")
When I try using CFShow(dictionary) I can see that it actually contains Strings and that everything. An example:
0 : <CFString 0xc3bd810 [0x1386400]>{contents = "BEMIRED"}
3 : <CFString 0xdf96ef0 [0x1386400]>{contents = "SUBJECTIFIED"}
Can anyone please help me here?
Thanks!

NSSet uses isEqual: to test for object equality, which NSString overrides to perform a string comparison as you would expect. The follow unit test passes:
- (void)testSetStrings
{
NSSet *set = [NSSet setWithObject:#"String 1"];
// I've used the UTF8 initializer to avoid any cleverness from reusing objects
NSString *string1 = [[[NSString alloc] initWithUTF8String:"String 1"] autorelease];
// Test the references/pointers are not the same
STAssertTrue([set anyObject] != string1, nil);
STAssertTrue([set containsObject:string1], nil);
}
We can see the two strings have different pointer values, but the set still returns YES for the containsObject: call.
So I would guess your strings are not in fact equal. I would check for hidden whitespace or other similar issues.

The -[NSSet containsObject:] seems to check for the pointer value only (the documentation is very lacking for that method), not for object equality. So you need to use -[NSSet member:] instead, which uses isEqual: to check whether an object that is considered to be equal is in your set.
if ([dictionary member:test])
NSLog(#"YES!");
Edit: Actually it seems that containsObject: does use isEqual: as well. They only seem to differ in what they return (containsObject: returns a BOOL while member: returns id). I'm letting this answer stay for documentation purposes.

Ok so I solved the problem and it had nothing to do with the containsObject method. As I commented i used Dave DeLongs DDFileReader found here: Dave DeLongs DDFileReader
So by using CFShow on the entire dictionary I noticed that every word had a new line at the end of it. So instead of the -readLine method i used the -readTrimmedLine (bot methods in above mentioned file reader). This solved the problem for me.
For future forum visitors I'd like to draw attention to the discussion DarkDust and zoul had about -containsObject and -member (both methods of NSSet) which it turns out both uses the -isEqual method.

Related

Return something other than nil from a method sent to nil

This is probably impossible with a category, but just in case it is doable, I wanted to find out.
I wrote a category on NSString and I have a category method that parses a comma delimited string into an NSArray cleaning up extra commas and spaces, and trimming the ends... so more comprehensive than the built-in method of similar functionality. Here's the rub though... what if the string is nil? Is there any way I can return an initialized NSArray with 0 objects instead of returning nil?
The scenario would go something like this...
NSArray *parsed = [someString parseStringIntoArray];
... assume someString is nil. Can I somehow get an initialized array out of this?
Obviously there are ways to work AROUND this, but keeping it clean and succinct, and using the category method... is it possible?
No. But yes, if you make some changes:
Since you call a instance method, this won't work. When you send a message to nil (aka call a method on a nil object) you will always get nil.
This is the key concept of nil itself.
You can for example add a class method and in this class method, you can then test against nil and return an empty array instead:
+ (NSArray *)parseStringIntoArray:(NSString *)string {
return [string componentsSeparatedByString:#","] ?: #[];
}
or you can simply use, what NSString has built in:
NSArray *parts = [#"foo,bar,la,le,lu" componentsSeparatedByString:#","] ?: #[];
EDIT
No, there's no way to return anything from a message sent to nil. That is baked into the very core of the runtime: objc_msgSend() is responsible for this behavior.
If the receiver is nil, objc_msgSend() resolves the expression to the appropriate 0 value for the return type of the method.
You will have to test for nil either before or after and change the value of the array manually.
(Incidentally, the fact that this is a category method is irrelevant.)

Why set types in Obj-c fast enumeration loops?

NSMutableArray *array = [[NSMutableArray alloc] init];
NSString *string = #"string";
[array addObject:string];
NSDate *date = [[NSDate alloc] init];
[array addObject:date];
for (*placeholder* stuff in array)
NSLog(#"one");
If I change placeholder to either NSString* or NSDate*, I expect to see "one", because the for loop should just ignore a non-matching type. However, the result is "one one".
Doesn't this imply that you should just have placeholder be id whatever the situation, since it doesn't seem to matter anyhow?
fast enumeration always iterates over all object in a collection. it does not filter.
The only thing that happens is, that you will have some strange casts.
if your array contains objects of differnt classes, you can determine the class for each object with isMemberOfClass:
if you would do for (NSDate *obj in array), any object in the array will be casts to NSDate, no matter if that is sense-full or not. and due to the nature of objective-c it will even work, as-long as you dont send a message that is only understandable by NSDate objects or send the object as an argument to a method that needs to receive a date object, as a cast does not change the object in anyway. A cast is just a promise you make to the compiler that you know what you are doing. Actually you also can call it a lie.
To answer your question title itself: You dont have to set the class inside the loop statement. the generic object type id is sufficient. But usually you have objects of one kind in an array — views, numbers, string, dates,…. by declaring the right class you gain some comfort like better autocompletion.
Yes, using id (or some other common ancestor class) is the correct approach, and then it's necessary to determine which type of class has been enumerated in order to handle it differently:
for (id obj in array)
{
if ([obj isMemberOfClass:[NSString class]])
{
NSString *str = (NSString *)obj;
NSLog("obj is a string: %#", str);
}
else if ([obj isMemberOfClass:[NSDate class]])
{
NSDate *date = (NSDate *)obj;
NSLog("obj is a date: %#", date);
}
}
The problem has nothing to do with fast enumeration, but with collections which can contain any type of object. The same question arises when you access an individual element of an array:
id lastObject = [array lastObject];
or
NSString *string = [array lastObject];
Which will you chose? It all depends on your code. If you're sure that array only contains strings, then in my opinion it is better to use the second choice, because you get additional type checking, autocompletion, and method matching from the compiler (i.e. you won't get warnings if you call a method that has different signatures for two different objects). The same applies to fast enumeration: if your collection can contain any kind of object, use id. If you know what it contains, use the specific type. (And the same also applies to block tests. In NSArray's method
- (NSUInteger)indexOfObjectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
if you know it only contains strings for instance, you can replace id with NSString * in the block arguments. It won't change at all the compiled code or the behavior of your application, it will only change the compiler type checking.

If I want to make a new instance of an object in a function whose pointer is passed by reference in it

- (void)createAString:(NSString **)str
{
*str = [NSString stringWithString:#"Hi all!"];
[*str autorelease]; // ???? is this right ?
}
How should I use release or autorelease ? I don't want to release outside of the function of course :)
...
NSString *createStr;
[self createAString:&createStr];
NSLog(#"%#", createStr);
You're correct that you'd generally want to return autoreleased (or the like) objects from out params when you use this form. Your assignment statement in the function that sets *str to a string:
*str = [NSString stringWithString:#"foo"];
is already doing the right thing, because that method returns an instance of NSString that the caller doesn't own. Just like you could return this string object from your function without any further memory management, you can set it as the outparam as you've done. Your second snippet showing the call site is fine.
This said, I'm worried about a few things in your code that you should be sure you understand:
The value of str inside the method is still a **, and sending that a message (as you've done for the speculative autorelease) is nonsense. Be sure you fully understand doubly indirected pointers before using them too liberally. :) If you need to send str a message after creating it, send it to *str, which is what contains the NSString *.
Setting an outparam like this when the function returns void is not idiomatic Cocoa. You would normally just return the NSString * directly. Outparams are rare in Cocoa. (Usually just NSErrors get this treatment from framework calls. Otherwise they conventionally use name like getString to differentiate them from normal get accessors which don't use the word "get".)
I hope -stringWithString was just an example. That method is almost never used in practice, since it's equivalent (in this case) to just using a #"string literal" (although that would muddy your example).
Instead of using a double pointer, would it not be more elegant to use an NSMutableString instead?
- (void)createAString:(NSMutableString *)str
{
[str setString:#"Hi all!"];
}
....
NSMutableString *createStr = [[NSMutableString alloc] init];
[self createAString: createStr];
NSLog(#"%#", createStr);
[createStr release];
Or, even better, just have the createAString method return an NSString.
- (NSString *)createAString
{
return #"Hi all!"; // this is autoreleased automatically
}
I wouldn't want to presume that your needs are this simple, though. =)

Objective-C: How do you append a string to an NSMutableString?

URL Download
http://code.google.com/p/mwiphonesdk/source/browse/#svn/trunk/iMADE/PrepTasks/08
I have code at the location at the link above and I am using NSMutableString to append strings as data is downloaded. But I am finding that using appendString does not increase the length of the mutable string. I must be doing something wrong.
And when I am done I need to convert NSMutableString to NSString to return it. I have looked for examples to do this but I do not see any yet.
I am most familiar with Java and C# where there is a StringBuffer/StringBuilder which allows you to append pieces and them simply call toString at the end to get a String. It does not appear to be this easy in Objective-C.
NSString* str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
#pragma mark TODO Confirm this is appending a value to the mutable string
[self.mutableString appendString:str];
NSLog(#"str length: %d, %d", [str length], [self.mutableString length]);
Above is the section of code that calls appendString.
I see that str has a length > 0 but calling appendString does not increase the length of self.mutableString.
What am I doing wrong here?
As for having an NSMutableString* and needing to return an NSString*, the former is a subclass of the latter so anywhere you see an NSString* an NSMutableString* will suffice as-is.
Your code looks OK from what you've posted. The only thing I can think of is that perhaps there isn't any data to speak of when initializing the str variable. In such a case appending an empty string will do nothing to mutableString.
You'll also want to make sure self.mutableString has been properly allocated and initialized. You can send messages to NSObject*s that are nil which may be misleading when [self.mutableString length] returns 0.
I have fixed the problem. I simply was not initializing the NSMutableString value and it was transparently not doing anything.
Before appending the string I put the following code.
if (_mutableString == nil){
_mutableString = [[NSMutableString alloc] init];
}
Thanks everyone for answering. And it is good to know that I can use NSMutableString in place of NSString. (that is too easy) :)

Change the values within NSArray by dereferencing?

I've come across a problem related to pointers within arrays in objective-c.
What I'm trying to do is take the pointers within an NSArray, pass them to a method, and then assign the returned value back to the original pointer(the pointer which belongs to the array).
Based on what I know from C and C++, by dereferencing the pointers within the array, I should be able to change the values they point to... Here is the code I'm using, but it is not working (the value phone points to never changes based on the NSLog output).
NSArray *phoneNumbers = [phoneEmailDict objectForKey:#"phone"];
for (NSString* phone in phoneNumbers) {
(*phone) = (*[self removeNonNumbers:phone]);
NSLog(#"phone:%#", phone);
}
And here is the method signature I am passing the NSString* to:
- (NSString*) removeNonNumbers: (NSString*) string;
As you can see, I am iterating through each NSString* within phoneNumbers with the variable phone. I pass the phone to removeNonNumbers:, which returns the modified NSString*. I Then dereference the pointer returned from removeNonNumber and assign the value to phone.
As you can tell, I probably do not understand Objective-C objects that well. I'm pretty sure this would work in C++ or C, but I can't see why it doesn't work here! Thanks in advance for your help!
Yeah, that's not going to work. You'll need an NSMutableArray:
NSMutableArray * phoneNumbers = [[phoneEmailDict objectForKey:#"phone"] mutableCopy];
for (NSUInteger i = 0; i < [phoneNumber count]; ++i) {
NSString * phone = [phoneNumbers objectAtIndex:i];
phone = [self removeNonNumbers:phone];
[phoneNumbers replaceObjectAtIndex:i withObject:phone];
}
[phoneEmailDict setObject:phoneNumbers forKey:#"phone"];
[phoneNumbers release];
You can't dereference Objective-C object variables. They are always pointers, but you should treat them as though they're atomic values. You need to mutate the array itself to contain the new objects you're generating.
NSArray is not a C/C++ style array. It's an Objective-C object. You need to use the instance methods of the NSArray class to perform operations on it.
In Objective-C you never "dereference" an object pointer to set its value.
Also, you're using what is called Fast Enumeration, which does not allow mutation.
You can also use enumerateObjectsUsingBlock:.
NSArray *array = [NSArray array];
__block NSMutableArray *mutableCopyArray = [array mutableCopy];
[mutableCopyArray enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
[mutableCopyArray replaceObjectAtIndex:idx withObject:[object modifiedObject]];
}];
Checkout How do I iterate over an NSArray?
While this may work to some degree, I haven't tested it, I'd file this under 'bad idea' and not touch. NSArray, and many other cocoa objects, a fairly complex and can have a variety of implementations under the hood as part of the class cluster design pattern.
So when it comes down to it you really won't know what you're dealing internally. NSArray is actually designed to be immutable so in place editing is even doubly a bad idea.
Objects that are designed to let you mess around with the internals expose those through api methods like NSMutableData's mutableBytes.
You're better off constructing a new NS(Mutable)Array with the processed values.