app的启动

前言

本篇文章讲解ios的应用程序的启动

应用程序的加载

点击一个app

首先,我们在手机上点击一个app图标

内核初始化

  • 操作系统收到启动app的消息后,会调用内核代码初始化内存空间,为app创建进程
  • 然后操作系统通过系统调用读取并解析app的可执行文件
  • 然后操作系统的动态链接器根据app的可执行文件的符号表去加载app运行依赖的动态库,或者叫共享库,并与符号表进行绑定,如果动态库已经加载过的话就直接绑定就行了,因为很多动态库是app共享的,可能在内存常驻。
  • 经过动态绑定以后,app的所有可执行代码和数据都已经准备就绪了,这个时候操作系统为app分配堆栈空间,拷贝代码和数据到内存等一系列操作
  • 然后确定程序启动的内存位置,开始执行具体的二进制指令

从开始到main函数

代码指令并不是从main函数开始执行的,在main函数之前还有一系类的关于代码的初始化操作,比如:

  • runtime运行时初始化。
  • 全局变量的初始化

main

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

上面代码是一个main函数的执行代码,一般我们不需要修改这个函数中的代码,main函数主要的操作就是执行函数UIApplicationMain,该函数的原型如下:

// argc、argv:直接传递给UIApplicationMain进行相关处理即可
// principalClassName:指定应用程序类名(app的象征)
// 	该类必须是UIApplication(或子类)。
// 	如果为nil,则用UIApplication类作为默认值
// delegateClassName:指定应用程序的代理类,该类必须遵守UIApplicationDelegate协议
int UIApplicationMain(int argc, char * _Nullable argv[_Nonnull], NSString * _Nullable principalClassName, NSString * _Nullable delegateClassName);
  • 这也是第一步,执行UIApplicationMain

  • 第二步在UIApplicationMain内部执行,根据principalClassName创建UIApplication对象

  • 第三步根据delegateClassName创建一个delegate对象,并将该delegate对象赋值给UIApplication对象中的delegate属性。所以,我们的AppDelegate遵循UIApplicationDelegate协议

    @interface AppDelegate : UIResponder 	<UIApplicationDelegate>
    
  • 开启一个主运行循环,处理事件,可以让程序保持运行

  • 接下来,要加载info.plist,info.plist相当于我们应用程序的配置文件,在这里我们需要分开说明一下接下来的步骤啦,因为苹果公司在iOS 13的时候引入了多窗口的概念

IOS 13之前

  • 如果我们在info.plist设置了Launch screen interface file base name,程序会先加载Launch screen界面,这是个启动界面

  • 如果我们在info.plist设置了Main storyboard file base name,程序会加载Main storyboard界面,该文件中保存了我们app的Root View Controller,否则直接跳到下一步

  • 调用代理的application:didFinishLaunchingWithOptions:方法,一般情况下,如果我们没有创建主界面的话,会在这一步创建主界面,如果我们设置了Main storyboard file base name,然后又在该函数内创建了一个新界面,新界面会覆盖之前的界面。

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

在IOS 13之前,应用程序的状态监听都是通过UIApplicationDelegate协议实现的,下面列出常用的UIApplicationDelegate协议。

UIApplicationDelegate协议
// 该方法是程序启动,但还没进入状态保存时执行。
// 该方法在启动界面之后执行
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(nullable NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions API_AVAILABLE(ios(6.0));

// 该方法是程序启动基本完成,准备开始运行时执行。
// 该方法在启动界面之后执行,一般需要在该方法内创建主界面
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions API_AVAILABLE(ios(3.0));

// 该方法是当程序将要进入非活动状态时执行,在此期间,应用程序不接收消息或事件,比如按下Home键或者来电话了。
- (void)applicationWillResignActive:(UIApplication *)application

// 该方法是当程序已经进入后台时执行
- (void)applicationDidEnterBackground:(UIApplication *)application

// 该方法是当程序将要进入前台时执行
- (void)applicationWillEnterForeground:(UIApplication *)application

// 该方法是当程序进入活动状态时执行
- (void)applicationDidBecomeActive:(UIApplication *)application

// 该方法是当程序程序将要退出时执行
- (void)applicationWillTerminate:(UIApplication *)application

IOS 13之后

如果我们设置ios程序的Mimimum Deployments的iOS版本小于13,程序依旧采用上述的UIApplicationDelegate协议,但是如果我们的iOS版本大于13,上述UIApplicationDelegate协议的

  • applicationWillResignActive
  • applicationDidEnterBackground
  • applicationWillEnterForeground
  • applicationDidBecomeActive

都不会执行,因为这几种状态被认为是scene的状态改变而不是应用程序的状态改变了。事实上,info.plist的加载都需要改变。

为了实现多窗口功能,苹果修改了使用多年的AppDelegate,把AppDelegate分为了两部分,AppDelegate和SceneDelegate,其中

  • AppDelegate处理应用程序状态的改变,比如应用程序的加载,退出

  • AppDelegate中新增对scene的支持

    // 新建场景时会调用,返回场景配置信息
    // 我们一般在这个里面记载Scene Configuration
    // 下面是一个例子:return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];

    • (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options

    // 关闭场景时调用

    • (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions
  • SceneDelegate处理场景的生命周期

    // 配置UIWindow"窗口"并将其附加到所提供的UIWindowScene"场景"
    // 如果在配置文件中配置了storyboard。UIWindow"窗口"将自动初始化并附加到场景

    • (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions

    // 在系统释放场景时或者场景进入后台调用

    • (void)sceneDidDisconnect:(UIScene *)scene

    // 当场景从非活动状态移动到活动状态时调用。
    // 使用此方法可以重新启动场景处于非活动状态时暂停(或尚未启动)的任何任务。

    • (void)sceneDidBecomeActive:(UIScene *)scene

    // 当场景将从活动状态移动到非活动状态时调用。
    // 比如来电话或切入后台

    • (void)sceneWillResignActive:(UIScene *)scene

    // 在场景从背景过渡到前景时调用。

    • (void)sceneWillEnterForeground:(UIScene *)scene

    // 场景进入后台后调用

    • (void)sceneDidEnterBackground:(UIScene *)scene

程序支持的场景需要在Info.plist中声明,由Info.plist->Application Scene Manifest->Scene Configuration->Application Session Role节点指定场景List,每一项包含以下节点:

  • Configuration Name:场景配置的唯一标识;
  • Delegate Class Name:实现UIWindowSceneDelegate代理类的名称;
  • Storyboard Name:Storyboard的名称(如果采用的是Storyboard方式实现UI),可选。

使用Scene Delegate之后,UIApplicationDelegate将不再持有UIWindow,它将转移至UIWindowSceneDelegate代理中

最后我们从加载配置文件开始重新梳理一下对于大于IOS13的启动过程

  • 首先调用AppDelegate中的configurationForConnectingSceneSession协议,该协议会返回默认加载场景的UISceneConfiguration*
  • Info.plist->Application Scene Manifest->Scene Configuration->Application Session Role中寻找场景列表,如果有,根据上一步返回的值匹配应该加载哪个场景,如果上一步返回nil,匹配列表中的第一个。如果列表为空,不加载场景,这时候应该是黑屏
  • 如果匹配到了场景信息
    • 如果包含storyboard,加载界面storyboard到UIWindow,执行下一步
    • 否则,继续执行下一步
  • 开始执行场景委托的willConnectToSession协议方法
  • 该协议中可以初始化并显示UIwinodw,如果不进行处理,UIWindow就用之前加载过的storyboard
  • 显示界面,完成。
相关推荐
OKXLIN5 小时前
IOS UITextField 无法隐藏键盘问题
ios·objective-c
Macdo_cn13 小时前
My Metronome for Mac v1.4.2 我的节拍器 支持M、Intel芯片
macos·音视频
吹泡泡的派大星14 小时前
从0-1搭建mac环境最新版
macos
zhouwu_linux14 小时前
MT7628基于原厂的SDK包, 修改ra1网卡的MAC方法。
linux·运维·macos
丁总学Java14 小时前
在 macOS 的 ARM 架构上按住 Command (⌘) + Shift + .(点)。这将暂时显示隐藏文件和文件夹。
macos
青木川崎14 小时前
Mac下常用命令
macos
ClaNNEd@14 小时前
Mac端homebrew安装配置
macos·brew
nicekwell15 小时前
macos sequoia 禁用 ctrl+enter 打开鼠标右键菜单功能
macos
丁总学Java19 小时前
在 Mac ARM 架构的 macOS 系统上启用 F1 键作为 Snipaste 的截屏快捷键
macos·snipaste
天下皆白_唯我独黑1 天前
brew Nushell mac升级版本
macos