Objective C /iPhone : Is it possible to re initialize an NSArray? - objective-c

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.

Related

Should I use defensive copies in Objective C? [closed]

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];

Fast Enumeration on NSArray of Different Types

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 *
// ...
}

NSString blowing my mind away

I have three NSString properties declared like this:
#property(nonatomic,retain) NSString *currentPassword;
#property(nonatomic,retain) NSString *newPassword;
#property(nonatomic,retain) NSString *confirmPassword;
I initialize them in a viewDidLoad method:
currentPassword = [[NSString alloc]init];
newPassword = [[NSString alloc]init];
confirmPassword = [[NSString alloc]init];
The funny thing is that they are the same object after initialize them as different objects!
Is this some kind of compiler optimization?
Thank you
Is this some kind of compiler optimization?
Not quite. It's a special case value for a constant, and an optimization of a common concrete immutable type/value which has been implemented by the NSString class.
NSString is immutable. There's no reason multiple instances of the same empty string are needed. In such simple cases, -[NSString init] can take the form:
static NSString* const EmptyNSString = #"";
- (id)init
{
self = [super init];
[self release];
return EmptyNSString;
}
similarly, + [NSString string]:
+ (id)string
{
return EmptyNSString;
}
So there are a few static immutable objects which are used this way where it makes sense. Other obvious examples include + [NSArray array] and + [NSNumber numberWithBool:].
Each one of these constants can represent what would have been many many many thousands of unique allocations produced during your program's execution.
This happens to work because NSString as a class cluster: You are returned an object of one of many opaque types which implements the interface declared by NSString. Therefore, a NSMutableString type could then implement init appropriately:
- (id)init
{
self = [super init];
if (nil != self) { ... }
return self;
}
Finally, you should in almost all cases declare your NSString properties as copy.
As NSString objects are immutable (i.e. cannot be changed after they are created) and there's no sense in creating several different instances of the same immutable strings, system tries to reuse existing objects whenever possible.
Using constructor with no parameters may be one of examples. You can also check that +stringWithString: (and -initWithString:) also return the (retained) parameter string, and copy method in NSString is equivalent to retain.
Remember that optimization is only possible because we know NSString instance is not going to change and the same tests with NSMutableString most likely will to create new string instances.
P.S. About NSAssert usage:
NSAssert Generates an assertion if a given condition is false.
So your assert condition should be reversed:
NSAssert(currentPassword && newPassword && confirmPassword,#"nil field");
When you have NSString as a property you should specify the attribute 'copy' instead.
NSString is defined as an immutable type, so whenever the compiler can optimize things by combining identical strings, it should. if you use #"myString" in two separate places in your code, they will be referencing the very same object. #"" strings are of class NSConstantString

NSMutableDictionary error

I want to use NSMutableDictionary to cache some data i will use later. My custom object is following:
#interface MyData : NSObject {
NSRange range;
NSMutableArray *values;
}
#property (nonatomic, retain) NSMutableArray *values;
and implement:
- (id)init {
if (self = [super init]) {
values = [[NSMutableArray alloc] init];
}
return self;
}
and when i wanna cache it, i use it like this:
NSMutableDictionary *cache = [[NSMutableDictionary alloc] init];
NSString *key = #"KEY";
MyData *data = [[MyData alloc] init];
// save some data into data
[data.values addObject:"DATA1"];
[data.values addObject:"DATA2"];
//... ...
[cache setObject:data forKey:key];
My questions is the count of cache.values is zero when i retrieve this object later as follow:
[cache objectForKey:#"KEY"];
i can retrieve "data" and the object's memory address is the same as the address when i put it into cache.
what's wrong? i need some kind guys help, any info is helpful. thanks
As Carl Norum pointed out, you're passing C strings to addObject:. addObject:, as its name suggests, requires a pointer to a Cocoa object; a C string is a pointer to characters. You need to pass NSString objects there; for literal strings, this simply requires prefixing them with #: "Fred" is a constant C string, whereas #"Fred" is a constant NSString object.
Is cache an instance variable? It looks like it's not; it appears to be a local variable, which means you're creating a new dictionary object every time. That's why there's nothing you've added previously (to previous dictionaries) in the new one. It also means you're leaking those previous dictionaries, since you're not releasing them (not in the code you showed, anyway).
Make cache an instance variable and only create the dictionary when you don't already have one (i.e., when cache == nil). Creating the dictionary in your init method is one good way. And make sure you manage its lifetime appropriately, so you don't leak and/or crash.
First of all your objects your adding don't look right it should have an # before the string. Like #"DATA1"
Second when you add an object to a dictionary or an array it does not make an actual copy of it. It just creates a pointer to it so if those objects are destroyed or moved somewhere also they are also gone out of your dictionary. A better way to make a cache of your values would be to copy the objects like so:
MyData* cache = [[MyData alloc] init];
for (int i = 0; i < [data.values count]; i ++){{
[cache.values addObject:[NSString stringWithString:[data.values objectAtIndex:i]]];
}
Don't use a dictionary in this situation.

Replace array display method?

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.