I have just spotted something that I am a little puzzled about, I wonder if someone would be so kind as to clarify it for me.
NSArray *nextArray = [NSArray arrayWithObjects:#"ONE", #"TWO", #"THREE", nil];
for(id eachObject in nextArray) {
NSLog(#"COUNT: %d", [eachObject length]);
}
Why does the above not complain/warn about the fact that I am asking for the length of an id?
In Objective-C id is the general type for any kind of object regardless of class and can be used for instances of a class and for class objects themselves.
The id type is completely nonrestrictive it has no information about an object, except that it is an object. So there's no way for the compiler to know whether or not that object can respond to a method because it doesn't know what kind of object it is.
By using it in your code you're basically saying 'to whatever this is pointing to, perform this operation'.
You use id when you specifically do not want compiler type checking. You can send any message to an id type without a warning, and you can assign an id to any other type without a type cast.
This allows you to fetch an object from an array without using a cast. E.g., you're free to assume that the array contains NSStrings:
NSString* someString = [myArray objectAtIndex:1];
It also allows you to send a message to an object without a cast or a warning. In fact, the message you wish to send may not be part of any formal class or protocol:
id someObject = [myArray objectAtIndex:1];
if ([someObject respondsToSelector:#selector(setName:)])
{
[someObject setName:#"Foo"];
}
The compiler will never type check messages sent to an id. It's partly what enables Objective-C's dynamism.
If eachObject was any other type, then you would get an error if the compiler couldn't resolve the method name.
The NSArray might contain different object types, for example:
NSArray *thArray = [[NSArray alloc] initWithObjects:#"Stack",#"Overflow",[NSNumber numberWithInt:10],nil];
for(id theObject in thArray) {
NSLog(#"COUNT: %lu", [theObject length]);
}
The id can represent any object (in this case NSString or NSNumber),
therefor the compiler cannot know whether the primitive method length exists.
Simply, id means all Objective-C Class. so, length method it belongs a NSString Class. Compiler no throw warning to you.
id is determined dynamically at runtime, is unknown at compile time.
Each object in Objective-C knows what class it has and if it can handle a message. It's not the compiler who checks the class, it is the object itself at runtime.
The class of an object can be undefined at compilertime, but at runtime each object has a defined class.
Related
I sometimes like to organize IB elements into NSArrays primarily to help me organize my elements. Most often, different classes of objects make it into the same array with each other. While this is a convenient way of organization, I can't seem to wrap my head around why if I have an array like this:
NSArray *array = [NSArray arrayWithObjects:((UITextField *)textField), ((UISegmentedController *)segmentedController), nil];
Why I get "Does not respond to selector" messages when I put a for loop like this:
for (UITextField *text in array) {
[text setText:#""];
}
The for loop seems to be passed objects that are not of class UITextField.
What is the point of declaring the object's class if all objects in the specified array are passed through the loop?
EDIT Just for reference, this is how I'm handling it as of now:
for (id *object in array) {
if ([object isMemberOfClass:[UITextField class]]) {
foo();
} else if ([object isMemberOfClass:[UISegmentedController class]) {
bar();
}
}
When you do
for (UITextField *text in...
the object pointers from the array are cast to UITextField* type - so if the object isn't actually a UITextField, all sorts of weird things may happen if you try to call UITextField methods.
So instead use the id type (no * needed, btw):
for (id obj in array)
Then check the type as you do and call the appropriate methods. Or, filter the array to get only objects of a certain type, then go though that type only:
for (UITextField* text in [array filteredArrayUsingPredicate:...])
Edit: here's how to build class filter predicates:
Is it possible to filter an NSArray by class?
What is the point of declaring the object's class if all objects in the specified array are passed through the loop?
The class name is just there to let the compiler know what it should expect to find. This allows it to try to figure out what methods it should expect you to call and how you might treat the object. It's the same idea as passing in an int to a method that takes float. The method will not ignore ints - it's assuming you're passing the correct type. You're just giving this construct a little more credit than it's due:
for (UITextField *text in array)
It just doesn't have that functionality. How you're handling it now is the correct way.
Are you sure you don't get an error when you run that code? The "does not respond to selector" message is a runtime error, not a compile time error. The compiler has no idea whether the objects in the array implement -setText:, but you should certainly get an error when you actually send that message to an instance of UISegmentedControl.
Another possibility is that you've got a class called UISegmentedController that does have a -setText: method. The name of the class that implements the multi-part bar-graph-looking user interface widget is UISegmentedControl. So either the code you're showing isn't real, tested code, or you've got a class that we don't know about.
Is there a return type for "any primitive" similar to the way you can use NSObject as the return type for any object? I tried using id, but the compiler was giving me an error that it was trying to convert a primitive to a reference.
Here's what I'm trying to do. :
-(void)setObject:(NSObject*)obj forKey:(NSString*)key {
[sharedInstance.variables setObject:obj forKey:key];
}
-(NSObject*)getObjectForKey:(NSString*)key {
return [sharedInstance.variables objectForKey:key];
}
-(void)setValue:(???)value forKey:(NSString*)key {
[sharedInstance.variables setValue:value forKey:key];
}
-(???)getValueForKey:(NSString*)key {
return [sharedInstance.variables valueForKey:key];
}
The alternative that I have though of is to use separate methods (getIntForKey, getFloatForKey, etc.) to access the values.
1) Read Key-Value Coding Article in XCode documentation - all answers are there
2) There's an object NSValue, which resembles your "NSObject". NSValue can store plain-old-data inside itself.
PS
"Scalar and Structure Support
Key-value coding provides support for scalar values and data structures by automatically wrapping, and unwrapping, of NSNumber and NSValue instance values.
Representing Data as Objects
The default implementations of valueForKey: and setValue:forKey: provide support for automatic object wrapping of the non-object data types, both scalars and structs.
Once valueForKey: has determined the specific accessor method or instance variable that is used to supply the value for the specified key, it examines the return type or the data type. If the value to be returned is not an object, an NSNumber or NSValue object is created for that value and returned in its place.
Similarly, setValue:forKey: determines the data type required by the appropriate accessor or instance variable for the specified key. If the data type is not an object, then the value is extracted from the passed object using the appropriate -Value method."
I would have thought id is the perfect candidate here too... could this just be a casting issue you're seeing?
i.e. the id implicitly implies a pointer so in my mind I see id as an objective c equivalent to the c void*
In other words where you have a NSObject* you could replace this with id such as
-(NSObject*)myMethod1
{
}
so can be done for any returned primitive with
-(id)myMethod1
{
}
i.e. not an id*
Also I expect this was just a copy/paste thing but incase it also causes issues
-(void)setValue:(???)value forKey:(NSString*)key {
[sharedInstance.variables setValue:num forKey:key];
}
should probably be
-(void)setValue:(???)value forKey:(NSString*)key {
[sharedInstance.variables setValue:value forKey:key];
}
I eventually worked through this. The ultimate solution was to have separate accessor/mutator methods per type. So now I have setIntForKey, setBoolForKey, getIntForKey, getBoolForKey, etc. The drawback is quite obvious, in that I can't call one method to set values and another to retrieve them. The advantages are numerous, however. Because the compiler knows what object or primitive type the method is expecting at compile time, I gain compile time checking for all of these methods. Additionally, I don't have to worry with casting the retrieved values to their primitive types (obviously the returned NSObjects are a different story).
I'm facing some difficulty in retrieving properties of "id" type object. This is how I'm accessing it:
I'm doing following to assign an object to id type object from a generic array containing different types of objects and calling method "savedata" to which I'm passing the object as well as its type:
for(id objInArray in genericArray){
NSString *objType = [objInArray valueForKey:#"type"];
[objInArray retain];
[self saveData:objInArray :objType];
}
In savedata method I'm writing following code to retrieve the properties of id object:
-(void)saveData:(id)object :(NSString *)objectType
{
self.managedObjectContext = appDelegate.managedObjectContext;
if([objectType isEqualToString:#"event"])
{
Event * newEvent = (Event *)[NSEntityDescription
insertNewObjectForEntityForName:#"Event"
inManagedObjectContext:self.managedObjectContext];
[newEvent setEletitle:[NSString stringWithFormat:#"%#", [object valueForKey:#"eletitle"]]];
[self saveAction];
}
But the object "object" containing the values fails to assign them to object newEvent.
I also tried to retrive this value in a string object like this:
NSString *eletit = [object valueForKey:#"eletitle"];
[eletit retain];
But eletit is also invalid at the end of this transaction.
Can anybody please help? This' really urgent.
Thanx in advance.
I don't have you answer unfortunately but I have few comments on your code.
Are you sure it's normal you array contain so generics object? It's strange because all your object contained in your array need to respond to "type" or "eletitle" messages, so I guess objInArray is less generic than just "id".
Second, it's not recommended to have selector like saveData::, in Objective-C it's usual and recommended to name the arguments, it's more understandable.
I'm attempting to create an NSArray with a grouping of string literals, however I get the compile error "Initializer element is not constant".
NSArray *currencies = [NSArray arrayWithObjects:#"Dollar", #"Euro", #"Pound", nil];
Could someone point out what I'm doing wrong, and possibly explain the error message?
New syntax for creating an array with string literals:
NSArray *currencies = #[#"Dollar", #"Euro", #"Pound"];
To fix your complication error the code must be in a method. If you want to use it statically then create a class method that follows the singleton pattern.
This isn't a problem with the NSArray creation itself (you would get the same error if you wrote [NSArray array] instead), but with where you've written it. I'm guessing this is a global or file-static NSArray. In C, that kind of variable has to have a constant initializer — meaning not a function call (or, by extension, a method call). The solution is to put the actual creation and assignment of the array into a method that will be called before you need the array, such as initialize.
It sounds like Chuck has spotted the problem. One thing you want to be aware of though in coding your solution is that you'll want to avoid storing an autoreleased instance of NSArray in a static variable. Also, a common pattern for these situations is to write a class method that creates and returns the value stored in the static variable, like so:
+ (NSArray *)currencies
{
static NSArray *_currencies;
// This will only be true the first time the method is called...
//
if (_currencies == nil)
{
_currencies = [[NSArray alloc] initWithObjects:#"Dollar", #"Euro", #"Pound", nil];
}
return _currencies;
}
Although this is old, please notice that Apple committed a new patch to the llvm project adding support for new Objective-C literal syntax for NSArray, NSDictionary and NSNumber.
See here and here
I'm a newbie in objective-c, but I think that the correct code is:
NSArray *currencies = [[NSArray alloc] initWithObjects:#"Dollar", #"Euro", #"Pound", nil];
I am not sure tho.
There's nothing wrong with that code. Are you sure the error is being produced at that line?
I have a class that I use to setup objects in an array. In this class I have a custom "initWithDictionary", where I parse a JSON dictionary. However, as I am running into NSNull, this crashes my app. To get around this, I set up a class that handles exceptions, so when a string is NSNull, it's replace it with #"". or -1 for integers.
This is my NullExtensions class:
#interface NSNull (valueExtensions)
-(int)intValue;
-(NSString *)stringValue;
#end
#implementation NSNull (valueExtensions)
-(int)intValue {
return -1;
}
-(NSString*)stringValue {
return #"";
}
#end
However, in my initWithDictionary method, the following code crashes my app:
self.bookTitle = [[parsedDictionary objectForKey:#"book_title"] stringValue];
It doesn't work regardless of the object in the parsed dictionary being NSNull or containing a valid string. Only if I do the following (and the string is not null):
self.bookTitle = [parsedDictionary objectForKey:#"book_title"];
Is stringValue incorrect in this case? And if so, how do I use it properly in order to setup proper NSNull replacements?
Thx
You really really don't want to add a category to NSNull that adds such common methods. That will change the behavior of NSNull for all instances in the application, including ones created by the underlying frameworks solely for their private use.
If you need a value class that represents the notion of "value doesn't exist and therefore I'm going to return these default values instead", create a class or instance that represents exactly that.
As for why it crashes, I couldn't tell you without seeing the actual details of the crash.
And, yes, it really is THAT bad to add a category to a class that adds such a common method. All it takes is one bit of code in a plug-in or framework that does:
if ([fooMaybeNull respondsToSelector: #selector(intValue)] bar = [fooMaybeNull intValue];
Not terribly farfetched -- I have had to debug nasty crashers or misbehaviors due to exactly this kind of willy-nilly category addition.
If you are going to add methods to a class via categories, prefix your method names so as to isolate them from existing functionality. It is still fragile, but manageably so.
Instead of creating categories on NSNull, for which you would also have to add a similar category to NSString (that's why it crashes, because real strings do not respond to stringValue) - instead try creating a helper category on NSDictionary like "stringForKey" that uses the code Johan posted and returns an NSString, probably also should enforce all other types get mapped to empty strings as well.
The NSNull extensions you have written look ok to me but using a method like stringValue may be confusing since other classes like NSNumber use this.
Personally though, I think NSNull replacement in this instance is unnecessary. If you just made a quick test you can replace the NSNull where you need to. e.g.
id testObject = [parsedDictionary objectForKey:#"book_title"];
self.bookTitle = testObject==[NSNull null] ? #"" : testObject;
You are asking an NSString for its stringValue. No need to convert a string to a string.
Try this:
if (![[parsedDictionary objectForKey:#"book_title"] isKindOfClass:[NSNull class]]) {
self.bookTitle = [parsedDictionary objectForKey:#"book_title"];
} else {
self.bookTitle = #"";
}
Edit: You should not use the category on NSNull you created. You don't need it, nor should you want it. If the source for the dictionary inserts NSNull instances, go ahead and use my code above. Normally you would expect to simple have no value inserted for the key, at which time you can simple see if [parsedDictionary objectForKey:#"book_title"] returns anything.
Are you sure that the dictionary is returning [NSNull null]? By default, dictionaries return nil, not [NSNull null], when an value isn't found for a key.
However, in my initWithDictionary method, the following code crashes my app:
self.bookTitle = [[parsedDictionary objectForKey:#"book_title"] stringValue];
It doesn't work regardless of the object in the parsed dictionary being NSNull or containing a valid string.
That makes sense, since stringValue is not a valid method on NSString. It will work for NSValue and its subclasses, but not NSString.