RYU源碼試讀-APP注冊(cè)

參考學(xué)習(xí)資料

本篇文章先從最簡(jiǎn)單的example_switch_l3.py進(jìn)行剖析,之后進(jìn)一步分析整個(gè)RYU框架。
在程序中經(jīng)常出現(xiàn)如下的修飾器:

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        datapath = ev.msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        # install the table-miss flow entry.
        match = parser.OFPMatch()
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                          ofproto.OFPCML_NO_BUFFER)]
        self.add_flow(datapath, 0, match, actions)
def set_ev_cls(ev_cls, dispatchers=None):
    def _set_ev_cls_dec(handler):
        if 'callers' not in dir(handler):
            handler.callers = {}
        for e in _listify(ev_cls):
            handler.callers[e] = _Caller(_listify(dispatchers), e.__module__)
        return handler
    return _set_ev_cls_dec

set_ev_cls函數(shù)接收兩個(gè)參數(shù)志膀,第一個(gè)代表需要監(jiān)聽的事件乎芳,第二個(gè)參數(shù)表示該事件在交換機(jī)與控制器交互的哪個(gè)階段發(fā)生有以下四個(gè)取值敛滋,可或烦味。

HANDSHAKE_DISPATCHER = "handshake"
CONFIG_DISPATCHER = "config"
MAIN_DISPATCHER = "main"
DEAD_DISPATCHER = "dead"

若監(jiān)聽到EventOFPSwitchFeatures事件苍凛,就會(huì)觸發(fā)下面的handler函數(shù)徒扶。
上述過程實(shí)際上是:
switch_features_handler=set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)(switch_features_handler)

?上面還是有一點(diǎn)疑問粮彤,具體的返回和調(diào)用關(guān)系,為什么返回_set_ev_cls_dec函數(shù)而set_ev_cls函數(shù)中參數(shù)ev_cls, dispatchers還能被使用姜骡。如何做到函數(shù)的事件驅(qū)動(dòng)調(diào)用导坟。

整個(gè)過程可以描述為:
首先switch_features_handler作為參數(shù)傳遞給 _set_ev_cls_dec函數(shù),先判斷該函數(shù)有沒有callers屬性(因?yàn)橥缓瘮?shù)也可能被其他事件觸發(fā))溶浴,若沒有則添加該屬性乍迄,要注意函數(shù)也是具有屬性的,并且可以在定義外面添加士败。
callers是一個(gè)字典闯两,鍵值key是傳入的事件類對(duì)應(yīng)的列表形式,這里就是[ofp_event.EventOFPSwitchFeatures]谅将,value是一個(gè)_Caller對(duì)象漾狼,該對(duì)象主要就是記下dispatchers,和這個(gè)事件的模塊名饥臂。
_Caller函數(shù)原型為:

class _Caller(object):
    """Describe how to handle an event class.
    """

    def __init__(self, dispatchers, ev_source):
        """Initialize _Caller.

        :param dispatchers: A list of states or a state, in which this
                            is in effect.
                            None and [] mean all states.
        :param ev_source: The module which generates the event.
                          ev_cls.__module__ for set_ev_cls.
                          None for set_ev_handler.
        """
        self.dispatchers = dispatchers
        self.ev_source = ev_source

這是第一個(gè)部分逊躁,總結(jié)下來完成的工作就是給switch_features_handler函數(shù)添加了一個(gè)callers字典屬性,里面保存了自己感興趣的事件和dispatcher隅熙。

事件是如何通知該函數(shù)的稽煤,ev這個(gè)類又是如何產(chǎn)生?

上面的問題也是最關(guān)心的問題囚戚,要理解這個(gè)還需要參考以下:
RYU main函數(shù)
RYU源碼解讀
RYU在運(yùn)行 ryu-manage <application>時(shí)酵熙,實(shí)際上先運(yùn)行以下main函數(shù)。

def main(args=None, prog=None):
    try:
        CONF(args=args, prog=prog,
             project='ryu', version='ryu-manager %s' % version,
             default_config_files=['/usr/local/etc/ryu/ryu.conf'])
    except cfg.ConfigFilesNotFoundError:
        CONF(args=args, prog=prog,
             project='ryu', version='ryu-manager %s' % version)

    log.init_log()
    logger = logging.getLogger(__name__)

    if CONF.enable_debugger:
        msg = 'debugging is available (--enable-debugger option is turned on)'
        logger.info(msg)
    else:
        hub.patch(thread=True)

    if CONF.pid_file:
        import os
        with open(CONF.pid_file, 'w') as pid_file:
            pid_file.write(str(os.getpid()))

    app_lists = CONF.app_lists + CONF.app
    # keep old behavior, run ofp if no application is specified.
    if not app_lists:
        app_lists = ['ryu.controller.ofp_handler']

    app_mgr = AppManager.get_instance()
    app_mgr.load_apps(app_lists)
    contexts = app_mgr.create_contexts()
    services = []
    services.extend(app_mgr.instantiate_apps(**contexts))

    webapp = wsgi.start_service(app_mgr)
    if webapp:
        thr = hub.spawn(webapp)
        services.append(thr)

    try:
        hub.joinall(services)
    except KeyboardInterrupt:
        logger.debug("Keyboard Interrupt received. "
                     "Closing RYU application manager...")
    finally:
        app_mgr.close()

首先就涉及到OSLO模塊的使用驰坊。留以后分析
app_lists從輸入的參數(shù)中獲得匾二,若沒有則指定為ryu.controller.ofp_handler,然后運(yùn)行
app_mgr = AppManager.get_instance()
調(diào)用APPManager類的靜態(tài)方法,改靜態(tài)方法為:

    @staticmethod
    def get_instance():
        if not AppManager._instance:
            AppManager._instance = AppManager()
        return AppManager._instance

很明顯察藐,獲得一個(gè)實(shí)例皮璧。
接下來,加載應(yīng)用分飞。
app_mgr.load_apps(app_lists)

    def load_app(self, name):
        mod = utils.import_module(name)  #加載這個(gè)模塊悴务,mod代表的是什么結(jié)構(gòu)?
        clses = inspect.getmembers(mod,
                                   lambda cls: (inspect.isclass(cls) and
                                                issubclass(cls, RyuApp) and
                                                mod.__name__ ==
                                                cls.__module__))
        if clses:
            return clses[0][1]
        return None

load_app函數(shù)是下面函數(shù)的調(diào)用函數(shù)浸须,app_lists是命令行傳入要運(yùn)行的APP惨寿,可以有多個(gè),通過依次 調(diào)用load_app進(jìn)行加載删窒。加載app為以下幾步,首先調(diào)用utils.import_module(name)顺囊,該函數(shù)返回整個(gè)模塊的環(huán)境肌索,包括類和方法,然后通過 inspect.getmembers過濾出我們寫的class特碳,也就是ExampleSwitch13诚亚,通過三個(gè)條件刪選:類,RyuApp的子類午乓,所在的文件為主文件(這個(gè)排除了其他地方import進(jìn)來的類)站宗。注意返回的是名字和對(duì)應(yīng)的屬性,也就是類名和對(duì)應(yīng)的類益愈,clses[0][1]表示返回第一個(gè)類梢灭,不是類名,類名是[0][0]蒸其。在官方文檔中也有提到只運(yùn)行APP實(shí)現(xiàn)的第一個(gè)類敏释。

    def load_apps(self, app_lists):
        app_lists = [app for app
                     in itertools.chain.from_iterable(app.split(',')
                                                      for app in app_lists)]
        while len(app_lists) > 0:
            app_cls_name = app_lists.pop(0)

            context_modules = [x.__module__ for x in self.contexts_cls.values()]
            if app_cls_name in context_modules:
                continue

            LOG.info('loading app %s', app_cls_name)

            cls = self.load_app(app_cls_name)
            if cls is None:
                continue

            self.applications_cls[app_cls_name] = cls

            services = []
            for key, context_cls in cls.context_iteritems():
                v = self.contexts_cls.setdefault(key, context_cls)
                assert v == context_cls
                context_modules.append(context_cls.__module__)

                if issubclass(context_cls, RyuApp):
                    services.extend(get_dependent_services(context_cls))

            # we can't load an app that will be initiataed for
            # contexts.
            for i in get_dependent_services(cls):
                if i not in context_modules:
                    services.append(i)
            if services:
                app_lists.extend([s for s in set(services)
                                  if s not in app_lists])

可能app_lists有多個(gè)app,先將他們統(tǒng)一為一個(gè)列表摸袁,并且依次加載執(zhí)行钥顽。
以上函數(shù)完成依次加載app,其實(shí)就是找到該APP對(duì)應(yīng)要運(yùn)行的類靠汁,并把該類的名字存到applications_cls中蜂大,key是app的名字,如module.path.module_name蝶怔,值就是返回要運(yùn)行的類奶浦。

加載的過程中順便加載每個(gè)app依賴的app類,而且把每個(gè)app的context保存到context_cls中添谊。依賴的APP保存在_CONTEXT中财喳,這也是ryu實(shí)現(xiàn)的一種模塊間通信機(jī)制。具體參考RYU模塊間通信

到這一步,除了applications_cls耳高,還完成了context_cls字典的構(gòu)造扎瓶。該字典內(nèi)容和 _CONTEXTS中內(nèi)容一致。是當(dāng)前app所依賴的服務(wù)泌枪,如其他APP概荷。并將該服務(wù)添加在services列表中。

def get_dependent_services(cls):
    services = []
    for _k, m in inspect.getmembers(cls, _is_method):
        if _has_caller(m):
            for ev_cls, c in m.callers.items():
                service = getattr(sys.modules[ev_cls.__module__],
                                  '_SERVICE_NAME', None)
                if service:
                    # avoid cls that registers the own events (like
                    # ofp_handler)
                    if cls.__module__ != service:
                        services.append(service)

    m = sys.modules[cls.__module__]
    services.extend(getattr(m, '_REQUIRED_APP', []))
    services = list(set(services))
    return services

接下來對(duì)依賴的服務(wù)和本身服務(wù)調(diào)用get_dependent_services碌燕,原型如上误证。get_dependent_services函數(shù)獲取其依賴應(yīng)用,最后將所有的依賴services添加到app_lists中修壕。該函數(shù)完成兩個(gè)操作愈捅,一是判斷該依賴項(xiàng)的method中有沒有訂閱事件,如果有就將ev_cls.__module__._SERVICE_NAME(也就是ofp_event._SERVICE_NAME)添加到services中.二是有沒有_REQUIRED_APP慈鸠,實(shí)際中都沒怎么用到蓝谨。
最后將依賴項(xiàng)添加到app_lists中。

create_contexts函數(shù):實(shí)例化context_cls中的context青团,如果這個(gè)context是app譬巫,就實(shí)例化該app。記得在load_apps中沒有管屬于context_cls的app嗎督笆,在這里直接初始化了芦昔。

 def create_contexts(self):
    for key, cls in self.contexts_cls.items():
        if issubclass(cls, RyuApp):
            # hack for dpset
            context = self._instantiate(None, cls) //初始化app
        else:
            context = cls() //實(shí)例化context
        LOG.info('creating context %s', key)
        assert key not in self.contexts
        self.contexts[key] = context //加入contexts字典
    return self.contexts //返回contexts字典

完成上述后,創(chuàng)建了contexts 字典娃肿,key是依賴項(xiàng)名字咕缎,value是初始化后的依賴項(xiàng)。
返回到main函數(shù)中咸作,接下來運(yùn)行

 services = []
services.extend(app_mgr.instantiate_apps(**contexts))

即調(diào)用

    def instantiate_apps(self, *args, **kwargs):
        for app_name, cls in self.applications_cls.items():
            self._instantiate(app_name, cls, *args, **kwargs)

        self._update_bricks()
        self.report_bricks()

        threads = []
        for app in self.applications.values():
            t = app.start()
            if t is not None:
                app.set_main_thread(t)
                threads.append(t)
        return threads

    def _instantiate(self, app_name, cls, *args, **kwargs):
        # for now, only single instance of a given module
        # Do we need to support multiple instances?
        # Yes, maybe for slicing.
        LOG.info('instantiating app %s of %s', app_name, cls.__name__)

        if hasattr(cls, 'OFP_VERSIONS') and cls.OFP_VERSIONS is not None:
            ofproto_protocol.set_app_supported_versions(cls.OFP_VERSIONS)

        if app_name is not None:
            assert app_name not in self.applications
        app = cls(*args, **kwargs)
        register_app(app)
        assert app.name not in self.applications
        self.applications[app.name] = app
        return app

_instantiate函數(shù)中就開始明朗了锨阿,先設(shè)置OFP_VERSIONS,做必要檢查后運(yùn)行 register_app(app)记罚。該函數(shù)完成兩件事墅诡,一是applications[app.name] = app該字典的構(gòu)造,比較applications_cls桐智,key是app的名字末早,如module.path.module_name,值就是返回要運(yùn)行的類说庭。而這里然磷,key是類名,而value是實(shí)例化的該類對(duì)象刊驴。二是注冊(cè)該APP姿搜,如下

def register_app(app):
    assert isinstance(app, RyuApp)
    assert app.name not in SERVICE_BRICKS
    SERVICE_BRICKS[app.name] = app
    register_instance(app)

完成了重要的數(shù)據(jù)結(jié)構(gòu)SERVICE_BRICKS的構(gòu)造寡润,其中key是要運(yùn)行的類的名字,value是實(shí)例好的該類對(duì)象舅柜。
app.name表示這個(gè)類的名稱梭纹,而app則代表這個(gè)類本身。name這個(gè)屬性在基類RyuApp中有定義致份。
將該APP加入到SERVICE_BRICKS字典中变抽,該字典代表一個(gè)服務(wù)鏈。值得注意的是氮块,只要繼承了基類RyuApp的app绍载,則app.name都是該類的類名,因?yàn)槎x為self.name = self.__class__.__name__滔蝉,但是ofp_handler中class OFPHandler對(duì)name屬性進(jìn)行了重寫击儡,self.name = 'ofp_event',在后面會(huì)有作用锰提。

def register_instance(i):
    for _k, m in inspect.getmembers(i, inspect.ismethod):
        # LOG.debug('instance %s k %s m %s', i, _k, m)
        if _has_caller(m):
            for ev_cls, c in m.callers.items():
                i.register_handler(ev_cls, m)

def _has_caller(meth):
    return hasattr(meth, 'callers')

def register_handler(self, ev_cls, handler):
    assert callable(handler)
    self.event_handlers.setdefault(ev_cls, [])
    self.event_handlers[ev_cls].append(handler)

和之前的開始連接起來曙痘,這里對(duì)APP的類中每一個(gè)method檢查是否有callers屬性,如果有立肘,就注冊(cè)句柄。最終將事件和對(duì)應(yīng)要觸發(fā)的函數(shù)以鍵值對(duì)形式保存在event_handlers字典中名扛。

至此谅年,生成了兩個(gè)字典,一是 SERVICE_BRICKS[app.name] = app肮韧,代表APP運(yùn)行的類融蹂,一是event_handlers,保存對(duì)應(yīng)事件和觸發(fā)函數(shù)弄企。

再次回到instantiate_apps函數(shù)超燃,還沒有運(yùn)行完呢。接下來是

self._update_bricks()
self.report_bricks()
    def _update_bricks(self):
        for i in SERVICE_BRICKS.values():
            for _k, m in inspect.getmembers(i, inspect.ismethod):
                if not hasattr(m, 'callers'):
                    continue
                for ev_cls, c in m.callers.items():
                    if not c.ev_source:
                        continue

                    brick = _lookup_service_brick_by_mod_name(c.ev_source)
                    if brick:
                        brick.register_observer(ev_cls, i.name,
                                                c.dispatchers)

                    # allow RyuApp and Event class are in different module
                    for brick in SERVICE_BRICKS.values():
                        if ev_cls in brick._EVENTS:
                            brick.register_observer(ev_cls, i.name,
                                                    c.dispatchers)

這個(gè)函數(shù)很關(guān)鍵拘领,第一步對(duì)APP中的類進(jìn)行檢查是否有callers意乓,如果有說明是修飾過,再進(jìn)一步看c.ev_source约素,回顧
handler.callers[e] = _Caller(_listify(dispatchers), e.__module__)
其中 e表示事件類届良,c.ev_source表示該事件類所在模塊名。其實(shí)c.ev_source如果是的module那么就是ofp_event,而OFPHandler類的名字正好就是'ofp_event'.

這里有一個(gè)技巧圣猎,其實(shí)c.ev_source如果是的module那么就是ofp_event,而OFPHandler類的名字正好就是'ofp_event'(在下文中會(huì)看到其實(shí)就是OpenFlowController),所以這里的brick就是OFPHandler士葫,然后將將每種ev_cls的類型和app名字注冊(cè)到該類中,其實(shí)本質(zhì)上就是OFPHandler作為了一個(gè)消息源送悔。這個(gè)函數(shù)非常重要慢显,將每個(gè)app的handler與消息源OFPHandler建立了聯(lián)系

def lookup_service_brick(name):
    return SERVICE_BRICKS.get(name)

def _lookup_service_brick_by_mod_name(mod_name):
    return lookup_service_brick(mod_name.split('.')[-1])

所以上面其實(shí)是返回SERVICE_BRICKS.get(‘ofp_event’)爪模,而對(duì)應(yīng)的類為OFPHandler。也就是說荚藻,這里是根據(jù)事件所在的模塊名加載服務(wù)塊屋灌,brick = OFPHandler對(duì)象。

    def register_observer(self, ev_cls, name, states=None):
        states = states or set()
        ev_cls_observers = self.observers.setdefault(ev_cls, {})
        ev_cls_observers.setdefault(name, set()).update(states)

上面程序細(xì)節(jié)比較多鞋喇,先看參數(shù)声滥,傳入的en_cls是事件類,即訂閱的事件侦香,name為該類的類名落塑,states為上文中提到的四中狀態(tài)之一。構(gòu)造observers字典罐韩,key是en_cls事件類憾赁,value是一個(gè)字典,過程如下:

>>> a.setdefault(4,{})
{}
>>> a.setdefault(4,{}).setdefault('yuan',set())
set([])
>>> b = a.setdefault(4,{})
>>> b.setdefault('yuan',set())
set([])
>>> a
{1: 'hello', 2: 'nihao', 3: 'world', 4: {'yuan': set([])}}
>>> b.setdefault('yuan',set()).update('abc')
>>> a
{1: 'hello', 2: 'nihao', 3: 'world', 4: {'yuan': set(['a', 'c', 'b'])}}
>>>

總結(jié)來說散吵,構(gòu)造了一個(gè)較為復(fù)雜的observers字典龙考,key是en_cls事件類,value為字典矾睦,其中key為類的名稱晦款,value為一個(gè)集合,其中保存的states枚冗。

整個(gè)函數(shù)_update_bricks函數(shù)對(duì)所有事件進(jìn)行了注冊(cè)缓溅,并生成了一個(gè)observers字典,注冊(cè)在OFPHander類下赁温,表示OFPHander為消息源坛怪。

接下來運(yùn)行self.report_bricks(),如下

    def report_bricks():
        for brick, i in SERVICE_BRICKS.items():
            AppManager._report_brick(brick, i)
    def _report_brick(name, app):
        LOG.debug("BRICK %s", name)
        for ev_cls, list_ in app.observers.items():
            LOG.debug("  PROVIDES %s TO %s", ev_cls.__name__, list_)
        for ev_cls in app.event_handlers.keys():
            LOG.debug("  CONSUMES %s", ev_cls.__name__)

這塊代碼主要負(fù)責(zé)顯示信息股囊⊥嗄洌可以理解,brick代表服務(wù)鏈中的一個(gè)服務(wù)稚疹,也就是一個(gè)要運(yùn)行的類居灯,每個(gè)服務(wù)對(duì)應(yīng)有兩個(gè)字典,一是observers贫堰,另一個(gè)event_handlers穆壕,event_handlers是該類中保存的對(duì)應(yīng)事件和觸發(fā)函數(shù)。運(yùn)行時(shí)會(huì)顯示CONSUMES ev_cls.__name__
啟動(dòng)一個(gè)服務(wù)可能要依賴其他服務(wù)其屏,比如ofp_event是所有APP都需要依賴的服務(wù)喇勋,被依賴的服務(wù)要提供相關(guān)服務(wù),提供的服務(wù)就保存在observers中偎行。
回到instantiate_apps函數(shù)川背,接下來贰拿,所有APP都啟動(dòng)。

        threads = []
        for app in self.applications.values():
            t = app.start()
            if t is not None:
                app.set_main_thread(t)
                threads.append(t)
        return threads
    def start(self):
        """
        Hook that is called after startup initialization is done.
        """
        self.threads.append(hub.spawn(self._event_loop))

    def _event_loop(self):
        while self.is_active or not self.events.empty():
            ev, state = self.events.get()
            self._events_sem.release()
            if ev == self._event_stop:
                continue
            handlers = self.get_handlers(ev, state)
            for handler in handlers:
                try:
                    handler(ev)
                except hub.TaskExit:
                    # Normal exit.
                    # Propagate upwards, so we leave the event loop.
                    raise
                except:
                    LOG.exception('%s: Exception occurred during handler processing. '
                                  'Backtrace from offending handler '
                                  '[%s] servicing event [%s] follows.',
                                  self.name, handler.__name__, ev.__class__.__name__)

hub.spawn(self._event_loop)熄云,利用eventlet框架創(chuàng)建一個(gè)協(xié)程膨更,具體細(xì)節(jié)不深究。重點(diǎn)關(guān)注_event_loop函數(shù)缴允。先搞清楚以下兩個(gè)屬性:

self.events = hub.Queue(128)
self._events_sem = hub.BoundedSemaphore(self.events.maxsize)

即調(diào)用hub文件中
Queue = eventlet.queue.LightQueue
BoundedSemaphore = eventlet.semaphore.BoundedSemaphore

查閱官網(wǎng)資料荚守,

class eventlet.queue.LightQueue(maxsize=None)
This is a variant of Queue that behaves mostly like the standard Stdlib_Queue. It differs by not supporting the task_done
or joinmethods, and is a little faster for not having that overhead.

創(chuàng)建隊(duì)列,和 Stdlib_Queue類似练般,但是性能相比較更好矗漾。

calss eventlet.semaphore.BoundedSemaphore(value=1)
A bounded semaphore checks to make sure its current value doesn’t exceed its initial value. If it does, ValueError is raised. In most situations semaphores are used to guard resources with limited capacity. If the semaphore is released too many times it’s a sign of a bug. If not given, value defaults to 1.
release(blocking=True)
Release a semaphore, incrementing the internal counter by one. If the counter would exceed the initial value, raises ValueError. When it was zero on entry and another thread is waiting for it to become larger than zero again, wake up that thread

值得注意的是:

其中一個(gè)特殊的app是opf_handler app,其重寫了start函數(shù)薄料,其實(shí)調(diào)用start后就是啟動(dòng)OpenFlowController類敞贡,OpenFlowController啟動(dòng)后,ryu開始監(jiān)聽來自交換機(jī)的新連接


總結(jié):

整個(gè)RYU程序的啟動(dòng)可以分為兩個(gè)部分摄职,第一部分是APP的加載誊役,上下文環(huán)境的加載,訂閱事件的注冊(cè)與分發(fā)谷市。
第一部分主要分為以下幾個(gè)步驟蛔垢,在一些步驟中會(huì)有比較重要的數(shù)據(jù)結(jié)構(gòu)需要注意,一開始有個(gè)數(shù)據(jù)結(jié)構(gòu)要特別注意迫悠, set_ev_cls修飾的函數(shù)啦桌,表示訂閱事件,會(huì)添加一個(gè)method及皂, handler.callers[e] = _Caller(_listify(dispatchers), e.__module__)。:

  • 1且改、初始化APP管理類验烧,AppManager
  • 2、 生成APP服務(wù)列表(并不實(shí)例化)又跛,只是保存在對(duì)應(yīng)字典中碍拆,self.applications_cls[app_cls_name] = cls,保存每個(gè)APP的名字和第一個(gè)功能類(滿足是RyuApp子類等條件)
  • 3、加載每個(gè)app所依賴的模塊慨蓝,保存在contexts_cls.setdefault(key, context_cls)
  • 4感混、實(shí)例化依賴模塊,即contexts_cls中的類。
  • 5礼烈、實(shí)例化APPs弧满。需要實(shí)例化的APP保存在applications_cls字典中。創(chuàng)建以下數(shù)據(jù)結(jié)構(gòu)此熬,一是SERVICE_BRICKS[app.name] = app表示服務(wù)鏈庭呜,二是所有訂閱事件集合event_handlers滑进,保存事件和觸發(fā)函數(shù)的字典。三是observers字典募谎,注冊(cè)在OFPHander中扶关,key是en_cls事件類,value為字典数冬,其中key為類的名稱节槐,value為一個(gè)集合,其中保存的states拐纱。
  • 6铜异、運(yùn)行每一個(gè)APP,并以協(xié)程方式管理戳玫。要注意的是 OFPHander重寫了start函數(shù)熙掺,在該函數(shù)中啟動(dòng)了控制器類。下一篇文章中做深入分析咕宿。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末币绩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子府阀,更是在濱河造成了極大的恐慌缆镣,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件试浙,死亡現(xiàn)場(chǎng)離奇詭異董瞻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)田巴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門钠糊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人壹哺,你說我怎么就攤上這事抄伍。” “怎么了管宵?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵截珍,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我箩朴,道長(zhǎng)岗喉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任炸庞,我火速辦了婚禮钱床,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘燕雁。我一直安慰自己诞丽,他們只是感情好鲸拥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著僧免,像睡著了一般刑赶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上懂衩,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天撞叨,我揣著相機(jī)與錄音,去河邊找鬼浊洞。 笑死牵敷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的法希。 我是一名探鬼主播枷餐,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼苫亦!你這毒婦竟也來了毛肋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤屋剑,失蹤者是張志新(化名)和其女友劉穎润匙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唉匾,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡孕讳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巍膘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厂财。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖峡懈,靈堂內(nèi)的尸體忽然破棺而出蟀苛,到底是詐尸還是另有隱情,我是刑警寧澤逮诲,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站幽告,受9級(jí)特大地震影響梅鹦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜冗锁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一齐唆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冻河,春花似錦箍邮、人聲如沸茉帅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽堪澎。三九已至,卻和暖如春味滞,著一層夾襖步出監(jiān)牢的瞬間樱蛤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工剑鞍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昨凡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓蚁署,卻偏偏與公主長(zhǎng)得像便脊,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子光戈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉哪痰,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,715評(píng)論 0 9
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,144評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)田度,斷路器妒御,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • 名稱 libev - 一個(gè) C 編寫的功能全面的高性能事件循環(huán)。 概要 示例程序 關(guān)于 libev Libev 是...
    hanpfei閱讀 15,269評(píng)論 0 5
  • 任用賢能,開疆?dāng)U土奸笤,統(tǒng)一貨幣惋啃,統(tǒng)一文字,統(tǒng)一度量衡监右,實(shí)行郡縣制边灭,每一項(xiàng)功績(jī)均可名留青史。但在后世的史書里健盒,他是殘暴...
    昨日學(xué)堂閱讀 432評(píng)論 1 3