iOS程序入口结构
盛年不重来,一日难再晨。及时宜自勉,岁月不待人。
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的目的是什么?
-
保证当前线程(主线程)不被退出
-
负责监听事件(包括触摸事件、网络等等)
2.3)UIApplicationMain函数做了哪些工作和任务
UIApplicationMain函数中创建了一个UIApplication对象,每个iOS程序有且仅有一个UIApplication对象,此对象是单例,负责单例对象的维护和循环运行事件。程序一旦创建了UIApplication单例对象,对象就会一直循环下去。当应用程序在运行过程中,UIApplication对象会在应用出现变化时,调用不同的delegate方法发送特定的消息。
-
创建UIApplication对象:
从给定的类名初始化应用程序对象,也就是初始化UIApplication或者子类对象的一个实例,如果你在这里给定的是nil,那么系统会默认UIApplication类,也就主要是这个类来控制以及协调应用程序的运行。在后续的工作中,你可以用静态方法sharedApplication 来获取应用程序的句柄。
-
设置了代理:创建APPDelegate对象,并且成为UIApplication对象代理属性:
从给定的应用程序委托类,初始化一个应用程序委托。并把该委托设置为应用程序的委托,这里就有如果传入参数为nil,会调用函数访问 Info.plist文件来寻找主nib文件,获取应用程序委托。
-
启动主事件循环(Runloop)并开始接收事件;
-
加载info.plist文件(只读);
-
通过info.plist的key
Main storyboard file base name
寻找是否有指定的main.storyboard,有则进入main.storyboard; -
如果没有给值,则跳转至自己设置在
keyWindow
上ViewController
; -
如果没有给值,也没有在window上设置控制器,那么启动app失败,进入黑屏模式;
-
3. UIApplication实例职责
上面讲到UIApplicationMain
函数的工作,接下来一个问题是应用程序视图的显示、消息的控制怎么办?下面就是UIApplication
(或者子类)对象的职责,这个对象主要做下面几件事:
- 负责处理到来的用户事件,并分发事件消息到应该处理该消息的目标对象(sender, action)。
- 管理以及控制视图,包括呈现、控制行为、当前显示视图等。
- 该对象有一个应用程序委托对象,当一些生命周期内重要事件(可以包括系统事件或者生命周期控制事件)发生时,应用程序通知该对象。例如,应用程序启动、内存不够了或者应用程序结束等,让这些事件发生时,应用程序委托去响应。
通过上面的分析,可以知道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类的新变化,后面会专门说明。