How to track touches in a consistent way between touchesBegan: and touchesEnded:?
In some maintenance of some multitouch "drawing on screen" code, I met a bug relative to how references to touches instances should be set between touchesBegan:withEvent:, touchesMoved:withEvent: and touchesEnded:withEvent:.
The code uses a NSDictionary instance to store references of touches in touchesBegan:withEvent:, as follow :
for (UITouch *touch in touches]) {
NSValue *touchValue = [NSValue valueWithPointer:touch];
NSString *key = [NSString stringWithFormat:@"%@", touchValue];
[myTouches setValue:squiggle forKey:key];
}
And retrieves them in touchesEnded:withEvent: this way:
for (UITouch *touch in touches) {
NSValue *touchValue = [NSValue valueWithPointer:touch];
NSString *key = [NSString stringWithFormat:@"%@", touchValue];
NSObject *valueFromDictionary = [myTouches valueForKey:key];
[myTouches removeObjectForKey:key];
}
In this last bit of code, valueFromDictionary happens to occasionally be nil, as the key is not known by the dictionary. By occasionnally, I mean about 1% of the time.
Now the context is set, my questions are:
Any idea why this code is buggy? I'm not sure why it doesn't work, especially because of the very low frequency of errors
Some doc from Apple states it's a good idea to use addresses of the UITouch objects as keys
: but it means using NSValue* objects rather than NSString* objects (and consequently using CFDictionaryRef rather than NSDictionary because of the fact UITouch doesn't implement the copying protocol).开发者_如何转开发How come storing a NSString representation of the address rather than the NSValue itself can not be an easy solution to be able to use NSDictionary?Update: Apple has updated the page and removed the example of storing a UITouch via a NSString and just mentions the not-object-at-all CFDictionaryRef solution.
I'm afraid I'm far from C and pointers stuff and need help to understand the reason there's a bug in this code.
You can use hash
of UITouch
property for both ObjC and Swift. It is the same for the same touch across multiple events (Up, Down, Move, etc). It is an Int
but you can convert it to String
When you use stringWithFormat to convert the NSValue instance to a string, I believe you're essentially calling [touchValue description], which I'm not sure guarantees a consistent result. For instance, what if the description included a pointer to the NSValue instance itself, as many objects do? In that case, two different NSValue instances storing the exact same pointer would produce different string keys.
I'd be interested in seeing what happens when you run the following code:
for (UITouch *touch in touches) {
NSValue *touchValue = [NSValue valueWithPointer:touch];
NSString *key = [NSString stringWithFormat:@"%@", touchValue];
NSObject *valueFromDictionary = [myTouches valueForKey:key];
if (valueFromDictionary == nil) {
NSLog(@"%@", [myTouches allKeys]);
NSLog(@"%@", key);
}
[myTouches removeObjectForKey:key];
}
This would tell us exactly what is changing about the key. But if you'd rather just fix the problem you're having, I bet it would work if you used the NSValue object as a key directly, since it does conform to the NSCopying protocol. Essentially, the pointer (UITouch *) is just a number that uniquely identifies where the UITouch instance is stored in memory. NSValue encapsulates this number much the same way NSNumber would, so you could think of it as a numeric key into the array. As long as the touch continues to occupy the same location in memory (as Apple suggests it will), it should work perfectly as a key, and your code would be simpler:
(touchesBegan:withEvent:)
for (UITouch *touch in touches]) {
NSValue *key = [NSValue valueWithPointer:touch];
[myTouches setValue:squiggle forKey:key];
}
(touchesEnded:withEvent:)
for (UITouch *touch in touches) {
NSValue *key = [NSValue valueWithPointer:touch];
NSObject *valueFromDictionary = [myTouches valueForKey:key];
[myTouches removeObjectForKey:key];
}
There's really no reason to confine yourself to string keys, unless you need to use key-value coding or serialize the dictionary to a property list.
This worked for me:
-(NSString * ) keyForTouch:(UITouch *) touch {
return [NSString stringWithFormat:@"%lu", (unsigned long)[touch hash]];
}
//Example method, how to use:
-(UIView *) getViewForTouch:(UITouch*) touch{
NSString * key = [self keyForTouch:touch];
return [self.myDictionary objectForKey:key];
}
Don't use
NSString *key = [NSString stringWithFormat:@"%@", touchValue];
for key. The best way would be to set touch pointer value as a key
id key = touch;
or if you prefer strings, use this case:
NSString *key = [NSString stringWithFormat:@"%x", touch]; // %x prints hex value of touch
精彩评论