博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅析回调函数和Schedule
阅读量:4986 次
发布时间:2019-06-12

本文共 4946 字,大约阅读时间需要 16 分钟。

Cocos2d-x中涉及到不少的回调函数,有时候我们在用的过程中并没有留意,但如果你仔细推敲一下,发现这里边的故事还真不少,本文借助菜单回调和schedule回调来说下3.0中的回调函数的一些应用。

1、回调的第一种方法,传统方法,传入target和回调函数,在适当的时候用target调用回调函数。

以下的函数调用是2.x创建菜单项使用回调函数的方法,第三个参数是target,第四个参数需要传入一个函数指针,这个函数指针的定义是:typedef void (Ref::*SEL_MenuHandler)(Ref*);传入的第三第四个参数的作用就是当需要回调函数的时候用target来调用你传入的这个函数,所以这个函数一定是target的成员变量。

1
auto menu1 = MenuItemImage::create(
"CloseNormal.png"
"CloseSelected.png"
this
, menu_selector(HelloWorld::closeCallback));

2、回调的第二种方法,使用std::bind

CC_CALLBACK_1的定义std::bind(&__selector__,__target__, std::placeholders::_1, ##__VA_ARGS__) bind其实就是传入一个函数,产生一个新的函数,第一个就是一个函数地址,如果这个函数是类的成员函数,我们需要第二个参数,告诉调用哪个对象的这个函数,第三个参数是一个占位符,CC_CALLBACK_0,CC_CALLBACK_1 ...的不同点就在这里,后边的数字代表的就是bind的占位符数量,引擎对他们进行了不同的宏定义,可以跟进查看,后边的##__VA_ARGS__可以传入多个参数

1
auto menu2 = MenuItemImage::create(
"CloseNormal.png"
"CloseSelected.png"
, CC_CALLBACK_1(HelloWorld::closeCallback, 
this
));

下面不使用Cocos2d-x的callback宏,我们来直接使用bind

1
auto menu3 = MenuItemImage::create(
"CloseNormal.png"
,
"CloseSelected.png"
,std::bind(&HelloWorld::callback,
this
,std::placeholders::_1,1,
false
));

这里是callback的定义void HelloWorld::callback(cocos2d::Ref * ref,int i,bool b),我们使用bind绑定了这个函数,将它的第一个参数使用占位符保留了下来,而第二个和第三个参数分别提供了值,这个时候通过bind就产生了一个新的函数callback(cocos2d::Ref * ref)。系统在回调的时候会传入一个ref的参数,而i和b就是我们在bind中提供的值。

最后我们来看一下menu3的create的第三个参数ccMenuCallback,它的定义如下:typedef std::function<void(Ref*)> ccMenuCallback;这个东西就是std::function,尖括号中的内容就是告诉你它需要的函数返回值是void,而需要的参数是Ref *,也就是说它需要的是返回值是void,参数是Ref *的函数,所以std::function其实就是一种类型,就像普通的数据类型那样的类型,一种用来说明函数类型的类型,最后的最后我们知道通过std::bind绑定的函数需要使用std::function类型去接受。

3、回调的第三种方法:使用lambda表达式

将lambda表达式直接写在参数中:

1
2
3
4
auto menu4 = MenuItemImage::create(
"CloseNormal.png"
,
"CloseSelected.png"
,
[](Ref * ref){
log
(
"close"
);
});

如果要使表达式能够访问外部变量,可以在[]内写入“&”或者“=”加上变量名,其中“&”表示按引用访问,“=”表示按值访问,变量之间用逗号分隔。

1
2
3
4
5
6
auto closeMenu1 = MenuItemImage::create(
"CloseNormal.png"
,
"CloseSelected.png"
,
                                            
[
this
,&size](Ref * ref){
                                    
auto sprite = Sprite::create(
"HelloWorld.png"
);
                                                
sprite->setAnchorPoint(Point(0,0));
                                    
this
->addChild(sprite);
                                            
});

为了说明lambda的类型,这次将lambda写到参数列表的外边。

1
2
3
std::function<
void
(Ref *)> call = [](Ref * ref)
{};
auto closeMenu2 = MenuItemImage::create(
""
,
""
,call);

更多的时候为了省事,我们都将std::function的那一长串东西用一个auto代替。通过上面的例子就说明了lambda返回的类型也是需要std::function接收的,通过上面的例子我们得出这样的结论,如果要回调某一个函数,就像菜单的回调,按钮的回调,schedule的回调,CCallFunc动作序列,我们可以传入函数指针+调用对象target,或者使用std::function,而std::function可以通过std::bind绑定获得,或者通过lambda表达式获得。

接下来看一下回调函数在schedule中是如何应用的。

1
2
this
->schedule(SEL_SCHEDULE(&HelloWorld::call), 1.0f);
this
->schedule(schedule_selector(HelloWorld::call), 1.0f);

以下的两种调用方式道理都是一样的,仔细比较一下发现使用SEL_SCHEDULE需要加一个取地址符,使用schedule_selector不需要,原因看下各自的定义大家就明白了。SEL_SCHEDULE直接就是函数指针,而schedule_selector是个宏定义,传入里边的参数,用static_cast转化为SEL_SCHEDULE类型的时候加上了取地址符。这里把这个基础的问题说下,也是对C++的一种复习吧!

1
2
typedef 
void 
(Ref::*SEL_SCHEDULE)(
float
);
#define schedule_selector(_SELECTOR) static_cast<cocos2d::SEL_SCHEDULE>(&_SELECTOR)

我们发现一个问题,用这种方法做回调的时候,不是需要传入一个target吗,为什么这种方法没有传入这个target,或者说是this参数,我们只好翻翻源码了,那就一步步跟进源码,跟到这里(代码在下边)的时候发现了用变量_schedule调用了一个schedule函数,这个_schedule是什么,我们在文件中查找一下,发现_scheduler = director->getScheduler();原来它是通过Director获得的,其实它的类型是Scheduler,这个东西是一个单例,在Director类中保持着对这个单利对象的引用。同时这个schedule函数的第二个参数就是this,而这个this就是我们要得target,所以这就说明这个target不是没有传入,而是在引擎的内部传入了this!

1
2
3
4
5
6
7
void 
Node::schedule(SEL_SCHEDULE selector, 
float 
interval, unsigned 
int 
repeat, 
float 
delay)
{
    
CCASSERT( selector, 
"Argument must be non-nil"
);
    
CCASSERT( interval >=0, 
"Argument must be positive"
);
  
    
_scheduler->schedule(selector, 
this
, interval , repeat, delay, !_running);
}

我们继续跟进schedule函数,发现文件跳到了Scheduler类中,调用了Scheduler类的schedule,到这里所有的东西就明白了。我们在类中调用schedule,表面上调用的是Node类的schedule,其实最后调用的是Scheduler类中得schedule函数。我们所说的第一种形式的回调this的传入是在node类中转调用scheduler中的schedule的时候帮我们传入了this。最后有必要看一下这个schedule的声明。

1
void 
Scheduler::schedule(SEL_SCHEDULE selector, Ref *target, 
float 
interval, unsigned 
int 
repeat, 
float 
delay, 
bool 
paused)

那我们想要的第二种回调的形式呢?我找了Node的整个schedule函数都没有发现,因为Node最终还是调用的scheduler类的函数,所以我们去scheduler类中找一找,果然,我发现了采用第二种回调形式的schedule函数,声明如下。

1
void 
Scheduler::schedule(
const 
ccSchedulerFunc& callback, 
void 
*target, 
float 
interval, unsigned 
int 
repeat, 
float 
delay, 
bool 
paused, 
const 
std::string& key)

我们跟进去看一下ccSchedulerFunc的定义,如下:

1
typedef 
std::function<
void
(
float
)> ccSchedulerFunc;

怎么样,一个std::function出来了吧,但是Node中却没有提供给我们这样的schedule函数调用,有如此简单的调用形式却不用岂不是可惜?那我们要怎么办?很简单,直接使用Scheduler中得函数不就可以了。所以采用第二种回调方式的调用方法如下。

1
2
auto _schedule = Director::getInstance()->getScheduler();
    
_schedule->schedule([](
float 
tm
){
log
(
"%f"
,
tm
);}, 
this
, 1.0f,!
this
->isRunning(),
"xiaota"
);

现在,我们可以尽情的使用lambda表达式了,省去了麻烦的函数头文件声明,这几个参数很简单是类似于普通的schedule函数,只是最后一个参数要注意一下,他是一个string类型,这个string类型有什么作用呢?我们让schedule跑了起来,停的时候该怎么办呢?对,就是用的这个string,当然还有第二个参数target,所以这里你传入的这个key值必须和其他的key值保持不同,否则的话停掉的时候就出错了。这个原因估计也是引擎组没有将这个接口放到Node中得因素吧!现在schedule的用法和小小的分析就到这里结束了,大家如果发现有什么错误之处还请指正!

来源网址:

转载于:https://www.cnblogs.com/lzya/p/4913003.html

你可能感兴趣的文章
管洪伟 130702010039 实验报告
查看>>
构建之法阅读笔记05
查看>>
纸上谈兵01 排序算法简介及其C实现
查看>>
哈夫曼树
查看>>
什么是提醒?
查看>>
AngularJS5.0 (第一篇)
查看>>
ORACLE基础
查看>>
redis-4.0.8 配置文件解读
查看>>
Ubuntu 16.04搭建原始Git服务器
查看>>
Ubuntu 16.04下没有/var/log/messages文件问题解决
查看>>
JSP指令
查看>>
[转]操作系统Unix、Windows、Mac OS、Linux的故事
查看>>
SQL Server中 sysobjects、syscolumns、systypes
查看>>
heredoc和nowdoc的区别
查看>>
mysql存储过程中遍历数组字符串的两种方式
查看>>
CCF201803-3-URL映射
查看>>
.NET程序开发中必须收藏的七个类型的经典工具
查看>>
Springboot重构-云笔记(2)
查看>>
数据库语句备份
查看>>
在对象之间搬移特性(读书摘要——重构改善既有代码的设计)
查看>>