【触摸消息的分发】
当我们触摸屏幕时触摸事件被CCTouchDispatcher类全盘接管,所以触摸消息也就由CCTouchDispatcher类来负责分发:
分发触摸消息的四个方法,注意接管的四个方法和Cocoa Touch的触摸方法的第一个参数一样是:(NSSet *)touches.
// 分发touchesBegan消息
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if( dispatchEvents )
[self touches:touches withEvent:event withTouchType:kCCTouchBegan];
}
// 分发touchesMoved消息
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if( dispatchEvents )
[self touches:touches withEvent:event withTouchType:kCCTouchMoved];
}
// 分发touchesEnded消息
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if( dispatchEvents )
[self touches:touches withEvent:event withTouchType:kCCTouchEnded];
}
// 分发touchesCancelled消息
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
if( dispatchEvents )
[self touches:touches withEvent:event withTouchType:kCCTouchCancelled];
}
@end
上述四个方法都调用同一个方法:
-(void) touches:(NSSet*)touches withEvent:(UIEvent*)event withTouchType:(unsigned int)idx;
该方法太长,我采取分段讲解:
第一段代码:
//断言检测idx的值,最多四个触摸方法,下标0、1、2、3
NSAssert(idx < 4, @"Invalid idx value");
id mutableTouches;
//注意开始分发触摸事件之前,先上锁
locked = YES;
// optimization to prevent a mutable copy when it is not necessary
//获取关于标准协议的触摸事件处理器的个数以及关于目标协议
的触摸事件处理器的个数
unsigned int targetedHandlersCount = [targetedHandlers count];
unsigned int standardHandlersCount = [standardHandlers count];
//此处的needsMutableSet是为了判断这两个个数是否都不为
零,为什么要进行判断呢?请看下面!
BOOL needsMutableSet = (targetedHandlersCount && standardHandlersCount);
//若这两个个数都不为零,说明我们既要分发关于标准协议的触
摸方法也要分发关于目标协议的触摸方法,那就必须两次使用
touches对象的值,为了安全起见,此处使用mutableCopy来
拷贝一份touches.
mutableTouches = (needsMutableSet ? [touches mutableCopy] : touches);
//根据idx值来取出handlerHelperData[idx]值,从而知道
了代理对象以及转发的方法选择器。
struct ccTouchHandlerHelperData helper = handlerHelperData[idx];
第二段代码:
//
//分发关于标准协议的触摸消息
//
if( targetedHandlersCount > 0 ) {
//标准协议的四个触摸方法的第一参数类型为UITouch *,
所以先从touches取出touch
for( UITouch *touch in touches ) {
//循环取出关于目标协议触摸事件处理器数组中的每一个触
摸事件处理器
for(CCTargetedTouchHandler *handler in targetedHandlers) {
//声明一个claimed标志来获取目标协议的ccTouchBegan方法的返回值,因为我们前面提到过关于目标协议的ccTouchBegan方法如果返回YES,后续的ccTouchMoved、ccTouchEnded、ccTouchCancelled才会被分发到触摸消息。
BOOL claimed = NO;
if( idx == kCCTouchBegan ) {
claimed = [handler.delegate ccTouchBegan:touch withEvent:event];
//如果ccTouchBegan方法返回YES就将触摸对象touch,存储到claimedTouches数组中。
if( claimed )
[handler.claimedTouches addObject:touch];
}
// else (moved, ended, cancelled)
//判断claimedTouches数组中是否包含touch对象,就可以知道是否哟转发后续的三个触摸方法了。
else if( [handler.claimedTouches containsObject:touch] ) {
claimed = YES;
//此处的按位与操作是为了验证enabledSelectors标记中是否包含helper.type—触摸方法的标记,若有则转发后续的三个触摸方法。
if( handler.enabledSelectors & helper.type )
[handler.delegate performSelector:helper.touchSel withObject:touch withObject:event];
//当我们触摸屏幕时必定会触发两个触摸事件:ccTouchBegan和ccTouchEnded,所以此处采用这种按位与操作是为了在ccTouchEnded或者ccTouchCancelled消息分发过来时移除claimedTouches数组中的touch对象。
if( helper.type & (kCCTouchSelectorCancelledBit | kCCTouchSelectorEndedBit) )
[handler.claimedTouches removeObject:touch];
}
//如果ccTouchBegan返回YES并且阻止消息进一步转发的话,那么就需要将 mutableTouches中的触摸点touch删除,这样会导致[mutableTouches count] == 0,从而保证对于CCLayer---标准协议的触摸事件也无法继续转发下去,从而达到swallowsTouches的目的。
if( claimed && handler.swallowsTouches ) {
if( needsMutableSet )
[mutableTouches removeObject:touch];
break;
}
}
}
}
第三段代码:
//
// 关于标准协议的触摸消息的转发
//
//验证注册的个数以及mutableTouches中触摸点的个数
if( standardHandlersCount > 0 && [mutableTouches count]>0 ) {
// 遍历standardHandlers数组取出每一个触摸事件处理器
for( CCTouchHandler *handler in standardHandlers ) {
//验证触摸方法的实现与否,并转发响应的触摸消息
if( handler.enabledSelectors & helper.type )
[handler.delegate performSelector:helper.touchesSel withObject:mutableTouches withObject:event];
}
}
//最后记得释放mutableTouches
if( needsMutableSet )
[mutableTouches release];
第四段代码:
//触摸消息全部分发完后,记得解锁
locked = NO;
//将等待移除的代理对象移除
if( toRemove ) {
toRemove = NO;
for( id delegate in handlersToRemove )
[self forceRemoveDelegate:delegate];
[handlersToRemove removeAllObjects];
}
//将等待加入的标准协议或者目标协议的触摸事件处理器加入到相
应的数组中
if( toAdd ) {
toAdd = NO;
for( CCTouchHandler *handler in handlersToAdd ) {
Class targetedClass = [CCTargetedTouchHandler class];
if( [handler isKindOfClass:targetedClass] )
[self forceAddHandler:handler array:targetedHandlers];
else
[self forceAddHandler:handler array:standardHandlers];
}
[handlersToAdd removeAllObjects];
}
//根据toQuit标记是否移除所有的代理对象
if( toQuit ) {
toQuit = NO;
[self forceRemoveAllDelegates];
}
至此,触摸消息分发完成。
最后介绍一下onExit方法:
i:
-(void)onExit
{
[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
[super onExit];
}
该方法中一般要移除代理对象,因为切换场景后,要避免再次触摸时将触摸消息分发到该对象上,所以必须要将此代理对象移除,告诉CCTouchDispatcher不要将触摸消息分发给我了!!!
ii:
-(void) removeDelegate:(id) delegate方法实现:
-(void) removeDelegate:(id) delegate
{
if( delegate == nil )
return;
//没有上锁的情况下,直接移除。
if( ! locked ) {
[self forceRemoveDelegate:delegate];
} else {
//若上锁的情况下,先暂时存在handlersToRemove数组
中,等待消息分发完成解锁后,再移除delegate
[handlersToRemove addObject:delegate];
toRemove = YES;
}
}
iii:最终移除delegate的方法:
//遍历targetedHandlers以及standardHandlers数组移除关于delegate的触摸事件处理器。
-(void) forceRemoveDelegate:(id)delegate
{
for( CCTouchHandler *handler in targetedHandlers ) {
if( handler.delegate == delegate ) {
[targetedHandlers removeObject:handler];
break;
}
}
for( CCTouchHandler *handler in standardHandlers ) {
if( handler.delegate == delegate ) {
[standardHandlers removeObject:handler];
break;
}
}
}