For the sake of my job, I need to study the principle of the iOS’s Run Loops in Chromium(Under the framework of chromium, we are able to implement app’s module which can cross platform).Let’s start with the usage code of Chromium:

This’s a simple example, the BasicFunction will be executed twice, and the second time will delay 5 seconds.

##The question is, how it work in iOS?

Before continue reading the content, you should know about iOS’s Run Loops.

Let’s introduce the “Thread-Task-Model” in the chromium first. Here is the major concepts of it:

  1. Thread
  2. MessageLoop
  3. Task

There will be a MessageLoop in each Thread, and each MessageLoop will contain a TaskQueue, which including a lot of task. So we can post a task to a MessageLoop which in another thread to schedule work. Here is a flowchart of how they work together:

As you saw, there is a Default RunLoop in Worker Thread. The RunLoop contains a incoming_queue and a work_queue. It was hung up when Run() called. When task comes, the Worker Thread will be woken up, and do all the tasks in the work_queue which is swapped from the incoming_queue. The code of “Run” in Default RunLoop is here:

void MessagePumpDefault::Run(Delegate* delegate) {
  DCHECK(keep_running_) << "Quit must have been called outside of Run!";
  for (;;) {
	#if defined(OS_MACOSX)
    	mac::ScopedNSAutoreleasePool autorelease_pool;
	#endif
	
    bool did_work = delegate->DoWork();
    if (!keep_running_)
      break;

    did_work |= delegate->DoDelayedWork(&delayed_work_time_);
    if (!keep_running_)
      break;

    if (did_work)
      continue;

    did_work = delegate->DoIdleWork();
    if (!keep_running_)
      break;

    if (did_work)
      continue;

    ThreadRestrictions::ScopedAllowWait allow_wait;
    if (delayed_work_time_.is_null()) {
      event_.Wait();
    } else {
      TimeDelta delay = delayed_work_time_ - TimeTicks::Now();
      if (delay > TimeDelta()) {
        event_.TimedWait(delay);
      } else {
        // It looks like delayed_work_time_ indicates a time in the past, so we
        // need to call DoDelayedWork now.
        delayed_work_time_ = TimeTicks();
      }
    }
    // Since event_ is auto-reset, we don't need to do anything special here
    // other than service each delegate method.
  }

  keep_running_ = true;
}

The code above told us that there is a “for(;;)” loop in the Worker Thread, keeps the thread running. But it doesn’t work when we put this “for(;;)” loop in UIThread. As we know, there are UIEvent to handle in addition to tasks, let’s take a look at the solution in chromium:

1, Attach Timer and Source to MainRunLoop

Chromium create a TimerContext and three SourceContext. they will be added to the main loop of ios in UIThread. The UIApplicationMain function will handle the main loop and deal with UIEvent inside. How we know that the UIEvent is handled inside UIApplicationMain, check below:

According to the call stack of an UIEvent, we know that there is a _UIApplicationHandleEventQueue which takes over the UIEvent and sents them to UIApplication.

2, Post task from work thread, signal the context to do work

When we post a task to UIThread, the task will be putted into the incoming_queue in the UILoop, and signals the work_source right away. The work_source will be activated, and chromium will do a work in the handler. After the work is done, it will “Resignal” if there are tasks in work_queue. Then exits the handler in the end.

That’s the principle of the chromium’s message loop in iOS. But how the delay tasks work with the timer, and what the meaning of nestable task? May by the article just use the little to get the big, you should find the answer in the code or discuss with me. In the end, give some code of loop below:

The attaching code:

run_loop_ = CFRunLoopGetCurrent();
CFRetain(run_loop_);

// Set a repeating timer with a preposterous firing time and interval.  The
// timer will effectively never fire as-is.  The firing time will be adjusted
// as needed when ScheduleDelayedWork is called.
CFRunLoopTimerContext timer_context = CFRunLoopTimerContext();
timer_context.info = this;
delayed_work_timer_ = CFRunLoopTimerCreate(NULL,                // allocator
                                           kCFTimeIntervalMax,  // fire time
                                           kCFTimeIntervalMax,  // interval
                                           0,                   // flags
                                           0,                   // priority
                                           RunDelayedWorkTimer,
                                           &timer_context);
CFRunLoopAddTimer(run_loop_, delayed_work_timer_, kCFRunLoopCommonModes);

CFRunLoopSourceContext source_context = CFRunLoopSourceContext();
source_context.info = this;
source_context.perform = RunWorkSource;
work_source_ = CFRunLoopSourceCreate(NULL,  // allocator
                                     1,     // priority
                                     &source_context);
CFRunLoopAddSource(run_loop_, work_source_, kCFRunLoopCommonModes);

source_context.perform = RunIdleWorkSource;
idle_work_source_ = CFRunLoopSourceCreate(NULL,  // allocator
                                          2,     // priority
                                          &source_context);
CFRunLoopAddSource(run_loop_, idle_work_source_, kCFRunLoopCommonModes);

source_context.perform = RunNestingDeferredWorkSource;
nesting_deferred_work_source_ = CFRunLoopSourceCreate(NULL,  // allocator
                                                      0,     // priority
                                                      &source_context);
CFRunLoopAddSource(run_loop_, nesting_deferred_work_source_,
                   kCFRunLoopCommonModes);

CFRunLoopObserverContext observer_context = CFRunLoopObserverContext();
observer_context.info = this;
pre_wait_observer_ = CFRunLoopObserverCreate(NULL,  // allocator
                                             kCFRunLoopBeforeWaiting |
                                                kCFRunLoopAfterWaiting,
                                             true,  // repeat
                                             0,     // priority
                                             StartOrEndWaitObserver,
                                             &observer_context);
CFRunLoopAddObserver(run_loop_, pre_wait_observer_, kCFRunLoopCommonModes);

pre_source_observer_ = CFRunLoopObserverCreate(NULL,  // allocator
                                               kCFRunLoopBeforeSources,
                                               true,  // repeat
                                               0,     // priority
                                               PreSourceObserver,
                                               &observer_context);
CFRunLoopAddObserver(run_loop_, pre_source_observer_, kCFRunLoopCommonModes);

enter_exit_observer_ = CFRunLoopObserverCreate(NULL,  // allocator
                                               kCFRunLoopEntry |
                                                   kCFRunLoopExit,
                                               true,  // repeat
                                               0,     // priority
                                               EnterExitObserver,
                                               &observer_context);
CFRunLoopAddObserver(run_loop_, enter_exit_observer_, kCFRunLoopCommonModes);

The handler of work_context

void MessagePumpCFRunLoopBase::RunWorkSource(void* info) {
  MessagePumpCFRunLoopBase* self = static_cast<MessagePumpCFRunLoopBase*>(info);
  self->RunWork();
}


评论需要翻墙 for disqus