There is an NSString method -characterAtIndex: which returns an unichar.
(unichar)characterAtIndex:(NSUInteger)index
I wonder if this unichar, which obviously is not an NSString, can be converted into an NSString or feeded to an NSString for comparing purposes?
And: Does an NSString internally just consist of an array of unichar items?
You have two options: the first is a category adding a stringWithUnichar method to NSString:
#interface NSString (MNNSStringWithUnichar)
+ (NSString *) stringWithUnichar: (unichar) value;
#end
#implementation NSString (MNNSStringWithUnichar)
+ (NSString *) stringWithUnichar:(unichar) value {
NSString *str = [NSString stringWithFormat: #"%C", value];
return str;
}
#end
Then call it with
NSString *otherString = #"TestString";
NSString *str = [NSString stringWithUnichar:[otherString characterAtIndex:1]];
or you could just directly go like this:
NSString *str = [NSString stringWithFormat:#"%C",[otherString characterAtIndex:1]];
Choose the category if you want to use it repeatedly, or if you only have to do it once, use the stringWithFormat example!!
Not to sure about the internals of NSString, but I'd guess it is probably wrapping a
unichar *
or an array of unichars (like a C char *)
A unichar is simply a 16bit value that is used to store a character. Unlike an unsigned char which is only 8 bits, it can hold more than 0-255, so it can hold unicode characters as well, which are 16bits. A NSString is a (cluster of) class[es] that contains an array of unichars
EDIT
Here are a few articles about what people think an NSString is:
http://cocoawithlove.com/2008/08/string-philosophies-char-arrays.html
http://www.reddit.com/r/programming/comments/6v1yw/string_philosophies_char_arrays_stdstring_and/
Another option is to use the class method:
+ (id)stringWithCharacters:(const unichar *)characters length:(NSUInteger)length;
So just using:
yourString = [NSString stringWithCharacters: &yourChar length: 1];
The yourChar parameter must be a pointer.
NSString is really a class cluster. That means for various reasons including performance based on content and size and to support toll free bridging to CFString when you create an NSString you may actually get one of many private classes that return YES to isKindOf: when you ask it if it is an NSString and it will respond to all of the methods in the NSString class. It's like a Protocol but all objects will say and do and be NSString in the public API.
As the documentation states for NSString and CFString it is conceptually an array of UTF16 unichar
It may and will use a different internal representation where it presents an advantage to the implementation. Usually performance sometimes memory size. But you can rely on it giving you a C array of unichar when asked or anything else the API promises.
It's had a long time to mature and is tweaked internally in nearly every release.
In short yes you can think of it as being backed by an array of UTF16 unichar largely matching the description of a logical string in the Unicode standard. But the thing to keep in mind is that you really should not need to worry what's inside, only what it tells you is inside.
It's one of the best implementations of Unicode.
If you want a fuller picture of how it works you can look at ( most ) of the source of CFString at opensource.apple.com
It's C and Core Foundation (object oriented C) and fairly stylized, so don't expect to understand it all at once.
NSString and CFString do have methods to return and create from unichar. Most people should not need those most of the time.
If you do, be careful, be prepared to read a lot and to make naive mistakes. Unicode is a big topic. Handling it correctly is a science and an art at that level. That is one of the reasons for NSString. It handles a lot of the hard stuff so you do not have to so often, unless you want to or need to.
Related
If I take a following question. What is the best way to initialize NSMutableString Class?
(All instance will be return at unexpected times... so I'll assume that the initialization as follows:)
If I know in advance the amount of work. ( expected )
NSMutableString *str1 = [NSMutableString stringWithString:#""];
NSMutableString *str2 = [NSMutableString stringWithCapacity:0];
NSMutableString *str3 = [NSMutableString stringWithCapacity:1000];
NSMutableString *str4 = [NSMutableString string];
for(int i = 0; i< 1000; i++)
{
//The following tasks.(Adding to the string to continue working.)
[/*str1~str4*/ appendFormat:#"%d", i];
}
If I don't know in advance the amount of work. ( unexpected )
NSMutableString *str1 = [NSMutableString stringWithString:#""];
NSMutableString *str2 = [NSMutableString stringWithCapacity:0];
NSMutableString *str3 = [NSMutableString stringWithCapacity:1000];
NSMutableString *str4 = [NSMutableString string];
for(int i = 0; i< /*a large of size(unpredictable)*/ ; i++)
{
//The following tasks.(Adding to the string to continue working.)
[/*str1~str4*/ appendFormat:#"%d", i];
}
Largely split into two when performing these tasks, What is the best way to initialize?
I sometimes when working with these task is also confusing.
Case 1
Of the options listed, I'd use:
NSMutableString *str3 = [NSMutableString stringWithCapacity:1000];
…if you know the destination size, or estimate it with a little room at the top and are able to quickly determine the exact size, or the size worst case scenario, this could save multiple reallocate and copy operations. If you don't know the size in the worst case scenario, or if it takes a lot of time to calculate, then you may as well use [NSMutableString string] or [NSMutableString new]. Also, *WithCapacity is a hint, which the frameworks are free to ignore.
Of course, the body of your loop and the size you reserve also implies that all the values are [0…9] (specifically, that all values consume one character), and you could in that case likely do far better by using format strings with more arguments. However, i is obviously larger than 9 for most iterations, and will consume on average 3 characters each, so 3000 would be a more appropriate reserve capacity for the exact code you posted.
Case 2
Of the options listed, I'd use:
NSMutableString *str4 = [NSMutableString string];
Even better, if you don't need to add it to an autorelease pool: [NSMutableString new] or [[NSMutableString alloc] init].
Other Notes
Yes, keeping objects out of autorelease pools (e.g. use alloc+init) can improve performance and reduce peak memory usage significantly. Sometimes, this is beyond your control, and in some environments (e.g. ARC), this may happen even though you use an autoreleased convenience constructor - e.g. [NSMutableString string].
The Faster Solution
Finally, if this case you have outlined really is a performance concern, the fastest way would be to create a char buffer on the stack and then create one NSString from the result of copying the numbers over to the char buffer. Assuming your ints are all 0-9, it would be very fast and easy, then simply create an NSString from the (terminated) cstring. You can even do this if the input size varies, or is very large (results in a very long string).
It doesn't really matter.
If you've optimized your program so far that this decision will have a measurable effect on its overall performance, pat yourself on the back or, as Sheldon from BBT would say, "have a chocolate!"
PS:
If you precisely know the size up front or have a really good estimate on it, then use that size in stringWithCapacity: or initWithCapacity: if you don't, then don't even bother — let the framework decide, it's pretty damn clever!
Here is the shortest way:
NSMutableString *string = [#"" mutableCopy];
NSMutableString *str1 = [NSMutableString stringWithString:#""];
Bad, a NSString (#"") is pointlessly created, then copied into a new NSMutable String
NSMutableString *str2 = [NSMutableString stringWithCapacity:0];
Bad, a NSMutableString with 0 capacity will need to be inflated upon the first addition to it.
NSMutableString *str3 = [NSMutableString stringWithCapacity:1000];
Good, if 1000 has some significance (i.e. it's an expected size for the content you're working with).
NSMutableString *str4 = [NSMutableString string];
Good, using a convenience method, which is is like doing [NSMutableString alloc] init];
If you know the size upfront, use
[NSMutableString stringWithCapacity: _Known_Size_];
If you don't know the size up front, use
[NSMutableString stringWithCapacity: _small_number_];
Then the mutable string will grow as it needs to.
In Swift 5:
var mutableString = NSMutableString("")
It is always better to allocate and initialize an object, not use a class method to create it (which puts it in the closest autorelease pool).
So,
NSMutableString* stringOne = [[NSMutableString alloc] initWithCapacity:capacity];
instead of
NSMutableString* stringTwo = [NSMutableString stringWithCapacity:capacity];
Just remember that you have to release stringOne when you are done with it, but not stringTwo, because the stringWithCapacity: class method returns an autoreleased object.
Read more about the topic here: http://www.mulle-kybernetik.com/artikel/Optimization/opti-5.html
I get the "format not a string..." message when doing the following:
NSString* string1 = [[NSString alloc] initWithFormat:NSLocalizedString(#"Update Now", #"Update Now Item")];
NSString* string2 = [[NSString alloc] initWithFormat:NSLocalizedString(#"Register", #"Register Now")];
It works fine i.e. the app doesn't crash on the device or simulator, and the localized text displays just fine also.
I'm trying to understand why is it then that I'm getting this particular error message. As far as I'm aware this is how you're meant to use localized strings in Objective C.
Simplest way to use localized string is:
NSString* myString =
NSLocalizedString(#"Update
Now",#"Update Now");
Now keep in mind myString is autoreleased - which you would generally need for a string.
In the examples you give your strings are retained (because you use initWithFormat). Guessing "update now" and such are going to be shown in the user UI, possibly trough a UILabel you don't need a retained string - when you do assign the string to a UILabel it will retain it automatically (as the text is stored in a retained property)
NSLocalizedString is a macro, which is actually;
[[NSBundle mainBundle] localizedStringForKey:(key) value:#"" table:nil]
So, why on earth do you need another NSString unless you do some post-processing like format replacing, it returns an NSString anyway!
you can retain it if you like...
You should not use initWithFormat: here at all because you are not dealing with a format string.
But if you use it, always use it like this:
[[NSString alloc] initWithFormat:#"%#", NSLocalizedString(#"Update Now", #"Update Now Item")];
Otherwise, the risk is that the result of NSLocalizedString contains a string format specifier (like %d or something), and that would crash your app.
The warning reported by the compiler is correct. The string returned from NSLocalizedString(#"Update Now", #"Update Now Item") is not a format string, because it does not have format specifier inside.
As Ican Zilb said in another answer, the best solution is to directly use:
NSString* myString = NSLocalizedString(#"Update Now",#"Update Now Item");
Using objective-c on the iPhone, what is wrong with this code?
Is it leaking memory? Why?
How would I do this correctly?
NSMutableString *result = [NSMutableString stringWithFormat:#"the value is %d", i];
... then later in my code... I might need to change this to:
result = [NSMutableString stringWithFormat:#"the value is now %d", i];
I need to use stringWithFormat a 2nd time... but isn't that creating a NEW string and not correctly freeing the old one?
No, it doesn't leak memory because stringWithFormat: returns an autoreleased object.
You could use the instance method "setString" for your already existing NSMutableString, like this:
[ result setString:[NSString stringWithFormat:#"the value is now %d", i] ];
If you really want to reuse the string, you can use something like
[result setString:#""];
[result appendFormat:#"the value is now %d", i];
However, unless you notice a performance/memory problem, just use
NSString *result = [NSString stringWithFormat:#"the value is %d", i];
/* ... */
result = [NSString stringWithFormat:#"the value is now %d", i];
It's generally easier to work with immutable objects because they can't change under your feet.
What you have seems to me to be the natural way to replace a mutable string with new content, unless you have other references to the same mutable string elsewhere.
If you don't have other references to it and you are reusing the string only to improve performance/memory footprint, that sounds like premature optimisation.
By the way, you do not own a string you obtain via stringWithFormat: so you do not need to (Indeed must not) release it.
I am reading string data from a PLIST which I am using to create a JSON string (incidentally for use within Facebook Connect).
NSString *eventLink = [eventDictionary objectForKey:EVENT_FIND_OUT_MORE_KEY];
NSString *eventLinkEscaped = [eventLink stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *eventName = [eventDictionary objectForKey:EVENT_NAME_KEY];
NSString *eventDescription = [eventDictionary objectForKey:#"Description"];
NSString *eventImageAddress = [eventDictionary valueForKey:#"Image URL"];
if ([eventImageAddress length] == 0)
{
eventImageAddress = NO_EVENT_IMAGE_URL;
}
// Publish a story to the feed using the feed dialog
FBStreamDialog *facebookStreamDialog = [[[FBStreamDialog alloc] init] autorelease];
facebookStreamDialog.delegate = self;
facebookStreamDialog.userMessagePrompt = #"Publish to Facebook";
facebookStreamDialog.attachment =[NSString stringWithFormat: #"{\"name\":\"%#\",\"href\":\"%#\",\"description\":\"%#\",\"media\":[{\"type\":\"image\",\"src\":\"%#\",\"href\":\"%#\"}]}", eventName, eventLinkEscaped, eventDescription, eventImageAddress, eventLinkEscaped];
[facebookStreamDialog show];
All this works well, but certain event descriptions (4 out of approx. 150) the text that appears in the dialog is blank. I have found the obvious candidates, i.e., the description contains the " character for instance or the copyright symbol. My question is, is there an easy method call, such as stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding that will ensure that any dodgy characters are escaped or ignored?
Thanks in advance,
Dave
I don't think there is an easy way to escape the problem strings. If you need JSON support anywhere else in your code, consider using one of the existing JSON parsing/generator frameworks such as yajl-objc or SBJSON. Either of these will let you build your response as Foundation objects (NSArray/NSDictionary) and then call a single method to generate the appropriate JSON. Your code will be cleaner and you have the benefit that both of these frameworks are well-tested.
If just need to generate this one bit of JSON, your best bet is probably to manually walk over the input strings, replacing potential problem characters with the appropriately escaped versions. Is is not as bad as you might think. Take a look at the source for SBJsonWriter
I'm trying to send the contents of UITextView or UITextField as parameters to a php file
NSString *urlstr = [[NSString alloc] initWithFormat:#"http://server.com/file.php?name=%#&tags=%#&entry=%#",nameField.text, tagsField.text, dreamEntry.text];
When i log urlstr, the url format is ok just as long as the UITextView or UITextField don't contain spaces. How would i go about converting the spaces to %20 ?
edit
here is the code at present, which not only crashes but isn't encoding the url properly.
name=John Doe&tags=recurring nightmare&entry=Testing testing testing
is converted to
name=John -1844684964oe&tags=recurringightmare&entry=Testing 4.214929e-307sting -1.992836e+00sting
- (IBAction)sendButtonPressed:(id)sender
{
NSString *urlString = [[NSString alloc] initWithFormat:#"http://server.com/file.php?name=%#&tags=%#&entry=%#", nameField.text, tagsField.text, dreamEntry.text];
NSString *encodedString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [[NSURL alloc] initWithString:encodedString];
NSLog(encodedString);
NSLog(urlString);
[urlString release];
[url release];
[encodedString release];
}
Actually, all of the previous answers contain at least some inaccuracies, which for many common values of user provided text in the TextFields would not correctly communicate with the server
stringByAddingPercentEscapesUsingEncoding: percent escapes all characters which are not valid URL characters. This method should applied once to the entire URL.
A previous answer claims that stringByAddingPercentEscapesUsingEncoding: works like the URL building classes in many scripting languages, where you should not apply it to the entire URL string, but it doesn't. Anyone can easily verify this by checking its output for unescaped &s and ?s. So it is fine to apply to the entire string, but it is not enough to apply to your 'dynamic' url content.
The previous answer is right in that you have to do some more work to the names and values that go into your CGI query string. Since CGI is specified by RFC3875, this is often referred to as RFC3875 percent escaping. It makes sure that your names and values don't contain characters that are valid URL characters but which are significant in other parts of the URL (;, ?, :, #, &, =, $, +, {, }, <, >, and ,)
However, it is very important to also finish by doing plain URL percent escapes on the full string to make sure that all characters in the string are valid URL characters. While you don't in your example, in general there could be characters in a 'static' part of the string which are not valid URL characters, so you do need to escape those as well.
Unfortunately, NSString doesn't give us the power to escape the RFC3875 significant characters so we have to dip down into CFString to do so. Obviously using CFString is a pain so I generally add a Category onto NSString like so:
#interface NSString (RFC3875)
- (NSString *)stringByAddingRFC3875PercentEscapesUsingEncoding:(NSStringEncoding)encoding;
#end
#implementation NSString (RFC3875)
- (NSString *)stringByAddingRFC3875PercentEscapesUsingEncoding:(NSStringEncoding)encoding {
CFStringEncoding cfEncoding = CFStringConvertNSStringEncodingToEncoding(encoding);
NSString *rfcEscaped = (NSString *)CFURLCreateStringByAddingPercentEscapes(
NULL,
(CFStringRef)self,
NULL,
(CFStringRef)#";/?:#&=$+{}<>,",
cfEncoding);
return [rfcEscaped autorelease];
}
#end
With this Category in place, the original problem could be correctly solved with the following:
NSString *urlEscapedBase = [#"http://server.com/file.php" stringByAddingPercentEscapesUsingEncoding:
NSUTF8StringEncoding];
NSString *rfcEscapedName = [nameField.text stringByAddingRFC3875PercentEscapesUsingEncoding:
NSUTF8StringEncoding];
NSString *rfcEscapedTags = [tagsField.text stringByAddingRFC3875PercentEscapesUsingEncoding:
NSUTF8StringEncoding];
NSString *rfcEscapedEntry = [dreamEntry.text stringByAddingRFC3875PercentEscapesUsingEncoding:
NSUTF8StringEncoding];
NSString *urlStr = [NSString stringWithFormat:#"%#?name=%#&tags=%#&entry=%#",
urlEscapedBase,
rfcEscapedName,
rfcEscapedTags,
rfcEscapedEntry];
NSURL *url = [NSURL URLWithString:urlStr];
This is a little variable heavy just be more clear. Also note that the variable list provided to stringWithFormat: should not be nil terminated. The format string describes the precise number of variables that should follow it. Also, technically the strings for query string names (name, tags, entry,..) should be run through stringByAddingPercentEscapesUsingEncoding: as a matter of course but in this small example we can easily see that they contain no invalid URL characters.
To see why the previous solutions are incorrect, imagine that the user input text in dreamEntry.text contains an &, which is not unlikely. With the previous solutions, all text following that character would be lost by the time the server got that text, since the unescaped ampersand would be interpreted by the server as ending the value portion of that query string pair.
You're not supposed to URL-escape the entire string, you're supposed to URL-escape the dynamic components. Try
NSString *urlStr = [NSString stringWithFormat:#"http://server.com/file.php?name=%#&tags=%#&entry=%#",
[nameField.text stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
[tagsField.text stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
[dreamEntry.text stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
nil];
NSURL *url = [NSURL URLWithString:urlStr];
The second issue with your code (and undoubtedly the reason for the odd printing) is you're passing the string directly to NSLog, so it's being treated as a format string. You need to use
NSLog(#"%#", encodedString);
instead. That will make it print as expected.
Edit: A third issue with your code is you're mixing autoreleased and owned objects, then releasing them all at the end. Go look at the 3 objects you create, and which you subsequently release later. One of them shouldn't be released later because it was produced by a method that did not start with the words alloc, copy, or new. Identifying the object in question is an exercise left to the reader.
You can take your URL and use:
NSString *urlStr = [[NSString alloc] initWithFormat:#"http://server.com/file.php?name=%#&tags=%#&entry=%#",nameField.text, tagsField.text, dreamEntry.text];
NSString *encStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];