Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I was playing around with mutability, and I came up with the following code in which an immutable object can be cast to a mutable one.
- (NSString *) getaString {
NSMutableString * string = [[NSMutableString alloc] init];
[string appendString:#"This "];
[string appendString:#"was mutable."];
return string;
}
- (void)viewDidLoad {
[super viewDidLoad];
//Get a string returns an NSString, which is then cast to a mutable string with a compler warning.
NSMutableString * string = [self getaString];
[string appendString:#" And apparently still is"];
_showText.text = string;
}
or with no compiler warning
- (NSArray *) getaString {
NSMutableString * string = [[NSMutableString alloc] init];
[string appendString:#"This "];
[string appendString:#"was mutable."];
//Cast to NSString.
NSString * immutableString = string;
NSArray * array = [[NSArray alloc] initWithObjects:immutableString, nil];
return array;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableString * string = [[self getaString] objectAtIndex:0];
[string appendString:#" And apparently still is"];
_showText.text = string;
}
The UITextField shows the whole string of "This was mutable. And apparently still is", with no compiler warning. I've seen multiple SO posts that recommend simply casting or using mutable objects as immutable objects, but as I've just shown, that can be dangerous. Also, the cast still works without the array, but I do get a compiler warning.
My question is, should I be considering using java style defensive copies? I haven't seen any mention of defensive copies in objective C, and all I could find in Apple's documentation was a vague mention that it’s best to adopt some defensive programming practices. I'm concerned both about security and about protecting against careless coding.
The example you've given is misleading.
You are storing a NSMutableString in an array and you are taking it out. Why would you expect the string to be immutable after that?
However there are cases in which defensive copies are pretty much a standard in Objective-C.
Consider a class (let's call it CustomClass) defining a property for an NSString and a method to print it:
#property (nonatomic, strong) NSString *aString;
- (void)printTheString;
Now, since NSMutableString is a subclass of NSString, a client of this class could potentially do something like:
CustomClass *anObject = [CustomClass new];
NSMutableString *aMutableString = [NSMutableString stringWithString:#"Hey!"];
anObject.aString = aMutableString;
[anObject printTheString]; // Hey!
[aMutableString appendString:#" Got you!"];
[anObject printTheString]; // Hey! Got you!
which can be dangerous in some cases.
It has then become common practice to use the copy attribute, instead of strong, for immutable classes with a mutable subclass:
#property (nonatomic, copy) NSString *aString;
In this way a defensive copy is made when the string is assigned, preventing the client to mess with the object later on.
The previous example would then print Hey! both times.
It's also worth noting that for most of this classes sending copy to an immutable object returns the same object, instead of an actual copy. In this way you can have your cake and eat it too, since the copy will be performed only when needed.
I don't know for sure whether you should make defensive copies in Obj-c, but it is possible to make mutable copies of an immutable string, and it is possible to make immutable versions of a mutable string.
There aren't any immutable strings in your code, by the way. NSMutableStrings in an NSArray are still mutable, so you aren't doing any conversion. The array is the thing that cannot have objects appended to it.
Here's why: The array is just holding a pointer to the NSMutableString, not the actual object, so you're not changing the contents of the array by changing the contents of the string, because the memory address pointed to by the array stays the same.
Example:
NSString *original = #"Hello, world!";
// putting the original in a mutable string
NSMutableString *copy = [original mutableCopy];
NSArray *array = [NSArray arrayWithObjects:copy, nil];
// the object in the array can still be modified
[[array objectAtIndex:0] appendString:#" Goodbye, cruel world!"];
// make an immutable version of the mutable string
NSString *copyOfTheCopy =[NSString stringWithString:[array objectAtIndex:0]];
NSLog(#"%#", [array objectAtIndex:0]);
// just to make sure...
[[array objectAtIndex:0] appendString:#"Got you!"];
NSLog(#"%#", [array objectAtIndex:0]);
NSLog(#"%#", copyOfTheCopy);
The output should be:
2013-11-03 21:16:06.099 SomeProject[7045:303] Hello, world! Goodbye, cruel world!
2013-11-03 21:16:06.100 SomeProject[7045:303] Hello, world! Goodbye, cruel world!Got you!
2013-11-03 21:16:06.100 SomeProject[7045:303] Hello, world! Goodbye, cruel world!
Another answer given by Gabriele Petronella caused me to make a change that gets rid of one of those gotchas with pointers. This one doesn't use a pointer to the NSMutableString and instead makes a new string from the old one. Sorry I can't vote you up yet, you taught me something :)
If you're looking to obtain an immutable NSString from an NSMutableString, you can do something like this:
NSString *anImmutableString = [NSString stringWithString: aMutableString];
So if we take your original code in the question, and I'm going to assume that your viewDidLoad is where you want an immutable string:
- (NSArray *) getaString {
NSMutableString * string = [[NSMutableString alloc] init];
[string appendString:#"This "];
[string appendString:#"was mutable."];
NSArray * array = [[NSArray alloc] initWithObjects:string, nil];
return array;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSString *string = [NSString stringWithString:[[self getaString] objectAtIndex:0]];
//[string appendString:#" And apparently still is"];
_showText.text = string;
}
Now, if you still want to append to string in viewDidLoad, you can, but you'd have to do it differently (the same way you append to any other NSString). It'd look something like this:
NSString *newString = [string stringByAppendingString:#" And apparently still is"];
Now look at the contents of the variables.
string = "This was mutable." //as you built it from an NSMutableString in the getaString method
newString = "This was mutable. And apparently still is" //as built from calling "stringByAppendingString" method on your variable string in viewDidLoad
It's important to note that the contents of newString are quite misleading here. newString is not an NSMutableString. It's a regular NSString. Calling stringByAppendingString returns an NSString that is the result of appending the argument you send to the end of the NSString you're calling the method on. (And the contents of the NSString you called the method on remain unchanged--it's immutable).
If you would like newString to be an NSMutableString, you'll have to do something like this:
NSMutableString *newString = [[string stringByAppendingString:" And apparently still is"] mutableCopy];
Related
str is a NSString I own. It is my responsibility to release it,
since I call initWithString:
if (![[str substringFromIndex:str.length-1] isEqualToString:#"\n"])
{
str = [str stringByAppendingString:#"\n"];
}
if the line inside the if statement reached, I will lose the ownership of the str var.
so my app crashes with a zombie instance, when I release str later:
[str release];
All goes fine if the if statement is NO(false).
What can I do to maintain the ownership of str?
Note that str could be very long I don't want to init another NSString
You need to do normal memory management:
if (![[str substringFromIndex:str.length-1] isEqualToString:#"\n"])
{
NSString *newStr = [str stringByAppendingString:#"\n"];
[str release];
str = [newStr retain];
}
Keep in mind that stringByAppendingString: returns an autoreleased string (and it also creates a whole new string).
Suggestion 1: Use ARC. It solves these problems for you. This is by far the best solution.
As rmaddy says, Xcode has an automated tool for converting apps to ARC. Look in the edit menu, under refactor>Convert to Objective-C ARC. The process is fairly painless. It flags things it wasn't able to figure out on it's own (usually only a few things.) After you clean up those issues you are off and running and never have to worry about retain counts again.
Suggestion 1a: Make str a mutable string, as #rmaddy suggested.
Then your code would look like this:
[str appendString: #"\n"];
That's simpler, easier to read, more memory-efficient, and works exactly the same in both ARC and manual reference counting.
Failing that, change str to be a retained property
#property (nonatomic, retain) NSString *str);
Then use property notation:
if (![[self.str substringFromIndex: self.str.length-1] isEqualToString:#"\n"])
{
self.str = [self.str stringByAppendingString:#"\n"];
}
When you do that the setter for the property takes care of releasing the old object in the str property before assigning a new value to the property.
Be aware, though, that assigning an object to a retained property increases it's retain count. This will create a leak:
self.str = [NSString alloc] initWithFormat: #"number %d", value];
(because all alloc/init calls return objects with a retain-count of 1)
and then the property retains it again.
That code should be written like this:
self.str = [[NSString alloc] initWithFormat: #"number %d", value] autorelease];
I would like to put a pointer to an NSString pointer into an NSDictionary, and naturally, get it back out again. But I can't figure out the syntax.
I think is is something like
NSString* myString = #"Hi";
NSString**myStringPointer = myString;
NSDictionary* dictionary = #{#"pointer":myStringPointer};
But that is clearly not correct.
I am trying to change what string an NSString points to inside a selector.
-(void) updateString:(NSString*) aString {
aString = #"Hello World"; //
}
-(void) testUpdateString {
NSString *textString = #"TEST";
[self updateString:testString];
// testString still is #"TEST";
}
Thank you.
You can only put (pointers to) things that inherit from NSObject in an NSDictionary; a pointer to (a pointer to) an NSString isn't such an object. You can wrap it in an NSValue to store it in a dictionary.
NSDictionary *dictionary = #{ #"pointer": [NSValue valueWithPointer:myStringPointer], };
While it's worth being aware of the general idea of wrapping things that couldn't otherwise be put into an NSDictionary, NSArray, etc., in NSValue for this purpose, I can't think of a good reason to store an NSString ** in an NSDictionary, so it might be better to look at why you're trying to do that and whether there's a better way to achieve your larger goal.
You don't need a dictionary here, you can pass the object by reference to the updating
method:
-(void) updateString:(NSString **) aString {
*aString = #"Hello World";
}
-(void) testUpdateString {
NSString *testString = #"TEST";
[self updateString:&testString];
}
(If you are curious what actually happens behind the scenes, look up
__autoreleasing in the "Transitioning to ARC Release Notes".)
I have this question here (as well other quesrtions on SO), and the Apple docs about Objective-C collections and fast enumeration. What is not made clear is if an NSArray populated with different types, and a loop is created like:
for ( NSString *string in myArray )
NSLog( #"%#\n", string );
What exactly happens here? Will the loop skip over anything that is not an NSString? For example, if (for the sake of argument) a UIView is in the array, what would happen when the loop encounters that item?
Why would you want to do that? I think that would cause buggy and unintended behavior. If your array is populated with different elements, use this instead:
for (id object in myArray) {
// Check what kind of class it is
if ([object isKindOfClass:[UIView class]]) {
// Do something
}
else {
// Handle accordingly
}
}
What you are doing in your example is effectively the same as,
for (id object in myArray) {
NSString *string = (NSString *)object;
NSLog(#"%#\n", string);
}
Just because you cast object as (NSString *) doesn't mean string will actually be pointing to an NSString object. Calling NSLog() in this way will call the - (NSString *)description method according to the NSObject protocol, which the class being referenced inside the array may or may not conform to. If it conforms, it will print that. Otherwise, it will crash.
You have to understand that a pointer in obj-c has no type information. Even if you write NSString*, it's only a compilation check. During runtime, everything is just an id.
Obj-c runtime never checks whether objects are of the given class. You can put NSNumbers into NSString pointers without problems. An error appears only when you try to call a method (send a message) which is not defined on the object.
How does fast enumeration work? It's exactly the same as:
for (NSUInteger i = 0; i < myArray.count; i++) {
NSString* string = [myArray objectAtIndex:i];
[...]
}
It's just faster because it operates on lower level.
I just tried a quick example... Here is my code.
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:1];
NSNumber *number = [NSNumber numberWithInteger:6];
[array addObject:number];
[array addObject:#"Second"];
Now if I simply log the object, no problem. The NSNumber instance is being cast as an NSString, but both methods respond to -description, so its not a problem.
for (NSString *string in array)
{
NSLog(#"%#", string);
}
However, if I attempt to log -length on NSString...
for (NSString *string in array)
{
NSLog(#"%i", string.length);
}
... it throws an NSInvalidArgumentException because NSNumber doesn't respond to the -length selector. Long story short, Objective-C gives you a lot of rope. Don't hang yourself with it.
Interesting question. The most generic syntax for fast enumeration is
for ( NSObject *obj in myArray )
NSLog( #"%#\n", obj );
I believe that by doing
for ( NSString *string in myArray )
NSLog( #"%#\n", string );
instead, you are simply casting each object as an NSString. That is, I believe the above is equivalent to
for ( NSObject *obj in myArray ) {
NSString *string = obj;
NSLog( #"%#\n", string );
}
I could not find precise mention of this in Apple's documentation for Fast Enumeration, but you can check it on an example and see what happens.
Since all NSObject's respond to isKindOfClass, you could still keep the casting to a minimum:
for(NSString *string in myArray) {
if (![string isKindOfClass:[NSString class]])
continue;
// proceed, knowing you have a valid NSString *
// ...
}
I am curious how I might override the description method that is used when you do the following (see below) for an object. I basically want to better format the output, but am unsure about how I might go about setting this up.
NSLog(#"ARRAY: %#", myArray);
many thanks
EDIT_001
Although subclassing NSArray would have worked I instead decided that I would add a category to NSArray (having not used one before) Here is what I added ...
// ------------------------------------------------------------------- **
// CATAGORY: NSArray
// ------------------------------------------------------------------- **
#interface NSArray (displayNSArray)
-(NSString*)display;
#end
#implementation NSArray (displayNSArray)
-(NSString*)display {
id eachIndex;
NSMutableString *outString = [[[NSMutableString alloc] init] autorelease];
[outString appendString:#"("];
for(eachIndex in self) {
[outString appendString:[eachIndex description]];
[outString appendString:#" "];
}
[outString insertString:#")" atIndex:[outString length]-1];
return(outString);
}
#end
gary
If you're doing this a lot, the easiest way to reformat the display of your array would be to add a new prettyPrint category to the NSArray class.
#interface NSArray ( PrettyPrintNSArray )
- (NSSTring *)prettyPrint;
#end
#implementation NSArray ( PrettyPrintNSArray )
- (NSString *)prettyPrint {
NSMutableString *outputString = [[NSMutableString alloc] init];
for( id item in self ) {
[outputString appendString:[item description]];
}
return outputString;
}
#end
Obviously you'd need to alter the for loop to get the formatting the way you want it.
I'm assuming that you myArray variable is an instance of the NSArray/NSMutableArray class.
When NSLog() encounters the # character in its format string, it calls the -description: method on the object. This is a method on the root class, NSObject from which all other Cocoa classes inherit. -description: returns an NSString allowing any object that implements this method to be passed into NSLog(#"#",anyObject) and have a nicely formatted output. The string returned can be anything you care to construct.
For your specific problem, you could subclass NSMutableArray and override the -description: method with your own implementation. Then utilise your subclass instead of NSMutableArray.
For more information on NSObject and -description: see Apple's docs.
From Formatting string objects:
NSString supports the format characters defined for the ANSI C functionprintf(), plus ‘#’ for any object. If the object responds to the descriptionWithLocale: message, NSString sends that message to retrieve the text representation, otherwise, it sends a description message.
So to customize array conversion to string you should change NSArray descriptionWithLocale: implementation. Here's an example of how you can replace object method in run-time.
I read that non mutable data types can't be modified once created.(eg NSString or NSArray).
But can they be re-initialized to point to a different set of objects?
If so, do I use release to free any alloc from first time round in between uses? eg:
myArray declared as NSArray *myArray in interface, and as nonatomic/retain property.myArray set in initialization code to a point to an array of strings as follows.
self.myArray = [myString componentsSeparatedByString:#","];
But later I want to re-initialize myArray to point to a different set of strings
self.myArray = [myOtherString componentsSeparatedByString:#","];
Is it possible? Thanks...
It really depends what you mean with re-initialize. You can assign another immutable object to a pointer, because the pointers aren't constant.
Example:
#interface MyObj : NSObject {
NSString *name; // not needed in 64bit runtime AFAIK
}
#property(retain) NSString *name; // sane people use copy instead of retain
// whenever possible. Using retain can
// lead to some hard to find errors.
#end
/* ... another file ... */
MyObj *theObject = [[[MyObj alloc] init] autorelease];
theObject.name = #"Peter";
NSString *oldName = theObject.name;
NSLog(#"%#", theObject.name); // -> Peter
NSLog(#"%#", oldName); // -> Peter
theObject.name = #"Martin";
NSLog(#"%#", theObject.name) // -> Martin
NSLog(#"%#", oldName) // -> Peter
If the behavior above is what you want, that's fine.
If you want that last line to return Martin you're in trouble. Those are constant strings and are not meant to be modified. You could, if you really want, modify the memory of the object directly, but this is dangerous and not recommended. Use mutable objects if you need such behaviour.
Yes you can reinitialized the NSArray. Here is the sample code that i used to re-initialized the NSArray.
NSString *keywords = #"FirstName|LastName|Address|PhoneNumber";
NSArray *arr = [keywords componentsSeparatedByString:#"|"];
NSLog(#"First Init - %#,%#,%#,%#",[arr objectAtIndex:0],[arr objectAtIndex:1],
[arr objectAtIndex:2],[arr objectAtIndex:3]);
arr = nil;
keywords = #"First_Name|Last_Name|_Address|_PhoneNumber";
arr = [keywords componentsSeparatedByString:#"|"];
NSLog(#"Second Init - %#,%#,%#,%#",[arr objectAtIndex:0],[arr objectAtIndex:1],
[arr objectAtIndex:2],[arr objectAtIndex:3]);
Of course they can. Saying that an NSArray is immutable doesn't mean that an attribute of a class of that type cannot be changed. You can't change the content, but you can assign new content to it.
If you want to make also changing the reference impossible you should use const keyword.