Why is an NSString 'forgetting' what it is? - objective-c

I was hoping for some help to understand why I need to re-cast my variable when it is a string from the start.
Here's the code:
+ (BOOL)hasOperandComponents:(NSString *)operandToTest
{
NSArray *componentsOfOperand = [[NSString stringWithFormat:#"%#",operandToTest] componentsSeparatedByString:#" "];
if (componentsOfOperand.count>1) return YES; return NO;
}
If I don't use the embedded call to 'stringWithFormat' then I get the rather common error:
-[__NSCFNumber componentsSeparatedByString:]: unrecognized selector sent to instance
I have been able to find answers to what this error means and hence how to avoid it (see my code above) by searching other Q's and A's... but no good explanation as to why my operandToTest seems to 'forget' that it is a NSString and become an _NSCFNumber.
I'm suspicious that it is because this is a Class method... but why would that matter when a specific instance of NSString *operandToTest is passed to the class method?
Please help?

The problem is you don't have a string to begin with. The value that you're passing into the method as operandToTest is, in fact, an NSNumber*. You need to look at the calling function to figure out why this is.

Related

Why does this typecast not apply itself?

I've been working on this problem for a while and can't seem to find the solution. In my app, I uniquely identify contacts from ABAddressBook by their creation date, as no two contacts can be created at the same time. In my app, the creation date is stored in a string called uid, as you can see below. I am checking to see if two contacts are the same, and I do this by performing the following conditional statement:
NSString *uid = #"some string";
if([(__bridge_transfer NSString *)ABRecordCopyValue(currentPersonRef, kABPersonCreationDateProperty) isEqualToString:uid]) {
//Do some code
}
However, when I run this code, I get the following error:
[__NSDate isEqualToString:]: unrecognized selector sent to instance 0x8c7c770
I'm pretty sure the problem is that ABRecordCopyValue() is returning an NSDate object that is not being casted to NSString by (__bridge_transfer NSString *), so when I compare it to uid using isEqualToString, it crashes. The thing is, I thought that by casting it to an NSString object, isEqualToString: would work. My question is:
Why is the (__bridge_transfer NSString *) not casting the NSDate object?
EDIT:
The answers provided have been useful, but they do not completely address my problem (well actually they do, but I have a follow-up question). In another part of my code, I run this:
NSString *uid = (__bridge_transfer NSString *)ABRecordCopyValue(currentPerson, kABPersonCreationDateProperty);
And if I NSLog uid I get this:
2012-10-28 21:55:29 +0000
So how would I compare uid in the above conditional statement if isEqualToString: doesn't work?
A cast never changes the class or type of an object. It just tells the compiler, that you are certain about the fact, that this object is of another type, than the compiler might assumes.
So if you cast a date object to a string, it will still be a date object.
In OOP you usually needs up-casting: a method wants you to pass in a object, but actually you pass in an object of a subclass. If you then need to access a property or method that is defined by the subclass, you will tell the compiler, that you have an object of the subclass by casting.
A very common example in iOS is tableview:cellForRowAtIndexPath: with subclassed cells.
In cocoa(-touch) you also know casting from toll-free bridging. see this documentation for valid casts.
The answers provided have been useful, but they do not completely
address my problem (well actually they do, but I have a follow-up
question). In another part of my code, I run this:
NSString *uid = (__bridge_transfer NSString *)ABRecordCopyValue(currentPerson, kABPersonCreationDateProperty);
And if I NSLog uid I get this:
2012-10-28 21:55:29 +0000
You are creating a variable, that you declare to be an NSString, but actually the object you assign to it, is a date object.
NSDate *uidDate = (__bridge_transfer NSDate *)ABRecordCopyValue(currentPerson, kABPersonCreationDateProperty);
NSString *uid = [NSString stringWithFormat:uidDate];
Well, I don't know the answer to your question, but why don't you just convert the ABRecordCopyValue() to NSString, then do the isStringEqual:?
Like -
NSString *string = [NSString stringWithFormat:#"%#", ABRecordCopyValue(currentPersonRef, kABPersonCreationDateProperty)];
if([string isEqualToString: uid]){
//do your thing
}

Instance Message Error in Objective-C, What does this mean? And why is it happening?

I get the below error hor no apparent reason, I would try to fix it myself but I can't understand what it means. Does anyone else understand it? Here is the error and below it is the code.
Error: Receiver Type 'NSdata' for instance message does not declare a method with selector type 'EncryptAES:'
I have added a comment to the line with the error on it:
//Change the Input String to Data
NSData *objNSData = [NSData dataWithData:[Input dataUsingEncoding: NSUTF8StringEncoding]];
//Encrypt the Data
[objNSData EncryptAES:Keyword.text]; //Error appears here
NSString *InputString = [[NSString alloc] initWithData:objNSData encoding:NSUTF8StringEncoding];
What does this mean, why is it happening and what can I do to fix it?
And, what is an instance message?
The problem here is that NSData does not respond to the EncryptAES: selector. You can only invoke selectors that exist on the class. With the Objective-C runtime's loose typing, you can also invoke a selector on a class that does not respond to said selector, as long as the selector appears in the implementation of at least one other class that the compiler is working with.
All standard NSData methods can be found on the NSData Class Reference. Apple has sample code for encryption, but it is not built-in to NSData.
My guess is that you're trying to use the AESEncrypt "category" for NSData, but you don't actually have the category installed in your project.

Objective C question: method won't return a string

This is my first question so please forgive me if it's obvious. I learned to program in Pascal a few years ago, so my terminology may be off. I've looked at a bunch of postings, but nothing seems to address my basic problem.
I have a lookup table that I use to convert decimals back into fractions. I am calling this method...
-(void) convertToFractions:(float *)float2 aString:(NSMutableString *) myString;
...with this..
[self convertToFractions:&float1 aString:outputFraction];
The idea is that float2 is the decimal that I pass to the method, and myString is the fraction returning.
This runs after the lookup:
myString = [NSString stringWithString:[[decimalInchArray objectAtIndex:x]objectAtIndex:1]];
NSLog(#"myString = %#",myString);
The log shows myString is the correct fraction i.e. myString is correctly displaying the fraction I want to return, but outputFraction is null.
I think it's a pointer issue. I tried *myString, but the compiler throws an error (incompatible types).
Any suggestions are really appreciated.
You want to change the output of your convertToFractions method from void to NSString.
It's returning null because the return type of your method, is void, so it returns nothing.
The return type of an Objective-C method is in the parenthesis, at the beginning of the method name.
Here,s an example, but I don't see where you define convertToString so, I'll use pseudocode.
- (NSString *) convertToFractions:(float *)float2{
NSString *fraction = *some code to lookup fraction from table;*
return fraction;
}
myString = [self convertToFractions:(float *)float2];
EDIT:
As others have suggested, you should give Objective-C a fresh look. I suggest you read this Objective-C Primer written by Apple.
Where do you define your outputFraction? Nowhere in the code above you mention it.
At a guess your conversion method is declared as (void) meaning it will not return anything. If you need it to return the result as a NSString declare it like
-(NSString*) convertToFractions:(float *)float2 aString:(NSMutableString *) myString;
And make sure you return an NSString before reaching the end of the method with
return MyNSStringVariable;
[EDIT]
I can see you are hoping that outputFraction will be returned by your method but that is not the case with Objective-C (not sure about Pascal). You are simply passing outputFraction as a second variable in your method.
So the "right" way of doing it would be to have a method
-(NSString*)convertToFraction:(float*)float2 {
...// Do your float to fraction calculation here
...// Make sure fraction is formatted as NSString
return YourFractionVariable;
}
Then you can assign the return value to a variable of your choice, for instance:
NSString *fraction = [self converToFraction:aFloatNumber];
NSLog (#"fraction is %#", fraction);
Cheers,
Rog
Why not just return the string?
- (NSString*)convertToFractions:(float)float {
//Bla bla do everything
return [myString autorelease]; //Autorelease is for memory-management
}
Btw: You seriously need to read into ObjC. Please don't try to use your old pascal-knowledge on ObjC. It's different, and your knowledge isn't really applicable.
I would recommend buying a book about ObjC or reading some good tutorials for it. (Apple itself has some very good ones)
If you don't want to return NSString from your method as others suggested you can pass a pointer to NSString pointer to your function (the same way you pass NSError** to some standard api callsm e.g. in NSFileManager methods). Your code will look something like:
NSString *outputFraction;
[self convertToFractions:&float1 aString:&outputFraction];
-(void) convertToFractions:(float *)float2 aString:(NSMutableString **) myString{
...
if (myString)
*myString = [NSString stringWithString:[[decimalInchArray objectAtIndex:x]objectAtIndex:1]];
}

If I want to make a new instance of an object in a function whose pointer is passed by reference in it

- (void)createAString:(NSString **)str
{
*str = [NSString stringWithString:#"Hi all!"];
[*str autorelease]; // ???? is this right ?
}
How should I use release or autorelease ? I don't want to release outside of the function of course :)
...
NSString *createStr;
[self createAString:&createStr];
NSLog(#"%#", createStr);
You're correct that you'd generally want to return autoreleased (or the like) objects from out params when you use this form. Your assignment statement in the function that sets *str to a string:
*str = [NSString stringWithString:#"foo"];
is already doing the right thing, because that method returns an instance of NSString that the caller doesn't own. Just like you could return this string object from your function without any further memory management, you can set it as the outparam as you've done. Your second snippet showing the call site is fine.
This said, I'm worried about a few things in your code that you should be sure you understand:
The value of str inside the method is still a **, and sending that a message (as you've done for the speculative autorelease) is nonsense. Be sure you fully understand doubly indirected pointers before using them too liberally. :) If you need to send str a message after creating it, send it to *str, which is what contains the NSString *.
Setting an outparam like this when the function returns void is not idiomatic Cocoa. You would normally just return the NSString * directly. Outparams are rare in Cocoa. (Usually just NSErrors get this treatment from framework calls. Otherwise they conventionally use name like getString to differentiate them from normal get accessors which don't use the word "get".)
I hope -stringWithString was just an example. That method is almost never used in practice, since it's equivalent (in this case) to just using a #"string literal" (although that would muddy your example).
Instead of using a double pointer, would it not be more elegant to use an NSMutableString instead?
- (void)createAString:(NSMutableString *)str
{
[str setString:#"Hi all!"];
}
....
NSMutableString *createStr = [[NSMutableString alloc] init];
[self createAString: createStr];
NSLog(#"%#", createStr);
[createStr release];
Or, even better, just have the createAString method return an NSString.
- (NSString *)createAString
{
return #"Hi all!"; // this is autoreleased automatically
}
I wouldn't want to presume that your needs are this simple, though. =)

Comparing strings in IF statements: unrecognised selector sent to instance

in my code, I need to compare two strings to see if they are equal. if they are it needs to preform a function. one of the strings is just a #"someString", the other is part of an object.
if ([[[mine metal] stringValue] isEqualToString:#"Gold"])
{
//some function
}
however there are some complications when I do this. first, it gives me a warning: NSString may not respond to -stringValue. and when I run the Application it quits out at the if statement: the console reports " -[NSCFString stringValue] : unrecognized selector sent to instance." mine.metal is defined through a fast enumeration loop across an array; the metal attribute is defined as an NSString, and NSLog is able to display this string. what else am I missing?
The compiler warning and the subsequent run-time error both tell you what the problem is.
[mine metal] returns an NSString. NSString doesn't have a method called stringValue.
If [mine metal] does indeed return an NSString then you can do this:
if ([[mine metal] isEqualToString:#"Gold"])
{
//some function
}