四月 26, 2018

应用运行剖析

介绍

不管是应用还是游戏,想用持续不断的运行下去,那么必须要有一个事件去持续不断的驱动这个app的运行。

在游戏里面,直接采用每隔固定事件触发一个事件用来更新UI、处理逻辑等。

而在应用里面,其实也是遵循这样的逻辑。只是跟游戏的处理方式有点不一样。

剖析

我们知道,在现代的操作系统中,要运行一个程序,系统会开启一个进程,在iOS中,开启一个进程,相当于创建一个沙盒,app的运行以及所有涉及到的资源都只能在这个沙盒中使用运行。进程创建完毕后系统就会创建一个/N个线程,所有运行中使用到的方法、资源都必须在某一个线程中使用。拿APP的运行来说,直接上代码,

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

当APP启动后,系统首先会创建一个沙盒,然后会开启一条线程(主线程),接着在这个线程上会调用main方法。这个main方法是一个C语言中的全局方法,然后系统会创建一个autoreleasepool,然后是创建AppDelegate。这些事情我们是在代码中看到的,可以从明面上就能知道的事情,但事实上,系统做了更多的事情。

如果我们单从这些代码层面看的话,会发现,一旦mian方法执行完毕,那么意味着这个线程(UI线程)应该会被回收掉,因为在这个线程上执行的方法已经完毕了,但事实上,该线程压根就没有被回收掉,还是继续运行着。

对于一条线程来说,线程有如下几种状态,我直接借用下网上的图片

从上图中可以看到,中间的那一部分其实一个循环,一个线程只有在运行状态,才是CPU真正的在执行线程中所调用的方法/资源。

对于用户而言,最直观的判断一个应用程序是不是在运行中,只要看这个应用对于交互是否有反应。因此,确保用户在交互的时候,主线程能获取到CPU的执行就行了(先忽略因为执行耗时方法阻塞线程的情况)。

线程还要一个优先级的概念,相对来说,高优先级的线程获取到CPU执行时间的机会越大,但是不存在一个可以获取所有CPU时间片的优先级。

应用在启动的时候也会对主线程设置一个优先级。

但这还不够,在线程中如果一个方法执行完毕了,那么该线程就会处于闲置状态。一般而言,在这条线程上干的活干好了,应该释放掉这个线程。但是UI线程不行,因为UI线程需要处理用户的交互啊,那怎么办呢?那就先等待(wait)好了,等到有用户交互的时候再通知UI线程执行方法即可。那么这就形成一个循环了,等待 -> 收到通知 -> 就绪 -> 运行 ->继续等待 。但是这个工作是谁来完成呢?我们没有看到代码中有这个逻辑啊。事实上,这部分工作是由Runloop来完成的。我们如何理解Runloop?其实最简单的理解即是,Runloop就是一个while循环,循环干着上面我们列出的那些事情。那么问题来了,Runloop是如何接受通知的呢?

在应用中,我们可以把上面的通知转换下概念,事件来的更合适,简单起见,我们可以把用户产生的交互行为定为事件,拿用户点击了屏幕来举例吧。

  1. 用户点击了屏幕。系统会产生一个点击事件,将整个事件添加到一个事件队列中。
  2. 系统从事件队列中获取一个事件,发送一个通知。
  3. 线程中的Runloop收到通知后,开始处理这个事件。比如:判断这个点击事件到底是哪个按钮触发的,也即是说判断应用能否处理这个点击事件,如果能够处理,那么就给出相应的交互回馈。
  4. 事件处理完了后继续处理队里中的下一个事件,直到队列中没有为止。
  5. 线程继续wait,等待下一个事件通知。

从这里可以看到,Runloop干的事情很简单,但是很重要。之所以在实际的项目中,我们不会去主动创建Runloop,那是因为对于UI线程,APP启动的时候系统已经默认给我们创建好了。而对于那些非UI线程,是需要我们去主动创建才能接收事件的,但事实上,在实际的需求中可能很少会出现在非UI线程需要创建Runloop的需求。

Posted in iOS

发表评论

电子邮件地址不会被公开。 必填项已用*标注