详解iOS应用程序的启动过程

关键步骤
一个程序从main函数开始启动。

代码如下:

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

可以看到main函数会调用UIApplicationMain函数,它的四个参数的意思是:

  • argc: 代表程序在进入main函数时的参数的个数。默认为1。
  • argv: 代表包含的各个参数。默认为程序的名字。
  • principalClassName: UIApplication或者它的子类的名字, 如果传入的是nil, 则表示UIApplication的名字, 即@"UIApplication"。
  • delegateClassName: UIApplication的代理的名字。

在UIApplicationMain函数中,根据传入的UIApplication名称和它的代理的名称,会主要做下面的事情:

  • 根据传入的名称创建UIApplication对象。
  • 根据传入的代理名称创建UIApplication代理对象。
  • 开启事件循环(如果不进行循环,那么在main函数结束后程序就结束了。要保证程序创建后可以一直存在)。
  • 解析Info.plist文件:

会在Info.plist文件里查找Main storyboard file base name这个Key对应的Value是否有值。如果有值,则表示之后会通过Storyboard加载控制器,AppDelegate会接收到didFinishLaunchingWithOptions消息(程序启动完成的时候),此时Storyboard会进行一系列的加载操作(后面会具体说);如果没有值,则不会通过Storyboard加载控制器,接着AppDelegate会接收到didFinishLaunchingWithOptions消息(程序启动完成的时候),在这个时候需要我们通过代码的方式加载控制器。

注意Info.plist中Main storyboard file base name这个Key并不是真正的Key,而是苹果为了增强可读性才这样写的,真正的Key为UIMainStoryboardFile(可以通过Info.plist文件的源代码查看)。
这就是在想要用代码方式创建控制器而不是Storyboard创建控制器的时候为什么先要将Main Interface设置为空白,这样在解析Info.plist文件的时候才会知道不通过Storyboard创建控制器。
由此可以知道,解析Info.plist文件这一操作主要是看我们用的是Storyboard方式加载还是代码的方式加载。默认Main storyboard file base name为Main,也就是通过Storyboard方式加载控制器。
现在具体分析一下,通过Storyboard方式加载控制器和代码方式加载控制器。

通过Storyboard
通过Storyboard,主要做了下面的事情(这些事情不需要我们做,是系统自动完成的,在程序启动完成的时候):

创建窗口。

创建一个UIWindow的实例用来显示界面。

设置窗口的根控制器。

根据Storyboard的设置,创建一个控制器。
并且设置这个控制器为之前创建的window的根控制器。
显示窗口。(相当于后面提到的makeKeyAndVisible)

设置self.window可见并且设置UIApplication的keyWindow。

在这一步中将根控制器的view添加到window上。

通过代码方式
通过代码的方式,需要我们在didFinishLaunchingWithOptions方法中进行加载控制器的相关操作。

代码如下:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    UIViewController *viewController = [[UIViewController alloc] init];
    self.window.rootViewController = viewController;
    // 此时根控制器的view还没有加到self.window上
    [self.window makeKeyAndVisible];
    // 此时根控制器的view加到self.window上
    return YES;
}

其实这里所做和系统所做是一样的。(相当于系统的做法)

首先创建窗口,得到一个正确的UIWindow实例对象用来显示界面。(self.window是系统自带的属性)

接着设置窗口的根控制器。

不再根据Storyboard中的设置加载,此时需要我们自己创建控制器。
设置这个控制器为self.window的根控制器。
注意这个时候根控制器的view还没有加到self.window上,当窗口要显示的时候,才会把窗口的根控制器的view添加到窗口。(可以输出self.window.subViews来验证)
显示窗口。

代码如下:

[self.window makeKeyAndVisible]实际上做了下面的事:

首先,将self.window设置为UIApplication的keyWindow,这么做是方便我们以后查看UIApplication的主窗口是哪一个。

接着,让self.window可见,相当于执行的代码是:

代码如下:

self.window.hidden = NO;

这么做的原因是self.window默认hidden = YES,所以需要让其显示出来。

那么既然makeKeyAndVisible执行的是以上的操作,实际上将[self.window makeKeyAndVisible]替换为self.window.hidden = NO,那么界面也会正常显示出来,因为makeKeyAndVisible内部就是这么做的。但是此时并没有设置UIApplication的keyWindow,为了以后方便访问,还是用makeKeyAndVisible更好一点。

经过这一步,界面将要显示,此时根控制器的view会加到self.window上以正常显示。

这里有一点要注意:

系统创建的AppDelegate自带一个属性位于.h文件中:

代码如下:

@property (strong, nonatomic) UIWindow *window;

当用Storyboard的方式加载控制器,在应用启动完成的时候(didFinishLaunchingWithOptions)需要一个UIWindow的实例来显示界面,所以Apple提供了这个window属性。系统根据storyboard自动创建一个window,然后将window赋值给这个window属性,以保证完成之后的工作。

当用代码的方式加载控制器,同样的,首先也需要一个UIWindow的实例来显示界面,因为不使用Storyboard所以这次要我们自己创建window。此时有两种做法,第一种是在didFinishLaunchingWithOptions方法中创建一个UIWindow对象:

代码如下:

UIWindow *myWindow = [[UIWindow alloc] initWithFrame:...];

但是如果用这种方法运行程序会发现界面依然无法显示出来,因为此时myWindow是一个局部变量,当didFinishLaunchingWithOptions方法执行完毕这个变量就会销毁。所以更好的办法是直接使用系统提供的window属性:

代码如下:

self.window = [[UIWindow alloc] initWithFrame:...];

之前的例子也是这么做的。

另外,仔细观察会发现这个window属性的修饰符是strong,而不是weak。想想之前使用weak来修饰一个控件是因为这个控件会被加到一个view中,这个view的subViews数组会有强引用指向控件,所以用weak是没有问题的。现在这种情况,因为window控件不会被加到其他view中,即没有其他的强指针指向这个对象,所以在创建的时候需要将修饰符设置成strong以保证创建出的window不会被销毁。(Apple创建的window属性的修饰符是strong)

UIWindow的补充
window是有层级的,并且可以有多个window同时存在。比如:状态栏就是一个window,键盘也是一个window。

可以通过设置UIWindow的对象的windowLevel属性来调整层级。

self.window.windowLevel = UIWindowLevelStatusBar;
window共有三种等级:UIWindowLevelNormal,UIWindowLevelStatusBar UIWindowLevelAlert。如果三种等级同时出现在屏幕上,那么alert在最上面,statusBar在中间,normal则在最下面。

注意:如果一个程序中有多个window,控制器默认会把状态栏隐藏。

解决办法:关闭控制器对状态栏的控制,(为Info.plist增加View controller-based status bar appearance这个key并设置为NO)这样这些window以及状态栏就可以按层级关系正常显示。

概览
这里PY为前缀名:

1.先执行main函数,main内部会调用UIApplicationMain函数

2.UIApplicationMain函数里面做了什么事情:

(1)创建UIApplication对象

(2)创建UIApplication的delegate对象—–PYAppDelegate

(3)开启一个消息循环:每监听到对应的系统事件时,就会通知MJAppDelegate

(4)为应用程序创建一个UIWindow对象(继承自UIView),设置为PYAppDelegate的window属性

(5)加载Info.plist文件,读取最主要storyboard文件的名称

(6)加载最主要的storyboard文件,创建白色箭头所指的控制器对象

(7)并且设置第6步创建的控制器为UIWindow的rootViewController属性(根控制器)

(8)展示UIWindow,展示之前会将添加rootViewController的view到UIWindow上面(在这一步才会创建控制器的view)

代码如下:

[window addSubview: window.rootViewControler.view];

进入main函数,在main.m的main函数中执行了UIApplicationMain这个方法,这是ios程序的入口点!

代码如下:

int UIApplicationMain(int argc, char argv[], NSString principalClassName, NSString *delegateClassName)

argc、argv:ISO C标准main函数的参数,直接传递给UIApplicationMain进行相关处理即可

principalClassName:指定应用程序类,该类必须是UIApplication(或子类)。如果为nil,则用UIApplication类作为默认值

delegateClassName:指定应用程序类的代理类,该类必须遵守UIApplicationDelegate协议

此函数会根据principalClassName创建UIApplication对象,根据delegateClassName创建一个delegate对象,并将该delegate对象赋值给UIApplication对象中的delegate属性

lUIApplication对象会依次给delegate对象发送不同的消息,接着会建立应用程序的main runloop(事件循环),进行事件的处理(首先会调用delegate对象的 application:didFinishLaunchingWithOptions:)

程序正常退出时这个函数才返回。如果进程要被系统强制杀死,一般这个函数还没来得及返回进程就终止了

下面我们有图有真相吧!!!

(0)

相关推荐

  • iOS应用程序之间的几种跳转情况详解

    前言 在iOS开发的过程中,我们经常会遇到比如需要从一个应用程序A跳转到另一个应用程序B的场景.这就需要我们掌握iOS应用程序之间的相互跳转知识.下面我们就常用到的几种跳转情况进行介绍. 一.跳转到另一个程序的主界面 每个程序都该有一个对应的Scheme,以确定对应的url 一个程序要跳转到(打开)另外一个程序,需要将另外一个程序的Scheme添加到自己的应用程序白名单中(在info.plist中配置:LSApplicationQueriesSchemes,类型为数组,在数组中添加相应的Sche

  • 使用设计模式中的Singleton单例模式来开发iOS应用程序

    单例设计模式确切的说就是一个类只有一个实例,有一个全局的接口来访问这个实例.当第一次载入的时候,它通常使用延时加载的方法创建单一实例. 提示:苹果大量的使用了这种方法.例子:[NSUserDefaults standerUserDefaults], [UIApplication sharedApplication], [UIScreen mainScreen], [NSFileManager defaultManager] 都返回一个单一对象. 你可能想知道你为什么要关心一个类有多个的实例.代码

  • IOS 应用程序管理的实现

    IOS 应用程序管理的实现 1. 项目名称:应用管理 2. 项目截图展示 3. 项目功能 展示应用图标,名称和下载按钮 点击下载按钮,出现"正在下载"图标 4. 项目代码 模型代码:AppInfo.h #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface AppInfo : NSObject @property (nonatomic, copy) NSString *name;

  • IOS应用程序多语言本地化的两种解决方案

    最近要对一款游戏进行多语言本地化,在网上找了一些方案,加上自己的一点点想法整理出一套方案和大家分享! 多语言在应用程序中一般有两种做法: 一.程序中提供给用户自己选择的机会: 二.根据当前用户当前移动设备的语言自动将我们的app切换对应语言. 第一种做法比较简单完全靠自己的发挥了,这里主要讲第二种做法,主要分一下几点: 本地化应用程序名称 本地化字符串 本地化图片 本地化其他文件 1.本地化应用程序名称 (1)点击"new file"然后在弹出窗口左侧选择iOS的resource项,在

  • iOS应用程序中通过dispatch队列控制线程执行的方法

    GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行,它类似NSOperationQueue但更复杂也更强大,并且可以嵌套使用.所以说,结合block实现的GCD,把函数闭包(Closure)的特性发挥得淋漓尽致. dispatch队列的生成可以有这几种方式: 1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_

  • 详解iOS应用程序内购/内付费(一)

    很久之前就想出一篇iOS内付费的教程,但是一查网上的教程实在太多了,有的写得真的蛮不错的,就心想算了,于是就保存在草稿箱了.至于为什么写完它呢!真是说来话长,最近公司有个项目经理跑来问我有关苹果内付费相关的细节,跟他聊了半天,从项目对接苹果官方支付接口聊到了如何查看App收益,最后终于使他有了一些眉目,但是悲催的是还要我继续去跟他们项目的程序员讲解(真是疯了),所以我就决定给他们项目写一个内购的文档,所以我顺便把这篇博客完成吧! 首先进入苹果的ItunesConnection(https://i

  • 老生常谈iOS应用程序生命周期

    开发应用程序都要了解其生命周期. 今天我们接触一下iOS应用程序的生命周期, iOS的入口在main.m文件: int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } main函数的两个参数,iOS中没有用到,包括这两个参数是为了与标准ANSI C保持一致.UIAppli

  • 详解iOS应用程序的启动过程

    关键步骤 一个程序从main函数开始启动. 复制代码 代码如下: int main(int argc, char * argv[]) {     @autoreleasepool {         return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));     } } 可以看到main函数会调用UIApplicationMain函数,它的四个参数的意思是: argc: 代表程序在进入m

  • 详解Android壁纸服务的启动过程

    壁纸基础 android中的壁纸分为动态壁纸和静态壁纸两种,两种类型的壁纸都以Service的类型运行在系统后台. 静态壁纸:仅以图片的形式进行展示对于静态壁纸,可以使用WallpaperManager中的getDrawable()等接口获取到当前的bitmap图像. 动态壁纸:显示的内容为动态的内容,同时可以对用户的操作做出响应对于动态壁纸的实时图像,是没办法通过android中原生的接口获取到,需要获取到动态壁纸的图像得自己修改源码. 壁纸实现时涉及的几个主要的类: WallpaperSer

  • 详解Android性能优化之启动优化

    1.为什么要进行启动优化 网上流行一种说法,就是8秒定律,意思是说,如果用户在打开一个页面,在8秒的时间内还没有打开,那么用户大概的会放弃掉,意味着一个用户的流失.从这里就可以看出,启动优化的重要性了. 2.启动的分类 2.1 冷启动 先来看看冷启动的流程图 从图中可以看出,APP启动的过程是:ActivityManagerProxy 通过IPC来调用AMS(ActivityManagerService),AMS通过IPC启动一个APP进程,ApplicationThread通过反射来创建App

  • 详解微信小程序工程化探索之webpack实战

    前言 微信小程序因为其便捷的使用方式,以极快的速度传播开来吸引了大量的使用者.市场需求急剧增加的情况下,每家互联网企业都想一尝甜头,因此掌握小程序开发这一技术无疑是一名前端开发者不可或缺的技能.但小程序开发当中总有一些不便一直让开发者诟病不已,主要表现在: 初期缺乏方便的npm包管理机制(现阶段确实可以使用npm包,但是操作确实不便) 不能使用预编译语言处理样式 无法通过脚本命令切换不同的开发环境,需手动修改对应环境所需配置(常规项目至少具备开发与生产环境) 无法将规范检查工具结合到项目工程中(

  • 详解Android广播Broadcast的启动流程

    目录 正文 广播的注册 广播的解注册 广播的发送 总结 正文 本文整体阅读下来相对Activity和Service的启动流程较容易,比较贴近我们日常代码开发习惯.我们曾经有个整机项目,多个APP跨进程交互,本来想采用AIDL进行的,但最终考虑到项目工期和其他同事的能力,最终在采用广播方式进行IPC. 那时,自己也在想,这么多个APP相互发信息,数据量也大,对整机性能有影响么?会不会存在丢失和内存问题.一脸茫然,网上也不会有类似信息告诉总结这种情况,本文也不会总结这个答案,因为看完之后心中自然有数

  • 详解IOS开发中生成推送的pem文件

    详解IOS开发中生成推送的pem文件 具体步骤如下: 首先,需要一个pem的证书,该证书需要与开发时签名用的一致. 具体生成pem证书方法如下: 1. 登录到 iPhone Developer Connection Portal(http://developer.apple.com/iphone/manage/overview/index.action )并点击 App IDs 2. 创建一个不使用通配符的 App ID .通配符 ID 不能用于推送通知服务.例如,  com.itotem.ip

  • 详解微信小程序 登录获取unionid

    详解微信小程序 登录获取unionid 首先公司开发了小程序, 公众号网页和app等, 之前都是用的openid来区分用户, 但openid只能标识用户在当前小程序或公众号里唯一, 我们希望用户可以在公司各个产品(比如公众号, 小程序, app里的微信登录)之间, 可以保持用户的唯一性, 还好微信给出了unionid. 下面分两步介绍一下 微信小程序 获取unionid的过程. 1. 首先 在微信公众平台注册小程序 , 然后在小程序上模拟登录流程. 注 : 这里只是简单登录流程, 实际中需要维护

  • 详解微信小程序轨迹回放实现及遇到的坑

    微信小程序轨迹回放主要使用到polyline进行划线操作,以及使用marker去进行小车移动操作.效果图如下: 具体实现代码: trackplay.wxml文件 <!--pages/tracker/tracker.wxml--> <map id="mymap" longitude="{{mapCenter.longitude}}" latitude="{{mapCenter.latitude}}" scale="{{s

  • 详解IOS UITableViewCell 的 imageView大小更改

    详解IOS UITableViewCell 的 imageView大小更改 实例代码: - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCell

随机推荐