I'm new to Objective-C, so I may be way off...
I have this in my 'viewDidLoad' method:
NSArray *myArray;
NSString *cow = #"Cow";
NSString *pig = #"Pig";
NSString *frog = #"Frog";
NSString *sheep = #"Sheep";
myArray = [NSArray arrayWithObjects: cow, pig, frog, sheep, nil];
randomNumber.text = [myArray objectAtIndex: arc4random() % (4)];
I want to make this its own method, so I can get a random animal any time I want...but I need this to happen when the program starts. How do I access a method like this?
I may be way wrong, so I'm open to suggestions, corrections, and anything you think is helpful.
Like this:
- (void)generateAnimal{
NSArray *myArray;
NSString *cow = #"Cow";
NSString *pig = #"Pig";
NSString *frog = #"Frog";
NSString *sheep = #"Sheep";
myArray = [NSArray arrayWithObjects: cow, pig, frog, sheep, nil];
randomNumber.text = [myArray objectAtIndex: arc4random() % (4)];
}
Also:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self generateAnimal;
}
As Sagi mentioned before, in this case [self generateAnimal]; would have the wanted effect. In general Objective-C (as any other object oriented language) attaches methods to classes/instances, so you can only call them on existing instances. (Obviously there are class methods etc, but more abstractly speaking)
Objective-C wants you to enclose these calls to methods in square brackets ([ ]), as seen both in Sagi's answer and in your own example ([super viewDidLoad]). All the calls follow this pattern [target method: parameter]. Hope it makes sense, just wanted to add a bit of context to Sagi's answer.
[self generateAnimal]; //would work great :)
Related
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".)
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];
As a relative Objective-C beginner, I'm obviously still not grasping certain memory management rules. I can't figure out how to make this not crash:
#interface MyClass { NSArray *playerArray4th; }
- (void) viewDidLoad { playerArray4th = [self getAudioPlayersForSoundFile:#"rimshot" ofType:#"aif"]; }
- (NSArray*) getAudioPlayersForSoundFile:(NSString*)soundFileName ofType:(NSString*)soundFileType {
//code instantiating objects....
NSArray *toRet = [[NSArray alloc] initWithObjects:toRetTickPlayer,toRetTickPlayerCopy,toRetTickPlayerCopy2,toRetTickPlayerCopy3, nil];
return toRet;
}
Then later, in a different function:
NSArray *currentArray = playerArray4th;
[currentArray release];
currentArray = nil;
currentArray = [self getAudioPlayersForSoundFile:fileName ofType:ofType];
And it crashes when trying to access the array again:
- (void) playSound:(NSString*)soundType {
AVAudioPlayer *currentPlayer;
if ([soundType isEqualToString:#"4th"]) {
if (playerArray4thCounter >= [playerArray4th count]) playerArray4thCounter = 0;
NSLog(#"Playing from array: %#",playerArray4th);
currentPlayer = [playerArray4th objectAtIndex:playerArray4thCounter];
playerArray4thCounter++;
}
}
Try to learn about properties and about using getters and setters. Don't take shortcuts unless you know exactly what's going on.
So define the playerArray4th property in your header file:
#property (nonatomic,retain) NSArray *playerArray4th;
And then in your .m file create getter/setter:
#synthesize playerArray4th;
Then, always use self.playerArray4th for assigning and getting the variable. The prior objects will be released when needed.
So this will not leak:
self.playerArray4th = [NSArray arrayWithObjects:#"text",#"text",nil];
self.playerArray4th = [NSArray arrayWithObjects:#"new array",#"text",nil];
because the second assignment releases the first array.
Furthermore, read about using autorelease. In short, if you alloc, copy or new, you should either release or autorelease. There's a lot to read about this here on SO which I will not repeat here now.
Don't forget to put self.playerArray4th = nil; in your dealloc method.
Can anyone please review the following method and tell me if it is necessary to release the enumerator for the dictionary keys after using it?
I'm new to objective c and not always sure, when to release variables returned from the SDK methods. I suppose the keyEnumerator method returns an autoreleased instance so I don't have to care about it when I'm finished with it. Right?
Hmm.. maybe anything else wrong in this method? ;)
#import "NSDictionaryURLEncoding.h"
#import <Foundation/NSURL.h>
#implementation NSDictionary(URLEncoding)
-(NSString *)urlEncoded
{
NSEnumerator *keyEnum = [self keyEnumerator];
NSString *currKey;
NSString *currObject;
NSMutableString *result = [NSMutableString stringWithCapacity: 64];
while ((currKey = [keyEnum nextObject]) != nil)
{
if ([result length] > 0)
{
[result appendString: #"&"];
}
currObject = [[self objectForKey: currKey] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
currKey = [currKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[result appendString: [NSString stringWithFormat:#"%#=%#", currKey, currObject]];
}
return result;
}
#end
You must never release objects for which you did not call the alloc method if you also didn't retain them. See the Memory Management Programming Guide for Cocoa.
Also, if you're programming for the iPhone OS or if you are targetting Mac OS X 10.5 or later, I suggest you use the for(... in ...) language construct to enumerate through your dictionary. It's much faster than using enumerators.
for(NSString* currKey in self)
{
NSString* currObject = [self objectForKey:currKey];
// the rest of your loop code
}
You should read the Fast Enumeration section of the Objective-C Programming Language Reference.
I also suggest, as a friendly advice, that you don't extend NSDictionary or create a category on it unless you really, really need it.
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.