How "id" type understands the receiver of method without casting?
Asked Answered
C

2

5

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?

Carley answered 5/10, 2018 at 9:37 Comment(3)
Which swift version you are using?Sidwell
@AnkitJayaswal 4.0Carley
I am trying it with same swift version, but not getting any error.Sidwell
V
5

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.

Vashti answered 5/10, 2018 at 13:40 Comment(1)
Very great explanation! Thanks!Carley
A
6

Compiler doesn't cast or check your id type. It just provides you all possible selectors. You said that this issue connected with importing "-Swift.h" file. In this case check you Swift code, probably you have count function visible for Objective C which returns something else than Int.

Also, you can check the issue in Issue navigator, select it and it will show all count calls visible in Objective C. Check them all, most of them will return NSUInteger, but there should be one that returns something else, for example:

SWIFT_CLASS("_TtC3dev19YourClass")
@interface YourClass : NSObject
- (int32_t)count SWIFT_WARN_UNUSED_RESULT;
@end

Aluminothermy answered 5/10, 2018 at 12:58 Comment(1)
Thanks! As you said, I have a method which returned not a NSUInteger. It is a ObjectiveC class, which is imported in BridgingHeader. As a result, I got an error when import "-Swift.h" file.Carley
V
5

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.

Vashti answered 5/10, 2018 at 13:40 Comment(1)
Very great explanation! Thanks!Carley

© 2022 - 2024 — McMap. All rights reserved.