How “id” type understands the receiver of method without casting?

  • A+

After merging master to my working branch I got compiler error on the line, which wasn't be changed. The error looks like

id test; [test count]; 

Multiple methods named 'count' found with mismatched result.

At first it looks clear, because compiler doesn't know which concrete type the "test" variable is. But I don't understand why it worked before.

  1. If I create a new file this line works, assuming that is a NSArray's method. Why compiler doesn't show error in this case?

  2. While showing error message, there is several possible receivers of count method are shown. (NSArray, NSDictionary, NSSet) Does it search all classes that can receive that message and show error if there are multiple?

  3. I noticed that error occurs when I import "-Swift.h" file. How it depends?


Objective-C doesn't need to know the type of the receiver. At run-time, all objects are just id, and everything is dynamically dispatched. So any message can be sent to any object, no matter its type. (At run-time, objects are free to decide what to do with messages they don't understand. The most common thing to do is raise an exception and crash, but there are many kinds of objects that can handle arbitrary messages that don't map directly to method calls.)

There is a couple of technical details, however, that complicate this.

The ABI (application binary interface) defines different mechanisms for returning certain primitive types. As long as the value is "a word-sized integer," then it doesn't matter (this includes things like NSInteger and all pointers, which means by extension all objects). But on some processors, floats are returned in different registers than integers, and structs (like CGRect) might be returned in a variety of ways depending on their size. In order to write the necessary assembly language, the compiler has to know what kind of return value it will be.

ARC has added additional wrinkles that require that the compiler know a more about the type of the parameters (specifically whether they're objects or primitives), and whether there are any memory-management attributes that have to be considered.

The compiler doesn't really care what "real" type test is, as long as it can figure out the types and attributes of -count. So when dealing with an id value, it looks through every known selector it can see (i.e. every one defined in an included header or the current .m). It's fine if there are many of them on different classes, as long as they all agree. But if it can't find the selector at all, or if some of the interfaces disagree, then it can't compile the line of code.

As lobstah notes, you likely have a type somewhere in your Swift code that has an @objc method called count() or an @objc property named count that returns something other than Int (which maps to NSInteger, and so match the usual signature of -count). You'll need to fix that method, or you'll need to hide it from ObjC (for example, by adding @nonobjc).

Or much better: get rid of the id, and use its actual type. id is generally a bad idea in Cocoa, and is especially a bad idea if you're calling methods on it, since the compiler can't check that the object will respond and you may crash.


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: