NSDictionary: URL-Encoding - objective-c

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.

Related

How do I put a pointer to an NSString pointer into a NSDictionary?

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".)

componentsJoinedByString causing crash with EXC_BAD_ACCESS

I'm pretty sure this is eactly the same problem as in componentsJoinedByString gives me EXC_BAD_ACCESS
basically, an array is populated using this code, with ARC turned on:
-(NSMutableArray *)getArrayOfCommaSeparatedSelectorStrings{
NSMutableArray *Array = [[NSMutableArray alloc] init];
for(NSMutableArray *e in [self getArrayOfSelectorArrays]) {
[Array addObject:[displayCSSInformation returnArrayAsCommaList:e]];
}
return Array;
}
and then displayCSSInformation tries to return a comma separated list with this method :
+(NSString *)returnArrayAsCommaList:(NSMutableArray *)ToBeConverted{
NSString *test = [ToBeConverted componentsJoinedByString:#", "];
return test;
}
Thanks for your help.
There's usually no need to use a separate method if all that method does is invoke another method. Remove your +returnArrayAsCommaList: method and just use componentsJoinedByString: on the array directly.
- (NSMutableArray *) getArrayOfCommaSeparatedSelectorStrings
{
NSMutableArray *array = [[NSMutableArray alloc] init];
for (NSMutableArray *e in [self getArrayOfSelectorArrays])
[array addObject:[e componentsJoinedByString:#", "]];
return array;
}
The above should work (it works in my small test example), if you are still getting errors:
Make sure that getArrayOfSelectorArrays is actually returning an array of array of strings. Log the output to the console or step through with a debugger.
Use the “Build & Analyze” option to have the static analyser check for any issues. This is less of an issue with ARC but it will still pick up things such as using uninitialised values.
Make sure you have properly bridged ownership from any Core Foundation objects.

iOS: Methods and Functions

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 :)

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

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

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.