In Objective-C's Wiki page, there is a section named Messages. It says when compiling, Objective-C doesn't care whether an object has a given method, because anyone can send a message to another. This is dynamic binding.
in C++, obj->method(argument); if no method, wrong.
in Objective-C, [obj method:argument]; if no method, can be fine.
But in my daily coding, with XCode, if compiler cannot find a public method of an object, it always prompt error even before build. like this,
no visible #interface for 'ClassName' declares the selector 'methodName'
I am a little confused about this 'contradiction'. Please forgive me if the question is silly. thanks in advance.
I think the compiler is just protecting you from yourself. In the case you note, the compiler knows that the method you're calling doesn't exist so it reports it as an error.
However, if you tell the compiler that you don't care or don't give it enough information, then it's perfectly valid.
Example:
NSString* var = #"Hello";
[(id)var thisDoesNotExist];
id var2 = #"Hello";
[var2 neitherDoesThis:var];
These (should) both compile.
Chances are you use ARC. For compiling ARC-enabled code, the compiler needs to know what type of objects a method expects as arguments and returns as its return value in order to be able to emit the necessary calls to memory management methods. So, when you are compiling ARC code, the compiler will check if a method signature exists.
If you, however, use manual reference counting (MRC), then the compiler doesn't need this information for this purpose (some of it is still necessary for generating code comformant to the ABI), and it doesn't issue an error if it can't find a certain message/method/selector. It does, however, emit a warning for safety.
Related
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.
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?
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?
I noticed that error occurs when I import "-Swift.h" file. How it depends?
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
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.
Update
Okay, first of all, thank you all for the huge amount of activity. It seems that I did not phrase my question too well, since many of the answers got (rightfully) stuck on the id input parameter, and following poor design patterns, but it was merely an example. I'll add some context to my question:
Suppose that there are multiple different implementations for doSomethingWithParameter:, requiring a specific instance as input parameter
My class in the example will only ever get called with an instance of SpecificClass as input parameter
With these assertions, here is my assumption: Given, that you know the type of the parameter, there is no benefit in type checking and casting, just for the sake of extra safety.
Original post
Suppose I have a general method in my protocol declaration, which takes an id input parameter:
#protocol MyProtocol <NSObject>
- (void)doSomethingWithParameter:(id)inputParameter;
#end
In a class, which conforms to MyProtocol, I usually prefer making the type of inputParameter explicit like so:
- (void)doSomethingWithParameter:(SpecificClass *)inputParameter
{
/... do something with param
}
Occasionally I received critique for choosing this solution, as opposed to the following:
- (void)doSomethingWithParameter:(id)inputParameter
{
if ([inputParameter isKindOfClass:[SpecificClass class]]) {
SpecificClass *myInstance = (SpecificClass *)inputParameter;
/... do something with param
}
}
I really prefer the first version, since it clearly states the parameter my instance is expecting. It is more concise, and clear. I generally don't think I can gain much from type checking/casting.
My question: from a coding standard standpoint, which one is the better solution? Does the first one have any disadvantages?
Update
From the update to your question, it seems that you are trying to achieve some variation of a functionality provided by the generics in modern languages.
Since Objective-C does not support this pattern, you can either sacrifice type safety, or rethink your design decisions.
If you go the first way, you should make it really clear by other means (naming, documentation) what types are you expecting. Then it might be reasonable to assume that your method will only be called with proper params.
But I would still add NSParameterAssert to simplify future debugging.
Original Answer
If you are using the first approach, you have a mismatch between declaration and definition of the method. Due to dynamic nature of obj-c (method signature does not include types of parameters), compiler does not complain about it.
However, when calling the method, only declaration is visible, so any information about the type of parameters is derived from that - all the type checking (yes, here compiler does it) is performed based on declaration.
In conclusion, to avoid confusing bugs and misuse of API, you should definitely use the second approach. Or change declaration together with definition.
Edit
Also, I can think of third solution, that somewhat merges convenience of the first approach with type safety of the second one:
- (void)doSomethingWithParameter:(SpecificClass *)inputParameter
{
NSParameterAssert([inputParameter isKindOfClass:[SpecificClass class]]);
// do something
}
First of all, when you use id for a parameter type that means either that type may vary or you may invoke method with ambiguous parameter. For both cases, second one is preferred as it checks type and prevents unwanted crash.
If you prefer the type of inputParameter explicit then simply define it in the protocol, like
#protocol MyProtocol <NSObject>
- (void)doSomethingWithParameter:(SpecificClass *)inputParameter;
#end
and for this forward declaration you may have to import module/class, like
#import "SpecificClass.h" // import class
OR
#class SpecificClass; // import module
What you do is perfectly fine. If your method is called with a parameter that is an instance of the wrong class, that is a bug in the caller. In Objective-C, you don't work around bugs, you make them crash your code, and then you fix the bug (that is why nobody handles exceptions, exceptions are bugs in your code and when they crash your code, the cause of the exception needs to be fixed).
This is much more common when you pass blocks, for example a block testing array elements, where you know exactly what type of array to expect.
I am trying to convert an iOS project into ARC.
I am using the compiler flag for some of the files.
But one of the files contains a variable declared within a method like the following:
aClass **obj;
With ARC turned off, it gives an error:
"pointer to non-const type without explicit ownership"
I could silence the warning by doing this:
aClass *__strong* obj;
Which I believe is not a good practice as far as ownership is concerned.
But the error didn't exist in non-ARC environment.
My question is simply as follows:
How would I change from non-ARC to ARC setup the declaration of the object without having to use *__strong*?
i.e., how could I declare (or make changes to declaring) aClass **obj under ARC without have to use *__strong*, which I am sure I have read somewhere it is not a good practice to do but I forgot where I read it.
And:
Why didn't it give error under non-ARC environment.
TL;DR: You probably don't want a pointer to a pointer unless you can avoid it. It's pretty poor design to do so under a system where memory is managed for you. This answer explains more: Pointer to a pointer in objective-c?.
More Details
Under non-ARC, the system leaves retain/release up to you so it doesn't matter who owns a pointer. You, the programmer, owns it. In ARC land, the system needs to know when to retain or release, and it can't always infer which class/object has ownership over a particular object. Other classes may need the reference but the class that declared it is done with the object already. Basically, the __strong tells the declaring class that it should be in charge of managing the pointer. It 'overrides' the ownership of the pointer in a way. So that's a way to get around it. The best way to get around it would be to refactor the code to not use explicitly managed memory, but how you've fixed it will work if that's not possible/too hard.
I have a method that I want to debug:
-(void)doAThingWithObject:(BaseDataObject *)dataObject //called VERY often
And I have an Xcode breakpoint inside this method which I want to only break on a certain subclass of BaseDataObject, so I add a breakpoint w/conditional to check for that class:
[dataObject isKindOfClass:[SubClassOfBaseDataObject class]]
However, doing so results in a parse error!
Stopped due to an error evaluating condition of breakpoint 11.1: "[dataObject isKindOfClass:[SubClassOfBaseDataObject class]]"
Couldn't parse conditional expression:
error: no known method '+class'; cast the message send to the method's return type
error: 1 errors parsing expression
I have made sure to import all classes in the file, but the debugger does not know what class I'm referencing in the conditional.
However, creating a temp variable of said Class inside the method before the breakpoint:
Class subClassCheck = [SubClassOfBaseDataObject class];
And updating the breakpoint conditional to reference the temp variable:
[dataObject isKindOfClass:subClassCheck]
Throws no errors.
I'm a bit of a novice when it comes to breakpoint conditionals, can someone explain why my first approach doesn't work?
One complication with debugging code that is based on big frameworks like Cocoa is that it is not practical for the compiler to emit or the debugger to consume every type and function in the whole closure of frameworks you include. So the compiler uses some heuristics to reduce the amount of debug information generated. It will emit type information only for types that you actually use, and function/ObjC method information where the method is defined (as opposed to declared in a header file.) There's another little subtlety that lldb will read the type information for methods out of the ObjC runtime, though this information is not complete, since it is meant for the runtime not for debuggers... So we sometimes seem to know things about ObjC methods that violate the previous rule.
Another important thing to note is that the calling conventions for functions that return something larger than a pointer (like NSMakeRect, etc) are such that if the debugger calls a function thinking it returns a pointer and it actually returns a bigger structure, that act will cause stack corruption in your program. If you are lucky you will crash right away when you continue, but if you are unlucky it will just change some data value and cause you to spend hours trying to chase down some funny behavior that is actually caused by the debugger. So the debugger will refuse to call functions whose return type it can't determine.
Anyway the error you got is because the debugger couldn't find debug information for the "+class" method on your object. That is not altogether surprising, since "class" is a method on NSObject and not your class. I'm not sure why we couldn't find it in the runtime, maybe because it is a class method? That's worth a bug. We obviously did get the type of isKindOfClass: from the runtime, or your workaround would have also failed.
In this case, since you actually know the return type of the class method, you can work around the debugger's lack of knowledge by explicitly casting it in your breakpoint expression. Casting a function return in the debugger's expression parser serves two purposes, one is the regular C language function, and the other is telling the debugger the return type of a function it wouldn't otherwise be able to figure out. A sort of short-hand prototype only for the return type.
So something like:
[dataObject isKindOfClass: (Class) [SubClassOfBaseDataObject class]]
should work without having to alter your code.
Note also, the breakpoint conditions are run using the same mechanism as the "expr" or "print" command. So the easiest way to experiment with breakpoint commands is to set an unconditional breakpoint, hit it, then go to the lldb console and play around with "print" till you get something that works.
I'm trying to understand where ARC is getting the method signature information to do its job.
In the following code, I send a message to the parent of this object without specifying its class.
If I don't typecast the parent ivar, the compiler issues a warning.
If I typecast it to id, then the program works and no warnings are issued. The same is true
if I use performSelector:withObject:
If the method on the parent is different to userSelected: then the only thing that works
is performSelector (while issuing a warning).
As I understand it, ARC is getting the method signature from the object the call to self.parent is made. Is this correct? Can you avoid telling ARC what class an object is if the method signature exists in the object from which the message is being sent?
- (void)userSelected:(id)sender
{
if ([self.parent respondsToSelector:#selector(userSelected:)]) {
//1: This fails with error (no visible interface).
[self.parent userSelected:self];
//2: This line works without warnings.
[(id)self.parent userSelected:self];
//3: This line also works
[self.parent performSelector:#selector(userSelected:)
withObject:self];
}
Wil Shipley is correct in his deleted answer when saying that this is not ARC related.
The warning you are getting is about the static type of the receiver (self.parent) and the compiler trying to help you to prevent sending messages to an object that doesn't respond to this selector. In other words: self.parent's class does not contain a declaration of userSelected:.
But the compiler does know a method named userSelected: (in some other class or category) because it lets you send this message to an object without static type information. It's a little like the C language lets you use a void pointer for any type of pointer.
So, again, all of that is not ARC related and would not change when switching to MRC.
Edit:
Yes, when sending messages to id the compiler considers any visible #interface and #protocol to find the selector's declaration. "Visible" in this case means any imported header, be it custom, framework or prefix. The method declaration is needed mainly to get type information of the parameters.
Note that ARC behavior is only affected in very rare scenarios (when the declaration includes information about ownership semantics, like objc_method_family and similar).
If the compiler finds conflicting declarations it emits an error. When trying to compile ...
[(id)nil type];
... you'll get ...
> error: multiple methods named 'type' found with mismatched result, parameter type or attributes
... plus a couple of differing declarations in UIKit, Foundation and other frameworks.