Xcode LLDB throws Parse error when printing out property - objective-c

I'm facing big issues when trying to print out Objective C properties in many situations when I'm almost certainly sure it SHOULD work.
Consider the following setup:
The view controller's property has strictly set class (Card *), but still, LLDB outputs an parsing error where subproperty cannot be found on object of type id.
Having an object property defined on a view controller (see points 5 & 6):
stop at breakpoint inside the controller code (f.e. in -viewDidAppear: method)
try to print out the property itself with po _card (points 1 & 2)
try to print out its subproperty with po _card.offlineURL (points 3 & 4)
an LLDB parsing error occurs
Printing out via [_card offlineURL] prints out proper object description as LLDB sends a message to Card object with no class check.
Definition of object property on the controller declares non-id class, though (point 5).
I'm expecting LLDB to print out the property object's subproperty description, NSString containing URL string in this case, but this annoying LLDB error occurs instead.
This is just a single example from many. Sometimes it affects direct property printout, numbers printing etc. These issues are way more frequent since integration of Swift began, being worse with every new version of Xcode since 6.2, including the latest 7.2.
This happens in my Objective C project in many situations, though sometimes it works fine in different cases.
Do you know about any work-arounds or fixes for this issue? I've already filed a report on Apple Bug Reporter, but this will certainly take time for Apple to even notice.

The most likely problem given the info in your question is that we don't actually have debug information for _card.
The lldb command:
(lldb) image lookup -t Card
will tell you whether lldb had debug information for Card available to it. If this doesn't find anything, then maybe some part of your project isn't getting built with debug info. If the command does find some correct definition of Card, then it must be that the debug info for the _card ivar is not getting hooked up to this type correctly.
If there is a definition of Card, then the workaround:
(lldb) po ((Card *) _card).offLineURL
is available.
For future reference, there are two other bits of behavior that are probably complicating your attempt to figure out what is going on here:
1) (w.r.t. picture 3) The Xcode IDE uses its built-in indexer for auto completion in the debug window as well as the Source Code editor. But the debugger runs off debug information, since we need to be able to debug things that aren't built in Xcode. So the fact that auto-completion can find a name in an expression doesn't tell you anything about what lldb will do.
2) (w.r.t. picture 2) po force-casts the expression you are trying to "po" to an ObjC object (i.e. "id") and then calls its description method. The description method of an ObjC object that doesn't override description prints the type name and the address. So lldb didn't need to know the type of the _card ivar to get the output you saw. We only start to need types when the expression is more complex and involves accesses to ivars or properties of an ObjC object in the expression.
Note also, by default if you do:
(lldb) print _card
lldb will evaluate the expression "_card", find it resolves to a pointer of at least type id, then follow that pointer's isa pointer into the ObjC runtime to figure out what the dynamic type is. So it is likely to print Card * in this case.
But the expression parser hasn't yet been taught to resolve the dynamic type of sub-expressions within the expression parser in mid-parse. That would actually be quite a trick... So if it doesn't know the full type of _card, then:
(lldb) print _card.offlineURL
isn't going to work, since id does indeed not have a property of that name.
You can work around this by doing:
(lldb) print _card
$0 = (Card *) 0x12345678
Then:
(lldb) print $0.offlineURL
since the result variable captures the full dynamic type information, that will also be available in subsequent expressions.

Related

Any message sent to id compiling, basis?

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!

objective c class type checking

I use MagicalRecord (which is a category for NSManagedObject) to fetch my models. Then I display them using a generic table view controller which display my data based on the Class name.
The problem is that when I want to fetch these data I use something like this:
Class type = NSClassFromString(modelName);
NSArray *model = [type MR_findAll];
This compile and run perfectly, excepts that xcode displays a nasty error: "No know class method for MR_findALL".
Can I do something like a "Class cast" to NSManagedObject so the error will disappear? I want to keep it dynamic, I don't want to define a condition for all my models.
The problem is not static type checking. Class, like id, disables static type checking.
The problem is something else: in Objective-C, in order to send a message using the [... ...] syntax, the compiler must have first seen the declaration of such a method somewhere. The reason that the compiler needs a declaration is that the compiler must compile a message dispatch to either a objc_msgSend or a objc_msgSend_stret call behind the scenes. Which one depends on the return type of the method. Therefore, it must know the types of the method (from a declaration) in order to compile a call to it.
Note, I said "somewhere", because that "somewhere" can be completely unrelated to where you use it. You could declare a dummy protocol that contains a declaration of the same message, that is never used anywhere, and it will serve the purpose of having a declaration. Or you can declare a category that contains it. Whatever. This might seem rather incredible to you. But if you take #HotLicks's solution with the protocol, and remove the cast, it will still not have the warning, because whether the protocol is used does not matter -- it's the declaration that is relevant here.
Define a protocol that includes MR_findAll. Cast your object to that protocol before making the call.
[(id<FindAllProtocol>)type MR_findAll]
Try to cast your type variable to id, which is a dynamic typing.

What would setting a getter do?

First, some context: while answering questions on SO, I came across a post wherein the author had been trying to set a getter with syntax similar to [self.propertyGetter:newValue];. For some reason, this compiles, and I thought to myself, "this would constitute a call to nil, wouldn't it?". So, my question is, why in the heck does this 'work'? (to be perfectly clear, the poster was complaining that this had no effect, so by 'work', I mean compile).
The code you quoted is [self.propertyGetter:newValue]. Here's how the Objective-C compiler parses this.
The first thing after the left bracket has to be the receiver of the message. In that code, the receiver is the value of self.propertyGetter. The compiler transforms self.propertyGetter into [self propertyGetter]. So we can rewrite the code you quoted as [[self propertyGetter]:newValue].
Now the compiler needs to figure out the selector of the message. You usually see a keyword selector like setStatusBarHidden:animated:, which is a series of keywords followed by colons. But it turns out a keyword can be zero-length. So a colon by itself is a valid keyword selector. So the compiler sees it like this: [[self propertyGetter] :newValue]. (Using a zero-length keyword is almost always very bad style.)
You can declare an instance method using that selector. For example:
- (void):(NSObject *)newValue;
If [self propertyGetter] returns an object of a class that has that method, the code will compile and run.
If the compiler has seen any class with a method named like that, and [self propertyGetter] returns type id, then the code will compile (because id is a wildcard type and the compiler will not complain if you try to send it any known message selector). However, the app will crash at runtime if [self propertyGetter] doesn't actually understand the : message.
This compiles because all objective-C objects are dynamic entities by default. It would compile, but would crash at runtime.
Source.

Why do I need to cast before a method of an item of a NSArray can be called?

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.

Should I set up a protocol for Objective C array objects to avoid a compiler warning?

I have a class, let's call it ABC, with an NSArray *objects property, and the objects in the NSArray can be one of two different classes, X and Y. Both X and Y have a path property, but the compiler doesn't know this.
Therefore, even though ABC will always find the path property on my array objects, I get compiler warnings on my code -- "No 'path' method found". App runs fine but I'm getting annoyed by the warnings. How can I fix my code (with protocols?) so that the compiler stops complaining?
Thank you!
You need to import some header that declares a path method. When it's compiling your code, the compiler looks at the list of methods this file knows about and it sees that there is no path method. You just need to tell it that the method exists and its signature is whatever and the compiler will be satisfied. No protocol is necessary.
Do both X and Y derive from the same super class that contains "path"? If "path" is conceptually the same value AND X and Y are related by this value, then it should either be in a super class or protocol.
Additionally you can use NSKeyValueCoding protocol to directly access values by string via - (id)valueForKey:(NSString *)key. You do lose any type safety using this method, however.
valueForKey:#"path"