Why use (id) in a method signature when (NSObject *) would be more precise? - objective-c

Whenever I implement a method in my own code that can accept or return objects of more than one class, I always try to use the most specific superclass available. For example, if I were going to implement a method that might return an NSArray * or an NSDictionary * depending on its input, I would give that method a return type of NSObject *, since that's the most direct common superclass. Here's an example:
#interface MyParser()
- (BOOL)stringExpressesKeyValuePairs:(NSString *)string;
- (BOOL)stringExpressesAListOfEntities:(NSString *)string;
- (NSArray *)parseArrayFromString:(NSString *)string;
- (NSDictionary *)parseDictionaryFromString:(NSString *)string;
#end
#implementation MyParser
- (NSObject *)parseString:(NSString *)string {
if ([self stringExpressesKeyValuePairs:string]) {
return [self parseDictionaryFromString:string];
}
else if ([self stringExpressesAListOfEntities:string]) {
return [self parseArrayFromString:string];
}
}
// etc...
#end
I've noticed many cases in Foundation and other APIs where Apple uses (id) in certain method signatures when (NSObject *) would be more precise. For example, here's a method of NSPropertyListSerialization:
+ (id)propertyListFromData:(NSData *)data
mutabilityOption:(NSPropertyListMutabilityOptions)opt
format:(NSPropertyListFormat *)format
errorDescription:(NSString **)errorString
The possible return types from this method are NSData, NSString, NSArray, NSDictionary, NSDate, and NSNumber. It seems to me that a return type of (NSObject *) would be a better choice than (id), since the caller would then be able to call NSObject methods like retain without a type-cast.
I generally try to emulate the idioms established by the official frameworks, but I also like to understand what motivates them. I'm sure that Apple has some valid reason for using (id) in cases like this, but I'm just not seeing it. What am I missing?

The reason why (id) is used in method declarations is two fold:
(1) The method may take or return any type. NSArray contains any random object and, thus, objectAtIndex: will return an object of any random type. Casting it to NSObject* or id <NSObject> would be incorrect for two reasons; first, an Array can contain non NSObject subclasses as long as they implement a certain small set of methods and, secondly, a specific return type would require casting.
(2) Objective-C doesn't support covariant declarations. Consider:
#interface NSArray:NSObject
+ (id) array;
#end
Now, you can call +array on both NSArray and NSMutableArray. The former returns an immutable array and the latter a mutable array. Because of Objective-C's lack of covariant declaration support, if the above were declared as returning (NSArray*), clients of the subclasses method would have to cast to `(NSMutableArray*). Ugly, fragile, and error prone. Thus, using the generic type is, generally, the most straightforward solution.
So... if you are declaring a method that returns an instance of a specific class, typecast explicitly. If you are declaring a method that will be overridden and that override may return a subclass and the fact that it returns a subclass will be exposed to clients, then use (id).
No need to file a bug -- there are several already.
Note that ObjC now has limited co-variance support through the instancetype keyword.
I.e. NSArray's +array method could now be declared as:
+ (instancetype) array;
And the compiler would treat [NSMutableArray array] as returning an NSMutableArray* while [NSArray array] would be considered as returning NSArray*.

Using id tells the compiler it will be an object of unknown type. Using NSObject the compiler would then expect you to only be using messages available to NSObject. So... If you know an array was returned and it's casted as id, you can call objectAtIndex: without compiler warnings. Whereas returning with a cast of NSObject, you'll get warnings.

You can already call -retain on pointers of type id without casting. If you use a specific superclass type, you'll have to cast the pointer every time you call a subclass's method in order to avoid compiler warnings. Use id so the compiler won't warn you and to better signify your intent.

(id) is also often returned in order to enable objects to be subclassed more easily. For instance, in initializer and convenience methods, returning (id) means that any subclass doesn't have to override the superclass's methods unless there is a specific reason to do so.

Related

Why can dot syntax not be used on a method whose return type is instancetype?

Pretend I have a category on NSObject that defines the following method:
+ (instancetype)allocTemplate
{
id instance = [self new];
return instance;
}
and I have the following class:
#interface FDActor : NSObject
#property (nonatomic, copy) NSString *name;
+ (void)sayHi;
#end
#implementation FDActor
+ (void)sayHi
{
[self allocTemplate].name;
}
#end
How come [self allocTemplate].name errors out at compile time if self is FDActor?
I am aware it works if you use normal message sending syntax but I am explicitly interested in the dot syntax error.
It would appear as though instancetype is used only for type checking during assignment, so
FDActor *actor = [FDActor allocTemplate] would not produce a warning.
If we cast the return type of allocTemplate the problem goes away.
- (void)sayHi
{
((__typeof__(self))[[self class] allocTemplate]).name;
}
But note that this only works in an instance method since the typeof an instance is another instance. Note also that since we now explicitly type the return value the allocTemplate method is no longer necessary, if all were looking for is type checking then we can even just cast nil and it will work.
If we try the same thing in a class method it doesn't work
+ (void)sayHi
{
((__typeof__(self) *)[self allocTemplate]).name;
}
This is because (__typeof__(self) *) doers not evaluate to FDActor * but Class * which ARC will complain about. It looks like there is no way to resolve the type information FDActor * in a generic way from within a class method at compile time.
I rather expected the instancetype keyword to be a little more useful.
The error message tells you. [self allocTemplate] is an id. Property syntax never works on id.
Note that this does work:
[FDActor allocTemplate].name;
I think the class to which instancetype is to be related must be specified in the call; otherwise we just fall back on id.
I'll take a stab at this, with the disclaimer that I don't fully understand how instancetype support is actually implemented, let alone all the nitty gritty details of the compiler's type checking system. In other words, this is my best guess, and not an authoritative or complete answer...
After compilation, Objective-C methods are actually functions, where self is the first argument to the function, and is typed id. So you have:
void sayHi(id self, SEL _cmd)
{
[self allocTemplate].name; // Actually calls to objc_msgSend, but that doesn't matter here
}
The compiler's treatment of the type of value returned by +allocTemplate here seems to be related to this. If you change self to an explicit FDActor the error goes away. Of course, for methods -- as opposed to properties -- the compiler types self as you'd expect, and will warn (or error under ARC) for methods that self doesn't appear to respond to. It seems like this is perhaps a difference (bug?) in the compiler's checking for available methods vs. properties in the context of instancetype.

Objective-c categories

I'm learning Objective-C and now I'm stuck with categories. Here is the code
#import <Foundation/Foundation.h>
#interface NSMutableArray (NSMutableArrayCategory)
-(NSMutableArray *)removeLastObject;
#end
#import "NSMutableArrayCategory.h"
#implementation NSMutableArray (NSMutableArrayCategory)
-(NSMutableArray *)removeLastObject
{
return [self removeObjectAtIndex:[self count] - 1];
}
#end
The problem is that I get
Returning void from a function with incompatible result type 'NSMutableArray' *
What is wrong here ?
You probably want to declare removeLastObject as returning void, not NSMutableArray.
#interface NSMutableArray (NSMutableArrayCategory)
- (void)removeLastObject;
#end
Then the implementation will look like this:
- (void)removeLastObject
{
[self removeObjectAtIndex:[self count] - 1];
}
This matches the other mutation methods on NSMutableArray.
The underlying problem is that removeLastObject is already defined and does what you want (so people don't recreate it). Some of the answers here are a little confusing, however, in that they suggest it's ok to use a category to override an existing method (or at least don't explicitly state that you must not do this). You must not do this. It is poorly defined behavior, and in some cases it is completely undefined behavior. You generally should subclass to override a method.
There is an advanced technique called swizzling that will allow you to do this without a subclass, but it is a very specialized and sometimes fragile technique, not for general use.
But in no case should you use a category to override an existing method.
Im not familiar with objective c. but i imagine that removeObjectAtIndex returns void. so I would try
[self removeObjectAtIndex:[self count] -1];
return self;
return self after removing item..

Why do I have to cast the delegate?

I have a class and this class has a delegate protocol. I create an object in the main class using this class and assigned the main class as the delegate. The main class has a property I would like to read inside the created class. Then I do this:
BOOL prop = [self.delegate myProperty];
Xcode complains that "delegate may not respond to myProperty"
If I am sending a message to self.delegate to read myProperty, and generally Xcode never complains when you send a message to an not casted object, why do I have to do that
BOOL prop = [(myMainClass *)self.delegate myProperty];
to make it work?
To be more clear, here is an example of a message sent to an object without having to cast:
[self.myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop){
int Number = [[obj dictionaryOfNames] count];
}];
self.myArray is an array of objects from another class that are dictionaries and have this property dictionaryOfNames. Xcode never asked me to cast this:
int Number = [[(myOtherClass *)obj dictionaryOfNames] count];
as I had to above for myProperty.
Different classes can conform to a protocol. If you declare that you conform to a protocol you just say that you will implement the requiered methods but you can implement it in a UIView, UIViewController, MyOwnClass, MyOtherClass etc.
Thats why a property is normally declared like this
#property (nonatomic, weak) id <MyProtocol> delegate;
So you just say your delegate is an object which conform to the protocol.
You haven't shown enough code to give a completely definitive answer, but in general terms I would expect that the definition of your delegate is not just id, you've probably used NSObject* or something similar.
The compiler is doing "static" analysis of your source code and trying to determine whether or not the object specified by "self.delegate" might implement that method. If the data type is, say, NSObject*, then the compiler looks through that specific class definition to see if your method is present; if it isn't, then you'll get a warning.
If the data type of the message receiver is id, the compiler tends to give up and say "well, it could be anything so I'll assume this will work".
The result of the expression [obj dictionaryOfNames] is probably of type NSDictionary and the compiler can see that that particular class does respond to the count method.
Note, you can also get this problem if you have specified a class name for a property, but the compiler cannot see the entire class definition from this file. For example, if you have
myobject.h:
#class Something;
#interface MyObject
#property (retain) Something *delegate;
#end
myobject.m:
#import "myobject.h"
[self.delegate doItYouFool];
then the compiler can see that the result of the expression 'self.delegate' is of type Something* but it can not see the actual definition of that class and thus can't look through its supported messages. This usually results in a warning about 'forward definitions'. To fix it, you should import "something.h" into the .m file, so that the compiler has full knowledge about the classes it is working with. To just silence the warning, you cast to id
[(id)self.delegate doItYouFool];
You may, of course, also be getting warnings that 'doItYouFool' isn't a known method, again because you haven't included the header file that defines that message.

NSString inheritance

I'm doing an useless thing for my first step in Obj-C
#interface String : NSString
{
int m_isnull;
}
- (id) init;
- (int) isNull;
#end
#implementation String
- (id) init
{
self = [super init];
m_isnull=1;
return self;
}
- (int) isNull
{
return m_isnull;
}
#end
test :
String *a;
a=#"ok";
Works fine, but just 2 little questions
1) When I'm compiling I have this warning
warning: incompatible Objective-C types assigning 'struct NSString *', expected 'struct String *'
I don't know how to avoid it !?
2) a=#"ok" is a fastest way to initialize a string, but when I'm debugging, I don't stop by at my init constructor why ?
#"ok" is actually a NSString like 1 is an integer. That's why you get this compiler warning.
There are also #"" NSString literals.
It is essentially shorthand for
NSString's +stringWithUTF8String
method. Mac Player
already stated that it is used to
distinguish this sort of string
literal from a char * string literal
in C.
Source http://guides.macrumors.com/Objective-C_Tutorial#The_.40_symbol
Normally you would create a Category in Objective-C to extend the NSString Class.
Take a look at the NSString class reference:
It is possible to subclass NSString (and NSMutableString), but doing so requires providing storage facilities for the string (which is not inherited by subclasses) and implementing two primitive methods. The abstract NSString and NSMutableString classes are the public interface of a class cluster consisting mostly of private, concrete classes that create and return a string object appropriate for a given situation. Making your own concrete subclass of this cluster imposes certain requirements (discussed in “Methods to Override”).
If you really want to add an -isNull method to NSString you would probably be better off adding it as a category.
I think you might also want to try writing -(BOOL) isNotBlank instead. Consider what happens if you call -isNull on a nil pointer, is that the return value you would expect?
#"ok" is an NSString object. You're creating an instance of the superclass and trying to assign it to a subclass pointer. Think of subclassing as an "is-a" relationship. In your example, String is an NSString. NSString is not a String. Therefore, you can't assign an NSString object to a String pointer.

Objective-C constants in protocol

In my objective-c project, I have a protocol like this:
#protocol MyProtocol
-(id) get:(NSString *) key;
-(void) set:(NSString *) key withValue:(id) value;
-(NSValue *) getSize;
-(void) setSize:(NSValue *) value;
-(NSValue *) getBounds;
-(void) setBounds:(NSValue *) value;
#end
OBJC_EXPORT const NSString *MYPROTOCOL_SIZE;
OBJC_EXPORT const NSString *MYPROTOCOL_BOUNDS;
And basically, those specific methods (getSize, getBounds, setSize, setBounds) are supposed the value that is supposed to be stored in MYPROTOCOL_SIZE and MYPROTOCOL_BOUNDS, respectively.
However, I cannot find an effective way to set those constant strings, by concatenating the results of other methods, because it gives me the error: initializer element is not constant when I try to set them directly. Is there a way I can guarantee that the objects will always be initialized. (e.g. in a classes load method), without having to manually call code when my program runs?
Well first of all, you should learn the naming convention, for accessors you have - (Type); and - (void)set:(Type)value; whereas in your case you did: - (Type)get; and - (void)set:(Type)value;
I advise you to use #property for your size and bounds accessors too.
Now about the "const" in the NSString variable declaration, it doesn't make sense. Const applies to the type on its left and in case it is at the beginning of the line it applies to the token directly on its right. So what you have is a "const NSString" which doesn't make sense because NSString is already immutable, and sending mutating messages to a const object doesn't issue any warning or errors...
What you actually want is "NSString *const" which states that the pointer to your NSString is constant, you can only assign it at initialization and then it doesn't change...
Now about the protocol... Are you sure you want a protocol in your case ? And not an abstract class that would have your 2 NSString as readonly accessors ?