盛年不重来,一日难再晨。及时宜自勉,岁月不待人。

1. 程序入口

  在我们开始开发app的时候,第一步往往是通过设置AppDelegate.m的代理方法开始写一些启动的东西,然后再通过控制器ViewController.m实现相应的布局。

// AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions; // 实现一些布局
- (void)viewDidLoad;

  而不会去过多关注程序的入口,真正的app程序入口是通过工程根目录下Supporting Files -> main.m 执行的main函数。如下:

#import <UIKit/UIKit.h>
#import "AppDelegate.h" int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

2. UIApplicationMain函数

2.1)UIApplicationMain函数

UIApplicationMain(<#int argc#>, <#char * _Nullable * _Nonnull argv#>, NSString * _Nullable principalClassName, <#NSString * _Nullable delegateClassName#>)

  四个参数:

  • argh :代表的是长度;
  • argv:代表的是char 型数组,系统默认传进来的;

  然后主要分析后面两个参数:

  • principalClassName:UIApplication类或者其子类的类名,如果传 nil 默认是 UIApplication;
  • delegateClassName:UIApplication 的代理类的类名;

If nil is specified for principalClassName, the value for NSPrincipalClass from the Info.plist is used. If there is no.

NSPrincipalClass key specified, the UIApplication class is used. The delegate class will be instantiated using init.

  UIApplicationMain方法定义,后面两个参数都是NSString类型的,根据参数字面意思都是类名,第一个默认传nil,那具体代表的是哪个类?我们先从最后一个参数看起,最后一个是一个代理类类名,即AppDelegate的类名,NSStringFromClass([AppDelegate class]等价于@“AppDelegate”,即把类名转换为字符串。AppDelegate这个是一个代理类,这个代理是实现的是谁的代理呢?查看AppDelegate.h发现是实现的UIApplication的代理,再根据苹果给出的注释来看,当这个类名为空时,先从Info.plist中读取NSPrincipalClass属性值,如果这个属性值不存在,则使用UIApplication类,说明最后两个参数一个是传UIApplication单例类,一个是实现UIApplication的代理AppDelegate,所以UIApplicationMain也可以改为UIApplicationMain(argc, argv, @"UIApplication", @"AppDelegate");其中第三个参数也可以是UIApplication类的子类。

2.2)UIApplicationMain死循环验证

  main函数的返回值是一个int类型那么我们定义一个变量接收并打印,看看这个参数是什么,能不能打印?

int main(int argc, char * argv[]) {
@autoreleasepool {
int value = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"%d",value);
return value;
//return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

  经过测试,日志是没有输出的,说明UIApplicationMain是个死循环。

  我们再看一下UIApplicationMain死循环是什么?其实就是我们所说的runloop,那么内部开启死循环runloop的目的是什么?

  1. 保证当前线程(主线程)不被退出

  2. 负责监听事件(包括触摸事件、网络等等)

2.3)UIApplicationMain函数做了哪些工作和任务

  UIApplicationMain函数中创建了一个UIApplication对象,每个iOS程序有且仅有一个UIApplication对象,此对象是单例,负责单例对象的维护和循环运行事件。程序一旦创建了UIApplication单例对象,对象就会一直循环下去。当应用程序在运行过程中,UIApplication对象会在应用出现变化时,调用不同的delegate方法发送特定的消息。

  1. 创建UIApplication对象:

    从给定的类名初始化应用程序对象,也就是初始化UIApplication或者子类对象的一个实例,如果你在这里给定的是nil,那么系统会默认UIApplication类,也就主要是这个类来控制以及协调应用程序的运行。在后续的工作中,你可以用静态方法sharedApplication 来获取应用程序的句柄。

  2. 设置了代理:创建APPDelegate对象,并且成为UIApplication对象代理属性:

    从给定的应用程序委托类,初始化一个应用程序委托。并把该委托设置为应用程序的委托,这里就有如果传入参数为nil,会调用函数访问 Info.plist文件来寻找主nib文件,获取应用程序委托。

  3. 启动主事件循环(Runloop)并开始接收事件;

  4. 加载info.plist文件(只读);

    • 通过info.plist的key Main storyboard file base name寻找是否有指定的main.storyboard,有则进入main.storyboard;

    • 如果没有给值,则跳转至自己设置在keyWindowViewController

    • 如果没有给值,也没有在window上设置控制器,那么启动app失败,进入黑屏模式;

3. UIApplication实例职责

  上面讲到UIApplicationMain函数的工作,接下来一个问题是应用程序视图的显示、消息的控制怎么办?下面就是UIApplication(或者子类)对象的职责,这个对象主要做下面几件事:

  1. 负责处理到来的用户事件,并分发事件消息到应该处理该消息的目标对象(sender, action)。
  2. 管理以及控制视图,包括呈现、控制行为、当前显示视图等。
  3. 该对象有一个应用程序委托对象,当一些生命周期内重要事件(可以包括系统事件或者生命周期控制事件)发生时,应用程序通知该对象。例如,应用程序启动、内存不够了或者应用程序结束等,让这些事件发生时,应用程序委托去响应。

  通过上面的分析,可以知道UIApplication对开发者来说,是一个黑箱。因为所有的操作,都可以由它的委托来帮我们完成,它只需要在后面维护一些不可更改的东西,如事件消息分发和传递、给委托发送事件处理请求等等,如,应用程序加载处理完毕,它会发送消息给委托,然后委托可以在 applicationDidFinishLanching委托函数中去实现开发者想要的动作。利用Xcode在创建应用程序时,会默认实现一个应用程序委托类。而对于加载的视图,则有视图相关的委托类来处理视图加载过程的生命事件。下面介绍委托主要可以办哪些事情。

4. 代理类(AppDelegate)职责

  • (1)控制应用程序的行为(运行状态)

    //程序即将启动完成
    //对应未启动状态,告诉代理程序已经进入启动状态但是还没有进入未激活状态
    -(BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    return YES;
    } //程序启动完成
    //对应已经启动状态,准备进入前台开始运行状态,当没有接收到事件时则表示的是未激活状态
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    return YES;
    } //应用程序进入激活状态,应用程序可以接受事件并对其进行处
    -(void)applicationDidBecomeActive:(UIApplication *)application{ } //应用程序放弃了活动状态进入未激活状态,在此状态中应用程序无法接受事件进行处理
    -(void)applicationWillResignActive:(UIApplication *)application{ } //应用程序进入后台,在后台继续执行的代码在此可以进行处理即可
    - (void)applicationDidEnterBackground:(UIApplication *)application { } //应用程序将要进入前台,包含两个状态未激活和激活状态
    -(void)applicationWillEnterForeground:(UIApplication *)application{ } //程序将要终止退出,用来保存一些数据和转状态,以及应用程序退出前的内存清理工作
    -(void)applicationWillTerminate:(UIApplication *)application{ } // 应用程序完成载入
    -(void)applicationDidFinishLaunching:(UIApplication*)application{ }
  • (2)通知委托,应用程序收到了来自系统的内存不足警告

    -(void)applicationDidReceiveMemoryWarning:(UIApplication *)application{}
  • (3)通知委托系统时间发生改变(主要是指时间属性,而不是具体的时间值)

    -(void)applicationSignificantTimeChange:(UIApplication *)application{}
  • (4)打开URL

    -(BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url{}
  • (5)控制状态栏方位变化

    - (void)application:(UIApplication *)application willChangeStatusBarOrientation:(UIInterfaceOrientation)newStatusBarOrientation duration:(NSTimeInterval)duration
  • (6)设备方向将要发生改变

    - (void)application:(UIApplication *)application didChangeStatusBarOrientation:(UIInterfaceOrientation)oldStatusBarOrientation{}

    各种状态的委托对应的通知

UIKIT_EXTERN NSNotificationName const UIApplicationDidEnterBackgroundNotification      NS_AVAILABLE_IOS(4_0);
UIKIT_EXTERN NSNotificationName const UIApplicationWillEnterForegroundNotification NS_AVAILABLE_IOS(4_0);
UIKIT_EXTERN NSNotificationName const UIApplicationDidFinishLaunchingNotification;
UIKIT_EXTERN NSNotificationName const UIApplicationDidBecomeActiveNotification;
UIKIT_EXTERN NSNotificationName const UIApplicationWillResignActiveNotification;
UIKIT_EXTERN NSNotificationName const UIApplicationDidReceiveMemoryWarningNotification;
UIKIT_EXTERN NSNotificationName const UIApplicationWillTerminateNotification;
UIKIT_EXTERN NSNotificationName const UIApplicationSignificantTimeChangeNotification;

  因此,在UIApplication中处理的系统事件时,只需转到delegate这个类去处理, 这个类对象就是应用程序委托对象。我们可以从应用程序的单例类对象中得到应用程序委托的对象

UIApplicationDelegate* myDelegate = [[UIApplication sharedApplication] delegate];

  UIApplication 接收到所有的系统事件和生命周期事件时,都会把事件传递给UIApplicationDelegate进行处理,对于用户输入事件,则传递给相应的目标对象去处理。比如我们在应用程序被来电等消息后,可以调用应用程序委托类的 applicationWillResignActive()方法,这个方法在用户锁住屏幕时,也会调用,与之相适应的是应用程序重新被用户打开时的委托方法。另外常用的就是内存不足的系统警告,此时会调用应用程序委托类的applicationDidReceiveMemoryWarning()方法, 然后我们就可以试着释放一些内存了。

  iOS 13的一大改进就是支持multiple windows(多窗口)功能,自从Xcode11发布以来,当你使用新XCode创建一个新的iOS项目时,SceneDelegate会被默认创建,iOS13 项目中的SceneDelegate类有什么作用?以及AppDelegate类的新变化,后面会专门说明。

iOS程序入口结构的更多相关文章

  1. 【Xamarin挖墙脚系列:Xamarin.IOS的程序的结构】

    原文:[Xamarin挖墙脚系列:Xamarin.IOS的程序的结构] 开始熟悉Xamarin在开发IOS的结构!!!!!!! 先看官方 这个是以一个单页面的程序进行讲述的. 1 程序引用的程序集,核 ...

  2. [iOS] 使用xib作为应用程序入口 with IDE

    [iOS] 使用xib作为应用程序入口 with IDE 在「使用xib做为应用程序入口 with Code」这篇文章中,介绍了如何透过写Code的方式,来使用xib做为应用程序的入口.但其实在Xco ...

  3. [iOS] 使用xib做为应用程序入口 with Code

    [iOS] 使用xib做为应用程序入口 with Code 前言 开发iOS APP的时候,使用storyboard能够快速并且直觉的建立用户界面.但在多人团队开发的情景中,因为storyboard是 ...

  4. iOS开发系列--IOS程序开发概览

    概览 终于到了真正接触IOS应用程序的时刻了,之前我们花了很多时间去讨论C语言.ObjC等知识,对于很多朋友而言开发IOS第一天就想直接看到成果,看到可以运行的IOS程序.但是这里我想强调一下,前面的 ...

  5. 转:iOS程序main函数之前发生了什么

    原文地址:http://blog.sunnyxx.com/2014/08/30/objc-pre-main/ 我是前言 一个iOS app的main()函数位于main.m中,这是我们熟知的程序入口. ...

  6. iOS程序main函数之前发生了什么

    我是前言 一个iOS app的main()函数位于main.m中,这是我们熟知的程序入口.但对objc了解更多之后发现,程序在进入我们的main函数前已经执行了很多代码,比如熟知的+ load方法等. ...

  7. iOS程序猿如何快速掌握 PHP,化身"全栈攻城狮"?

    这是一篇以 iOS 开发人员的视角写给广大iOS 程序猿的 PHP 入门指南.在这篇文章里我努力去发掘 objectiv-c 与 php 之间的共性,来帮助有一定 iOS 开发经验的攻城狮来快速上手一 ...

  8. 【转】漫谈iOS程序的证书和签名机制

    转自:漫谈iOS程序的证书和签名机制 接触iOS开发半年,曾经也被这个主题坑的摸不着头脑,也在淘宝上买过企业证书签名这些服务,有大神都做了一个全自动的发布打包(不过此大神现在不卖企业证书了),甚是羡慕 ...

  9. 漫谈iOS程序的证书和签名机制

    接触iOS开发半年,曾经也被这个主题坑的摸不着头脑,也在淘宝上买过企业证书签名这些服务,有大神都做了一个全自动的发布打包(不过此大神现在不卖企业证书了),甚是羡慕和崇拜.于是,花了一点时间去研究了一下 ...

  10. 深入浅出-iOS程序性能优化 (转载)

    iOS应用是非常注重用户体验的,不光是要求界面设计合理美观,也要求各种UI的反应灵敏,我相信大家对那种一拖就卡卡卡的 TableView 应用没什么好印象. iOS应用是非常注重用户体验的,不光是要求 ...

随机推荐

  1. CSS3属性 2D转换

    * { margin: 0; padding: 0 } table { border-spacing: 0; border-collapse: collapse; margin: 10px auto ...

  2. msvc++工程之vs版本升级及工程目录规范

    为什么要升级msvc++工程版本 对msvc++工程进行vs版本升级,一方面是可以使用较新的C++标准及对64位更好的支持. 首先你需要对msvc++ project文件有一定的了解,主要是vcxpr ...

  3. uniapp 只选择月份与日的时间选择器

    1.使用 <picker> 组件的 mode 属性设置为 "multiSelector",然后通过设置 range 属性来提供可选的月份和日的列表. <templ ...

  4. nflsoj 5924 选排列

    与全排列略微有些不同,只需要将退出条件需要改成 u==r #include <iostream> using namespace std; const int N = 15; int r, ...

  5. Git-更换服务器问题

    一.Permission denied (publickey) git指令出现Permission denied (publickey),是ssh key过期的问题,需要对ssh key进行更新,所有 ...

  6. 实在智能TARS-RPA-Agent,业界首发的产品级大模型Agent有何非凡之处?

    融合LLM的RPA进化到什么程度? AIGC如何借AI Agent落地? 像生成文本一样生成流程的ChatRPA,能够提升RPA新体验? 边探索边创建的ChatRPA,能否破解RPA与LLM融合难题? ...

  7. QA|20221010|SecureCRT|我们5分钟前执行了a指令,但因为执行b指令打印了大量日志,把指令记录冲掉了,以后如何避免这种情况?

    Q:我们5分钟前执行了a指令,但因为执行b指令打印了大量日志,把指令记录冲掉了,以后如何避免这种情况? A:如下配置

  8. 一键安装lnmp 环境

    一键安装lnmp 环境 目录 一键安装lnmp 环境 操作步骤 1.添加网站(虚拟主机) 2.伪静态管理 3.上传网站程序 4.已存在虚拟主机添加ssl证书开启https 5.列出网站(虚拟主机) 6 ...

  9. defined('BASEPATH') OR exit('No direct script access allowed'); 的作用

    起到保护.php文件的作用, 如果直接访问此php文件会得到"不允许直接访问脚本"的错误提示 如果你是用ci框架或者其他的什么, 就建议加上, 如果你怕别人恶意攻击你的话

  10. 2.14 PE结构:地址之间的转换

    在可执行文件PE文件结构中,通常我们需要用到地址转换相关知识,PE文件针对地址的规范有三种,其中就包括了VA,RVA,FOA三种,这三种该地址之间的灵活转换也是非常有用的,本节将介绍这些地址范围如何通 ...