美文网首页iOS精英班八宝粥OC&iOS
iOS程序的启动流程 及 UIViewController和UI

iOS程序的启动流程 及 UIViewController和UI

作者: 我有小尾巴快看 | 来源:发表于2017-11-04 12:30 被阅读601次

    参考了http://www.cnblogs.com/junhuawang/p/5742535.html的内容。

    一、iOS程序的启动流程

    1.程序入口
    进入main函数,设置AppDelegate为应用代理

    2.程序完成加载
    [AppDelegate application:didFinishLaunchingWithOptions:]

    3.创建window窗口

    4.程序被激活
    [AppDelegate applicationDidBecomeActive:]

    5.点击Home键
    [AppDelegate applicationWillResignActive:]; //程序取消激活状态
    [AppDelegate applicationDidEnterBackground:];// 程序进入后台

    6.点击APP
    [AppDelegate applicationWillEnterForeground:]// 程序进入前台
    [AppDelegate applicationDidBecomeActive:];// 程序被激活

    分析
    1. applicationWillResignActive(非活动)与applicationDidEnterBackground(后台)的区别。
    • applicationWillResignActive:
      比如当有电话进来或短信进来或锁屏等情况下,这时应用程序挂起进入非活动状态,也就是手机界面还是显示着你当前的应用程序的窗口,只不过被别的任务强制占用了,也可能是即将进入后台状态(因为要先进入非活动状态然后进入后台状态)

    • applicationDidEnterBackground:
      指当前窗口不是你的App,大多数程序进入这个后台会在这个状态上停留一会,时间到之后会进入挂起状态(Suspended)。如果你程序特殊处理后可以长期处于后台状态也可以运行。
      Suspended : 程序在后台不能执行代码。系统会自动把程序变成这个状态而且不会发出通知。当挂起时,程序还是停留在内存中的,当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存。

    2.UIApplicationMain:

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

    如同任何基于C的应用程序,程序启动的主入口点为iOS应用程序的main函数。在iOS应用程序,main函数的作用是很少的。它的主要工作是控制UIKit framework。因此,你在Xcode中创建任何新的项目都配备了一个默认的主函数。除了少数特例外,你永远不应该改变这个函数的实现。
    UIApplicationMain函数有四个参数,并使用这些参数来初始化应用程序。你不应该改变传递给这个函数的默认值。

     UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);
    
    • argc和 argv:是ISO C标准的main函数的参数,直接传递给UIApplicationMain进行相关处理。参数包含应用程序何时从系统启动等信息。这些参数是由UIKit的基础设施解析,否则可以忽略不计。

    • principalClassName : 这个参数标识了应用程序的类的名称(该类必须继承自UIApplication类)。这是负责运行应用程序的类。建议为这个参数传nil。如果principalClassName是nil,那么它的值将从Info.plist去获取,如果Info.plist没有,则默认为UIApplication。principalClass这个类除了管理整个程序的生命周期之外什么都不做,它只负责监听事件然后交给delegateClass去做。

    • delegateClassName :delegateClass 是应用程序类的代理类。应用程序的代理负责管理系统和你的代码之间的高层次的互动。 Xcode的项目模板会自动将该参数设置为一个适当的值。将在工程新建时实例化一个对象。NSStringFromClass([AppDelegate class])。

    • AppDelegate 类实现文件

     - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        NSLog(@"%s",__func__);
        return YES;
    }
    
    - (void)applicationWillResignActive:(UIApplication *)application {
         NSLog(@"%s",__func__);
    }
    
    - (void)applicationDidEnterBackground:(UIApplication *)application {
       NSLog(@"%s",__func__);
    }
    
    - (void)applicationWillEnterForeground:(UIApplication *)application {
       NSLog(@"%s",__func__);
    }
    
    - (void)applicationDidBecomeActive:(UIApplication *)application {
      NSLog(@"%s",__func__);
    }
    
    - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
         NSLog(@"%s",__func__);
    }
    
    - (void)applicationWillTerminate:(UIApplication *)application {
        NSLog(@"%s",__func__);
    }
    
    启动APP
    -[AppDelegate application:didFinishLaunchingWithOptions:]
    -[AppDelegate applicationDidBecomeActive:]
    
    点击Home键
    -[AppDelegate applicationWillResignActive:]
    -[AppDelegate applicationDidEnterBackground:]
    
    返回APP
    -[AppDelegate applicationWillEnterForeground:]
    -[AppDelegate applicationDidBecomeActive:]
    
    内存警告
    -[AppDelegate applicationDidReceiveMemoryWarning:]
    
    分析:
    1. application :willFinishLaunchingWithOptions:
      application:didFinishLaunchingWithOptions:
      分别是程序首次将要和已经完成启动时执行,一般在这个函数里创建window对象,将程序内容通过window呈现给用户。
      ①检查启动选项字典中的内容,查看程序启动的方式,并做出适当的反应。
      ②初始化应用程序的关键数据结构。
      ③准备好你的应用程序的窗口和视图进行显示。
    注意
    1. 使用OpenGL ES的应用程序不应该使用这个方法来准备他们的绘图环境。相反,他们应该推迟到application:DidBecomeActive:方法调用时启动OpenGL ES绘图方法。
    2. 您的应用程序方法应该总是尽可能为轻量,以减少你的应用程序的启动时间。应用预期将启动并初始化自身,并开始处理不到5秒的事件。如果一个应用程序没有及时完成它的启动周期,系统会杀死它。因此,有可能你的启动慢下来(如接入网络)的任何任务,应在异步辅助线程执行。
    3. 当程序启动到前台,该系统还会调用applicationDidBecomeActive:方法来完成过渡到前台。因为这种方法既在启动时与从后台过渡到前台时被调用,使用它来执行所共有的两个转变的任何任务。
    1. applicationWillResignActive(非活动)
      程序将要失去Active状态时调用,比如有电话进来或者按下Home键,之后程序进入后台状态,对应的applicationWillEnterForeground(即将进入前台)方法。
      该函数里面主要执行操作:
      a . 暂停正在执行的任务
      b. 禁止计时器
      c. 减少OpenGL ES帧率
      d. 若为游戏应暂停游戏

    2. applicationDidEnterBackground(已经进入后台)
      该方法用来:
      a. 释放共享资源
      b. 保存用户数据(写到硬盘)
      c. 作废计时器
      d. 保存足够的程序状态以便下次修复;

    3. applicationWillEnterForeground(即将进入前台)
      这个方法用来: 撤销applicationWillResignActive中做的改变。

    4. applicationDidBecomeActive(已经进入前台)
      若程序之前在后台,在此方法内刷新用户界面

    5. applicationWillTerminate
      程序即将退出时调用。记得保存数据,如applicationDidEnterBackground方法一样。

    二、UIViewController的生命周期

    .m文件
    // 没有通过storyBoard(即xib或非xib)调用这个方法
    - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        NSLog(@"%s", __func__);
        }
        return self;
    }
    
    // 通过storyBoard会调用这个方法
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        if (self = [super initWithCoder:aDecoder]) {
         NSLog(@"%s", __func__);
        }
        return self;
    }
    
    // xib加载完成
    - (void)awakeFromNib {
        [super awakeFromNib];
         NSLog(@"%s", __func__);
    }
    
    // 加载视图(默认从nib)
    - (void)loadView {
         NSLog(@"%s", __func__);
        self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
        self.view.backgroundColor = [UIColor redColor];
    }
    
    //视图控制器中的视图加载完成,viewController自带的view加载完成
    - (void)viewDidLoad {
         NSLog(@"%s", __func__);
        [super viewDidLoad];
    }
    
    //视图将要出现
    - (void)viewWillAppear:(BOOL)animated {
         NSLog(@"%s", __func__);
        [super viewWillAppear:animated];
    }
    
    // view 即将布局其 Subviews
    - (void)viewWillLayoutSubviews {
         NSLog(@"%s", __func__);
        [super viewWillLayoutSubviews];
    }
    
    // view 已经布局其 Subviews
    - (void)viewDidLayoutSubviews {
         NSLog(@"%s", __func__);
        [super viewDidLayoutSubviews];
    }
    
    //视图已经出现
    - (void)viewDidAppear:(BOOL)animated {
         NSLog(@"%s", __func__);
        [super viewDidAppear:animated];
    }
    
    //视图将要消失
    - (void)viewWillDisappear:(BOOL)animated {
         NSLog(@"%s", __func__);
        [super viewWillDisappear:animated];
    }
    
    //视图已经消失
    - (void)viewDidDisappear:(BOOL)animated {
         NSLog(@"%s", __func__);
        [super viewDidDisappear:animated];
    }
    
    //出现内存警告  //模拟内存警告:点击模拟器->hardware-> Simulate Memory Warning
    - (void)didReceiveMemoryWarning {
         NSLog(@"%s", __func__);
        [super didReceiveMemoryWarning];
    }
    
    // 视图被销毁
    - (void)dealloc {
         NSLog(@"%s", __func__);
    }
    
    查看 打印 结果
    -[ViewController initWithCoder:]
    -[ViewController awakeFromNib]
    -[ViewController loadView]
    -[ViewController viewDidLoad]
    -[ViewController viewWillAppear:]
    -[ViewController viewWillLayoutSubviews]
    -[ViewController viewDidLayoutSubviews]
    -[ViewController viewWillLayoutSubviews]
    -[ViewController viewDidLayoutSubviews]
    -[ViewController viewDidAppear:]
    -[ViewController viewWillDisappear:]
    -[ViewController viewDidDisappear:]
    -[ViewController dealloc]
    -[ViewController didReceiveMemoryWarning]
    
    分析
    1. initWithNibName:bundle:
      初始化UIViewController,执行关键数据初始化操作,非StoryBoard创建UIViewController都会调用这个方法。
      注意: 不要在这里做View相关操作,View在loadView方法中才初始化。

    2. initWithCoder:
      如果使用StoryBoard进行视图管理,程序不会直接初始化一个UIViewController,StoryBoard会自动初始化或在segue被触发时自动初始化,因此方法initWithNibName:bundle不会被调用,但是initWithCoder会被调用。

    3. awakeFromNib
      当awakeFromNib方法被调用时,所有视图的outlet和action已经连接,但还没有被确定,这个方法可以算作适合视图控制器的实例化配合一起使用的,因为有些需要根据用户洗好来进行设置的内容,无法存在storyBoard或xib中,所以可以在awakeFromNib方法中被加载进来。

    4. loadView
      当执行到loadView方法时,如果视图控制器是通过nib创建,那么视图控制器已经从nib文件中被解档并创建好了,接下来任务就是对view进行初始化。
      loadView方法在UIViewController对象的view被访问且为空的时候调用。这是它与awakeFromNib方法的一个区别。
      假设我们在处理内存警告时释放view属性:self.view = nil。因此loadView方法在视图控制器的生命周期内可能被调用多次。
      loadView方法不应该直接被调用,而是由系统调用。它会加载或创建一个view并把它赋值给UIViewController的view属性。
      在创建view的过程中,首先会根据nibName去找对应的nib文件然后加载。如果nibName为空或找不到对应的nib文件,则会创建一个空视图(这种情况一般是纯代码)
      注意:在重写loadView方法的时候,不要调用父类的方法。

    5. viewDidLoad
      当loadView将view载入内存中,会进一步调用viewDidLoad方法来进行进一步设置。此时,视图层次已经放到内存中,通常,我们对于各种初始化数据的载入,初始设定、修改约束、移除视图等很多操作都可以这个方法中实现。
      视图层次(view hierachy):因为每个视图都有自己的子视图,这个视图层次其实也可以理解为一颗树状的数据结构。而树的根节点,也就是根视图(root view),在UIViewController中以view属性。它可以看做是其他所有子视图的容器,也就是根节点。

    6. viewWillAppear
      系统在载入所有的数据后,将会在屏幕上显示视图,这时会先调用这个方法,通常我们会在这个方法对即将显示的视图做进一步的设置。比如,设置设备不同方向时该如何显示;设置状态栏方向、设置视图显示样式等。
      另一方面,当APP有多个视图时,上下级视图切换是也会调用这个方法,如果在调入视图时,需要对数据做更新,就只能在这个方法内实现。

    7. viewWillLayoutSubviews
      view 即将布局其Subviews。 比如view的bounds改变了(例如:状态栏从不显示到显示,视图方向变化),要调整Subviews的位置,在调整之前要做的工作可以放在该方法中实现

    8. viewDidLayoutSubviews
      view已经布局其Subviews,这里可以放置调整完成之后需要做的工作。

    9. viewDidAppear
      在view被添加到视图层级中以及多视图,上下级视图切换时调用这个方法,在这里可以对正在显示的视图做进一步的设置。

    10. viewWillDisappear
      在视图切换时,当前视图在即将被移除、或被覆盖是,会调用该方法,此时还没有调用removeFromSuperview。

    11. viewDidDisappear
      view已经消失或被覆盖,此时已经调用removeFromSuperView;

    12. dealloc
      视图被销毁,此次需要对你在init和viewDidLoad中创建的对象进行释放。

    13. didReceiveMemoryWarning
      在内存足够的情况下,app```的视图通常会一直保存在内存中,但是如果内存不够,一些没有正在显示的viewController就会收到内存不足的警告,然后就会释放自己拥有的视图,以达到释放内存的目的。但是系统只会释放内存,并不会释放对象的所有权,所以通常我们需要在这里将不需要显示在内存中保留的对象释放它的所有权,将其指针置nil``。

    Controller的生命历程

    • [ViewController initWithCoder:][ViewController initWithNibName:Bundle]:首先从归档文件中加载UIViewController对象。即使是纯代码,也会把nil作为参数传给后者。
    • [UIView awakeFromNib]: 作为第一个方法的助手,方法处理一些额外的设置。
    • [ViewController loadView]: 创建或加载一个view并把它赋值给UIViewControllerview属性。
    • -[ViewController viewDidLoad]:此时整个视图层次(view hierarchy)已经放到内存中,可以移除一些视图,修改约束,加载数据等。
    • [ViewController viewWillAppear:]: 视图加载完成,并即将显示在屏幕上。还没设置动画,可以改变当前屏幕方向或状态栏的风格等。
    • [ViewController viewWillLayoutSubviews]:即将开始子视图位置布局
    • [ViewController viewDidLayoutSubviews]:用于通知视图的位置布局已经完成
    • [ViewController viewDidAppear:]:视图已经展示在屏幕上,可以对视图做一些关于展示效果方面的修改。
    • [ViewController viewWillDisappear:]:视图即将消失
    • [ViewController viewDidDisappear:]:视图已经消失'

    三、UIView的生命周期

    .m文件
    - (instancetype)init{
        if (self = [super init]) {
            NSLog(@"%s",__func__);
        }
        return self;
    }
    
    //通过代码创建控件就会调用这个方法
    - (instancetype)initWithFrame:(CGRect)frame {
        if (self = [super initWithFrame:frame]) {
            NSLog(@"%s",__func__);
        }
        return self;
    }
    
    //通过storyboared或者xib中创建控件就会调用这个方法
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        if (self = [super initWithCoder:aDecoder]) {
            NSLog(@"%s",__func__);
        }
        return self;
    }
    
    - (void)awakeFromNib {
        [super awakeFromNib];
        NSLog(@"%s",__func__);
    }
    
    //如果在initWithFrame中添加子视图会调用两次
    - (void)layoutSubviews{
        [super layoutSubviews];
            NSLog(@"%s",__func__);
    }
    
    - (void)didAddSubview:(UIView *)subview{
        [super didAddSubview:subview];
            NSLog(@"%s",__func__);
    }
    
    - (void)willRemoveSubview:(UIView *)subview{
        [super willRemoveSubview:subview];
            NSLog(@"%s",__func__);
    }
    
    - (void)willMoveToSuperview:(nullable UIView *)newSuperview{
        [super willMoveToSuperview:newSuperview];
            NSLog(@"%s",__func__);
    }
    
    - (void)didMoveToSuperview{
        [super didMoveToSuperview];
            NSLog(@"%s",__func__);
    }
    
    - (void)willMoveToWindow:(nullable UIWindow *)newWindow{
        [super willMoveToWindow:newWindow];
            NSLog(@"%s",__func__);
    }
    
    - (void)didMoveToWindow{
        [super didMoveToWindow];
            NSLog(@"%s",__func__);
    }
    
    - (void)removeFromSuperview{
        [super removeFromSuperview];
            NSLog(@"%s",__func__);
    }
    
    - (void)dealloc{
            NSLog(@"%s",__func__);
    }
    
    当创建view时
    2017-11-06 10:35:12.347153+0800 IOSLife[7587:2353869] -[MyView willMoveToSuperview:]
    2017-11-06 10:35:12.347312+0800 IOSLife[7587:2353869] -[MyView didMoveToSuperview]
    2017-11-06 10:35:12.353483+0800 IOSLife[7587:2353869] -[MyView willMoveToWindow:]
    2017-11-06 10:35:12.353644+0800 IOSLife[7587:2353869] -[MyView didMoveToWindow]
    2017-11-06 10:35:12.363861+0800 IOSLife[7587:2353869] -[MyView layoutSubviews]
    2017-11-06 10:35:12.655946+0800 IOSLife[7607:2356750] -[MyView drawRect:]
    
    
    当view销毁时
    2017-11-06 10:41:28.152448+0800 IOSLife[7607:2356750] -[MyView willMoveToWindow:]
    2017-11-06 10:41:28.152693+0800 IOSLife[7607:2356750] -[MyView didMoveToWindow]
    2017-11-06 10:41:28.155160+0800 IOSLife[7607:2356750] -[MyView willMoveToSuperview:]
    2017-11-06 10:41:28.155281+0800 IOSLife[7607:2356750] -[MyView didMoveToSuperview]
    2017-11-06 10:41:28.155336+0800 IOSLife[7607:2356750] -[MyView removeFromSuperview]
    2017-11-06 10:41:28.155399+0800 IOSLife[7607:2356750] -[MyView dealloc]
    
    

    可以看出上面方法中只会执行一次的方法有removeFromSuperviewdealloc两个方法(layoutSubviews在子视图布局变动时会多次调用),所以可以在这两个方法中执行释放内存等操作(e.g:移除观察者,定时器等)。

    给view添加子视图时
    2017-11-06 10:45:47.749310+0800 IOSLife[7614:2358053] -[MyView didAddSubview:]
    2017-11-06 10:45:47.750111+0800 IOSLife[7614:2358053] -[MyView layoutSubviews]
    

    didAddSubview:willRemoveSubview:需要有子视图才能执行。

    此时再销毁该view
    2017-11-06 10:46:28.022473+0800 IOSLife[7617:2358497] -[MyView willMoveToSuperview:]
    2017-11-06 10:46:28.022643+0800 IOSLife[7617:2358497] -[MyView didMoveToSuperview]
    2017-11-06 10:46:28.031533+0800 IOSLife[7617:2358497] -[MyView willMoveToWindow:]
    2017-11-06 10:46:28.031738+0800 IOSLife[7617:2358497] -[MyView didMoveToWindow]
    2017-11-06 10:46:28.036048+0800 IOSLife[7617:2358497] -[MyView layoutSubviews]
    2017-11-06 10:46:28.719103+0800 IOSLife[7617:2358497] -[MyView didAddSubview:]
    2017-11-06 10:46:28.719873+0800 IOSLife[7617:2358497] -[MyView layoutSubviews]
    2017-11-06 10:46:30.529769+0800 IOSLife[7617:2358497] -[MyView willMoveToWindow:]
    2017-11-06 10:46:30.530059+0800 IOSLife[7617:2358497] -[MyView didMoveToWindow]
    2017-11-06 10:46:30.532931+0800 IOSLife[7617:2358497] -[MyView willMoveToSuperview:]
    2017-11-06 10:46:30.533055+0800 IOSLife[7617:2358497] -[MyView didMoveToSuperview]
    2017-11-06 10:46:30.533112+0800 IOSLife[7617:2358497] -[MyView removeFromSuperview]
    2017-11-06 10:46:30.533183+0800 IOSLife[7617:2358497] -[MyView dealloc]
    2017-11-06 10:46:30.533256+0800 IOSLife[7617:2358497] -[MyView willRemoveSubview:]
    

    willRemoveSubview是在dealloc后面执行的。如果有多个子视图,willRemoveSubview会循环执行,直到移除所有子视图。

    注意:
    - (void)willMoveToSuperview:(nullable UIView *)newSuperview;
    - (void)willMoveToWindow:(nullable UIWindow *)newWindow;
    这俩个方法可以根据参数判断,nil则为销毁,否则为创建;
    - (void)didMoveToSuperview;
    - (void)didMoveToWindow;
    这个方法可以根据self.superview判断,nil则为销毁,否则为创建。

    四、总结:

    Controller
    • 只有init系列的方法,如initWithNibName需要自己调用,其他方法如loadViewawakeFromNib则是系统自动调用。而viewWill/Did系列的方法则类似于回调和通知,也会被自动调用。

    • self.view是在loadView方法中创建并建立联系的,如果想要自定义该view,不要调用[super loadView],并且最后要将自定义的view赋值给self.view。如果该控制器没有xib文件,重写了loadView但没有做任何事情(也就是self.view为空),在viewDidLoad中还使用了self.view(self.view为空时会调用loadView),这样会造成死循环。

    • 除了initWithNibNameawakeFromNib方法是处理视图控制器外,其他方法都是处理视图。这两个方法在视图控制器的生命周期里只会调用一次。

    View
    • 如果在initWithFrame中有view的初始化相关操作,layoutSubviews会调用两次。layoutSubviews只进行布局相关处理,不能有view初始化操作(当子视图布局改变时会调用,能多次调用),下面是layoutSubviews的触发条件。
      1、init初始化不会触发layoutSubviews,但是是用initWithFrame进行初始化时,当rect的值不为CGRectZero时,也会触发。
      2、addSubview会触发layoutSubviews
      3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
      4、滚动一个UIScrollView会触发layoutSubviews
      5、旋转Screen会触发父UIView上的layoutSubviews
      6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews

    相关文章

      网友评论

        本文标题:iOS程序的启动流程 及 UIViewController和UI

        本文链接:https://www.haomeiwen.com/subject/nawzpxtx.html