NSInvocation and ARC (Automatic Reference Counting) - cocoa

When trying to migrate my current code to ARC, I'm getting errors whenever I pass an NSString as an NSInvocation argument.
Example:
NSInvocation inv = ...;
NSString *one = #"Hello World!";
[inv setArgument:&one atIndex:2];
The error happens when I use the Refactor -> Convert to Objective-C ARC option from the Edit menu. The text is "NSInvocation's setArgument is not safe to be used with an object with ownership other than __unsafe_retained."
How would I get around this?

This might work;
__unsafe_unretained NSString *one = #"Hello World";

As Joshua Weinberg commented, using NSInvocation is not recommended anymore.
If you have up to two parameters you can use performSelector.
For three parameters or more, you can use NSObject's -methodForSelector: as explained here.

Related

How to use NSIndexSet

In Objective-C, my program opens a window and displays a table. I want to have a specified row of the table highlighted.
How do I do this?
I seem to need the code
[myTableView selectRowIndexes:(NSIndexSet *) byExtendingSelection:(BOOL)];
I looked at the developer documentation, and figured out that the BOOL should be NO.
By looking at the NSIndexSet docs, I can't figure out what the right syntax should be.
it would be the proper way:
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 3)];
or you can use the NSMutableIndexSet for the random indexes:
NSMutableIndexSet *mutableIndexSet = [[NSMutableIndexSet alloc] init];
[mutableIndexSet addIndex:0];
[mutableIndexSet addIndex:2];
[mutableIndexSet addIndex:9];
etc.
Printing out an NSIndexSet in the debugger will show you that they are internally NSRanges. To create one, you can either specify the range or a single explicit index (from which it will create the range); something like
NSIndexSet *indexes = [[NSIndexSet alloc] initWithIndex:rowToHighlight];
[myTableView selectRowIndexes:indexes byExtendingSelection:NO];
[indexes release];
Note that the index(es) must all be unsigned integers (NSUIntegers, specifically).
I'd use a factory method to avoid having to manage memory:
[myTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:indexes]
byExtendingSelection:NO];
I seem to need the code
[myTableView selectRowIndexes:(NSIndexSet *) byExtendingSelection:(BOOL)];
No; those are casts without anything to cast, which is invalid.
Remove the casts and put values there instead.
I looked at the developer documentation, and figured out that the BOOL should be NO.
Yes, because you don't want to extend the selection, you want to replace it.
By looking at the NSIndexSet docs, I can't figure out what the right syntax should be.
The same as for passing any other variable or message expression.
You need to create an index set and then either stash it in a variable and pass that or pass the result of the creation message directly.

componentsJoinedByString gives me EXC_BAD_ACCESS

I have an NSMutableArray i am trying to convert into a string.
Declaring my NSMutableArray...
NSMutableArray *listData;
And later inside a method...
NSString *foo = [listData componentsJoinedByString:#"|"];
NSLog(#"%#",foo);
It seems no matter what i try i keep getting EXC_BAD_ACCESS.
To make sure each element in my array was an NSString i also tried this...
NSMutableArray *mArray = [[NSMutableArray alloc] init];
for (id ln in listData) {
NSString *boo = [NSString stringWithFormat: #"%#",ln];
[mArray addObject:boo];
}
NSString *foo = [mArray componentsJoinedByString:#"|"];
NSLog(#"%#",foo);
I can manipulate my NSMutableArray by adding/deleting objects in the same method or other methods inside my class. But when i try "componentsJoinedByString" the error pops up. Does anyone have any advice or another way i can combine this array into a single NSString?
In the code you've given, there will never be an NSMutableArray for listData. At some point in your code, you'll need to create one, and presumably populate it.
Edit
Okay, so you may get into memory management problems here, so let's be a bit clearer:
You're synthesizing getters and setters for the instance variable, so it's good practice to use those to access it, they'll take care of retain and releasing appropriately.
To set listData you can simply use
self.listData = [listManage getList:[[NSUserDefaults standardUserDefaults] stringForKey:#"list_name"] list:#"LIST"];
or
[self setListData:[listManage getList:[[NSUserDefaults standardUserDefaults] stringForKey:#"list_name"] list:#"LIST"]];
if you prefer.

NSAppleScript Leaking TONS of Memory

I have the following class method to execute an AppleScript:
+ (NSString *) executeAppleScript:(NSString *) scriptToRun{
NSAutoreleasePool *thePool = [[NSAutoreleasePool alloc] init];
NSAppleScript *appleScriptObject = [[NSAppleScript alloc] initWithSource:scriptToRun];
NSAppleEventDescriptor *objectReturned = [appleScriptObject executeAndReturnError:nil];
[appleScriptObject release];
appleScriptObject = nil;
NSString *charToReturn = [objectReturned stringValue];
if (charToReturn == nil ){
charToReturn = [NSString stringWithString:#"error"];
}
[charToReturn retain];
[thePool drain];
[charToReturn autorelease];
return charToReturn;
}
The problem is, this is leaking tons of memory. I admit fully that I do not completely understand memory allocations in Cocoa, so I was hoping someone might be able to explain to me why this is so leaky even with the autorelease pool.
Any help is greatly appreciated.
NSAppleEventDescriptor *objectReturned = [appleScriptObject executeAndReturnError:nil];
Don't ever do this. If you use this method wrong (unlikely) or give it a bad script (quite possible) or something doesn't work on the other application's end (very likely), you will be unable to find out what the problem is. Let the framework tell you what's wrong.
Plus, nil is the wrong constant here. nil is the null pointer for object pointer types; Nil is for Class values, and NULL is for everything else.
charToReturn = [NSString stringWithString:#"error"];
This is already a string. You don't need to create another string with it.
The problem is, this is leaking tons of memory.
Have you verified with Instruments that you are actually leaking AppleScript-related objects that originate in this method?
I can't see anything in the method that looks wrong. The pool should be unnecessary, but your use of it is valid and correct.
You might try using the OSAKit, particularly its OSAScript class, instead. It's not documented, but the two classes' interfaces are pretty much the same.

What is the better way of handling temporary strings?

I have a situation where I need to use some strings temporarily but I've read so many conflicting things that I'm a bit confused as to what the best way to proceed is.
I need to assign some strings inside an if structure but use them outside the if structure so they need to be created outside the if, I was thinking something like:
NSString *arbString = [[NSString alloc] init];
if(whatever)
{
arbString = #"Whatever"
}
else
{
arbString = #"SomethingElse"
}
myLabel.text = arbString;
[arbString release];
I have seen examples where people just used:
NSString *arbString;
to create the string variable
Google's Objective C guide says it's preferred to autorelease at creation time:
"When creating new temporary objects, autorelease them on the same line as you create them rather than a separate release later in the same method":
// AVOID (unless you have a compelling performance reason)
MyController* controller = [[MyController alloc] init];
// ... code here that might return ...
[controller release];
// BETTER
MyController* controller = [[[MyController alloc] init] autorelease];
So I have no idea, which is the best practice?
In the example you posted, you actually lose the reference to the NSString you created when you assign it in arbString = #"Whatever". You then release the string constant (which is unreleasable, by the way).
So there's a memory leak, since you never release the NSString you created.
Remember that all these types are pointers, so = only reassigns them.
As for the question, in this example, you don't need the [[NSString alloc] init]. You don't need to copy the string into a local variable anyway, you can just set myLabel.text to the string constant #"Whatever".
(edit: that's not to say that you can't use your pointer arbString, arbString = #"Whatever"; myLabel.text = arbString is fine. But this is just pointer assignment, not copying)
If you needed to manipulate the string before you returned it, you would create an NSMutableString, and either release or auto-release it. Personally, create autoreleased objects using class methods, so in this example, I'd use [NSString string], or [NSString stringWithString:], which return autoreleased objects.

Easiest way to format a number with thousand separators to an NSString according to the Locale

I can't seem to find an easy way to do it. The exact thing I need is:
[NSString stringWithFormat:#"%d doodads", n];
Where n is an int. So for 1234 I'd want this string (under my locale):
#"1,234 doodads"
Thanks.
For 10.6 this works:
NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior: NSNumberFormatterBehavior10_4];
[numberFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
NSString *numberString = [numberFormatter stringFromNumber: [NSNumber numberWithInteger: i]];
And it properly handles localization.
I have recently discovered this one-liner:
[#1234567 descriptionWithLocale:[NSLocale currentLocale]]; // 1,234,567
Or in Swift 2:
1234567.descriptionWithLocale(NSLocale.currentLocale()) // 1,234,567
Swift 3/4:
(1234567 as NSNumber).description(withLocale: Locale.current)
Formatted per the question:
[#(n) descriptionWithLocale:[NSLocale currentLocale]];
Formatted without Objective-C literals:
[[NSNumber numberWithInt:n] descriptionWithLocale:[NSLocale currentLocale]];
This is the solution I was looking for when I asked the question. Available since iOS 2.0 and OS X 10.0, documented to return a string version of the number formatted as per the locale provided. stringValue is even documented to use this method but passing nil.
Seeing as it is my question and this fits my answer best, I am tempted to change the tick, but it seems cruel. Update I changed the tick, this answer is the answer.
The below doesn't address the locale, but it is a better way (in my opinion) of setting the thousand separator on the number formatter.
NSNumberFormatter *numberFormat = [[[NSNumberFormatter alloc] init] autorelease];
numberFormat.usesGroupingSeparator = YES;
numberFormat.groupingSeparator = #",";
numberFormat.groupingSize = 3;
Todd Ransom answered this perfectly.
I would just like to add (in a separate comment, so I can show some nicely formatted code), that if you plan to do this regularly, it's worth creating an NSString helper class.
So, create yourself an NSStringHelper.h containing this:
#import <Foundation/Foundation.h>
#interface NSString (NSStringHelper)
+(NSString*)formatWithThousandSeparator:(NSInteger)number;
#end
..and an NSStringHelper.m file containing this:
#import "NSStringHelper.h"
#implementation NSString (NSStringHelper)
+(NSString*)formatWithThousandSeparator:(NSInteger)number
{
// Format a number with thousand seperators, eg: "12,345"
NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior: NSNumberFormatterBehavior10_4];
[numberFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
NSString *result = [numberFormatter stringFromNumber:[NSNumber numberWithInteger:number]];
return result;
}
#end
This gives you the perfect way to reuse this code in future projects.
#import "NSStringHelper.h"
NSInteger numOfUsers = 12345;
NSString* strNumberOfUsers = [NSString formatWithThousandSeparator:numOfUsers];
Cool, hey ?
Again, apologies for reposting Todd's answer (which was exactly what I was looking for !), but this is a great way to solve the problem, and have it ready to be used in your future XCode projects.
Use an NSNumberFormatter.

Resources