011-Application,VIewController,V

2017-11-26  本文已影响26人  Yasic

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

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 架构

当有用户交互事件到来时,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 的一些属性

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

寻找最合适视图

一般步骤如下

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

具体过程如下,当一个 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 状态与生命周期

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

如何在这几个状态中切换就需要 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 被切出的时候,有三种情况

当 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 就开始初始化自己的根视图。

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

通过实践得出以下结论:

上一篇 下一篇

猜你喜欢

热点阅读