应用程序 = 代码 + 系统框架
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 向下依次进行布局操作
网友评论