PDA

View Full Version : NSRangePointer trouble!




Byzanti
Jun 7, 2012, 04:28 PM
Hi

I'm trying to write a method that returns a NSRangePointer, but I'm getting garbage values back. When the method is part of the same object that's calling it, it works fine. When the method is part of another object it doesn't. Can anyone help?

The method:
MyObject

-(BOOL)test:(NSRangePointer)range {
range->length = 6;
range->location = 1;
return YES;
}


Getting the range pointer:
ADifferentObject

NSRange charRange;
[myObjectInstance test:&charRange];
NSLog(@"Test range: %@",NSStringFromRange(charRange));


The output is something like: Test range: {140734799801952, 4300215136}

Thanks!



robbieduncan
Jun 7, 2012, 04:36 PM
Seems to be a problem with the basics of C. You are calling by value. You need to call by reference.

chown33
Jun 7, 2012, 05:13 PM
What is the value of myObjectInstance when it fails? Is it nil? If so, what effect would that have? That is, what happens when you send a message to nil when it has no method to do what you ask?

Byzanti
Jun 7, 2012, 05:17 PM
Thanks! I've read up on reference v value, and I'm still confused. What is more confusing is that the above code - the test method - seems to be working fine. The problem is apparently with the way I am calling the method just before, which does exactly the same thing (with some other stuff thrown in). In this method, when I try to do range->length = 6; it gives me an EXC_BAD_ACCESS?

As I see it,
Range is declared: NSRange range;
Pointer to memory location is sent along with the method: &range
I set a value at the memory location (this bit must be wrong?).

I've been at this for about 5 hours now and can't figure it out. What should I be doing instead?

Much appreciated...

Byzanti
Jun 7, 2012, 05:43 PM
chown, thanks. I did realise it was a nil value a few hours ago. I should have written the first post better! I just can't figure out how to set it. Actually, the reason I am using -> instead of using the passed through range in the instance directly is because it was the only way I could at least get it to partially work.

Edit: eg, in the instance I've also tried variations on:

NSRange someRange = NSMakeRange(1, 8);
range = &someRange;

range = &((NSRange){1,8});

chown33
Jun 7, 2012, 05:51 PM
... What should I be doing instead?

Posting complete testable code.

If something crashes, post complete testable code.

If something works as expected, post the complete testable code.

By "complete" I mean in a form that someone else can actually compile, not just isolated fragments.

Posting isolated fragments is useless when we have to write our own surrounding code. Why? Because what we might write may not be what you wrote. And the problem lies with what you wrote, not with what someone else might write. So you have to post exactly what you wrote that's causing the problem. Not just the part you think is causing the problem; the whole thing. Why? Because if you can't see the problem, then your view on where the problem lies is probably wrong. And if you're wrong about where the problem lies, that means you're also wrong about where the problem doesn't lie. But you haven't posted any code for the part where the problem doesn't lie. Which means you haven't posted the code that could contain the actual problem. That's not meant to be harsh, it's just plain simple logic with plain logical consequences.

If the process of posting complete compilable code means you have to make a small isolated test class that clearly illustrates the problem, then that's what you should do. If in the process you discover your error, then you've learned something useful: that isolating a problem can lead to discovering its cause.
http://en.wikipedia.org/wiki/Rubber_duck_debugging


I just can't figure out how to set it.
Can't figure out how to set what?

Post your code. You're asking us to diagnose problems in code we haven't seen.

Byzanti
Jun 7, 2012, 06:21 PM
chown, I do appreciate that, but I was lead to believe the issue - at least according to robbieduncan - was with my understanding of the principle of pointers. Hence my follow up posts asking questions about the principle, rather than containing further code.

If however there were no issues with the way I was setting and passing the range, and that the problem lied outside my initial code, it would be more helpful to say so. I would then have an avenue to explore.

I do appreciate your help, but there is art in not only asking a question well (which I am still learning!), but also answering it well. My code at the start with the exception of a wrapping method and an [alloc] [init] IS full code that demonstrates setting a rangepointer (please excuse the terminology).

However, I did take your advice, and started a new project with the code in my initial post. It seems to work fine, so the issue must lie elsewhere.

Thanks for your help.

chown33
Jun 7, 2012, 06:50 PM
My code at the start with the exception of a wrapping method and an [alloc] [init] IS full code that demonstrates setting a rangepointer (please excuse the terminology).

Saying that it's full code without an alloc init is exactly my point: it's not complete code unless you show complete code.

You didn't show an alloc or init. If a variable is nil, then either you didn't alloc and assign the result to the variable, or you did something after the alloc that assigned nil. Either way, it's incomplete as you posted it. Saying it's complete except for showing the code that assigns a value to the variable is missing the point: assigning a value to the variable is important and relevant.

You didn't show the declaration of the variable, either. It could be a variable that's local to a function, or it could be an ivar, or even a global variable. That means the context for understanding the posted code is incomplete. Without complete code, all anyone else can do is guess what the real code is. And guessing at missing code is one of the worst possible ways to debug it.

Frankly, this is why I pointed you at the Rubber-Duck Debugging article. You should read it carefully, then think about what is required to explain a problem in detail to a rubber duck. Really, you should go through the exercise in actual fact, rather than just mentally. Why? Because doing it is not the same as thinking about it. Seriously, the actual practice is precisely the point: doing something only mentally makes it too easy to skip over some required part. When you sit down and do it, out loud, in front of a real inanimate object, you get the actual experience. And the actual experience is the point.

Would you need to show the rubber duck the code that assigned the variable a value? Since the duck hasn't seen your code before, yes, you would. Well, that's exactly the same thing we'd need to see. If the duck isn't sitting on your shoulder seeing everything you typed, and it's not looking at your screen, then that's the position we're in. You have to show all your code, not just the parts you may think are relevant.

Byzanti
Jun 7, 2012, 07:30 PM
Point accepted. Creating the code in my initial post, I thought I had produced code which only showed the problem. I should definitely have stuck it in a new project to make sure it reproduced the error. I apologise. As I said, still learning to write good questions. Thanks for the link.

Still, what would be the harm in saying "you've got the principle fine - the issue isn't with the code you've posted" if that is indeed the case? That would be almost as good as somebody identifying the actual issue. People asking questions aren't always looking for someone to hand something on a platter to them, they're just looking to get unstuck. If they think the issue is with A, but actually there's no problem there at all, that is very useful to know in itself.

But, have accepted your comments, and will post complete code if I have questions in the future.

Thanks,

B

chown33
Jun 7, 2012, 08:25 PM
Still, what would be the harm in saying "you've got the principle fine - the issue isn't with the code you've posted" if that is indeed the case? That would be almost as good as somebody identifying the actual issue. People asking questions aren't always looking for someone to hand something on a platter to them, they're just looking to get unstuck. If they think the issue is with A, but actually there's no problem there at all, that is very useful to know in itself.

If I had the full answer at the time, I probably would have posted it. But I didn't, so I didn't.

Here's what I did before my first post:

I looked at your code. It looked fine on first inspection. For a method so small, my next step is usually to paste it into a file and compile it, then run it. Unfortunately, there wasn't enough code to do that without having to:
A. Create the class header.
B. Create the class implementation (including relevant init method).
C. Write a main() that instantiated the class (with relevant alloc and init being called)
D. Add code to the class that worked in the same way you described as working (i.e. when the method is invoked on self).

Steps A-D were more than I wanted to do at the time, because I had other real work to do (finished and working now, so now I have more time to reply).

So I looked at the posted code again, and thought about possible failure scenarios. Well, the output values (140734799801952, 4300215136) look like stack garbage to me, which suggests an uninitialized variable. Sure enough, you have an uninitialized variable at the declaration of charRange. So whatever was happening between that declaration and the NSLog was clearly having no effect. One of simplest and most common ways for some method to have no effect is when its called on nil. Since you didn't show any code assigning a value to myObjectInstance, I arrived at my posted question:
What is the value of myObjectInstance when it fails?

The subsequent leading questions were there to hint at the simple common case when calling a method has no effect.


Later on, when you still hadn't posted code showing the assignment of something useful to myObjectInstance, it seemed necessary to explain why posting complete and compilable code is actually necessary for debugging something. In short, I wanted you to provide the code for the steps A-D I outlined above.

It all comes down to a very simple thing: I wanted to compile and run the code myself, so I could see it work (or not work) myself. I could have written my own code that does that, but what does that prove? My code for doing those things isn't your code for doing those things, and the problem was clearly in your code. More specifically, your unposted code.

How was I able to deduce that the problem was in your unposted code? Because you said your posted code worked in a specific case, although you didn't ever post complete code for it. That is, you never actually showed the code for "When the method is part of the same object that's calling it, it works fine." So, there was one case where it worked and you got the results you expected. Clearly, something was working, even though no one else could see your complete code for it. So even in the working case, I'd have to write code, guessing that what I would write would be similar to what you wrote. Sorry, didn't have time for that, either.

By the time I did have time to write longer explanations, you still hadn't posted any additional code that was testable or compilable, though you had said you found there was a nil pointer. Yet once again, you didn't post code for it, so once again we went for a ride on the "Post your code" carousel.

And that's pretty much how we got here.

Byzanti
Jun 8, 2012, 05:29 AM
Many thanks for your considered replies, especially given my obstinance.

-----

For anyone else who has stumbled across this thread looking for an answer to NSRangePointer problems, here is most of an answer:

You can set the range to a range pointer by using:
(rangepointer)->length/location (value). Eg:

rangePointer->length = 5;
rangePointer->location = 1;

//or

rangePointer->length = someRange.length;
rangePointer->location = someRange.location;


There is probably a better way to do this on one line, but I can't figure out what this is. Eg, rangePointer = &someRange doesn't work.

However, in your method you must remember to check if the pointer exists before assigning it a value. If you don't, and subsequently pass NULL to the rangePointer, it will crash.

The following code works:


-(void)someMethod {
NSRange range;
[self test:&range];
NSLog(@"%@",NSStringFromRange(range));

//If you forget to include the if statement below, the following will crash
[self test:NULL];
}

-(BOOL)test:(NSRangePointer)range {
if (range) {
range->length = 5;
range->location = 1;
}
return YES;
}

gnasher729
Jun 8, 2012, 05:40 AM
The following code works:


-(void)someMethod {
NSRange range;
[self test:&range];
NSLog(@"%@",NSStringFromRange(range));

//If you forget to include the if statement below, the following will crash
[self test:NULL];
}

-(BOOL)test:(NSRangePointer)range {
if (range) {
range->length = 5;
range->location = 1;
}
return YES;
}


That code doesn't actually do anything because it isn't called. If you try to call it in the exact some way as you called the method test before (by sending a message to a nil object) then nothing will happen. So it will only "work" if you changed some other code that again you are not posting.

The "if" statement should _not_ be there unless you documented that it is allowed to call test: with a NULL argument and documented what happened. The call [self test:NULL] is a bug in the caller that would need to be fixed. (There are plenty of Cocoa methods with an argument of type NSError** where it is _documented_ that they can be called by passing NULL. If it isn't documented, it is an error).

Byzanti
Jun 8, 2012, 07:40 AM
That code doesn't actually do anything because it isn't called. If you try to call it in the exact some way as you called the method test before (by sending a message to a nil object) then nothing will happen. So it will only "work" if you changed some other code that again you are not posting.

Quite right. Anyone wanting to use the code would need to copy and paste it into a blank project, and add -(void)awakeFromNib { [self someMethod]; } or something similar to get it to work.


The "if" statement should _not_ be there unless you documented that it is allowed to call test: with a NULL argument and documented what happened. The call [self test:NULL] is a bug in the caller that would need to be fixed. (There are plenty of Cocoa methods with an argument of type NSError** where it is _documented_ that they can be called by passing NULL. If it isn't documented, it is an error).

Ah. I included the option to pass NULL, as that's how the Cocoa methods which pass a NSRangePointer back work (that is, they say you can pass NULL). I'm afraid I don't know how to use NSError.

Clearly the code isn't perfect, but it was the best I could do. Given that I could find nothing else available on the internet on this topic, I imagine it would still be of use to anyone in the same predicament as me. If there are errors in the way I have failed to use NSError, perhaps you might correct the code for the benefit of those stumbling across this thread?

Full project code (object instantiated in interface builder)
h

#import <Foundation/Foundation.h>

@interface BZRange : NSObject

//NULL can be passed
-(BOOL)test:(NSRangePointer)range;

@end


m

#import "BZRange.h"

@implementation BZRange

-(void)awakeFromNib {
NSRange range;
[self test:&range];
NSLog(@"%@",NSStringFromRange(range));
}

-(BOOL)test:(NSRangePointer)range {
if (range) {
range->length = 5;
range->location = 1;
}
return YES;
}

@end