Why doesn't NSString getCharacters work in xcode? - objective-c

-(void)myFunction {
unichar myBuffer[5];
NSRange range = {0, 3};
NSString *string = [NSString alloc];
string = #"123";
[string getCharacters:myBuffer :range];
}
Gets warning
! NSString may not respond
to '-getCharacters::'
then does a SIGABRT when I run it.
Why????? Or better yet how can I get it to work?

To use getCharacters:range:, write this line of code:
[string getCharacters:myBuffer range:range];
In Objective-C, method names are split up by arguments (a feature that derives from its Smalltalk heritage). So your original code was trying to send a message that's essentially named getCharacters: :, which isn't found. The corrected version sends the message getCharacters: range:.
The getCharacters:myBuffer construct says that the first argument to getCharacters:range: is myBuffer. Similarly, the range:range construct says the second argument is your NSRange named range.

Related

Weird error with NSString: No known class method for selector 'stringWithBytes:length:encoding:'

I am attempting to use scanf to assign a value to an NSString, as per the answers to this question by Omar. This is the code, taken straight from progrmr's answer:
char word[40];
int nChars = scanf("%39s", word); // read up to 39 chars (leave room for NUL)
NSString* word2 = [NSString stringWithBytes:word
length:nChars
encoding:NSUTF8StringEncoding];
However, I'm getting an error on the last line that makes absolutely no sense to me:
No known class method for selector 'stringWithBytes:length:encoding:'
What in the world could be causing this error?
And yes, I do have #import <Foundation/Foundation.h> at the top of the file.
NSString does not have a stringWithBytes:length:encoding: class method, but you can use
NSString* word2 = [[NSString alloc] initWithBytes:word
length:nChars
encoding:NSUTF8StringEncoding];
Note however, that scanf() returns the number of scanned items and
not the number of scanned characters. So nChars will contain 1 and not the string length, so you should set nChars = strlen(word) instead.
A simpler alternative is (as also mentioned in one answer to the linked question)
NSString* word2 = [NSString stringWithUTF8String:word];
NSString does not respond to the selector stringWithBytes:length:encoding:. You probably wanted initWithBytes:length:encoding:.
Story in short: you might want to consider a const char C-string suitable initializer for your NSString object. Also, allocate memory before sending any initializer message to the NSString object. I would expect something like:
char word[40];
int nChars = scanf("%39s", word);
NSString *word2 = [[NSString alloc] initWithCString:word encoding:NSASCIIStringEncoding];
Note that initWithCString per design only supports properly null '\0' terminated 8-bit character arrays. For unterminated bytes arrays you have initWithBytes:length:encoding: instead.
For Unicode characters you could consider initWithCharactersNoCopy:length:freeWhenDone:.

How to check if NSString format contains the same number of specifiers as there are variadic arguments?

To ensure that a formatted string returned by NSString initWithFormat:arguments: is as expected, I need to determine if there are the same number of format specifiers as arguments. Below is a (slightly contrived and highly edited) example:
- (void)thingsForStuff:(CustomStuff)stuff, ...
{
NSString *format;
switch (stuff)
{
case CustomStuffTwo:
format = #"Two things: %# and %#";
break;
case CustomStuffThree:
format = #"Three things: %#, %#, and %#";
break;
default:
format = #"Just one thing: %#";
break;
}
va_list args;
va_start(args, method);
// Want to check if format has the same number of %#s as there are args, but not sure how
NSString *formattedStuff = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
NSLog(#"Things: %#", formattedStuff);
}
Using this method, [self thingsForStuff:CustomStuffTwo, #"Hello", #"World"] would log
"Two things: Hello and World"
...but [self thingsForStuff:CustomStuffTwo, #"Hello"] would log
"Two things: Hello and "
...something that would be preferred to be caught before it happens.
Is there a way to count the format specifiers in a string, preferably something lightweight/inexpensive?
Well, I created my own regex, I have no idea if it's going to catch all of them, and may end finding some false positives, but seems to be working for me:
static NSString *const kStringFormatSpecifiers =
#"%(?:\\d+\\$)?[+-]?(?:[lh]{0,2})(?:[qLztj])?(?:[ 0]|'.{1})?\\d*(?:\\.\\d+)?[#dDiuUxXoOfeEgGcCsSpaAFn]";
You can count the number of arguments using:
NSRegularExpression *regEx = [NSRegularExpression regularExpressionWithPattern: kStringFormatSpecifiers options:0 error:nil];
NSInteger numSpecifiers = [regEx numberOfMatchesInString: yourString options:0 range:NSMakeRange(0, yourString.length)];
Is there a way to count the format specifiers in a string, preferably
something lightweight/inexpensive?
Nope -- really isn't. At least, not if you want it to work across all possible format strings. You would have to duplicate the parser that is used by stringWithFormat:. I.e. don't try to validate everything.
You could count the number of %, but that would not catch things like %% or other special cases. That may be good enough for your purposes.
Because of the way C and Objective-C handle variadic functions/methods like yours, you cannot in general tell how many arguments the user has provided.
Here are two ways to handle your situation.
First, look for another way to do this. The number of arguments you pass to the method is determined at compile-time. So maybe instead of using a variadic method, you should just have three methods:
- (void)doStuff:(CustomStuff)stuff withThing:(Thing *)thing;
- (void)doStuff:(CustomStuff)stuff withThing:(Thing *)thing1 thing:(Thing *)thing2;
- (void)doStuff:(CustomStuff)stuff withThing:(Thing *)thing1 thing:(Thing *)thing2 hatWearer:(Cat *)cat;
And you select the right method to call at compile-time based on how many arguments you want to pass, eliminating the switch statement entirely.
Second, I see that your predefined format strings only use the %# format. Does this mean that you expect the user to only pass objects to your method (aside from the (CustomStuff)stuff argument)?
If the user will only pass objects to your method, and you require those arguments to be non-nil, then you can get the compiler to help you out. Change your method to require the user to pass nil at the end of the argument list. You can tell the compiler that the argument list has to be nil-terminated by declaring the method (in your #interface) like this:
#interface MyObject : NSObject
- (void)thingsForStuff:(CustomStuff)stuff, ... NS_REQUIRES_NIL_TERMINATION
#end
Now the compiler will warn the user “Missing sentinel in method dispatch” if he calls your method without putting a literal nil at the end of the argument list.
So, having changed your API to require some non-nil arguments followed by a nil argument, you can change your method to count up the non-nil arguments like this:
- (void)thingsForStuff:(CustomStuff)stuff, ... {
int argCount = 0;
va_list args;
va_start(args, stuff);
while (va_arg(args, id)) {
++argCount;
}
va_end(args)
int expectedArgCount;
NSString *format;
switch (stuff) {
case CustomStuffTwo:
expectedArgCount = 2;
format = #"Two things: %# and %#";
break;
case CustomStuffThree:
expectedArgCount = 3;
format = #"Three things: %#, %#, and %#";
break;
// etc.
}
NSAssert(argCount == expectedArgCount, #"%# %s called with %d non-nil arguments, but I expected %d", self, (char*)_cmd, argCount, expectedArgCount);
va_start(args, stuff);
NSString *formattedStuff = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
NSLog(#"Things: %#", formattedString);
}
You could count the number of format specifiers, but IIRC you will never be able to count the number of arguments passed into a variable-argument method. This is because of the way C pushes arguments on the stack without specifying how many it has pushed.
Most functions overcome this by requiring that the last argument be nil or some kind of terminator (see [NSArray arrayWithObjects:]). There's even a macro that allows the compiler to check this and emit a warning at compile time.
You can use NS_FORMAT_FUNCTION at the end of your function prototype, as in stringWithFormat method of NSString.
So your method's prototype should be like this:
- (void)thingsForStuff:(CustomStuff)stuff, ... NS_FORMAT_FUNCTION(1,2);
long specifierCount = [myFormatString componentsSeparatedByString:#"%"].count;
This will get you close. Its just a simple split. You would have to account for escaped % values.

EXC_BAD_ACCESS memory issue when accessing instance variables

I'm having issues porting a Java project I worked on a while ago to Objective-C code - I get "Program received signal: "EXC_BAD_ACCESS"", on the first line in this code:
-(Point3D *) unit {
NSLog(#"%#%#", "X: ", x);
double length = [self length];
return [[Point3D alloc] initWithX:x/length andY:y/length andZ:z/length];
}
called from here:
-(id) initWithStart:(Point3D *)start andDirection:(Point3D *)dir {
if ( self = [super init] ) {
NSLog(#"%#%#", #"Direction:", [dir toString]);
printf("Trying to find unit length of direction...\n");
NSLog(#"%#", [[dir unit] toString]);
self.start = start;
self.direction = [dir unit];
}
return self;
}
Console output is:
2011-12-09 17:20:14.021 RayTracerProject[16607:407] Direction:(0,0,20)
Trying to find unit length of direction...
The toString method of the Point3D looks like this:
-(NSString *) toString {
NSNumber *xstring = [NSNumber numberWithDouble:self.x];
NSNumber *ystring = [NSNumber numberWithDouble:self.y];
NSNumber *zstring = [NSNumber numberWithDouble:self.z];
NSString * str = #"(";
str = [str stringByAppendingString:[xstring stringValue]];
str = [str stringByAppendingString:#","];
str = [str stringByAppendingString:[ystring stringValue]];
str = [str stringByAppendingString:#","];
str = [str stringByAppendingString:[zstring stringValue]];
str = [str stringByAppendingString:#")"];
return str;
}
So, from what I can see, my (Point3D *) dir is alive and well when I check what the value is using my [dir toString] call. But when I try to call [dir unit], it seems I no longer have the variables I did in the object, hence the EXC_BAD_ACCESS error.
What am I doing wrong here? I think it's something to do with the way I'm managing (or not) my memory usage, but I don't know what it is.
The NSLog in your method 'unit' should be:
NSLog(#"%# %g", #"X:", x);
Moving the space is optional, but just makes it clearer. The key issues were that you were missing the # before the "X: ", and you need to use %g rather than %# because from your other code x is a double rather than an NSObject. Equivalent—but simpler and thus better—would also be:
NSLog(#"X: %g", x);
Apple's documentation provides the definitive guide to string format specifiers and that and the surrounding documentation explain the use of format specifiers like %# (to reference an Objective C object) and %g (to reference a 64-bit floating-point number) when constructing an NSString.
It seems this line here:
NSLog(#"%#%#", "X: ", x);
should look more like this:
NSLog(#"%#%lf", #"X: ", self.x);
Note a few changes here:
From your other code, it seems you have a property named 'x'. self.x references that property. (You may or may not also have an instance variable named 'x')
Also from your other code, it seems your property 'x' is a double. So the placeholder for a double is %lf, not %#. %# is for NSObjects (and descendants)
NSString literals start with #. So #"X: " is an NSString literal. What you had is a C string.
EDIT
Well, I'm slow as usual. :) Ignore what I said and accept Duncan's answer, which was faster and now cleaner, actually.

Got an error when try to print stringByReplacingOccurrencesOfString method

When i try to print this string i got bad access error:
NSString *myPath = [myPath stringByReplacingOccurrencesOfString:#"/Users/Me/Library/iPhone/4.2/MyApp/Documents/Photos/pic1.png"
withString:#"/Users/John/Library/iPhone/5/MyApp/Documents/Photos/picture.png"];
NSLog(#"%#", myPath);
Why?
Thanks.
The error is because you are calling the method stringByReplacingOccurrencesOfString:withString on a variable (myPath) that has not been instantiated. You need to call that method on an instance of the NSString class that already contains the string you are replacing text in.
When you call a method you call it on the receiver. Therefore you are calling stringByReplacingOccurrencesOfString:withString: on myPath.
You are assigning the value of the method into
NSString *myPath
which makes me assume that the myPath in
[myPath ....
is not actually set to anything. (potentially pointing to garbage)
What you want is something like this
NSString *startString = #"hello";
// Receiver Message
// | |
// v v
NSString *replacedString = [startString stringByReplacingOccurrencesOfString:#"hello"
withString:#"bye bye"];
NSLog(#"Results in => %#", replacedString);
// Output
2011-12-11 20:50:01.964 Untitled 2[779:707] Results in => bye bye
In your above comment you tried NSString *myPath = [[NSString alloc] init]; this would create an empty string. An empty string does not contain any occurrences of #"/Users/Me/Library/iPhone/4.2/MyApp/Documents/Photos/pic1.png" therefore it can't replace them.

stringByAppendingFormat not working

I have an NSString and fail to apply the following statement:
NSString *myString = #"some text";
[myString stringByAppendingFormat:#"some text = %d", 3];
no log or error, the string just doesn't get changed. I already tried with NSString (as documented) and NSMutableString.
any clues most welcome.
I would suggest correcting to (documentation):
NSString *myString = #"some text";
myString = [myString stringByAppendingFormat:#" = %d", 3];
From the docs:
Returns a string made by appending to the receiver a string constructed from a given format string and the following arguments.
It's working, you're just ignoring the return value, which is the string with the appended format. (See the docs.) You can't modify an NSString — to modify an NSMutableString, use -appendFormat: instead.
Of course, in your toy example, you could shorten it to this:
NSString *myString = [NSString stringWithFormat:#"some text = %d", 3];
However, it's likely that you need to append a format string to an existing string created elsewhere. In that case, and particularly if you're appending multiple parts, it's good to think about and balance the pros and cons of using a mutable string or several immutable, autoreleased strings.
Creating strings with #"" always results in immutable strings. If you want to create a new NSMutableString do it as following.
NSMutableString *myString = [NSMutableString stringWithString:#"some text"];
[myString appendFormat:#"some text = %d", 3];
I had a similar warning message while appending a localized string. This is how I resolved it
NSString *msgBody = [msgBody stringByAppendingFormat:#"%#",NSLocalizedString(#"LOCALSTRINGMSG",#"Message Body")];