Fix unsafe unretained references in ASControlNode.

Fix regression from the ARC conversion.  Change the raw, unretained
pointers in ASControlNode to weak references.  Use NSMapTable instead of
the pointer-as-NSValue system.
This commit is contained in:
Nadine Salter
2014-09-30 14:24:44 -07:00
parent af6c11ade7
commit 20631a632f
2 changed files with 59 additions and 92 deletions

View File

@@ -9,8 +9,8 @@
#import <AsyncDisplayKit/ASDisplayNode.h> #import <AsyncDisplayKit/ASDisplayNode.h>
/** /**
@abstract Kinds of events possible for control nodes. @abstract Kinds of events possible for control nodes.
@discussion These events are identical to their UIControl counterparts. @discussion These events are identical to their UIControl counterparts.
*/ */
enum _ASControlNodeEvent enum _ASControlNodeEvent
{ {
@@ -27,74 +27,74 @@ enum _ASControlNodeEvent
typedef NSUInteger ASControlNodeEvent; typedef NSUInteger ASControlNodeEvent;
/** /**
@abstract ASControlNode is the base class for control nodes (such as buttons), or nodes that track touches to invoke targets with action messages. @abstract ASControlNode is the base class for control nodes (such as buttons), or nodes that track touches to invoke targets with action messages.
@discussion ASControlNode cannot be used directly. It instead defines the common interface and behavior structure for all its subclasses. Subclasses should import "ASControlNode+Subclasses.h" for information on methods intended to be overriden. @discussion ASControlNode cannot be used directly. It instead defines the common interface and behavior structure for all its subclasses. Subclasses should import "ASControlNode+Subclasses.h" for information on methods intended to be overriden.
*/ */
@interface ASControlNode : ASDisplayNode @interface ASControlNode : ASDisplayNode
#pragma mark - Control State #pragma mark - Control State
/** /**
@abstract Indicates whether or not the receiver is enabled. @abstract Indicates whether or not the receiver is enabled.
@discussion Specify YES to make the control enabled; otherwise, specify NO to make it disabled. The default value is YES. If the enabled state is NO, the control ignores touch events and subclasses may draw differently. @discussion Specify YES to make the control enabled; otherwise, specify NO to make it disabled. The default value is YES. If the enabled state is NO, the control ignores touch events and subclasses may draw differently.
*/ */
@property (nonatomic, assign, getter=isEnabled) BOOL enabled; @property (nonatomic, assign, getter=isEnabled) BOOL enabled;
/** /**
@abstract Indicates whether or not the receiver is highlighted. @abstract Indicates whether or not the receiver is highlighted.
@discussion This is set automatically when the there is a touch inside the control and removed on exit or touch up. This is different from touchInside in that it includes an area around the control, rather than just for touches inside the control. @discussion This is set automatically when the there is a touch inside the control and removed on exit or touch up. This is different from touchInside in that it includes an area around the control, rather than just for touches inside the control.
*/ */
@property (nonatomic, readonly, assign, getter=isHighlighted) BOOL highlighted; @property (nonatomic, readonly, assign, getter=isHighlighted) BOOL highlighted;
#pragma mark - Tracking Touches #pragma mark - Tracking Touches
/** /**
@abstract Indicates whether or not the receiver is currently tracking touches related to an event. @abstract Indicates whether or not the receiver is currently tracking touches related to an event.
@discussion YES if the receiver is tracking touches; NO otherwise. @discussion YES if the receiver is tracking touches; NO otherwise.
*/ */
@property (nonatomic, readonly, assign, getter=isTracking) BOOL tracking; @property (nonatomic, readonly, assign, getter=isTracking) BOOL tracking;
/** /**
@abstract Indicates whether or not a touch is inside the bounds of the receiver. @abstract Indicates whether or not a touch is inside the bounds of the receiver.
@discussion YES if a touch is inside the receiver's bounds; NO otherwise. @discussion YES if a touch is inside the receiver's bounds; NO otherwise.
*/ */
@property (nonatomic, readonly, assign, getter=isTouchInside) BOOL touchInside; @property (nonatomic, readonly, assign, getter=isTouchInside) BOOL touchInside;
#pragma mark - Action Messages #pragma mark - Action Messages
/** /**
@abstract Adds a target-action pair for a particular event (or events). @abstract Adds a target-action pair for a particular event (or events).
@param target The object to which the action message is sent. If this is nil, the responder chain is searched for an object willing to respond to the action message. target is not retained. @param target The object to which the action message is sent. If this is nil, the responder chain is searched for an object willing to respond to the action message. target is not retained.
@param action A selector identifying an action message. May optionally include the sender and the event as parameters, in that order. May not be NULL. @param action A selector identifying an action message. May optionally include the sender and the event as parameters, in that order. May not be NULL.
@param controlEvents A bitmask specifying the control events for which the action message is sent. May not be 0. See "Control Events" for bitmask constants. @param controlEvents A bitmask specifying the control events for which the action message is sent. May not be 0. See "Control Events" for bitmask constants.
@discussion You may call this method multiple times, and you may specify multiple target-action pairs for a particular event. @discussion You may call this method multiple times, and you may specify multiple target-action pairs for a particular event. Targets are held weakly.
*/ */
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEvents; - (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEvents;
/** /**
@abstract Returns the actions that are associated with a target and a particular control event. @abstract Returns the actions that are associated with a target and a particular control event.
@param target The target object. May not be nil. @param target The target object. May not be nil.
@param controlEvent A single constant of type ASControlNodeEvent that specifies a particular user action on the control; for a list of these constants, see "Control Events". May not be 0 or ASControlNodeEventAllEvents. @param controlEvent A single constant of type ASControlNodeEvent that specifies a particular user action on the control; for a list of these constants, see "Control Events". May not be 0 or ASControlNodeEventAllEvents.
@result An array of selector names as NSString objects, or nil if there are no action selectors associated with controlEvent. @result An array of selector names as NSString objects, or nil if there are no action selectors associated with controlEvent.
*/ */
- (NSArray *)actionsForTarget:(id)target forControlEvent:(ASControlNodeEvent)controlEvent; - (NSArray *)actionsForTarget:(id)target forControlEvent:(ASControlNodeEvent)controlEvent;
/** /**
@abstract Returns all target objects associated with the receiver. @abstract Returns all target objects associated with the receiver.
@result A set of all targets for the receiver. The set may include NSNull to indicate at least one nil target (meaning, the responder chain is searched for a target.) @result A set of all targets for the receiver. The set may include NSNull to indicate at least one nil target (meaning, the responder chain is searched for a target.)
*/ */
- (NSSet *)allTargets; - (NSSet *)allTargets;
/** /**
@abstract Removes a target-action pair for a particular event. @abstract Removes a target-action pair for a particular event.
@param target The target object. Pass nil to remove all targets paired with action and the specified control events. @param target The target object. Pass nil to remove all targets paired with action and the specified control events.
@param action A selector identifying an action message. Pass NULL to remove all action messages paired with target. @param action A selector identifying an action message. Pass NULL to remove all action messages paired with target.
@param controlEvents A bitmask specifying the control events associated with target and action. See "Control Events" for bitmask constants. May not be 0. @param controlEvents A bitmask specifying the control events associated with target and action. See "Control Events" for bitmask constants. May not be 0.
*/ */
- (void)removeTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEvents; - (void)removeTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEvents;
/** /**
@abstract Sends the actions for the control events for a particular event. @abstract Sends the actions for the control events for a particular event.
@param controlEvents A bitmask specifying the control events for which to send actions. See "Control Events" for bitmask constants. May not be 0. @param controlEvents A bitmask specifying the control events for which to send actions. See "Control Events" for bitmask constants. May not be 0.
@param event The event which triggered these control actions. May be nil. @param event The event which triggered these control actions. May be nil.
*/ */
- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event; - (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event;

View File

@@ -16,7 +16,6 @@
// Initial capacities for dispatch tables. // Initial capacities for dispatch tables.
#define kASControlNodeEventDispatchTableInitialCapacity 4 #define kASControlNodeEventDispatchTableInitialCapacity 4
#define kASControlNodeTargetDispatchTableInitialCapacity 2
#define kASControlNodeActionDispatchTableInitialCapacity 4 #define kASControlNodeActionDispatchTableInitialCapacity 4
@interface ASControlNode () @interface ASControlNode ()
@@ -43,7 +42,7 @@
... ...
} }
*/ */
__block NSMutableDictionary *_controlEventDispatchTable; NSMutableDictionary *_controlEventDispatchTable;
} }
// Read-write overrides. // Read-write overrides.
@@ -51,37 +50,21 @@
@property (nonatomic, readwrite, assign, getter=isTracking) BOOL tracking; @property (nonatomic, readwrite, assign, getter=isTracking) BOOL tracking;
@property (nonatomic, readwrite, assign, getter=isTouchInside) BOOL touchInside; @property (nonatomic, readwrite, assign, getter=isTouchInside) BOOL touchInside;
/** //! @abstract Indicates whether the receiver is interested in receiving touches.
@abstract Indicates whether the receiver is interested in receiving touches.
*/
- (BOOL)_isInterestedInTouches; - (BOOL)_isInterestedInTouches;
/** /**
@abstract Returns a key to be used in _controlEventDispatchTable that identifies the control event. @abstract Returns a key to be used in _controlEventDispatchTable that identifies the control event.
@param controlEvent A control event. @param controlEvent A control event.
@result A key for use in _controlEventDispatchTable. @result A key for use in _controlEventDispatchTable.
*/ */
id<NSCopying> _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent); id<NSCopying> _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent);
/** /**
@abstract Returns a key to be used inside the dictionaries within _controlEventDispatchTable that identifies the target. @abstract Enumerates the ASControlNode events included mask, invoking the block for each event.
@param target A target. May safely be nil. @param mask An ASControlNodeEvent mask.
@result A key for use in in the dictionaries within _controlEventDispatchTable. @param block The block to be invoked for each ASControlNodeEvent included in mask.
*/ @param anEvent An even that is included in mask.
id<NSCopying> _ASControlNodeTargetKeyForTarget(id target);
/**
@abstract Returns the target for invocation from a given targetKey.
@param targetKey A target key created with _ASControlNodeTargetKeyForTarget(). May not be nil.
@result The target, or nil if no target was originally used.
*/
id _ASControlNodeTargetForTargetKey(id<NSCopying> targetKey);
/**
@abstract Enumerates the ASControlNode events included mask, invoking the block for each event.
@param mask An ASControlNodeEvent mask.
@param block The block to be invoked for each ASControlNodeEvent included in mask.
@param anEvent An even that is included in mask.
*/ */
void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent)); void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent));
@@ -222,10 +205,6 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v
return YES; return YES;
} }
#pragma mark - Control Attributes
#pragma mark - Tracking Touches
#pragma mark - Action Messages #pragma mark - Action Messages
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask - (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask
{ {
@@ -238,23 +217,22 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v
{ {
// Do we already have an event table for this control event? // Do we already have an event table for this control event?
id<NSCopying> eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent); id<NSCopying> eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent);
NSMutableDictionary *eventDispatchTable = [_controlEventDispatchTable objectForKey:eventKey]; NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:eventKey];
// Create it if necessary. // Create it if necessary.
if (!eventDispatchTable) if (!eventDispatchTable)
{ {
// Create the dispatch table for this event. // Create the dispatch table for this event.
eventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeTargetDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries eventDispatchTable = [NSMapTable weakToStrongObjectsMapTable];
[_controlEventDispatchTable setObject:eventDispatchTable forKey:eventKey]; [_controlEventDispatchTable setObject:eventDispatchTable forKey:eventKey];
} }
// Have we seen this target before for this event? // Have we seen this target before for this event?
id<NSCopying> targetKey = _ASControlNodeTargetKeyForTarget(target); NSMutableArray *targetActions = [eventDispatchTable objectForKey:target];
NSMutableArray *targetActions = [eventDispatchTable objectForKey:targetKey];
if (!targetActions) if (!targetActions)
{ {
// Nope. Create an actions array for it. // Nope. Create an actions array for it.
targetActions = [[NSMutableArray alloc] initWithCapacity:kASControlNodeActionDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. targetActions = [[NSMutableArray alloc] initWithCapacity:kASControlNodeActionDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries.
[eventDispatchTable setObject:targetActions forKey:targetKey]; [eventDispatchTable setObject:targetActions forKey:target];
} }
// Add the action message. // Add the action message.
@@ -271,12 +249,12 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v
NSParameterAssert(controlEvent != 0 && controlEvent != ASControlNodeEventAllEvents); NSParameterAssert(controlEvent != 0 && controlEvent != ASControlNodeEventAllEvents);
// Grab the event dispatch table for this event. // Grab the event dispatch table for this event.
NSDictionary *eventDispatchTable = [_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)]; NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)];
if (!eventDispatchTable) if (!eventDispatchTable)
return nil; return nil;
// Grab and return the actions for this target. // Return the actions for this target.
return [eventDispatchTable objectForKey:_ASControlNodeTargetKeyForTarget(target)]; return [eventDispatchTable objectForKey:target];
} }
- (NSSet *)allTargets - (NSSet *)allTargets
@@ -284,11 +262,11 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v
NSMutableSet *targets = [[NSMutableSet alloc] init]; NSMutableSet *targets = [[NSMutableSet alloc] init];
// Look at each event... // Look at each event...
for (NSDictionary *eventDispatchTable in [_controlEventDispatchTable allValues]) for (NSMapTable *eventDispatchTable in [_controlEventDispatchTable allValues])
{ {
// and each event's targets... // and each event's targets...
for (id <NSCopying> targetKey in eventDispatchTable) for (id target in eventDispatchTable)
[targets addObject:_ASControlNodeTargetForTargetKey(targetKey)]; [targets addObject:target];
} }
return targets; return targets;
@@ -304,15 +282,15 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v
{ {
// Grab the dispatch table for this event (if we have it). // Grab the dispatch table for this event (if we have it).
id<NSCopying> eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent); id<NSCopying> eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent);
NSMutableDictionary *eventDispatchTable = [_controlEventDispatchTable objectForKey:eventKey]; NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:eventKey];
if (!eventDispatchTable) if (!eventDispatchTable)
return; return;
void (^removeActionFromTarget)(id <NSCopying> targetKey, SEL action) = ^ void (^removeActionFromTarget)(id <NSCopying> targetKey, SEL action) = ^
(id <NSCopying> targetKey, SEL theAction) (id aTarget, SEL theAction)
{ {
// Grab the targetActions for this target. // Grab the targetActions for this target.
NSMutableArray *targetActions = [eventDispatchTable objectForKey:targetKey]; NSMutableArray *targetActions = [eventDispatchTable objectForKey:aTarget];
// Remove action if we have it. // Remove action if we have it.
if (theAction) if (theAction)
@@ -324,7 +302,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v
// If there are no actions left, remove this target entry. // If there are no actions left, remove this target entry.
if ([targetActions count] == 0) if ([targetActions count] == 0)
{ {
[eventDispatchTable removeObjectForKey:targetKey]; [eventDispatchTable removeObjectForKey:aTarget];
// If there are no targets for this event anymore, remove it. // If there are no targets for this event anymore, remove it.
if ([eventDispatchTable count] == 0) if ([eventDispatchTable count] == 0)
@@ -337,11 +315,11 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v
if (!target) if (!target)
{ {
// Look at every target, removing target-pairs that have action (or all of its actions). // Look at every target, removing target-pairs that have action (or all of its actions).
for (id <NSCopying> targetKey in eventDispatchTable) for (id aTarget in eventDispatchTable)
removeActionFromTarget(targetKey, action); removeActionFromTarget(aTarget, action);
} }
else else
removeActionFromTarget(_ASControlNodeTargetKeyForTarget(target), action); removeActionFromTarget(target, action);
}); });
} }
@@ -354,13 +332,12 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v
_ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^ _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^
(ASControlNodeEvent controlEvent) (ASControlNodeEvent controlEvent)
{ {
NSDictionary *eventDispatchTable = [_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)]; NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)];
// For each target interested in this event... // For each target interested in this event...
for (id <NSCopying> targetKey in eventDispatchTable) for (id target in eventDispatchTable)
{ {
id target = _ASControlNodeTargetForTargetKey(targetKey); NSArray *targetActions = [eventDispatchTable objectForKey:target];
NSArray *targetActions = [eventDispatchTable objectForKey:targetKey];
// Invoke each of the actions on target. // Invoke each of the actions on target.
for (NSString *actionMessage in targetActions) for (NSString *actionMessage in targetActions)
@@ -387,16 +364,6 @@ id<NSCopying> _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEv
return [NSNumber numberWithInteger:controlEvent]; return [NSNumber numberWithInteger:controlEvent];
} }
id<NSCopying> _ASControlNodeTargetKeyForTarget(id target)
{
return (target ? [NSValue valueWithPointer:(__bridge const void *)(target)] : [NSNull null]);
}
id _ASControlNodeTargetForTargetKey(id<NSCopying> targetKey)
{
return (targetKey != [NSNull null] ? [(NSValue *)targetKey pointerValue] : nil);
}
void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent)) void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent))
{ {
// Start with our first event (touch down) and work our way up to the last event (touch cancel) // Start with our first event (touch down) and work our way up to the last event (touch cancel)