PDA

View Full Version : Help With Bug: -[NSConcreteValue class]: message sent to deallocated instance




ArtOfWarfare
Jul 9, 2012, 07:36 PM
Here's the error message I'm getting:
*** -[NSConcreteValue class]: message sent to deallocated instance 0x100cef990

I have a view which accepts multiple touches. Here's the behavior I want:
- When one touch is present, its movements should tell its delegate to perform a translation.
- When two touches are present, the movements of the first one should tell the delegate to perform a translation, while movements of either should tell its delegate to perform a rotation (based on the angle between the two points.)
- Whenever two touches were present, but the first one goes away, the second one should become the new first one.
- Other touches should be ignored.

Here's my code:
- (void)touchesBeganWithEvent:(NSEvent*)event
{
NSMutableSet* unprocessedTouches = [[event touchesMatchingPhase:NSTouchPhaseAny inView:self] mutableCopy];
if (!touchIdentifier[0])
{
NSLog(@"First touch began.");
NSTouch* firstTouch = (NSTouch*)[unprocessedTouches anyObject];
touchIdentifier[0] = firstTouch.identity;
currentPosition = firstTouch.normalizedPosition;
[unprocessedTouches removeObject:firstTouch];
}

else
{
NSLog(@"First touch already exists.");
}

if (!touchIdentifier[1] && [unprocessedTouches count])
{
NSLog(@"Second touch began.");
NSTouch* secondTouch = (NSTouch*)[unprocessedTouches anyObject];
touchIdentifier[1] = secondTouch.identity;
otherPosition = secondTouch.normalizedPosition;
currentRotation = atan((secondTouch.normalizedPosition.y - currentPosition.y)/(secondTouch.normalizedPosition.x - currentPosition.x));
}

else
{
NSLog(@"Either there wasn't another touch, or the second touch already existed.");
}
[unprocessedTouches release];
}

- (void)touchesMovedWithEvent:(NSEvent*)event
{
NSMutableSet* unprocessedTouches = [[event touchesMatchingPhase:NSTouchPhaseAny inView:self] mutableCopy];
if (touchIdentifier[0])
{
[unprocessedTouches enumerateObjectsUsingBlock:^(NSTouch* obj, BOOL* stop)
{
if ([obj.identity isEqual: touchIdentifier[0]])
{
NSTouch* firstTouch = obj;
[delegate translation:CGPointMake((firstTouch.normalizedPosition.x - currentPosition.x), (firstTouch.normalizedPosition.y - currentPosition.y))];
currentPosition = firstTouch.normalizedPosition;
[unprocessedTouches removeObject:firstTouch];
*stop = YES;
}
}];
}

if (touchIdentifier[1] && [unprocessedTouches count])
{
[unprocessedTouches enumerateObjectsUsingBlock:^(NSTouch* obj, BOOL* stop)
{
if ([obj.identity isEqual: touchIdentifier[1]])
{
NSTouch* secondTouch = obj;
otherPosition = secondTouch.normalizedPosition;
float newRotation = atan((secondTouch.normalizedPosition.y - currentPosition.y)/(secondTouch.normalizedPosition.x - currentPosition.x));
[delegate rotation:newRotation-currentRotation];
currentRotation = newRotation;
[unprocessedTouches removeObject:secondTouch];
*stop = YES;
}
}];
}
[unprocessedTouches release];
}

- (void)touchesEndedWithEvent:(NSEvent*)event
{
NSMutableSet* unprocessedTouches = [[event touchesMatchingPhase:NSTouchPhaseEnded | NSTouchPhaseCancelled inView:self] mutableCopy];
if (touchIdentifier[1])
{
[unprocessedTouches enumerateObjectsUsingBlock:^(NSTouch* obj, BOOL* stop)
{
if ([obj.identity isEqual: touchIdentifier[1]])
{
NSLog(@"Second touch ended.");
touchIdentifier[1] = NULL;
[unprocessedTouches removeObject:obj];
*stop = YES;
}
}];
}

[unprocessedTouches enumerateObjectsUsingBlock:^(NSTouch* obj, BOOL* stop)
{
if (touchIdentifier[0] && [obj.identity isEqual: touchIdentifier[0]])
{
if (touchIdentifier[1])
{
NSLog(@"First touch ended. Second touch is new first touch.");
currentPosition = otherPosition;
touchIdentifier[0] = touchIdentifier[1];
touchIdentifier[1] = NULL;
}

else
{
NSLog(@"First touch ended.");
touchIdentifier[0] = NULL;
}
[unprocessedTouches removeObject:obj];
*stop = YES;
}
}];
[unprocessedTouches release];
}

- (void)touchesCancelledWithEvent:(NSEvent *)event
{
[self touchesEndedWithEvent:event];
}

I have noticed that just before the crash occurs, I will have two fingers down but it will act as if only one is down. It appears that it properly logs that both fingers are there.

Other times it'll crash if I have zero fingers present, but it'll act like I already have one down.



chown33
Jul 10, 2012, 01:18 PM
Show the type declaration for touchIdentifier.

AFAICT, you're not taking ownership of any object returned by the identifier property of any NSTouch object. As a result, one or more of these objects is being dealloc'ed out from under you. When you attempt to use these objects later, they're long dead and gone.

I suspect a static analyzer pass might complain about the non-ownership, or maybe there's even a warning about it you're overlooking.


I see no reason for the touch identifiers to be an array. Every reference you've shown has a constant array index: 0 or 1. Since there are no array indexes that are variables, the array-ness serves no purpose. You could name two separate variables, say touchID1 and touchID2, and achieve the same purpose. Better still, separate names can be properties with the retain attribute, something that's impossible with a C-style array.

Personally, I'd call them something other than 1 and 2, maybe translateID and pivotID, for the translation and rotational roles they play.


I'm unsure if a block enumerator is permitted to modify the collection being iterated. A fast enumerator isn't, but I see no such restriction documented for block enumerators.

ArtOfWarfare
Jul 10, 2012, 11:23 PM
I declared them in my interface header:

id <NSObject, NSCopying> touchIdentifier[3];

It seems to me that I'm making sure they exist before using them by having lines like:

if (touchIdentifier[0])

If they're deallocated, they'd be NULL, wouldn't they?

Analysis shows no issues with my code. I suppose I'll try just using several separate variables and see how that works. I just used numbers because I thought, hey, maybe later I'll want to rewrite my code to handle 4 or 5 fingers at once, and maybe it'll be easier to just use a C-array right now.

I'll post back about how that works later.

(Regarding the issue with changing the collection during iteration, it does make run time errors get logged sometimes, but it doesn't crash. I modified my code so it doesn't cause that error, but the change is so minor I didn't update my OP.)

chown33
Jul 11, 2012, 12:38 AM
If they're deallocated, they'd be NULL, wouldn't they?

No. What you have left is a dangling pointer (http://en.wikipedia.org/wiki/Dangling_pointer).

How would anything other than your code be able to set values in your variables to NULL?

It's exactly the same as if you had a scalar variable instead of an array, and you failed to retain an autoreleased object. I assume you're familiar with the memory management rules (https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html). If not, you need to learn them. If you've seen the rules before, you need to review them.

gnasher729
Jul 11, 2012, 05:29 AM
If they're deallocated, they'd be NULL, wouldn't they?

That's a new feature of ARC - if you have a "weak" reference, then on 10.7 and above the variable will be set to nil (I always use NULL for C++ and C pointers vs. nil for Objective-C objects); this will not work on 10.6.

With manual reference counting, no.

ArtOfWarfare
Jul 11, 2012, 11:13 PM
Of course, you're all correct. I don't know why I thought that memory management wasn't relevant. I now have it set to copy the identifiers each time they're set to something new, and to release when they're set to nil.

Edit:

A second issue with my code appears to be this:

[event touchesMatchingPhase:NSTouchPhaseAny inView:self]

I use it in both my touchesMovedWithEvent: and touchesBeganWithEvent: methods.

To fix it, all I had to do was change it to NSTouchPhaseMoved and NSTouchPhaseBegan, respectively. Without that, it ended up assigning a touch that was ending as touchIdentifier[0] or [1]... I'm not quite sure how, but I suspect by me both lifting one finger from the trackpad within the same fraction of a second as I placed a second one down.