一、前文回顾
在之前已经介绍了 iOS的学习路线图,因为中间遇到一些Android开发问题,所以就耽搁了一段时间,那么接下来的这段时间我们将继续开始iOS的狂暴之路学习,按照国际惯例,第一个应用当然是我们的HelloWorld程序了。那么本文将会通过这么一个简单的程序来讲解一下iOS中的程序生命周期,应用中关键的几个对象,项目结构,最后在手把手的创建一个空项目。
二、建立简单程序HelloWorld
下面先用Xcode来新建一个HelloWorld程序:
点击下一步即可:
这里和我们在AndroidStudio中新建Android程序非常类似,不多说了,点击下一步即可:
这样项目就建立好了,下面我们让程序运行起来,展示一个HelloWorld文字。这个有两种方式,在Android中也是类似,我们这里使用UILabel来进行操作。类似于Android中的TextView控件,一种方式是代码写布局,一种是采用拖的方式类似于Android中的布局文件。这里为了方便而且以后会详细介绍拖的那种方式,直接用代码进行编写了。
那么我们在哪里编写代码呢?这里的入口是在根控制器ViewController类:
这个入口类似于Android中的Activity的onCreate方法,我们一般都是在这里初始化一个View,这里看到代码逻辑不是很复杂,但是隐含的知识点很多,不过不是本文的重点,比如这里涉及到了iOS的坐标问题,View之间的关系等,这个是在后面章节会详细介绍View知识点在加以分析。代码写完了,运行程序吧,运行也很简单:
直接点击左上角的运行按钮即可,同时可以选择项目运行的目标设备。运行结果如下:
这里看到了,在4s设备中一个简单的HelloWorld就展示出来了。好了,下面就要开始我们今天的重要内容了,我们从这个简单的程序入手能够学习到哪些知识。
三、程序知识点分析
第一、程序的入口和生命周期
我们在开发Android程序的时候都知道其实一个应用程序的入口并不是MainActivity的onCreate方法也不是Application的onCreate方法,而是ActivityThread的main方法,与之类似,在iOS中一个程序的入口也是main方法,而这个main类在main方法中:
每个程序都有一个main.m这个类,内部有一个main方法,而这个方法我们看到和C语言中的main函数形式是一致的,入口就在这里,那么这里干了一件事就是托付应用程序的代理对象AppDelegate类,也就是把整个应用程序的逻辑都托付给了AppDelegate类,在iOS中这种方式叫做代理,而在Android中我们通常叫做回调机制,然后UIApplicationMain类就会和AppDelegate类进行交互,比如应用的生命周期,事件处理等,那么下面就来看一下AppDelegate这个类:
在这个类中我们可以看到很多时机的回调方法,这个就是和应用的生命周期方法相关联的,下面就来一一分析这些方法的回调时机:
1、告诉代理启动基本完成程序准备开始运行
- (BOOL)application:(UIApplication *)application www.yghrcp88.cn didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
2、告诉代理进程启动但还没进入状态保存
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
3、当应用程序将要入非活动状态执行,在此期间,应用程序不接收消息或事件,比如来电话了
- (void)applicationWillResignActive:www.huacairen88.cn(UIApplication *)application
4、当程序被推送到后台的时候调用。所以要设置后台继续运行,则在这个函数里面设置即可
- (void)applicationDidEnterBackground:(UIApplication *)application
5、当程序从后台将要重新回到前台时候调用,这个刚好跟上面的那个方法相反
- (void)applicationWillEnterForeground:(UIApplication *)application
6、当应用程序入活动状态执行,这个刚好跟上面那个方法相反
- (void)applicationDidBecomeActive:(UIApplication *)application
7、当程序将要退出是被调用,通常是用来保存数据和一些退出前的清理工作
- (void)applicationWillTerminate:www.zhenloyl88.cn(UIApplication *)application
8、当应用接收到内存警告回调方法
-(void)applicationDidReceiveMemoryWarning:www.yinbaovip.cn/(UIApplication *)application
看完上面的这些生命周期的回调方法之后,大家可能会想到这个和Android中的Activity非常类似了,而不是Application类了,在Android中比较复杂一般有三个物理按键关系Activity的生命周期,而在iOS中只有一个Home键,所以这里的生命周期很好理解的。
第二、应用的窗口
上面就介绍完了iOS程序的生命周期相关的类AppDelegate,下面还需要继续分析,怎么关联上ViewController类的,把HelloWorld展示出来的?
首先我们在程序启动完成之后的回调方法中打印一个日志,把当前应用的跟控制器打印出来:
注意:iOS中的打印日志方法和Android也很类似,使用字符串格式化打印即可,一般打印对象的占位符是%@,而如果想打印一个对象特定值的话,就需要实现这个类的description方法,这个和Java中的toString方法一样的。这里在多说几句,就是一般打印基本类型的格式如下:
%d,%i 整型 (%i的老写法)
%hd 短整型
%ld,%lld 长整型
%u 无符整型
%f 浮点型和double型
%0.2f 精度浮点数,只保留两位小数
%x 为32位的无符号整型数(www.zhenlyule.cn/ unsigned int),打印使用数字0-9的十六进制,小写a-f;
%X 为32位的无符号整型数(unsigned int),打印使用数字0-9的十六进制,大写A-F;
%o 八进制
%p 指针地址
%s char*类型的字符串
%c char字符类型
%C unichar类型
而这些类型打印格式不用背的,用多了就自然记住了,而且这里用的最多的应该就是%i,%s,%p,%c,%f这几个。
打印结果如下:
看到了当一个应用启动了之后,有哪些生命周期的回调方法会执行,同时看到了根控制器就是我们上面添加HelloWorld控件的入口ViewController类对象。那么这里就引出了两个对象:一个是selfwww.myqunliphoto.com .window对象,一个是window.rootViewController对象。下面先来看看self.window这个对象了,其实这个对象是一个UIWindow类型。和Android一样,一个程序就对应一个窗口是后续展示View的基础。
通常一个应用就对应一个UIWindow,就是应用的窗口,但是这话也不能说的太绝对,因为后面不管是Android还是iOS都会出现多屏开发模式,到时候就不止一个Window窗口了。他是后续展示View的基石,如果没有了这个UIWindow的话,那么应用所有的UI都展示不出来,其实UIWindow是一个特殊的View,他继承了UIView类的,也是可以直接添加View控件的以及你可以像使用View那样去操作它:
而上面是说到了,一个应用只能有一个UIWindow,但是我们可以定义多个UIWindow,然后设置指定的UIWindow为主窗口,也是可以的。如果想让哪个窗口成为主窗口并且展示的话,只需要调用makeKeyAndVisible方法即可。
第三、应用的根控制器
上面介绍完了UIWindow知识点,下面在来看一下UIViewController类,在iOS中UIViewController类就相当于Android中的Activity,用于控制View的展示功能,他是一个应用中每个页面的载体,在Android中一个程序都有一个默认或者是主Activity,在iOS中也是一样,有一个根控制器,而这个根控制器就是需要设置到应用的唯一窗口中,也就是UIWindow的rootViewController属性值。关于控制器也有一些生命周期方法,用的最多也是最主要的一个就是View加载完成之后的方法:
- (void)viewDidLoad
这个方法有点类似于Activity的onCreate方法,我们如果想自己添加View的话,就是在这个方法中进行操作的。从上面的那个例子中我们可以看到在这个方法中我们定义了一个UILabel属性,而每个控制器又有一个非常重要的属性就是父View,我们自己需要添加的View都可以通过addSubView方法添加即可。
注意:
在iOS中添加View非常简单和方便,因为任何一个控件包括上面讲到的UIWindow都是继承UIView类,而这些控件可以随便添加其他控件,没有任何限制,比如你可以在一个按钮UIButton中添加一个标签UILabel完全可以的,但是你要是在Android中就不可以了,Android中如果一个控件要添加子View,他必须继承ViewGroup,比如我们常用的几种布局类,这个看来iOS真的比Android简单多了。
我们可以看到一个应用程序肯定会包含多个控制器的,因为一般应用都会包含多个页面,而且每个页面之间还可以跳转,在Android中Activity之间的跳转使用Intent来进行操作的,但是在iOS中没有这种机制,而是用特定的控制器管理类,一般是用到的导航控制器和选项卡控制器来做管理,具体知识到后面接受控制器在详细讲解。
第四、总结四大对象关系
到这里,我们就分析完了一个iOS程序中非常重要的四个对象:UIApplicationDelegate,UIWindow,UIViewController,UIView
从这张图中,可以了解到,一个应用程序一定包含了程序应用的代理对象UIAppDelegate类,应用窗口UIWindow类。然后窗口还有一个根控制器页面UIViewController类,每个控制器都有一个展示UI的父View,根控制器也不例外。最后是每个控制器都会关联一个父View用于展示每个页面的UI。
四、新建一个空项目
当我们知道了一个应用的结构以及关键对象之后,下面我们就来手把手的新建一个空项目,然后自己去新建这四个关键对象。因为Xcode8之后就不在支持新建一个空项目了,为了新建一个空项目,我们可以新建一个项目然后把类都删了,就变成这样了:
第一步:新建一个UIViewController根控制器
一定要选择CocoaTouch Class文件:
到这里我们其实就相当于完成了一个简单的应用程序了,下面就需要在应用的回调方法中设置窗口信息和根控制器信息即可:
这时候运行程序可以看到,一片红色的应用:
从这里就可以看到,一个应用其实最基本的元素一定有UIApplicationDelegate和UIWindow和根控制器即可,那么这个其实也是一个简单的应用了,但是在实际开发中肯定是包含多个控制器的,并且每个根控制器都对应一个View布局文件,所以下面就开始手把手的创建对应的View信息。当然这里可以可以在控制器的回调方法中手动的编码添加View,但是这里我们在介绍iOS中其实和Android一样,每个控制器都有对应的布局文件,这里叫做xib文件。
第二步:新建一个xib文件
选择下一步,命名root.xib即可
新建完成之后,可以看到什么都没有:
我们就需要一步一步的创建了。
第三步:关联xib和控制器
然后我们把当前的xib的File's Owner设置成我们的根控制器,这里必须得设置xib的File' Owner,不然会报错的。
所以从这里可以看到,这里的xib其实就相当于Android中的布局文件xml,每个布局文件都是和一个Activity进行关联的,而这里也是一样的原理,一个xib必须依托一个控制器,所以这里的File's Owner设置对应的控制器即可。
注意:
但是iOS中的xib文件和Android中的布局文件又有点差别,这里xib不仅可以设置视图布局格式,后面再会介绍一个案例,这里可以包含一些特殊的对象,后面介绍如何把控制器,窗口等对象都搞到一个xib中,完全不用编写代码可以实现一个应用的创建了,而关于xib和Xcode默认创建的stroyboard有什么区别呢?就可以简单的理解,stroyboard更好的管理多个控制器和视图之间的关系,比如现在我想创建一个控制器,那么就要创建对应的xib文件了,而一个项目中如果有多个控制器页面,那么工程中就会有多个xib文件,这样可能会不好管理,而stroyborad就可以把这些文件都搞到一起管理,但是从其他iOS老鸟中得知,这个其实还是要具体项目和每个人的开发习惯了,并不是强烈要求一定要用stroyborad的。
第四步:在xib中添加父View
上面设置好了xib文件和控制器的关系,下面还得设置控制器的父View属性值,首先我们得给这个xib中添加一个View,如果不设置的话,运行程序会报错的,添加View很简单,我们在Xcode的最右下角有一个可以选择各种控件和对象的界面:
搜索View控件,找到之后,直接拖到xib内容中即可。托成功之后,我们还需要把控制器的view属性关联到这个View控件中,不然也是会报错的。这里就很简单了,对于Xcode开发工具来说,只要一拖连接即可。如下图所示:
注意:
在iOS中有一个很方便的功能,就是给对象赋值以及后面给控件添加事件,有时候可以选择拖这种快捷方式的,不过得有一个前提,就是这个属性必须设置IBOutlet特性,设置之后就可以看到属性前面有一个小圆圈了:
表示会出现在拖的列表中了,然后如果想赋值给这个属性,就把他拖到指定对象即可,所以中这里也可以看到iOS中的xib和Android中的布局文件有点区别。还有一个是IBAction特性,这个主要是添加控件的事件的,这个一般是添加在一个方法中的,然后这个方法就会出现在拖的列表中,比如我们在控制器中定义一个这个方法:
这里可以看到也是有一个小圆圈的,下面在回去看xib中的File's Owner:
这里可以看到了,当你去拖doClick方法的右边加号圆圈到一个UIButton控件上的时候,会出现这个界面,其中列举了多个事件,我们可以选中一个之后,就表示这里的UIButton控件的这个事件触发的方法是doClick了。从这个可以看到给View控件赋值和添加事件的方法还是非常方便的,而在Android中就需要findViewById方法,然后在set一些事件回调了,当然Android中现在有一些开源框架做到了这种类似功能,主要采用注解功能,无序findViewById赋值和添加事件了。不过我不怎么喜欢使用这个框架,感觉原生最好!
第五步:加载xib文件
好了,到这里我们就新建好了一个根控制器以及对应的xib文件了,那么下面如果想让这个控制器展示这个xib内容,还需要做一些工作就是要手动的加载这个xib文件,上面那个连接操作只是简单的建立关系,加载还得自己写代码,就相当于Android中的setContentView方法效果一下,这里就需要改一下控制器的初始化方法了:
控制器提供了一个initWithNibName:bundle:方法,这个方法可以显示的加载一个xib文件,这里需要注意两点就是xib文件名不能携带后缀名,其次就是第二个参数表示xib文件的路径,传入nil表示从项目的根目录中查找。
设置了控制器加载xib代码之后,下面就可以运行程序了:
看到效果之后,发现我们不仅可以在控制器中手动编写代码添加View,也可以使用xib文件来进行页面布局的。
到这里我们就手把手的新建了一个简单项目,在这个过程中我们将更了解了程序中的四个重要对象的关系了,下面在来总结一下:
1、UIAppDelegate类
这个类主要是用来管理应用的生命周期和一些事件的回调方法,每个应用都将有一个这个类。
2、UIWindow类
这个类主要是用来展示应用UI界面的窗口,他其实是一个特殊的UIView,可以像操作控件一样来操作它,而每个应用都有一个唯一的主窗口,也就是UIAppDelegate类中的window属性值。
3、UIViewController类
这个类相当于管理应用中每个页面的功能类,他和Android中的Activity非常类似,而一个应用一般都是会包含多个控制器的,但是一定有一个根控制器,这个控制器一般要设置到UIWindow的rootViewController属性值中。同时每个控制器如果要用代码布局,就在viewDidLoaded方法中进行添加,如果要用xib布局文件来操作,就需要手动的进行加载操作,使用控制器的initWithNibName:bundle:方法。
4、UIView类
其实这个类是iOS中每个控件的父类,但是每个控制器都有一个父View,这个就是UIView类,一般要设置到UIViewController的view属性值中的。
五、知识点补充
第一、解析xib文件
xib文件的解析过程:
1> 创建好Objects下面的所有对象
2> 建立好Objects下面的所有对象和File's Owner的关联关系
在上面介绍xib中说到了,xib不仅可以添加控件视图,也可以添加各种对象的,比如这里我们可以添加一个UIWindow和UIViewController对象:
看到了吧,一个xib其实可以管理一个简单的应用程序的所有对象的,然后我们可以这是这些对象的关系就好了。
第二、应用四大对象设置关系和步骤
1> 调用init方法或者initWithNibName:bundle:方法创建控制器
self.viewController = [[RootViewController alloc] init];(创建好控制器之后,并没有马上初始化它内部的view)
2> 设置控制器为UIWindow的根控制器
self.window.rootViewController = self.viewController;(设置完根控制器之后并没有将控制器的view添加到窗口中)
3> 显示窗口,并且把控制器的view添加到窗口中
[self.window makeKeyAndVisible];
首先调用控制器的loadView进行初始化控制器的view(可以通过xib文件\可以通过代码创建)
第三、UIViewController生命周期方法的调用顺序
1> view初始化完毕后,就会调用控制器的viewDidLoad方法
2> view初始化完毕后,就会把这个根控制器的view添加到窗口中
3> 当view即将被添加到窗口中时,就会调用控制器的viewWillAppear:方法
4> 当view已经被添加到窗口中时,就会调用控制器的viewDidAppear:方法
5> 如果控制器的view即将从窗口中移除时,就会调用控制器的viewWillDisappear:方法
6> 如果控制器的view已经从窗口中移除时,就会调用控制器的viewDidDisappear:方法
7> 如果控制器接收到内存警告的时候,就会调用控制器的didReceiveMemoryWarning方法
didReceiveMemoryWarning方法的默认实现是:如果控制器的view没有显示在窗口中,也就是说controller.view.superview为nil时,系统就会销毁控制器的view.
8> 销毁完毕后会调用控制器的viewDidUnload方法
9> 如果控制器的view以前因为内存警告被销毁过,现在需要再次访问控制器的view时,会重复前面的步骤初始化view
六、总结
本文介绍的内容有点多,但是是我们开发iOS的入门,也是我们的第一个iOS程序,我们从这个简单的程序中可以了解到了一个程序涉及到的生命周期以及几个重要对象,他们之间的关联关系等。同时也了解了iOS中的界面布局功能和两种方法。后面讲继续介绍iOS中的控制器的内容。