Accessing Class in a Breakpoint Conditional - ios

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.

Related

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

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.

Method mysteriously exits execution in the middle to resume the rest of the program?

So i've got this code that tries to find an unused upload name, using the user's email and a number at its end. It does this with a list of uploaded objects we've already collected, the user's email.(upload_name), and the
current number that might be open (it is incremented when a match is found).
The list is not sorted, and it's pretty tricky to sort for a few reasons, so I'm having the method read through the list again if it reaches the end and the upload_number has changed.
- (NSString*)findUnusedUploadNameWithPreviousUploads:(NSMutableArray*)objects withBaseUploadName:(NSString*)upload_name {
previous_upload_number = upload_number;
for (NSString *key in objects) {
// the component of the object name before the first / is the upload name.
NSLog([key componentsSeparatedByString:#"/"][1]);
if ([[key componentsSeparatedByString:#"/"][1]
isEqualToString:([NSString stringWithFormat:#"%#_%ld", S3KeyUploadName1, upload_number])]) {
upload_number++;
NSLog([NSString stringWithFormat:#"upload name: %#_%ld", S3KeyUploadName1, upload_number]);
}
NSLog(#"pang");
}
NSLog(#"ping");
if (previous_upload_number == upload_number) {
return [NSString stringWithFormat:#"%#%ld", upload_name, upload_number];
}
return [self findUnusedUploadNameWithPreviousUploads:objects withBaseUploadName:upload_name];
}
The problem is, the program never reads the "ping". it just leaves the method after the first for loop is done.
Edit: No the NSlogs are fine, you can do simple string OR StringWithFormat.
Edit: Don't mind the unnecessary use of recursion, I did this because the simple way was having the same problem and i wanted to see if a different (albeit unnecessarily recursive) way would share that problem. It does.
Edit: I set a breakpoint in the for loop, and I set a break point at the "ping". It does not reach the ping. It completes the for loop and the ditches the whole thing.
Edit: Please try to help me figure out why it's exiting the the method immediately after the for loop. I'm aware this is stylistically meh, and I promise I'll make it shiny and spotless when it works. =]
Edit: to be clear, the method DOES exit. it does so early I know this because the rest of the program following this method (which is not threaded such that it wouldn't have to wait for it) runs AFTER this for loop, consistently.
There are a couple of possible explanations for the described behavior:
The method never exits. For some reason it blocks or performs infinitely somewhere in the loop. Make sure this is not the case by setting a breakpoint after the place where the message is called (i.e. the place to where it should return).
The method, or some method it calls, throws an exception. While seldom and unsupported in productive Cocoa code it could be some misbehaving 3rd party library or just a simple programmer error, where Cocoa actually does throw. Make sure this does not happen by setting an exception breakpoint in Xcode.
Undefined behavior. This is, sadly, part of official C and heavily exploited by modern compilers. It basically means: Anything can happen, if there's something in your code, where the standard says that the behavior is not defined. One example would be accessing a deallocated object. Another fine reason of undefined behavior can be threads accessing common data in an unsynchronized way.
Other than exceptions there's no explanation for a method to "exit early". If you still can't find the reason I suggest you invest some time to learn the debugger. Single stepping, like Aris suggested, might be a way to find out what's going on.

isKindOfClass returning nil inside PromiseKit?

My colleague and I are building an async data layer heavily based on PromiseKit v1.5.3. We've noticed in certain circumstances, when returning a promise (call it X) from a block passed to then, the next then block actually passes Promse X as the argument to the block, rather than what the former promise actually resolved to. Chaining thenable promises is a pretty important feature for most Promise implementations, so we were pretty surprised.
After some pretty lengthy debug sessions, we've found the problem to be within PromiseKit. During the resolution process, an IsPromise call fails to identify the object as a promise, which is really a simple call to
[result isKindOfClass:[PMKPromise class]]
This call returns nil, and an incorrect branch is executed. Here's the source
The baffling thing about this, is that I don't see any reason for this to happen. I don't think this is a bug in PromiseKit since their code appears to be sound. I've confirmed the underlying object is indeed a PMKPromise since it responds to promise methods such as value and fulfilled. I've even pushed it through the correct branch using the debugger and it executes correctly from there!
Here's an interesting log from some tests while halted at the given line.
Given that isKindOfClass is returning nil, it sounds like that object isn't responding to the message... but it's certainly an NSObject. I'm curious if this may be a weird compiler setting or something. I currently have my optimizations set to none if that's relevant. Has anyone ever seen anything like this or know what's going on? What should I check?
isKindOfClass returns unexpected results when you manage to have the same class twice in your project. So you may have an object of class PMKPromise, but it is a different class (with exactly the same class name, exactly the same behaviour, just a second class). Maybe that's what happens. Obviously setting a breakpoint and checking what the object is would help.

ARC and sending messages to objects without specifying the class at compile time

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.

Message passing - compiler won't check whether method is existing?

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.

Resources