UIViewController 视图控制器

前言

  • Objective-C

    1
    2
    NS_CLASS_AVAILABLE_IOS(2_0) @interface UIViewController : UIResponder <NSCoding, UIAppearanceContainer, 
    UITraitEnvironment, UIContentContainer, UIFocusEnvironment>
  • Swift

    1
    2
    @available(iOS 2.0, *) public class UIViewController : UIResponder, NSCoding, UIAppearanceContainer, 
    UITraitEnvironment, UIContentContainer, UIFocusEnvironment
  • 视图控制器负责页面的创建、事件处理等。

  • 每一个视图控制器(UIViewController)内部都有个默认的 UIView 属性,控制器中管理的其他所有控件都是这个 view 的子控件(直接或者间接)。

1、ViewController 的创建

  • Objective-C

    1
    2
    3
    4
    5
    // 实例化视图控制器
    UiViewController *viewController = [[UiViewController alloc] init];

    // 设置 window 的根视图控制器
    self.window.rootViewController = viewController;
  • Swift

    1
    2
    3
    4
    5
    // 实例化视图控制器
    let viewController: UiViewController = UiViewController()

    // 设置 window 的根视图控制器
    self.window?.rootViewController = viewController

2、ViewController 的设置

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 设置 viewController 的背景颜色
    self.view.backgroundColor = [UIColor redColor];

    // 向 viewController 上添加视图
    [self.view addSubview:button];

    // 向 viewController 上添加视图控制器
    [self addChildViewController: viewController2];
    [self.view addSubview:viewController2.view];
  • Swift

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 设置 viewController 的背景颜色
    self.view.backgroundColor = UIColor.redColor()

    // 向 viewController 上添加视图
    self.view.addSubview(button)

    // 向 viewController 上添加视图控制器
    self.addChildViewController(viewController2)
    self.view.addSubview(viewController2.view)

3、ViewController 的界面跳转

  • Objective-C

    1
    2
    3
    4
    5
    // 跳转到 指定 页面
    [self presentViewController:viewController1 animated:YES completion:nil];

    // 返回到 上一个 页面
    [self dismissViewControllerAnimated:YES completion:nil];
  • Swift

    1
    2
    3
    4
    5
    // 跳转到 指定 页面
    self.presentViewController(viewController1, animated: true, completion: nil)

    // 返回到 上一个 页面
    self.dismissViewControllerAnimated(true, completion: nil)

4、ViewController 的生命周期

viewcontroller1

  • 相关方法执行顺序:

    init -> loadView -> viewDidLoad -> viewWillAppear -> viewWillLayoutSubviews -> viewDidLayoutSubviews -> viewDidAppear -> viewWillDisappear -> viewDidDisappear -> viewWillUnload -> viewDidUnload —> dealloc

    分配内存 -> 加载视图 -> 视图已经加载 -> 视图将要出现 -> 将要布局子视图 -> 已经布局子视图 -> 视图已经出现 -> 视图将要消失 -> 视图已经消失 -> 将要销毁视图 (iOS 6+ 已废弃)-> 已经销毁视图(iOS 6+ 已废弃) -> 释放内存

  • viewController 对 view 加载过程:

    • 1、先判断子类是否重写了 loadView,如果有直接调用。之后调 viewDidLoad 完成 View 的加载。
    • 2、如果是外部通过调用 initWithNibName:bundle 指定 nib 文件名的话,ViewController 加载此 nib 来创建 View。
    • 3、如果 initWithNibName:bundle 的 name 参数为 nil,则 ViewController 会通过以下两个步骤找到与其关联的 nib。
      • A、如果类名包含 Controller,例如 ViewController 的类名是 MyViewController,则查找是否存在 MyView.nib;
      • B、找跟 ViewController 类名一样的文件,例如 MyViewController,则查找是否存在 MyViewController.nib。
    • 4、如果子类没有重写的 loadView,则 ViewController 会从 StroyBoard 中找或者调用其默认的 loadView,默认的 loadView 返回一个空白的 UIView 对象。
  • loadView

    1
    2
    3
    - (void)loadView;

    public func loadView()
    • 加载视图层次结构,用纯代码开发应用程序时使用,功能和 Storyboard & XIB 是等价的,如果重写了 loadView,Storyboard & XIB 都无效。如果控制器的 view 为 nil,会自动调用 loadView 方法,创建视图。
  • awakeFromNib

    1
    2
    3
    - (void)awakeFromNib;

    public func awakeFromNib()
    • 从 Stroyboard 加载,还没有被设置 frame & bounds。这个方法用的时候,outlet 还没有连接起来,是 ViewController 刚从 storyboard 建的时候没有完全建好,不过可能有一些事情要在这个方法里面完。
  • viewDidLoad

    1
    2
    3
    - (void)viewDidLoad;

    public func viewDidLoad()
    • 用这个的时候,ViewController 已经完全好了,outlet 也已经连接好了。但是还没有在屏幕上显示出来。这个方法里面可以放很多设置的代码。这个方法执行的时候,view 的 bounds 还没有。先 load,再 appear。

    • 视图加载完成后执行,可以做一些数据初始化的工作,如果用纯代码开发,不要在此方法中设置界面 UI。如果是 Storyboard 开发,可以动态添加一些控件,以及加载数据。

  • viewWillAppear

    1
    2
    3
    - (void)viewWillAppear:(BOOL)animated;

    public func viewWillAppear(animated: Bool)
    • 这个方法调用的时候,视图的 bounds 已经有了。

    • 视图只会 loaded 一次,但是会 appear 或者 disappear 很多次。不变的东西,放在 viewDidLoad 里面。和几何相关的,放在 viewWillAppear 里面。这点对项目的优化很重要。就好似顶层的 view,旋转 ipad 什么的都需要改变顶层的 view 的大小,当一个 ViewController 的生命周期到这里的时候,就可以在这里的最后时刻来调整 view 的排列或者几何特性。

    • 这里也设置做一些 lazy execution for performance。比如:需要按一个 button,出现一个 view 什么的。这里设置,开销很大。耗时很长的事情最好在 viewWillAppear 里另开一个线程运行,然后在 view 里面放一个小小的 spinning wheel。

  • viewWillLayoutSubviews

    1
    2
    3
    - (void)viewWillLayoutSubviews;

    public func viewWillLayoutSubviews()
    • 这个方法专门用来布局子控件,一般在这里设置子控件的 frame,当控件本身的尺寸发生改变的时候,系统会自动调用这个方法。

    • layoutSubviews 在以下情况下会被调用:

      • 1、init 初始化不会触发 layoutSubviews。
      • 2、addSubview 会触发 layoutSubviews。
      • 3、设置 view 的 Frame 会触发 layoutSubviews,当然前提是 frame 的值设置前后发生了变化。
      • 4、滚动一个 UIScrollView 会触发 layoutSubviews。
      • 5、旋转 Screen 会触发父 UIView 上的 layoutSubviews 事件。
      • 6、改变一个 UIView 大小的时候也会触发父 UIView 上的 layoutSubviews 事件。
  • viewWillDisappear

    1
    2
    3
    - (void)viewWillDisappear:(BOOL)animated;

    public func viewWillDisappear(animated: Bool)
    • 这个方法在视图将要消失的时候调用。要消失的时候,如果要记得现在的运行情况,如可以记的 scroll 的 position。但是,不要在这个方法里面写太多的东西,否则 App 会崩溃的。另外开线程来处理任何 UI 的改变,或者如果是不怎么废资源的话就直接写入硬盘。
  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    // 纯代码加载视图
    - (void)loadView {

    [super loadView];

    }

    // 从 Nib 加载视图
    - (void)awakeFromNib {

    [super awakeFromNib];

    }

    // 视图已经加载
    - (void)viewDidLoad {

    [super viewDidLoad];

    }

    // 视图将要出现,在 viewDidLoad 执行完成之后执行
    - (void)viewWillAppear:(BOOL)animated {

    [super viewWillAppear:animated];

    }

    // 将要布局子视图
    - (void)viewWillLayoutSubviews {

    [super viewWillLayoutSubviews];

    }

    // 已经布局子视图
    - (void)viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

    }

    // 视图已经出现
    - (void)viewDidAppear:(BOOL)animated {

    [super viewDidAppear:animated];

    }

    // 视图将要消失
    - (void)viewWillDisappear:(BOOL)animated {

    [super viewWillDisappear:animated];

    }

    // 视图已经消失,不是销毁
    - (void)viewDidDisappear:(BOOL)animated {

    [super viewDidDisappear:animated];

    }
  • Swift

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    // 纯代码加载视图
    override func loadView() {

    super.loadView()

    }

    // 从 Nib 加载视图
    override func awakeFromNib() {

    super.awakeFromNib()

    }

    // 视图已经加载
    override func viewDidLoad() {

    super.viewDidLoad()

    }

    // 视图将要出现,在 viewDidLoad 执行完成之后执行
    override func viewWillAppear(animated: Bool) {

    super.viewWillAppear(animated)

    }

    // 将要布局子视图
    override func viewWillLayoutSubviews() {

    super.viewWillLayoutSubviews()

    }

    // 已经布局子视图
    override func viewDidLayoutSubviews() {

    super.viewDidLayoutSubviews()

    }

    // 视图已经出现
    override func viewDidAppear(animated: Bool) {

    super.viewDidAppear(animated)

    }

    // 视图将要消失
    override func viewWillDisappear(animated: Bool) {

    super.viewWillDisappear(animated)

    }

    // 视图已经消失,不是销毁
    override func viewDidDisappear(animated: Bool) {

    super.viewDidDisappear(animated)

    }

5、ViewController 更新布局约束

  • 通过代码为 xib 或 storyboard 中 view 增加约束时,尽量避免在 viewDidLoad 中执行,最好放在 updateViewConstraints(UIViewController 中) 或者 updateConstraints(UIView 中)中,记得调用 [super updateViewConstraints] 或者 [super updateConstraints];

  • 如果你真的写在 viewDidLoad 里了,那么可能会遇到这种崩溃错误 Terminating app due to uncaught exception “NSInternalInconsistencyException”。

  • 代码设置约束时,需设置该 view 的 translatesAutoresizingMaskIntoConstraints 属性已设置为 NO。

  • Objective-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 更新视图控制器中视图的布局约束
    - (void)updateViewConstraints {

    // 在这里为你的 view 添加约束

    [super updateViewConstraints];
    }

    // 更新视图布局约束
    - (void)updateConstraints {

    // 在这里为你的 view 添加约束

    [super updateConstraints];
    }
  • Swift

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 更新视图控制器中视图的布局约束
    override func updateViewConstraints() {

    // 在这里为你的 view 添加约束

    super.updateViewConstraints()
    }

    // 更新视图布局约束
    override func updateConstraints() {

    // 在这里为你的 view 添加约束

    super. updateConstraints()
    }

6、ViewController 的内存警告

  • iPhone 下每个 app 可用的内存是被限制的,如果一个 App 使用的内存超过 20M,则系统会向该 App 发送 Memory Warning 消息。苹果公司系统工程师建议,应用程序所占内存不应该超过 20MB,开发人员圈内流传着一个粗略的经验法则:当应用程序占用了大约 20MB 内存时,iPhone 开始发出内存警告。收到消息后 App 必须尽可能多的释放一些不必要的内存,当应用程序所占内存大约为 30MB 时,iPhone OS 会关闭应用程序。收到此消息后,App 必须正确处理,否则可能出错或者出现内存泄露。App 收到 Memory Warning 后会调用:UIApplication::didReceiveMemoryWarning -> UIApplicationDelegate::applicationDidReceiveMemoryWarning,然后调用当前所有的 viewController 进行处理。因此处理的主要工作是在 viewController。

  • 内存警告处理思路

    • 通常一个应用程序会包含多个 viewController,当从 view 跳转到另一个 view 时,之前的 view 只是不可见状态,并不会立即被清理掉,而是保存在内存中,以便下一次的快速显现。但是如果应用程序接收到系统发出的 low-memory warning,我们就不得不把当前不可见状态下的 views 清理掉,腾出更多的可使用内存,当前可见的 viewController 也要合理释放掉一些缓存数据,图片资源和一些不是正在使用的资源,以避免应用程序崩溃。具体的实施根据系统版本不同而略有差异。
  • iOS 5 的处理

    • 在 iOS 6 之前,如果应用程序接收到了 low-memory 警告,当前不可见的 viewController 会接收到 viewDidUnload 消息(也可以理解为自动调用 viewDidUnload 方法),所以我们需要在 viewDidUnload 方法中释放掉所有 outlets ,以及可再次创建的资源。当前可见的 viewController 通过 didReceiveMemoryWarning 合理释放资源。

    • 有这样一个 viewController。

      1
      2
      3
      4
      5
      6
      7
      @interface MyViewController : UIViewController { 
      NSArray *dataArray;
      }

      @property (nonatomic, strong) IBOutlet UITableView *tableView;

      @end
    • 对应的处理为。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      - (void)didReceiveMemoryWarning {

      // Releases the view if it doesn't have a superview.
      [super didReceiveMemoryWarning];

      // Relinquish ownership any cached data, images, etc that aren't in use.
      }

      - (void)viewDidUnload {

      // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
      // For example: self.myOutlet = nil;
      self.tableView = nil;
      dataArray = nil;

      [super viewDidUnload];
      }
  • iOS 6+ 的处理

    • iOS 6 废弃了 viewDidUnload 方法,这就意味着一切需要我们自己在 didReceiveMemoryWarning 中操作。

    • 1、将 outlets 置为 weak。当 view dealloc 时,没有人握着任何一个指向 subviews 的强引用,那么 subviews 实例变量将会自动置空。

      1
      @property (nonatomic, weak) IBOutlet UITableView *tableView;
    • 2、在 didReceiveMemoryWarning 中将缓存数据置空。不要忘记一点,每当 tableview reload 的时候,需要判断一下 dataArray ,若为空则重新创建。

      1
      2
      3
      4
      5
      6
      7
      - (void)didReceiveMemoryWarning { 

      [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated.

      dataArray = nil;
      }
  • 兼容 iOS 5 与 iOS 6+

    • 倘若希望程序兼容 iOS 5 与 iOS 6+ 怎么办呢?这里有一个小技巧,我们需要对 didReceiveMemoryWarning 做一些手脚。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      - (void)didReceiveMemoryWarning {

      [super didReceiveMemoryWarning];

      if (self.isViewLoaded && self.view.window == nil) {
      self.view = nil;
      }

      dataArray = nil;
      }
      • 判断一下 view 是否是 window 的一部分,如果不是,那么可以放心的将 self.view 置为空,以换取更多可用内存。这样会是什么现象呢?假如,从 viewController A 跳转到 viewController B ,然后模拟 low-memory 警告,此时,viewController A 将会执行 self.view = nil ; 当我们从 B 退回 A 时, A 会重新调用一次 viewDidLoad ,此时数据全部重新创建,
文章目录
  1. 1. 前言
  2. 2. 1、ViewController 的创建
  3. 3. 2、ViewController 的设置
  4. 4. 3、ViewController 的界面跳转
  5. 5. 4、ViewController 的生命周期
  6. 6. 5、ViewController 更新布局约束
  7. 7. 6、ViewController 的内存警告
隐藏目录