Basic Objective-C typecasting question - objective-c

Consider the following code:
if([obj isKindOfClass:[NSString class]]) {
NSString *s = [(NSString *)obj stringByAppendingString:#"xyzzy"];
}
I'm a bit confused here. The if statement checks whether or not obj is of the NSString class. If it is, it assigns the object and an appended string to NSString *s, do I understand this correctly? If so, why would you still cast it to (NSString *)?
Doesn't the if statement already check for that and doesn't that make the typecasting unnecessary?
Wouldn't it be perfectly fine to just say:
NSString *s = obj stringByAppendingString:#"xyzzy"];
Thanks in advance.

It all depends on how obj is defined. If it is id obj then no casting is needed, but if it was defined as NSObject *obj the cast is necessary to suppress the compiler warning that stringByAppendingString: is not defined on NSObject. The cast is not needed to make the code work at runtime, it only tells the compiler the "correct" type so it can tell whether the method should exist on the object.
The reason why the cast isn't needed for id is because id means "an object of any type", while NSObject * means "an object of type NSObject".

Related

How to get classname in objective c Like 'NSString'

I want to get the class name of an object as what we are using.
That means now if I write this code
NSString *s = [NSString string];
NSLog(#"%#",[s class]);
The output is __NSCFConstantString
How can I get it as NSString itself ?
Note : NSString is just an example
I know __NSCFConstantString is correct. But my intention is to get like NSString. Is there any way to acheive this?
Give these a try, they'll output NSString. Keep in mind, the second set requires importing the Objective-C runtime header.
#import <objc/runtime.h>
NSString *string = #"I'm a string.";
NSLog(#"%#",NSStringFromClass([string classForCoder]));
NSLog(#"%#",NSStringFromClass([string classForKeyedArchiver]));
NSLog(#"%s",class_getName([string classForCoder]));
NSLog(#"%s",class_getName([string classForKeyedArchiver]));
Now, this won't work in all cases. For example, trying to get the class of NSConstantString, in this manner will output NSString. If you require checking the class name as a string in this way, you probably should reconsider your approach to solving the problem.
NSString is a so-called "class cluster". That means that the init methods will return
an instance of some subclass (such as __NSCFConstantString or __NSCFString).
You will never get an instance with the class equal to NSString.
If your intention is to check whether an object is a NSString or not then
use isKindOfClass:
if ([s isKindOfClass:[NSString class]]) {
// this is a string …
}
Other examples of class clusters are NSNumber, NSDictionary, NSArray
and their mutable variants.
NSLog(#"%#", NSStringFromClass([s class]));

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
}

Warning - incompatible pointer types initializing 'NSString *__strong' with an expression of type 'UITextField'

I got this code and XCode warns me of "incompatible pointer types initializing NSString *__strong with an expression of type UITextField".
NSString *name = (UITextField *)searchText.text;
but this one is fine
NSString *name2 = [(UITextField *)searchText text];
and this one is fine too
NSString *name3 = [(UITextField *)searchText.text mutableCopy];
I have two questions:
I am confused with obj.* and [obj *]
Why is the "mutableCopy" correct is this case?
I don't know how to search in Apple developer documentation for these questions.
In the first version, due to operator precedence, you're casting searchText.text to a UITextField*, what you want to do is probably to cast searchText;
NSString *name = ((UITextField *)searchText).text;
In the second version you don't have the dot, so the compiler understands your cast to be casting searchText to UITextField and send the text message to it. In other words, exactly right.
The last case is a bit tricky since it involves both runtime and compile time. As I understand it;
You cast searchText.text to a UITextField*. The runtime still knows that the object is an NSString and the mutableCopy message that exists on both will go to the correct method [NSString mutableCopy] anyway and create/return a mutable copy of the NSString so runtime it works out ok.
Since mutableCopy returns id (referencing a NSMutableString), the assignment to an NSString is ok by the compiler (id can be assigned to anything), so compile time it's ok.
A question, what is searchText originally? That the last version compiled without warning indicates that it's already an UITextField*, or at least a type that can take the text message. If so, you should be able to just do;
NSString *name3 = [searchText.text mutableCopy];
In the second and third examples, the cast just operates on searchText. So with these you are sending a message to a UITextField object.
In the first one, the cast applies to the whole of searchText.text. Assigning a UITextField object to a NSString variables is not what you want. The code you're looking for is:
NSString *name = ((UITextField *)searchText).text;
The mutableCopy message returns a copy of your string as a NSMutableString object, which can be assigned to a NSString as NSMutableString derives from it. In this case using the 'copy' message is just as good.
Hope that helps.

what is this weird code notation mean

what's this line mean when using the second NSDictionay beside the message body:
NSDictionary *item = (NSDictionary *) [self.content objectAtIndex:indexPath.row];
(NSDictionary *) a type cast. It tells the compiler to assume that the object returned by the objectAtIndex: method is of the type NSDictionary * even though the return type of the method is different.
self.content is a property of type NSArray (I guess!)
This line returns you the Object (which seams to be a NSDictionary) at Index indexPath.row. (NSDictionary*) casts the object to NSDictionary.
This is a cast, as in C.
In your case, "self.content" seems to be an NSArray. So [self.content objectAtIndex:indexPath.row] would be an NSObject. Except that here, for some reason, you know it's an NSDictionary. So you explicitly cast it in order to avoid a compiler warning (that would tell you "hey, you're assigning an NSObject to an NSDictionary variable)

How to use #encode() to get #"NSArray" in Objective-C

I'm using the runtime functions to get the type of a property (thanks to eJames for helping me to figure out this way).
The attribute string of the property looks like this:
T#"NSArray",&,Vstuff
I need to check if the property type is an array, at the moment I'm doing it like this:
- (BOOL)valueForKeyIsArray:(NSString *)key fromTagret:(id)target
{
NSString *lowerCaseKey = [self convertToKVCKey:key];
objc_property_t property = class_getProperty([target class], [lowerCaseKey UTF8String]);
NSString *propertyAttrs = [NSString stringWithUTF8String:property_getAttributes(property)];
NSString *encodedType = #"#\"NSArray\"";
NSRange range = [propertyAttrs rangeOfString:encodedType options:NSLiteralSearch];
return range.location != NSNotFound;
}
But since Apple can change the type definition string at any time, I would like to generate this #"NSArray" type string. I tried it with #encode(), but it did not work:
NSString *encodedType = [NSString stringWithUTF8String:#encode(NSArray *)];
So how can I generate this type string? Or is there a better way to check if this property attributes contain the array type?
There is no way to check this. In Objective-C source code the variables being typed as NSArray * is only there for the compiler to issue warnings. It has no meaning, and does not exist at runtime. If you mis-typed an NSArray as an NSString, you would get lots of warnings when compiling, but your code would behave exactly the same when run. At runtime all that is known is that the ivar/property is "an object".
Another way to think of it, is that once Objective-C is compiled, all object references are id references.
Just accept that if the runtime changes, your code will break, and move on. However, I think you might be miscategorizing ivars of type NSMutableArray *, CFArrayRef, or CFMutableArrayRef. You also seem to be assuming all keys correspond directly to a declared property.
The cleanest solution might be to assert that the sample object being used for the test (the target) must have a non-nil value for that key, and just grab the value and test that [[target valueForKey:key] isKindOfClass:[NSArray class]].