无论是回调函数还是消息机制都是函数调用的方式。
对我们来说,函数调用就是我们认为在需要的时候调某个方法,这就是最常见的函数调用的方式——方法调用。
方法调用都是通过类名来调用某个方法,实现方法调用最关键的就是怎么找到这个类的引用。如果是静态类,或者单例类,或者静态方法,就很方便,否则我们需要通过各种各样的方式将这个类的引用传递过来。
我们用方法调用的时候是满足了这样的条件:我们需要在满足某个条件A的时候去调用某个函数B,而目前条件A刚好满足。(也可以说满足了调用函数B的条件A)
方法调用通常会不断循环,在一个方法里调用另一个方法。例如,在满足A时调用函数B获得结果C,又立刻根据结果C,如果结果C大于多少,去调用函数D,否则调用函数E。如果等待调用函数和函数调用者在统一类中呢?
例如,在A类的a1方法中,会调用B类的b1方法,A类持有B类引用,可以采用方法调用在a1中完成对b1的调用。而A类中的a2方法在满足某个条件时需要被调用,而恰恰在b1方法中这个条件会被满足。那么在b1方法中如何调用a2方法呢?
如果A类时静态类,或单例类,a2为静态方法,B类持有A类引用,那么直接通过方法调用即可。但总有例外的情况,我们也不想让A类成为静态或单例类,或者让a2为静态方法,或者让B类持有A类的引用,那么就需要在a1中调用(call)b1时,把a2作为参数传递给b1。a2就是回调函数。
对A类来说,B类中的b1调用了a2的方法,是在回调。但对B类来说,在b1中调a2的方法就是正常的方法调用,同样是满足了某个条件就调某个函数,只不过这个函数对A类来说是个回调函数。
回到函数解决了知道在满足某个条件的时候该调用什么函数,但不知道这个条件何时被满足时的问题。这是什么意思呢?
我们在a1中调用b1方法,b1里会去调其他方法,得到返回值,然后根据返回值调a2方法,在b1中我们只是知道了在满足某个条件时去调用a2,但不知道这个条件在a1调b1的过程中是否满足。有可能在其他地方调用b1的时候满足。
这就是函数调用的另一中方式,采用回调的形式调用某个函数。
可以认为回调函数在方法调用上包装了一层,是变相的方法调用,而消息是在回调函数的基础上包装了一层,是变相的回调函数。
在回调函数中将等待被调用方法a2传递了调用者b1,而在消息中,需要将a2传递给C,b1调用C的方法c1,然后c1调用a2。C就是消息的管理者,将a2传递给C换句话说就是注册某个消息,b1调用C的方法c1换句话说就是发送某个消息。无论是注册还是发送某个消息,都可以持有C的引用,进行方法调用。
可以看到,消息机制将回调函数中的调用者和被调用者隔离开了,调用者在消息机制中被称为消息发送者,被调用者被称为消息接收者。仅仅是这一个简单的改变,就会造成以下区别:
事件也是一种广义回调,事件通常只有一个事件触发者,多个事件监听者;回调调用者和被调用者都只有一个;消息发送者和接收者都可以有多个。
事件的触发通常是由系统外部的动作或者行为导致的,而消息由系统内部某件事或某个条件发生了而产生。注册事件或接受消息者只需要知道某件事发生了或某个条件满足即可。
这里的系统指:对于用户而言,应用或App是一个系统,用户的输入操作是事件。对于系统内某个模块而言,其他模块的输入是事件。
由于事件的触发者通常可以只有一个,A触发事件a,B响应事件a后触发事件b,C响应事件b后触发事件c,依次类推,可以构成一个事件队列。而消息通常不能构成这样的消息队列。
例如某事发生了,进行了函数调用,但你想在这个事发生后进行一些处理,根据处理后的结果来确定是否进行函数调用。如果代码时自己写的,那么直接在函数调用前插入新的代码即可。如果代码是系统或者某个API之类的,你就不能乱修改代码,得向中间插入一个函数,这个函数就是钩子函数,可以说钩子函数也是回调函数的一种。之所以可以插入一个函数,是因为写系统和API的人考虑到有人想做自定义的行为。
那么某事发生了,后面进行的哪种函数调用,可以让一个钩子函数插入呢?如果发生的事和函数调用耦合的紧,就很难插入。显然,消息机制耦合度低,容易插入。所以大多数用钩子函数的时候都是对某个消息进行拦截。
从计算机的角度,函数调用时怎么实现的,或者说过程是怎么样的,可以看以下链接深入理解下。
C++函数调用 入栈以及出栈_George熊的博客-CSDN博客_c++出栈函数
C++ 函数调用过程中栈区的变化——(栈帧、esp、ebp)_JMW1407的博客-CSDN博客_c++栈帧
C函数调用过程原理及函数栈帧分析 - stardsd - 博客园