views:

2554

answers:

3

I'd like to do a series of string substitutions to removed xml-escaped chars such as '&'.

1) Is there an existing UIKit function that can do this?

2) If not, what's the best way to do it without leaking memory? Here's the idea:

-(NSString*) unescape:(NSString*)string
{
    string = [string stringByReplacingOccurrencesOfString:@"'" withString:@"'"];
    string = [string stringByReplacingOccurrencesOfString:@"&" withString:@"&"];
    string = [string stringByReplacingOccurrencesOfString:@""" withString:@"\""];
    string = [string stringByReplacingOccurrencesOfString:@">" withString:@">"];
    string = [string stringByReplacingOccurrencesOfString:@"&lt;" withString:@"<"];
    return string;
}

But doesn't that leak memory with each assignment? Or does stringByReplacingOccurrencesOfString return autoreleased strings? How do we confirm that stringByReplacingOccurrencesOfString strings are autoreleased? Or should we wrap them with [... autorelease]?

Even if they are autoreleased, it's preferable to avoid autorelease on the iPhone. (See here). So then we would do:

-(NSString*) unescape:(NSString*)string
{
    NSString* string2 = [string stringByReplacingOccurrencesOfString:@"&apos;" withString:@"'"];
    // don't release 'string' because we didn't allocate or retain it
    NSString* string3 = [string2 stringByReplacingOccurrencesOfString:@"&apos;" withString:@"'"];
    [string2 release];
    NSString* string4 = [string3 stringByReplacingOccurrencesOfString:@"&apos;" withString:@"'"];
    [string3 release];
    //...and so on
}

But that's pretty ugly code. What's the best way to write this code to do multiple substitutions? How would you do it?

+4  A: 

Any cocoa method which returns a new object via a method that does not start with init or contain the word copy will return an autoreleased object. So the above code should have noleaks.

Although it may be easier to use a NSMutableString here. Then you just modify the string in place rather than creating a pile of autoreleased string objects, which should make things cleaner.

Also, how about a dictionary of mappings that you iterate through, finding the key and replacing with the value of each item. Maybe even save this as a plist in your app for easy tweaking later.

Squeegy
+3  A: 

As Squeegy said, all of the above methods you reference return autoreleased objects. The second code example will crash because you are overreleasing the string objects.

Squeegy pointed one way of handling this, using NSMutableStrings. I'd recommend that approach. For example, the following rewrite of your method:

-(NSString*) unescape:(NSString*)string
{
    NSMutableString *unescapedString = [[NSMutableString alloc] initWithString:string];
    [unescapedString replaceOccurrencesOfString:@"&apos;" withString:@"'" options:0 range:NSMakeRange(0, [unescapedString length])];
    [unescapedString replaceOccurrencesOfString:@"&amp;" withString:@"&" options:0 range:NSMakeRange(0, [unescapedString length])];
    [unescapedString replaceOccurrencesOfString:@"&quot;" withString:@"\"" options:0 range:NSMakeRange(0, [unescapedString length])];
    [unescapedString replaceOccurrencesOfString:@"&gt;" withString:@">" options:0 range:NSMakeRange(0, [unescapedString length])];
    [unescapedString replaceOccurrencesOfString:@"&lt;" withString:@"<" options:0 range:NSMakeRange(0, [unescapedString length])];
    return [unescapedString autorelease];
}

only returns an autoreleased NSMutableString at the end.

Even better would be if you passed in a pointer to an NSMutableString pointer (NSMutableString **). That way, you could modify the string that you'd created outside of your method without creating a new temporary string. If this seems strange, take a look at methods that deal with NSError instances as an example.

EDIT: Ignore my statement about needing a double pointer in the previous paragraph. As erikprice points out, you just need to pass in an NSMutableString pointer. The double pointer is only needed if you create a new NSMutableString instance in your method to replace the one being passed in, which is not the case here.

Brad Larson
Why would it need to be a pointer to an NSMutableString pointer? I would think that the "point" (heh) of using a NSMutableString is that it's mutable, so couldn't you just pass in a simple NSMutableString pointer, and have the method body mutate the NSMutableString it points to?
erikprice
You're right. For some reason, I was thinking about creating an entirely new instance within the method, which is what happens with NSError. I'll fix the text.
Brad Larson
+2  A: 

I recommend you read the Cocoa memory management rules.

August