NSString internals - how does length works? - iphone

I've a question about NSString internals.
I want to check a string length and basically I wanted to know if a NSString knows its length / count each time / count & cache the result.
Should I store it's length and compute or call the length method each time ?
To test a string I can test against nil OR ask for it's length.
if (str != nil) {
// compute
}
if ([str length]) {
// compute
}
Which one is the fastest ?
Which one is the more memory efficient ?
Thanks

Checking for nil ("no object") is most definitely not the same as sending the length message to the (NSString) object. Only one of the conditional checks is valid to test for an "empty" string. (An "empty" string is an object and, therefore, not nil.)
The bigger question is: does NSString store a length or is it sentinel-terminated (like a "normal c string")? NSString stores the length as an internal property so it, length, is as O(1) operation.
Happy coding.

Here is how CFStringGetLength works:
(from http://opensource.apple.com/source/CF/CF-550.43/CFString.c)
/* Returns length; use __CFStrLength2 if contents buffer pointer has already been computed.
*/
CF_INLINE CFIndex __CFStrLength(CFStringRef str) {
if (__CFStrHasExplicitLength(str)) {
if (__CFStrIsInline(str)) {
return str->variants.inline1.length;
} else {
return str->variants.notInlineImmutable1.length;
}
} else {
return (CFIndex)(*((uint8_t *)__CFStrContents(str)));
}
}
So it should be O(1) for all cases.

The two -- testing a NSString pointer for nil and testing the length of an NSString -- are not in any way equivalent. An NSString object with a zero length can exist, and a pointer to it will not compare equal to nil.
To my knowledge (and I'd be quite surprised to discover I was wrong), the length of an NSString is stored within the object as an efficiently-referenced property. Caching the length would generally be unnecessary complexity.

NSString is immutable class, so the length stays the same all of the time.

Addendum: Testing against [string length] evaluates to 0/nil/NO in both cases (string being nil and string having zero length).

Related

NSString or NSCFString in xcode?

I m taking a NSMutabledictionary object in NSString like this :
NSString *state=[d valueForKey:#"State"];
Now sometimes state may be null and sometimes filled with text.So Im comparing it.While comparing state becomes NSString sometimes and NSCFString othertimes..So unable to get the desired result..
if([state isEqualToString#""])
{
//do something
}
else
{
//do something
}
So while comparing it is returning nil sometimes.So immediately jumping into the else block.
I need a standard way to compare if the state is empty whether it is a NSString or NSCFString ...
How can I do it?
If you're unable to get the result you want, I can assure you it's not because you get a NSCFString instead of a NSString.
In Objective-C, the framework is filled with cluster classes; that is, you see a class in the documentation, and in fact, it's just an interface. The framework has instead its own implementations of these classes. For instance, as you noted, the NSString class is often represented by the NSCFString class instead; and there are a few others, like NSConstantString and NSPathStore2, that are in fact subclasses of NSString, and that will behave just like you expect.
Your issue, from what I see...
Now sometimes state may be null and sometimes filled with text.
... is that in Objective-C, it's legal to call a method on nil. (Nil is the Objective-C concept of null in other languages like C# and Java.) However, when you do, the return value is always zeroed; so if you string is nil, any equality comparison to it made with a method will return NO, even if you compare against nil. And even then, please note that an empty string is not the same thing as nil, since nil can be seen as the absence of anything. An empty string doesn't have characters, but hey, at least it's there. nil means there's nothing.
So instead of using a method to compare state to an empty string, you probably need to check that state is not nil, using simple pointer equality.
if(state == nil)
{
//do something
}
else
{
//do something
}
You can do this
if([state isEqualToString:#""])
{
//do something
}
else
{
//do something
}
You must have to type cast it to get the correct answer.
NSString *state = (NSString *) [d valueForKey:#"State"];
if(state != nil)
{
if(state.length > 0)
{
//string contains characters
}
}

Testing object equality, i.e. the same physical address?

I am checking if an object I am getting back from the NSURLConnectionDataDelegate is the same object that I originally created. What I have been doing is:
// TESTING TO SEE IF THE RETURNED OBJECT IS THE SAME ONE I CREATED
if(connection == [self connectionPartial]) {
But was just curious is this is the same as doing:
if([connection isEqual:[self connectionPartial]]) {
It's not the same.
if(connection == [self connectionPartial]) {
This compares the address of the objects, eg. if the pointers point to the same instance.
if([connection isEqual:[self connectionPartial]]) {
This compares the contents of the objects. For instance for two separate NSString instances, this will return YES as long as the string content is the same:
NSString *s1 = #"Something";
NSString *s2 = #"Something";
BOOL sameInstances = (s1 == s2); // will be false, since they are separate objects.
BOOL sameContent = [s1 isEqual:s2]; // will be true, because they both are "Something"
The first snippet compares the values of the pointers themselves, just as if they were any primitive type like an int. If the addresses are the same, the expression will evaluate true.
The second sends the message isEqual: to one of the connection instances. Any class can override isEqual: to define "equality" with another instance. It's entirely possible for a class's implementation of isEqual: to be:
- (BOOL)isEqual: (id)obj
{
return arc4random_uniform(2) ? YES: NO;
}
So, no, for almost all classes they are not equivalent. (NSObject, which has the "default" implementation of isEqual:, uses the objects' hashes, which, again by default, are their addresses.)
It sounds like using the equality operator, ==, is correct in your case.

Can't do mathematical operations with int from NSUserDefaults

i have integer data, stored in NSUserDefaults, there is my code:
- (IBAction)addButton:(id)sender {
NSInteger oldValue = [[NSUserDefaults standardUserDefaults] integerForKey:#"myValue"];
NSString *string1=[addTextField text];
int add = [string1 floatValue];
int new = globalCalories;
int old=oldValue;
if(recomended.text==[NSString stringWithFormat:#"%i", oldValue]){
**self.day = (int)roundf(old-add);
dayLeft.text=[NSString stringWithFormat:#"%d", oldValue-add];
}**
else
{
self.day=(int)roundf(new-add);
dayLeft.text=[NSString stringWithFormat:#"%d", day];
}
}
I copied all of button action code, just in case, but i did mark with bold strings of code, that appear to not work. So, it suppose to do mathematical operations with stored data (oldValue), but when i launch programs, it dosnt, in fact, it does, but instead of valid value program "think" that oldValue is 0 (instead of valid value).
So, when it contain, for example, number 2000, and I launch program and enter in text field 500, it suppose to be 1500 (2000-500), but it shows -500.
You can convert recommended.text to integer by using this:
int recommendedValue = [recommended.text intValue];
then compare the numbers.
The problem is that == compared the address es of the NSString and not their values (see many SO questions). To compare strings use the isEqualToString: method.
However in this case it would be even better to compare the numbers ie convert recomended.text to the number and use a intValue method on dayLeft.

What's most performant way in iOS to check if a string is one of a list of strings?

I want to see if stringA is equal to any of a list of strings -- string1, string2, string3. What's the most performant way to do the comparison?
Since my comparison list is rather small, I'm currently trying this:
- (BOOL) isStringInList:(NSString *)testString{
if ([testString caseInsensitiveCompare:#"string1"] == NSOrderedSame)
return YES;
else if ([testString caseInsensitiveCompare:#"string2"] == NSOrderedSame)
return YES;
else if ([testString caseInsensitiveCompare:#"string3"] == NSOrderedSame)
return YES;
return NO;
}
This obviously does not scale well if I have many strings to compare against. I'd prefer more of a method signature like this -(BOOL) isString:(NSString *)testString inList:(NSString *)listString where listString is a space-separated string of keywords.
Any thoughts on how to improve performance would be appreciated.
The most performant way is to construct an NSSet of the strings you want to compare against and use -member: to test. Once the set is constructed, this will be a constant-time test. If you have a space-separated list to start with, you can use
NSSet *set = [NSSet setWithArray:[listOfWords componentsSeparatedByString:#" "]]
Constructing the set will be linear on the size of the input string. If your set is the same every time, you can construct it once and hold on to the result. To do the actual test you can use
[set member:myWord]
If the result is nil, your word isn't in the set. If it's non-nil, it is. Note, this is a case-sensitive search. If you need case-insensitivity, then you should either lowercase or uppercase both the list of words and the input word before performing your test.
- (BOOL)isString:(NSString*)testString inList:(NSString*)listString
{
BOOL result = NO;
if (testString != nil)
{
NSRange range = [listString rangeOfString:testString];
result = (range.location != NSNotFound);
}
return result;
}
- (BOOL)isString:(NSString *)testString inList:(NSString *)spaceSeparatedStrings
{
NSArray *list = [spaceSeparatedStrings componentsSeparatedByString:#" "];
return [list containsObject:testString];
}
Note: the above will be case-sensative, unlike your example. For case-insensative, you will probably have to iterate over some portion of the NSArray.
According to this, containsObject is more efficient than I would have guessed. Good news.
A binary search tree might be better, but obviously, that would take some work.

If UITextField or NSString is empty

If I want to check whether a UITextField or NSString is empty, can I compare it with NULL or nil?
Neither of the methods you suggest are foolproof. The best tests are:
if ([myTextField.text length] > 0) ...
or
if ([myString length] > 0) ...
if i want to check whether a textfield or string is empty i compare it with NULL or nil?
No.
An empty string object (a string object containing no characters) or a text-field object containing an empty string object is not the same as nil, which is no object at all. You need to ask the (text field's) string how long it is, or ask it whether it is equal to an empty string you have on hand (#"").
NULL, while also a null pointer, should be used for general pointers, not pointers to Objective-C instances (for which you have the more specific nil) or classes (for which you have the more specific Nil).
I had a similar problem but no method other than this worked for me:
NSString *string = textfield.text;
if ([string isEqualToString:#""]) {
....
}