作为应用程序的委托对象,AppDelegate类在应用生命周期的不同阶段会回调不同的方法。
iOS应用程序都遵循Model-View-Controller的架构,Model负责存储数据和处理业务逻辑,View负责显示数据和与用户交互,Controller是两者的中介,协调Model和View相互协作。它们的通讯规则如下:
Not Running(非运行状态):应用没有运行或被系统终止运行;
Inactive(前台非活跃状态):应用正式进入前台状态,但是还没有接受事件处理;
Active(前台活跃状态):应用进入前台状态,能接受事件并且进行处理;
Background(后台状态):应用进入后台之后,依然能够执行代码。如果有可以执行的代码,就会执行,如果没有可执行的代码或者将可执行的代码执行完毕,应用会马上进入挂起状态;
Suspended(挂起状态):被挂起的应用进入一种“休眠”状态,不能执行任何代码。当手机系统内存不足时,应用会被终止。
在应用状态有变化的过程中,iOS系统会调用AppDelegate中的一些方法,并且发送一些通知。下面汇总了一部分主要的方法和和通知。
//应用启动并进行初始化时会调用该方法并发出通知。这个阶段会实例化跟试图控制器。
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
//本地通知:
UIApplicationDidFinishLaunchingNotification
//应用进入前台并处于活跃状态时调用该方法并发出通知。这个阶段可以恢复UI的状态。
- (void)applicationDidBecomeActive:(UIApplication*)application
//本地通知:
UIApplicationDidBecomeActiveNotification
//应用从活跃状态进入到非活跃状态时调用该方法并发出通知。这个阶段可以保存UI的状态。
- (void)applicationWillResignActive:(UIApplication*)application
//本地通知:
UIApplicationWillResignActiveNotification
//应用进入后台是调用该方法并发送通知。这个阶段可以保存用户数据,释放一些数据库资源等。
- (void)applicationDidEnterBackground:(UIApplication*)application
//本地通知:
UIApplicationDidEnterBackgroundNotification
//应用进入到前台,但是还没有处于活跃状态是调用该方法并发出通知。这个阶段可以恢复用户数据。
- (void)applicationWillEnterForeground:(UIApplication*)application
//本地通知:
UIApplicationWillEnterForegroundNotification
//应用被终止时调用该方法并发出通知,内存清除时除外。这个阶段会释放一些资源,也可以保存用户数据。
- (void)applicationWillTerminate:(UIApplication*)application
//本地通知:
UIApplicationWillTerminateNotification
1、程序启动:状态由Not running -> Inactive -> Active
willFinishLaunchingWithOptions
didFinishLaunchingWithOptions
applicationDidBecomeActive
2、点击home键|锁屏:由Active -> Inactive -> Backgroud
applicationWillResignActive
applicationDidEnterBackground
3、重新进入前台:Backgroud -> Inactive -> Active applicationWillEnterForeground
applicationDidBecomeActive
4、在前台,双击home键,手动杀掉APP:Active -> Inactive -> Backgroud -> end
applicationWillResignActive
applicationDidEnterBackground
applicationWillTerminate
当URL到达时,如果你的应用没在正在运行,则会被启动并且移到前台运行以打开URL
application:didFinishLaunchingWithOptions:
application:openURL:sourceApplication:
applicationDidBecomeActive
当URL到达时,如果你的应用正在background运行或被suspended,它将会被移到前台以打开URL
applicationWillEnterForeground
application:openURL:sourceApplication:
applicationDidBecomeActive
基于C编写的app的入口都是main函数,但iOS应用程序有点不同。不同就是你不需要为iOS应用程序而自己编写main函数,当你使用Xcode创建工程的时候就已经提供了。除非一些特殊情况,否则你不应该修改Xcode提供的main函数实现。示例代码如下:
#import
#import "AppDelegate.h"
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
上面实例代码中有一个很重要的函数UIApplicationMain,它主要是创建app的几个核心对象来处理以下过程:
- 从可用Storyboard文件加载用户界面
- 调用AppDelegate自定义代码来做一些初始化设置
- 将app放入Main Run Loop环境中来响应和处理与用户交互产生的事件
UIApplication对象
用户与iOS设备交互时产生的事件(Multitouch Events,Motion Event,Remote Control Event)交由UIApplication对象来分发给control objects(UIControl)对应的target objects来处理并且管理整个事件循环,而一些关于app运行时重要事件委托给app delegate来处理。
App delegate对象
App delegate对象遵循UIApplicationDelegate协议,响应app运行时重要事件(app启动、app内存不足、app终止、切换到另一个app、切回app),主要用于app在启动时初始化一些重要数据结构;例如,初始化UIWindow,设置一些属性,为window添加rootViewController。
View controller对象
View Controller有一个view属性是view层次结构中的根view,你可以添加子view来构建复杂的view;controller有一些viewDidLoad、viewWillAppear等方法来管理view的生命周期;由于它继承UIResponder,所有还会响应和处理用户事件。
Documents和data model对象
data model对象主要用来存储数据。例如,饿了么app在搜索切换地址后,有历史记录搜索地址历史,当app下次启动时,读取和显示搜索地址历史。
document对象(继承UIDocument)用来管理一些或所有的data model对象。document对象并不是必须的,但提供一种方便的方式来分组属于单个文件或多个文件的数据。
UIWindow对象
UIWindow对象位于view层次结构中的最顶层,它充当一个基本容器而不显示内容,如果想显示内容,添加一个content view到window。
它也是继承UIResponder,所以它也是会响应和处理用户事件。
View、control、layer对象
View对象可以通过addSubview和removeFromSuperview 等方法管理view的层次结构,使用layoutIfNeeded和setNeedsLayout等方法布局view的层次结构,当你发现系统提供view已经满足不了你想要的外观需求时,可以重写drawRect方法或通过layer属性来构造复杂的图形外观和动画。还有一点,UIView也是继承UIResponder,所以也能够处理用户事件。
Control对象通常就是处理特定类型用户交互的View,常用的有button、switch、text field等。
除了使用View和Control来构建view层次结构来影响app外观之外,还可以使用Core Animation框架的Layer对象来渲染view外观和构建复杂的动画。
一个iOS应用程序的main run loop主要作用是处理所有与用户相关的事件。UIApplication对象在启动时就设置main run loop和使用它来处理事件和更新基于view的界面。正如它名字所示,main run loop是运行在应用程序的主线程。这样就确保与接收到用户相关的事件被有序地处理。
下图显示main run loop的架构和用户事件最终是怎样被应用程序处理。当用户与设备交互时,系统就会生成与交互关联的事件,然后被应用程序的UIKit通过一个特殊的端口来分发。应用程序把事件放入队列,然后逐个分发到main run loop来执行。UIApplication对象是第一个对象接收到事件,然后决定怎样处理它。一个touch event通常都被分发到main window对象,然后依次分发到发生触碰的view。其他event的接收事件对象路径可能有点不同。
大多数的事件通过使用main run loop来分发,但有些不是。有些事件被发送到一个delegate对象或传递到你提供的block中。想了解更多如何处理大多数类型的事件,其中包括touch、remote control、motion、accelerometer和gyroscopic等事件,请查阅Event Handle Guide for iOS。
ios中UI是按层来算的,如下图,新生成的界面都往上堆叠,可以使用navigation controller来控制界面跳转,而且都是带左上角返回图标的,使用navigation controller之后这一系列页面都是可以使用其控制,可以获取rootView。
有关UIWindow的介绍参考iOS开发UI篇–UIWindow简单介绍
UIWindow是一种特殊的UIView,通常在一个app中只会有一个UIWindow
iOS程序启动完毕后,创建的第一个视图控件就是UIWindow,接着创建控制器的view,最后将控制器的view添加到UIWindow上,于是控制器的view就显示在屏幕上了
一个iOS程序之所以能显示到屏幕上,完全是因为它有UIWindow。也就说,没有UIWindow,就看不见任何UI界面
先创建UIwindow,再创建控制器,创建控制器的view,然后将控制器的view添加到UIWindow上。
ViewController是IOS开发中MVC模式中的C,ViewController是view的controller,ViewController的职责主要包括管理内部各个view的加载显示和卸载,同时负责与其他ViewController的通信和协调。在IOS中,有两类ViewController:
- 一类是显示内容的,比如UIViewController、UITableViewController等,同时还可以自定义继承自UIViewController的ViewController;
- 另一类是ViewController容器,UINavigationViewController和UITabBarController等,UINavigationController是以Stack的形式来存储和管理ViewController,UITabBarController是以Array的形式来管理ViewController。
和Android中Activity一样,IOS开发中,ViewController也有自己的生命周期(Lifecycle)。
从图中可以看到,在view加载过程中首先会调用loadView方法,在这个方法中主要完成一些关键view的初始化工作,比如UINavigationViewController和UITabBarController等容器类的ViewController;接下来就是加载view,加载成功后,会接着调用viewDidLoad方法,这里要记住的一点是,在loadView之前,是没有view的,也就是说,在这之前,view还没有被初始化。完成viewDidLoad方法后,ViewController里面就成功的加载view了,如上图右下角所示。
在Controller中创建view有两种方式,一种是通过代码创建、一种是通过xib的方式可视化创建,后者可以比较直观的配置view的外观和属性,Storyboard配合IOS6后推出的AutoLayout。通过xib或Storyboard创建view,在Controller中创建view后,会在Controller中对view进行一些操作,会出现如下代码:
xib方式(官方推荐)
通过storyboard拖拉的方式,控件和事件通过拖拉对应然后通过xib加载
CCTableViewCell_nib *cell=(CCTableViewCell_nib *)
[tableView dequeueReusableCellWithIdentifier:CellTableIdentifier
forIndexPath:indexPath];
代码方式(推荐)
CCTableViewController *vc =
[[CCTableViewController alloc] init];
[self.navigationController
pushViewController:vc animated:YES];
当一个视图控制器被创建,并在屏幕上显示的时候。 代码的执行顺序
1、 alloc
创建对象,分配空间
2、init (initWithNibName|initWithCoder)
初始化对象,初始化数据
3、awakeFromNib
所有视图的outlet和action已经连接,但还没有被确定。
4、loadView
完成一些关键view的初始化工作,加载view。
5、viewDidLoad
载入完成,可以进行自定义数据以及动态创建其他控件
6、viewWillAppear
视图将出现在屏幕之前
7、viewWillLayoutSubviews
将要对子视图进行调整
8、viewDidLayoutSubviews
对子视图进行调整完毕
9、viewDidAppear
视图已在屏幕上渲染完成
10、viewWillDisappear
视图将被从屏幕上移除
11、viewDidDisappear
视图已经被从屏幕上移除
12、dealloc
视图被销毁,此处需要对你在init和viewDidLoad中创建的对象进行释放
13、didReceiveMemoryWarning
内存警告
ViewController的生命周期中各方法执行流程如下:
alloc -> init -> loadView -> viewDidLoad -> viewWillAppear -> viewWillLayoutSubviews -> viewDidLayoutSubviews -> viewDidAppear -> viewWillDisappear -> viewDidDisappear -> dealloc
在使用navigation controller时,只需这样操作:
在storyboard视图点击view页面,在xcode导航栏选择Editor->Embed in->Navigation Controller,即可使用
self.navigationController
控制页面跳转。
使用navigation controller控制页面,代码类似这样:
//点击按钮新建一个页面,然后再写一个页面的controller代码里面控制下一个页面里的东西。
- (IBAction)buttonPressed:(UIButton *)sender {
CCTableViewController *vc = [[CCTableViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
//点击tableview里面的cell,然后实现页面跳转
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//弹出当前界面,返回上一层
//[self.navigationController popViewControllerAnimated:YES];
//返回到根页面
//[self.navigationController popToRootViewControllerAnimated:YES];
// 创建一个控制器,将其页面设为绿色的空白页面
UIViewController *vc = [[UIViewController alloc] init];
vc.view.backgroundColor = [UIColor greenColor];
[self.navigationController pushViewController:vc animated:YES];
}
WKWebView是iOS8推出的,WKWebView有一个突出特点,就是内存占用少。Webview的使用,通常包含以下几个部分:浏览器的基本设置,浏览器的各种回调,浏览器中js如何调用原生方法。
WKWebView基本使用
self.webview = [[WKWebView alloc]init];
[self.view addSubview:self.webview];
[self.webview mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view);
make.right.equalTo(self.view);
make.top.equalTo(self.view);
make.bottom.equalTo(self.view);
}];
[_webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]]];
WKWebView与其他WebView的基本使用没有太大区别,将它当作一个普通的UIView进行布局,然后调用loadRequest方法,即可展示相应的网页。
WKWebView的设置
WKWebView的主要设置项都在configuration成员中(比如是否允许浏览器手指缩放,h5的浏览器能否自动播放等)这些都属于浏览器本身的设置项。
在此列举几项及其作用,由于类目繁多,会有遗漏,真正需要使用时,可以在xcode的help中寻找,查阅。
// 默认值为NO,用户不可以放大或缩小页面;如果设置为YES,页面可以通过放大缩小去适应,用户也可以通过手势来放大和缩小
[self.webview.configuration ignoresViewportScaleLimits];
//这个值决定了网页内容的渲染是否在把内容全部加载到内存中再去处理。如果设置为YES,只有网页内容加载到内存里了才会去渲染
[self.webview.configuration suppressesIncrementalRendering];
// 默认使NO。这个值决定了用内嵌HTML5播放视频还是用本地的全屏控制。
[self.webview.configuration allowsInlineMediaPlayback];
// A Boolean value indicating whether AirPlay is allowed.
[self.webview.configuration allowsAirPlayForMediaPlayback];
// A Boolean value indicating whether HTML5 videos can play picture-in-picture.
[self.webview.configuration allowsPictureInPictureMediaPlayback];
// 网页中的多媒体是否需要手势才能开始播放(iOS 10) 可以设置仅音频需要、仅视频需要等四种状态
self.webview.configuration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
// 控制用户与webview进行选择交互时的粒度,可以选择整个块儿,或单个符号.
self.webview.configuration.selectionGranularity = WKSelectionGranularityDynamic;
WKWebView的回调
WKWebView中包含了两个delegate。WKNavigationDelegate和WKUIDelegate。
WKNavigationDelegate
这个是WKWebView的导航的代理。它控制了WKWebView在加载一个页面流程中的各个关键时间节点的。相当于WKWebView加载的生命周期方法。
#pragma mark - WKNavigationDelegate
// 页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
NSLog(@"页面开始加载");
}
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{
NSLog(@"页面开始返回");
}
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
NSLog(@"页面完成加载");
}
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{
NSLog(@"页面加载失败");
}
// 接收到服务器跳转请求之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
NSLog(@"页面重定向");
}
// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
NSLog(@"%@",navigationResponse.response.URL.absoluteString);
//允许跳转
decisionHandler(WKNavigationResponsePolicyAllow);
//不允许跳转
//decisionHandler(WKNavigationResponsePolicyCancel);
}
// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
NSLog(@"%@",navigationAction.request.URL.absoluteString);
//允许跳转
decisionHandler(WKNavigationActionPolicyAllow);
//不允许跳转
//decisionHandler(WKNavigationActionPolicyCancel);
}
WKUIDelegate
WKUIDelegate控制了WKWebView的UI绘制。即我们可以掌管,部分H5中的绘制行为。
#pragma mark - WKUIDelegate
// 创建一个新的WebView
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{
return [[WKWebView alloc]init];
}
// 输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler{
completionHandler(@"http");
}
// 确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{
completionHandler(YES);
}
// 警告框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
NSLog(@"%@",message);
completionHandler();
}
js调用原生方法
拦截url
此方法的本质是,js会尝试加载某个URL,客户端在加载前拦截这个URL,通过解析这个URL识别它的内容,调用相应的原生方法,并阻上浏览器加载这个URL。
所以我们在decidePolicyForNavigationAction这个方法中进行拦截。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
NSLog(@"%@",navigationAction.request.URL.absoluteString);
NSURL *url = navigationAction.request.URL;
NSString *urlStr = url.absoluteString;
if ([urlStr isEqualToString:@"xxx"]) {
// do something ;
//不允许跳转
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
//允许跳转
decisionHandler(WKNavigationActionPolicyAllow);
}
contentController
A WKUserContentController object provides a way for JavaScript to post messages and inject user scripts to a web view.
contentController苹果官方提供的js调用原生方法的类。它的使用方法是:
...
self.contentController = self.webview.configuration.userContentController;
[_contentController addScriptMessageHandler:self name:@"closeWebView"];
...
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
if ([message.name isEqualToString:@"closeWebView"]) {
//做处理 do something
//message.body 为此 ScriptMessage 传递的消息内容
}
}
js的调用方法:
window.webkit.messageHandlers.<事件名>.postMessage(需要传递的数据)
原生调用js方法
[self.webView evaluateJavaScript:@"function('action')" completionHandler:nil];
大致有几种方式:
参见:页面传值的几种方式
由于iOS每一个大的页面都是由一个viewcontroller控制的,其上的控件也由这个viewcontroller控制,所以关键就是只要做好页面间的传值就好,那么页面可以理解成一个类,所以类之间的传值的方法也都是可以用的。
对应与c++中的方法,由于下一个页面要在当前页面创建出来,所以可以再下一个页面的controller中写一个方法,在当前页面调用一下:
//ViewControllerB.h
@interface ViewControllerB : UIViewController
- (void)setValue(NSString *)value;
@end
//viewControllerA.h
@interface ViewControllerA ()
@property (nonatomic, copy) NSString *valueFromA;
- (void)btnClicked;
@end
//ViewControllerA.m
- (void)btnClicked
{
ViewControllerB *vcB = [ViewControllerB new];
[vcB setValue:valueFromA];
[self.navigationController pushViewController:vcB animated:YES];
}
//ViewControllerB.h
//创建协议
@protocol VcBDelegate <NSObject>
- (void)sendValue:(NSString *)value; //声明协议方法
@end
//ViewControllerB.h
@interface ViewControllerB : UIViewController
@property (nonatomic, weak)id<VcBDelegate> delegate; //声明协议变量
@end
//viewControllerA.m
@interface ViewControllerA ()<VcBDelegate>//遵循协议
@property (nonatomic, strong) UILabel *recievedLB;
@end
- (void)backActionClicked
{
//当代理响应sendValue方法时,把_tx.text中的值传到VCA
if ([_delegate respondsToSelector:@selector(sendValue:)]) {
[_delegate sendValue:_tx.text];
[self.navigationController popViewControllerAnimated:YES];
}
}
//ViewControllerA.m
- (void)btnClicked
{
ViewControllerB *vcB = [ViewControllerB new];
vcB.delegate = self;
[self.navigationController pushViewController:vcB animated:YES];
}
//ViewControllerA.m
//实现协议方法,把接收到的值展示到Label中
- (void)sendValue:(NSString *)value
{
_recievedLB.text = value;
}
具体设置需要3步:
1.App需要开启Associated Domains服务,并设置Domains,注意必须要applinks:开头。
2.域名必须要支持HTTPS。
3.上传内容是Json格式的文件,文件名为apple-app-site-association到自己域名的根目录下,或者.well-known目录下。iOS自动会去读取这个文件。具体的文件内容请查看官方文档。
参考:
iOS开发UI篇–UIWindow简单介绍
iOS应用程序的生命周期
iOS应用程序的生命周期_view
WWDC 2018:效率提升爆表的 Xcode 和 LLDB 调试技巧
iOS 组件化 —— 路由设计思路分析
iOS的WebView——WKWebView
iOS APP生命周期 和 UIViewController的生命周期
iOS ViewController的生命周期
iOS 中delegate的理解与使用(传值)