美文网首页
011-Application,VIewController,V

011-Application,VIewController,V

作者: Yasic | 来源:发表于2017-11-26 19:21 被阅读26次

    应用程序 = 代码 + 系统框架

    iOS 应用入口 —— Main 函数

    main 函数由 Xcode 在创建工程时提供,一般不改变其中内容,除非真的知道自己在做什么。

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

    main 函数做的事情是调用 UIApplicationMain 函数,这个函数的作用是创建用户程序对象、从 可用的 storyBoard 或 nib 文件(在 info.plist 文件定义)加载用户界面、创建应用程序委托和初始化主事件循环(main event loop),这个函数虽然定义了返回值为 int,但 app 在运行时这个函数就不会运行结束,所以不会返回值。

    iOS MVC 架构

    MVC 架构

    • Model——负责定义和存储数据及数据相关的业务逻辑
    • View——负责响应用户交互Controller——负责联系 Model 和 View

    当有用户交互事件到来时,view 通过 target-action 来处理,当需要处理特殊 UI 逻辑或者获得数据源时,View 与代理 Controller 通信。Model 不主动与 Controller 通信,一旦有数据更新需要通知会采用 NSNotification 或 KVO 进行通知。

    一个 application 的总体架构如下图

    Eventloop 会将用户交互事件交个 UIApplication进行分发,可能是分发给 UIView 或者 AppDelegate。AppDelegate 主要管理 app 的生命周期,如初始化、加载 UIWindow、设置 rootViewController、内存不足时处理、app切换、app终止等。ViewCtroller 的属性 view 类似一个根视图,ViewController 负责管理 view 的生命周期。DataModel 用于存储数据,Document 用于管理 Model,但不是必须的。UIWindow 是 View 层次结构中的最高层,添加一个 contentView 就可以显示视图。View 对象组成和布局 view 的层次结构,其中有特定用户交互的称为 Control。

    Main Event Loop 事件循环

    MEL 负责处理所有用户交互事件,运行在主线程中。当发生交互事件时,系统会生成交互关联事件,然后通过应用端口放入事件队列,依次由 MEL 执行,MEL 传递给 UIApplication 进行分发。

    用户交互事件有四类,在 UIEvent 类中用一个枚举类定义。

    typedef NS_ENUM(NSInteger, UIEventType) {
        UIEventTypeTouches, //触摸事件,按钮,手势
        UIEventTypeMotion, // 运动事件,摇一摇,指南针
        UIEventTypeRemoteControl, // 远程遥控,耳机
        UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0), // 3D touch
    };
    

    UIEvent 就是一个响应事件的类,会在交互事件发生时被系统实例化,由于可能被系统复用,所以应用不能持有事件对象。

    这里以 UIEventTypeTouches 为例,讲解事件传递和响应过程。UIEvent 会持有一些UITouch 对象,UITouch 类用于表示一个具体的手指触摸对象,通过它可以获得 touch event 的一些属性

    • window:触摸时所在的窗口
    • view:触摸时所在视图
    • tapCount:短时间内点击的次数
    • timestamp:触摸产生或变化的时间戳
    • phase:触摸周期内的各个状态
    • locationInView:方法:取得在指定视图的位置
    • previousLocationInView:方法:取得移动的前一个位置

    一个 touch event 事件发生后首先会被 UIApplication 先发送到 UIWindow,然后依次按照 view 层次结构向下传递,找到最合适的视图。

    寻找最合适视图

    一般步骤如下

    • 首先判断 UIWindow 能否接受 touch event,如果能则判断 touch point 是否在 window 内
    • 从后向前遍历子 View,重复第一步
    • 如果遍历完没有找到则自己作为最合适的 view,如果找到则结束

    一个 UIView 不能接受 touch event 的可能情况有

    • 不接受用户交互:userInteractionEnabled = NO;
    • 隐藏:hidden = YES;
    • 透明:alpha = 0.0~0.01
    • 没有实现开始触摸方法(注意是touchesBegan:withEvent:而不是移动和结束触摸事件)

    具体过程如下,当一个 View 接收到一个 touch event后,会调用 hitTest: withEvent: 方法

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
        // 1.判断当前控件能否接收事件
        if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
        // 2. 判断点在不在当前控件
        if ([self pointInside:point withEvent:event] == NO) return nil;
        // 3.从后往前遍历自己的子控件
        NSInteger count = self.subviews.count;
        for (NSInteger i = count - 1; i >= 0; i--) {
            UIView *childView = self.subviews[i];
            // 把当前控件上的坐标系转换成子控件上的坐标系
            CGPoint childP = [self convertPoint:point toView:childView];
            UIView *fitView = [childView hitTest:childP withEvent:event];
            if (fitView) { // 寻找到最合适的view
                return fitView;
            }
        }
        // 循环结束,表示没有比自己更合适的view
        return self;
    }
    

    这个方法在 UIView 中定义,UIWindow 继承了这个类,所以也有这个方法。其中有个方法 (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 用于判断触摸点是否在自己身上。如果都满足,会继续遍历自己的 subView,对每一个 subView 调用 hit 方法,从而找到最合适的 view。

    响应事件

    UIView 继承自 UIResponder 类,UIResponder 类中就定义了具体的响应事件,要注意 UIVIewController 也继承了 UIResponder,也可以响应事件。

    // Generally, all responders which do custom touch handling should override all four of these methods.
    // Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each
    // touch it is handling (those touches it received in touchesBegan:withEvent:).
    // *** You must handle cancelled touches to ensure correct behavior in your application.  Failure to
    // do so is very likely to lead to incorrect behavior or crashes.
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
    - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
    - (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);
    
    // Generally, all responders which do custom press handling should override all four of these methods.
    // Your responder will receive either pressesEnded:withEvent or pressesCancelled:withEvent: for each
    // press it is handling (those presses it received in pressesBegan:withEvent:).
    // pressesChanged:withEvent: will be invoked for presses that provide an analog value
    // (like thumbsticks or analog push buttons)
    // *** You must handle cancelled presses to ensure correct behavior in your application.  Failure to
    // do so is very likely to lead to incorrect behavior or crashes.
    - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
    - (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
    - (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
    - (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
    
    - (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
    - (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
    

    默认的响应方法是将事件按照“响应链”上传,当一个事件发生后首先看 initial view 能否处理这个事件,如果不能则会将事件传递给其上级视图(inital view 的 superView);如果上级视图仍然无法处理则会继续往上传递;一直传递到视图控制器 view controller,首先判断视图控制器的根视图 view 是否能处理此事件;如果不能则接着判断该视图控制器能否处理此事件,如果还是不能则继续向上传递;(对于视图控制器本身还在另一个视图控制器中,则继续交给父视图控制器的根视图,如果根视图不能处理则交给父视图控制器处理);一直到 window,如果 window 还是不能处理此事件则继续交给 application(UIApplication 单例对象)处理,如果最后 application 还是不能处理此事件则将其丢弃。所以如果要自己响应事件,就必须复写这些事件。

    其中响应 touch 的方法如下

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
    - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
    

    APP 状态与生命周期

    一个应用的运行状态可以包括以下几个

    • Not running:app还没运行
    • Inactive:app运行在foreground但没有接收事件
    • Active:app运行在foreground和正在接收事件
    • Background:运行在background和正在执行代码
    • Suspended:运行在background但没有执行代码

    如何在这几个状态中切换就需要 AppDelegate 的参与,确切的说是实现了 UIApplicationDelegate 协议的委托对象来负责。在协议中以下几个方法会被调用

    application:willFinishLaunchingWithOptions: - 启动时的第一次执行
    application:didFinishLaunchingWithOptions: - 在显示app给用户之前执行最后的初始化操作
    applicationDidBecomeActive: - app已经切换到active状态后执行
    applicationWillResignActive: - app将要从前台切换到后台时需要执行的操作
    applicationDidEnterBackground: - app已经进入后台后需要执行的操作
    applicationWillEnterForeground: - app将要从后台切换到前台需要执行的操作,但app还不是active状态
    applicationWillTerminate: - app将要结束时需要执行的操作
    

    一个 app 第一次启动时会依次调用 willFinishLaunchingWithOptions、didFinishLaunchingWithOptions、applicationDidBecomeActive三个方法,app 状态由 Not running -> Inactive -> Active。

    当 app 被切出的时候,有三种情况

    • 先切换到多任务窗口,会调用 applicationWillResignActive 方法,app 状态 Active -> Inactive,再切换回来会调用 applicationDidBecomeActive 方法
    • 直接退出到 home 、进入其他应用时,依次调用 applicationWillResignActive 和 applicationDidEnterBackground 方法,状态 Active -> Inactive -> Backgroud
    • 中断发生,如锁屏和电话接入,依次调用 applicationWillResignActive 和 applicationDidEnterBackground 方法

    当 app 被切回时,依次调用 applicationWillEnterForeground 和 applicationDidBecomeActive 方法。

    当 app 被退出时,调用 applicationWillTerminate。

    UIViewController 生命周期

    UIViewController 是 MVC 架构中的 C,主要负责管理视图、处理 Model 业务和作为 view 的数据源和委托。

    UIViewCtroller 可以通过 nib、StoryBoard 或 NSCoding 协议初始化,也可以用代码直接构建。

        self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
        self.window.backgroundColor = [UIColor whiteColor];
        
        MainTabbarControllerViewController *tabbarController = [[MainTabbarControllerViewController alloc] init];
        self.window.rootViewController = tabbarController;
        [self.window makeKeyAndVisible];
    

    这里是在 AppDelegate 类的 didFinishLaunchingWithOptions 方法内进行的 rootViewCtroller 的初始化工作。

    接下来 UIViewController 就开始初始化自己的根视图。

    • loadView:加载view,没有特殊情况只执行一次
    • viewDidLoad:view加载完毕,只会在将要布局时调用一次
    • viewWillAppear:控制器的view将要显示
    • viewWillLayoutSubviews:控制器的view将要布局子控件
    • viewDidLayoutSubviews:控制器的view布局子控件完成
    • viewDidAppear:控制器的view完全显示
    • viewWillDisappear:控制器的view即将消失的时候
    • viewDidDisappear:控制器的view完全消失的时候
    • viewWillUnload: iOS6以上的 UIKit 不会在内存警告时自动释放视图,所以viewWillUnload和viewDidUnload将不再触发
    • viewDidUnload
    • dealloc: 析构,回收内存资源

    ViewController 的 view 会采用懒加载方式加载,加载的 view 会一直保持在内存中,即使 app 进入后台。而如果在 TabBarViewController 和 NavigationController 中进行视图切换时,就会循环调用 viewWillDisappear,viewDidDisappear,viewWillAppear,viewDidAppear 这些方法。

    UIView 的生命周期

    UIView,尤其是 UIViewcontroller 的根 View,其生命周期都在 UIViewController 生命周期内,也可以理解为被 Controller 所持有。

    具体来说有以下几个方法

    - (instancetype)init
    
    - (void)layoutSubviews
    
    - (void)didAddSubview:(UIView *)subview
    
    - (void)willRemoveSubview:(UIView *)subview
    
    - (void)willMoveToSuperview:(UIView *)newSuperview
    
    - (void)didMoveToSuperview
    
    - (void)willMoveToWindow:(UIWindow *)newWindow
    
    - (void)didMoveToWindow
    
    - (void)removeFromSuperview
    
    - (void)dealloc
    

    通过实践得出以下结论:

    • Controller 在 loadView 方法里会调用 didAddSubview,willMoveToSuperview,didMoveToSuperview 等方法将 子 view 初始化并加入到根 view 中
    • Controller 在 viewWillLayoutSubviews 方法中会调用 willMoveToWindow,didMoveToWindow 等方法,将view 移动到 UIWindow 中
    • Controller 在 viewWillLayoutSubviews 方法中会调用 layoutSubviews 方法,此时子 view 对自己的子 view 向下依次进行布局操作

    相关文章

      网友评论

          本文标题:011-Application,VIewController,V

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