从 C 语言的 main 入手看 iOS 应用启动过程及进化
太阳火神的美丽人生 (http://blog.csdn.net/opengl_es)
本文遵循“署名-非商业用途-保持一致”创作公用协议
iOS 应用启动流程,这个话题早在09年就非常熟悉,然而时隔多年,不知是否还熟悉,尤其 StoryBoard 的引入,那么下面就一起来看看吧,如果确实说明白了,给个评论,或哪里有不足,需要完善,也给个指点。
由于 Objective-C 是对 C 的扩展,那么 main 函数理所当然地继承了程序入口的位置,而不像安卓,虽然它的程序入口点可能也是 main ,但那是掩埋在系统框架之内根源处的,也或许叫别的名字,想了解可参考Android系统启动过程。
在 XCode 5.1.1 (2014-07-20 周日,此时 iOS 8 已经发布,但还未正式上架应用,beta 3 据说已经可以供开发者偿鲜)中新建一个单视图应用 (Single View Application)。
XCode 工程中总有很多罗里巴山的文件,不过这也正是它先进之处,控制权集中,撒出多个点,来供开发者配置以改变应用的运行效果,或许用傻瓜式的应用架构方式更贴切一些,不过,像 iOS 这样不开源的架构,是否长此以往,我们的后代人是否会真的变成傻瓜,对架构内部的程序艺术完全不了解,丧失了这种架构能力了呢?!
切入主题,程序入口 main.m 文件如下:
#import <UIKit/UIKit.h> #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }main 函数,和 C 语言中的一模一样,带两个参数,argc 是参数个数,argv 是参数的字符串数组,或者叫列表也行。
上面两行中 #import 是Objective-C 中新引入的和 #include 宏指令一样的功能,引入其它头文件。
之所以要引入这个新的指令来包含头文件,是因为 #include 会存在重复引入的问题,即一个头文件被引入多次,那么就可能定义了多个对象或变量,那是会出错的。
所以,在 C 中,会使用宏指令来判断一个头文件中的预定义宏名是否存在,不存在则在宏条件判断中使用 #include 引入头文件,否则不走这一宏分支,头文件就不会被引入。
Availability.h 头文件的精简结构如下,虽然在 -Prefix.pch 文件中是使用 #import 引入的该头文件,但也不可掉以轻心,因为该文件还有可能在 C 代码中使用 #include 引入,所以仍然加了 C 样式的唯一引入宏结构:
#ifndef __AVAILABILITY__ #define __AVAILABILITY__ #include <AvailabilityInternal.h> #endif /* __AVAILABILITY__ */
这么麻烦地对头文件进行处理,真是浪费时间,那么 #import 应运而生,不用担心头文件重复引入的问题。不过,别高兴得太早,循环引用的问题,还是没办法解决的,这个就要用到另一个 @class 来声明类名的存在性以便在声明文件中定义对象引用,而实际头文件引入放在实现文件中。对于复杂些的逻辑,还是得靠你的整体把握能力来避勉循环引用:即 A 引入 B,B 引入 A 。
iOS 应用程序中最常,也算是基本约定俗成的,要引入两个框架:UIKit 和 Foundation。
而在 main.m 文件中,注释掉 UIKit 的引入,程序也是会正常运行的,因为并未用到 UIKit,只是程序模板默认加上的,可能是其它类型应用会用到吧,也许!
自动释放池,这里不作深入介绍,因为我们当前创建的应用,都默认是支持 ARC (自动引用计数 Automatic Reference Counting)的,所以使用这种配套的自动释放池方法。
@autoreleasepool {
}
早先的方式,已经过时,这种新的方式,也支持 MRC(手动引用计数 Manual Reference Counting)源文件的引入,只不过需要对源文件编译部分进行相应参数设置,需加上
-fno-objc-arc
相反,在早期的 MRC 项目中,所使用的方式已经过时,当下的 XCode 不会给你创建这样的模板代码,而且在用以 ARC 为主的工程时,那个也不会得到预编译的很好处理。如果确实需要的话,在 MRC 项目中引入 ARC 的源文件,在该 ARC 源文件的编译选项中,输入
-fobjc-arc
ARC 无非就是预编译时,由编译器根据需要替你把需要加上的 MRC 中的相应内存管理的代码加上去,而非真正的动态内存管理,即垃圾回收,所以它的效率和内存占用率要优质得多。
接下来,
UIApplicationMain将成为重点内容,它就相当于 MFC 中 WinMain 函数。
对于有窗体的应用,它要呈现界面元素,并响应用户的操作,比如鼠标、键盘、触屏等用户操作事件,以及移动设备的各类传感器事件等等。
这就要求带窗体的应用,要能够循环查询到这样的事件,或许事件直接触发可能会更快捷和节省资源,但那样的后果是耦合性太强了。
所以,轮询和事件触发相结合,适当的分配,才能达到预期的效果。事件由系统分发给每个或者移动设备的当前应用,这需要由事件缓存池来完成,而每个应用轮询期自身得到的事件,或由外部置入事件池,或由内部发生,得到这个事件后,再进行触发,而将需要直接调用的回调函数进行缓存,注意啦,在应用内,变成了缓存事件回调函数,而非事件本身,事件本身相当于直接传递。由此就很好地解决了轮询带来的低效率和高资源占用与直接回调的耦合紧密等等弱点。
UIApplicationMain 函数有四个参数:
UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
第一个和第二个参数是 C 程序体的传入参数,直接传入,不做任何加工;
第三个参数是要创建的 iOS 应用实例的类,该类是 UIApplication 或其子类,每个应用仅此一个;
第四个参数是要创建的应用代理类,该类的功能其实是可以在应用类中完成的,不过这种代理模式能很好地把这部分功能从应用类中分离出来,这也是苹果采用代理模式处理各种事件响应的高明之处。开发人员只要按代理方法的要求进行处理就可以了,对于统一规范预期工作任务很有帮助,不至于由于开发人员的疏忽,而把程序结构搞得混乱。
下面就针对 UIApplicationMain 未指定代理类和指定代理类两种情况对进一步的应用启动过程作以简要叙述。
还有就是主窗体的来源,应用类实例会从 xxx-Info.plist 中读取相关配置信息。
其中 Main nib file base name 决定了从 nib 文件还是 StoryBoard 中获取应用的窗口信息,
1、如果是 nib,那么主窗口也从该文件中读取,接下来,就可以从应用代理中,给主窗口设置根视图控制器;
如果 UIApplicationMain 的第四个参数应用代理类为 nil 的话,应用不知如何找到应用代理类进行实例化,它就会在这个主 nib 文件中寻找,寻找的规则是:
(这句话是错的,不是第四参为空,才从 nib 中取应用代理,而是指定了主 nib ,就会默认在这里找,如果找不到,再使用第四参)
a、主 nib 文件中的 File's Owner 是当前应用类,所以设成 UIApplication 或你指定的子类,然后在主 nib 中添加一个对象,设定它为应用代理类,
并从 File's Owner 拉线到刚加入的设成应用代理类的对象上,这时会弹出选项,选择 Outlets 下的 delegate,这样就把应用代理类关联到了应用类的代理引用上了。
以下 UIApplication.h 中 UIApplication 的声明可以让你明白应用类与应用代理类的关系是组合的方式,至于面向对象复用机制的两种方式继承与组合,可以参见 继承和组合:
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIApplication : UIResponder <UIActionSheetDelegate> { @package id <UIApplicationDelegate> _delegate;b、如果仍找不到的话,也即 File's Owner 所对应的应用类的 delegate 未指定应用代理类,就会偿试使用 UIApplicationMain 的第四个参数应用代理类。
c、如果 主 nib 中 File's Owner 没给应用的 delegate 指定代理类,而且 UIApplicationMain 的第四个参数也为 nil,这时应用一样会启动, nib 中的主窗口一样会显示,但没有代理类,无法接收到应用类的应用生命周期的相关事件。
2、如果是 StoryBoard,主窗口就由应用自动创建,并将 StoryBoard 的第一个视图控制器作为主窗口的根视图控制器。
3、如果为空,则应用就不管主窗口的事情了,由开发者自已来搞定,此时运行,会在模拟器中看到黑屏应用,即没有窗口,当然了,状态栏作为窗口的一部分。
那么,主窗口从哪儿创建呢?这就涉及到应用代理类了,因为窗口是可以动态创建并指定的,而且窗口是可以有多个的,但主窗口只有一个,并且只有一个处于可见状态。
写了一下午,边测试,边确认,边构思、组织语言与代码,尚缺一些实际测试过程的截图确认,这个后续再补上。
好像是缺点什么,当时想到了,专注于某一个话题后再回到上面来俯视,发现忘记了......
回头再说,想起来,随时补充。
当然了,这其中还包括,查到的与我这里话题相当的十几二十篇非常不错的参考资料,先建个所有Chrome 的标签组吧,今天真的有点累了,该天再续。