In Xcode, if I write -
id a;
As I start writing message to a, [a ..., Xcode gives a long list of autocomplete suggestions. On what basis these suggestions are coming (apparently all possible method names)?
I can compile any method call, like
[a viewDidLoad];
I know id can take any message.
But
[a asdfgh]; //asdfgh random string
doesn't compile.
On what basis these suggestions are coming (apparently all possible method names)?
These suggestions are somewhat intelligent: as far as I can tell, they count in the class and the adopted protocols of an object. Since id is generic, and as such, any existent message can potentially be sent to it (which it may or may not respond to), Xcode suggests whatever it finds in any class or protocol.
but [a asdfgh]; doesn't compile
Because the compiler does provide some level of protection. If a selector is not declared anywhere at all, it assumes that selector to be nonexistent, thus it's impossible to send it to any object, hence it complains.
Here you see what is id
typedef struct objc_object {
Class isa;
} *id;
It is not void * at all, but rather a pointer to a struct that contains a single member, pointing at the class definition. Therefore it can take form of any data type at runtime. Dynamically Typed.
At compile time it checks for correct method name, if it exists?
[a asdfgh]; produces error as there is not method/selector with name asdfgh.
While [a viewDidLoad]; compiles fine, however at runtime it may crash!
Related
I have the following class (picked out of a Apple example):
#interface LeTemperatureAlarmService : NSObject <CBPeripheralDelegate>
#property (readonly) CBPeripheral *servicePeripheral;
#end
and in a different class's method I use the following code:
NSMutableArray *connectedServices = [[NSMutableArray alloc] init];
... // Adding some myService objects to connectedServices
for (id service in connectedServices) {
if ([service servicePeripheral] == parameter) {
...
}
}
Now the thing which drives me crazy is the part where I can send servicePeripheral to service.
As far as I understand how id works, it's basically a pointer which can be literally point to any object. My NSMutableArray is an untyped array which can hold any type of object in it, even mixed, so I don't have to be careful what I put in.
So how can it be that I can use [service servicePeripheral] even though I never specified the type of service? And how does Xcode know that and even suggest that method in code completion?
Objective-C works different in the respect of method invocation than say C++. The compiler doesn't have to know, because it's not done at compile time, methods are invoked at runtime. Specifically, methods are send to objects, instead of called on them. You send the servicePeripheral method to the object and the runtime takes care of calling the right function. This also makes it possible for you to send methods to nil without crashing (it will return false/0/NULL)
Types in Objective-C are mostly used for compile time safety, which you lose with your approach. The compiler can't warn you that the types don't match, for instance, your array can very well contain NSString instances or anything, and the compiler can't help you there since you tell it that you expect id (aka anything, really) and servicePeripheral is a perfectly valid and known method. You can add type safety by checking the class of the object at runtime using isKindOfClass:, for example like this:
for (id service in connectedServices) {
if ([service isKindOfClass:[LeTemperatureAlarmService class]] && [service servicePeripheral] == parameter) {
...
}
}
So how can it be that I can use [service servicePeripheral] even though I never specified the type of service?
It is exactly because you declared service as an id. This tells the compiler to turn off all static type-checking and permit you to send any message to service. That is what id is: it is the universal recipient (any message can be sent to it, any object value can be assigned to it) and the universal donor (it can be assigned to any object variable).
And you are perfectly right to be wary of this, since it can cause you to crash later. It is not (as your question title has it) "type safety". It is type unsafety! The compiler will happily let you say (for example) [service count] (because service is typed as an id), but you will crash later when the app runs, because this object does not respond to the count message.
So don't do that! Use explicit types so the compiler can help you in advance.
In ObjC we can use protocols to restrict an id behavior, so we can declare something like
-(void)aMethod:(id<aProtocol>)aVar which works very well until we provide a value or a non-id variable as aVar, but this gets completely broken since we can pass a generic id variable delcared without protocols specifiers... Is this normal? Is there any workaround? Am I missing something?
Just use id less, and declare variables and parameters using the correct types, where possible. That is to say: don't pass ids around. If you are implementing a collections class (for example), then id's often useful.
My approach is to specify types, and introduce that type as local as possible in the source. So I omit id and add the type, and when (for instance) I take a reference from a collection, I create a variable:
MONType<MONProtocol>* thing = [array objectAtIndex:idx];
// now thing is correctly typed. use thing.
Similarly, if I have an id parameter, I declare a new variable:
- (IBAction)someAction:(id)sender
{
NSButton * button = sender;
// now use button, not sender
Protocols are extremely useful. Very often, better/cleaner than subclassing.
You're missing the understanding that types in Objective-C are determined at runtime, not compile time. Just because you say that an object will be of type id<aProtocol> does not mean that at runtime it is guaranteed to be so.
The idea of specifying something as id<aProtocol> is to aid you as a developer and people using your code. It aids you as a developer because the compiler will warn (or error under ARC) if you attempt to call a method on something that the compiler can determine it doesn't think exists on instances of its supposed type (excluding forwarding which could mean an instance responds to something the compiler cannot determine). It aids people using your code as it tells them the contract that they should adhere to when interfacing with your code.
So, in your question you say that:
but this gets completely broken if we pass a generic id variable delcared without protocols specifiers
Well, the compiler would warn and tell you that you're trying to pass something that does not conform to that protocol, except for the case of passing id. That's why you generally should try to type things more precisely than just id.
If you have a method defined like so:
- (void)aMethod:(id<aProtocol>)aVar
Then aVar could be of type SomeSubclass where that is defined like so:
#interface SomeSubclass : NSObject <aProtocol>
And you could then use aMethod like this:
SomeSubclass *obj = [SomeSubclass new];
[other aMethod:obj];
I (FINALLY) found out that using Objective-C++ is the way to go. Let's suppose I want to be able to pass NSString or NSNumber (instead of a too much generic id and instead of using protocols which become useless passing id values): well, I can create a C++ class having two distinct constructors, one for each ObjC class, so passing id values cannot be done anymore (almost directly). For example, let's take a look at
class NSStringOrNSNumber{
public:
NSStringOrNSNumber(NSString *);
NSStringOrNSNumber(NSNumber *);
};
The great advantage is that methods/functions taking a NSStringOrNSNumber parameter can get NSString/NSNumber values DIRECTLY, since the constructor acts as an implicit cast. In other words, if we have
void aFunction(NSStringOrNSNumber param);
the following calls are perfectly valid:
aFunction(#"Hello!");
aFunction(#25);
The only (little) downside is that we need the class to implement a function if we want to get back the value passed to the constructor.
Using a C++ class constructor to get something like id<NSCoding> is still better the using id<NSCoding> directly: in fact, if we do the following
#class classOne, classTwo;
class NSCodingClass{
private:
NSCodingClass(classOne *);
NSCodingClass(classTwo *);
public:
NSCodingClass(id<NSCoding>);
}
we won't be able to pass a generic id as a parameter (since it would be ambiguous: the compiler cannot know which constructor to call among the two private ones)
When you retrieve the ID of a selector with #selector(), is the selector value different depending on the types of the arguments?
Here's what I mean. I have a method that takes an object reference and a selector, then calls it with a parameter:
-(void)CallLater:(NSObject*) Obj Sel: (SEL)Sel
{
//Some stuff, then:
[Obj performSelector: Sel withObject: SomeOtherObject];
}
I'm using this method with a selector to a function that takes a typed object reference, not an id:
-(void)MyMethod: (MyObject*) a
{
}
[self CallLater: self Sel:#selector(MyMethod:)];
It seems to work, but my senses are tingling. In a statically typed language like C# this would be a foul, an upcast - CallLater is expecting a selector for a function that takes an id and I'm giving it a function that takes a MyObject.
On the other hand, the compiler does not complain, and both id and concrete object references seems to be mere pointers deep down, trivially castable to one another. Then again, there are many fouls that Objective C compiler does not complain about.
So the real question is - is it safe? Language lawyers welcome.
It's safe; objects are objects. A selector parameter for an NSObject * is exactly the same as a selector parameter for a MyObject *.
If you want MyMethod to verify that it's being called with an object of a particular type, it should do a NSParameterAssert on it:
NSParameterAssert([obj isKindOfClass: [MyObject class]]);
Personally, I rarely do this check. It's enough that the actual object acts like the type I want it to be, and if it doesn't I'll get a runtime error (usually unrecognized selector). You'll get a compiler warning in the simple cases, and it's worth paying attention to this warning (and silencing it with an id cast when necessary).
I'm a bit confused here about your use of id in your question, so I want to make sure you understand this: An NSObject * is exactly as much an id as a MyObject * is. id is a generic instance pointer class, whereas NSObject * is a NSObject instance (or a subclass of NSObject). You can have objects that don't descend from NSObject. But you're unlikely to ever have to know this.
Other notes, re: convention:
Selectors (both the name and parameters) start with lowercase letters, so CallLater:Sel: should be callLater:sel:.
Variable and parameter names start with lowercase letters; Obj above should be obj.
Class names do start with an uppercase letter. :)
Clang adds a keyword instancetype that, as far as I can see, replaces id as a return type in -alloc and init.
Is there a benefit to using instancetype instead of id?
Yes, there are benefits to using instancetype in all cases where it applies. I'll explain in more detail, but let me start with this bold statement: Use instancetype whenever it's appropriate, which is whenever a class returns an instance of that same class.
In fact, here's what Apple now says on the subject:
In your code, replace occurrences of id as a return value with instancetype where appropriate. This is typically the case for init methods and class factory methods. Even though the compiler automatically converts methods that begin with “alloc,” “init,” or “new” and have a return type of id to return instancetype, it doesn’t convert other methods. Objective-C convention is to write instancetype explicitly for all methods.
Emphasis mine. Source: Adopting Modern Objective-C
With that out of the way, let's move on and explain why it's a good idea.
First, some definitions:
#interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
#end
For a class factory, you should always use instancetype. The compiler does not automatically convert id to instancetype. That id is a generic object. But if you make it an instancetype the compiler knows what type of object the method returns.
This is not an academic problem. For instance, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData] will generate an error on Mac OS X (only) Multiple methods named 'writeData:' found with mismatched result, parameter type or attributes. The reason is that both NSFileHandle and NSURLHandle provide a writeData:. Since [NSFileHandle fileHandleWithStandardOutput] returns an id, the compiler is not certain what class writeData: is being called on.
You need to work around this, using either:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
or:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
Of course, the better solution is to declare fileHandleWithStandardOutput as returning an instancetype. Then the cast or assignment isn't necessary.
(Note that on iOS, this example won't produce an error as only NSFileHandle provides a writeData: there. Other examples exist, such as length, which returns a CGFloat from UILayoutSupport but a NSUInteger from NSString.)
Note: Since I wrote this, the macOS headers have been modified to return a NSFileHandle instead of an id.
For initializers, it's more complicated. When you type this:
- (id)initWithBar:(NSInteger)bar
…the compiler will pretend you typed this instead:
- (instancetype)initWithBar:(NSInteger)bar
This was necessary for ARC. This is described in Clang Language Extensions Related result types. This is why people will tell you it isn't necessary to use instancetype, though I contend you should. The rest of this answer deals with this.
There's three advantages:
Explicit. Your code is doing what it says, rather than something else.
Pattern. You're building good habits for times it does matter, which do exist.
Consistency. You've established some consistency to your code, which makes it more readable.
Explicit
It's true that there's no technical benefit to returning instancetype from an init. But this is because the compiler automatically converts the id to instancetype. You are relying on this quirk; while you're writing that the init returns an id, the compiler is interpreting it as if it returns an instancetype.
These are equivalent to the compiler:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
These are not equivalent to your eyes. At best, you will learn to ignore the difference and skim over it. This is not something you should learn to ignore.
Pattern
While there's no difference with init and other methods, there is a difference as soon as you define a class factory.
These two are not equivalent:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
You want the second form. If you are used to typing instancetype as the return type of a constructor, you'll get it right every time.
Consistency
Finally, imagine if you put it all together: you want an init function and also a class factory.
If you use id for init, you end up with code like this:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
But if you use instancetype, you get this:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
It's more consistent and more readable. They return the same thing, and now that's obvious.
Conclusion
Unless you're intentionally writing code for old compilers, you should use instancetype when appropriate.
You should hesitate before writing a message that returns id. Ask yourself: Is this returning an instance of this class? If so, it's an instancetype.
There are certainly cases where you need to return id, but you'll probably use instancetype much more frequently.
There definitely is a benefit. When you use 'id', you get essentially no type checking at all. With instancetype, the compiler and IDE know what type of thing is being returned, and can check your code better and autocomplete better.
Only use it where it makes sense of course (i.e. a method that is returning an instance of that class); id is still useful.
Above answers are more than enough to explain this question. I would just like to add an example for the readers to understand it in terms of coding.
ClassA
#interface ClassA : NSObject
- (id)methodA;
- (instancetype)methodB;
#end
Class B
#interface ClassB : NSObject
- (id)methodX;
#end
TestViewController.m
#import "ClassA.h"
#import "ClassB.h"
- (void)viewDidLoad {
[[[[ClassA alloc] init] methodA] methodX]; //This will NOT generate a compiler warning or error because the return type for methodA is id. Eventually this will generate exception at runtime
[[[[ClassA alloc] init] methodB] methodX]; //This will generate a compiler error saying "No visible #interface ClassA declares selector methodX" because the methodB returns instanceType i.e. the type of the receiver
}
You also can get detail at The Designated Initializer
**
INSTANCETYPE
**
This keyword can only be used for return type, that it matches with return type of receiver. init method always declared to return instancetype.
Why not make the return type Party for party instance, for example?
That would cause a problem if the Party class was ever subclassed. The subclass would inherit all of the methods from Party, including initializer and its return type. If an instance of the subclass was sent this initializer message, that would be return? Not a pointer to a Party instance, but a pointer to an instance of subclass. You might think that is No problem, I will override the initializer in the subclass to change the return type. But in Objective-C, you cannot have two methods with the same selector and different return types (or arguments). By specifying that an initialization method return "an instance of the receiving object," you would never have to worry what happens in this situation.
**
ID
**
Before the instancetype has been introduced in Objective-C, initializers return id (eye-dee). This type is defined as "a pointer to any object". (id is a lot like void * in C.) As of this writing, XCode class templates still use id as the return type of initializers added in boilerplate code.
Unlike instancetype, id can be used as more than just a return type. You can declare variables or method parameters of type id when you are unsure what type of object the variable will end up pointing to.
You can use id when using fast enumeration to iterate over an array of multiple or unknow types of objects. Note that because id is undefined as "a pointer to any object," you do not include an * when declaring a variable or object parameter of this type.
The special type instancetype indicates that the return type from the init method will be the same class as the type of object it is initializing (that is, the receiver of the init message). This is an aid for the compiler so that it can check your program and flag potential
type mismatches—it determines the class of the returned object based on context; that is, if you’re sending the init message to a newly alloc’ed Fraction object, the compiler will infer that the value returned from that init method (whose return type has been declared as type instancetype) will be a Fraction object. In the past the return type from an initialization method was declared as type id. This new type makes more sense when you consider subclassing, as the inherited initialization methods cannot explicitly define the type of object they will return.
Initializing Objects, Stephen G. Kochan, Programming in Objective-C, 6th Edition
I am fairly new to Objective-C. Currently porting my own library from C#/Java to objective C.
I now run into a very strange problem for me.
I have a NSArray with several Note objects. I want to transpose on of these notes:
//Note.h
- (Note *) transpose: (int) semitones;
//Main
NSArray *notes = [get it from somewhere];
Note *transposedNote = [[notes objectAtIndex:0]transpose:1]; //Doesn't compile
Note *transposedNote = [(Note*)[notes objectAtIndex:0]transpose:1]//Does compile
Is this happening because there is already a transpose method available in the general libraries?
I thought due to the dynamic nature of objective-C at runtime it would be checked which class objectAtIndex returns and then sends the message to it?
It is my understanding that there is no runtime type checking for the assignment operator in Objective C. Since an array can contain a mixture of types, there is no way for the system to know what objectAtIndex returns.
How about
Note *transposedNote = [notes objectAtIndex:0]; // first line
[transposedNote transpose:1]; // second line
? Notice in the reference that objectAtIndex: returns an id, you will see it is pretty obvious:
In the code above, because id can fit into any object, the first line doesn't need to cast it into Note. In the second line I'm just calling a method on a Note so the compiler is happy.
In your code you are calling methods on the returned id object, so the compiler doesn't understand what you are trying to do. Just assign it to a Note reference and it will be fine.
Yes, the error is because there's already a transpose: method in AppKit. And you're also right that it normally doesn't cause an error when you have two unrelated classes implementing methods with the same name. The reason you get an error is because the two methods either return incompatible types or take incompatible types as arguments. In your particular case, you're seeing both problems:
-[NSResponder transpose:] takes an id and returns void
-[Note transpose:] takes an int and returns an id
These are totally incompatible types, and the compiler does need to know the types involved even if it doesn't know what exact method is going to be called.
It does compile unless you have -Werror set to treat warnings as errors.
It might produce a warning if the compiler doesn't already know about the selector or if the selector is declared in more than one class. In the former case, it should be necessary only to import the interface containing the selector. In the latter case, you'll need to do the cast to suppress the error.