1.iOS第一个简单APP

jopen 9年前

大纲:

  1. iOS 系统发展
  2. UI OC
  3. 简单的 APP 程序
  4. 程序的生命周期

1.iOS 的系统发展

1983 年OC程序开始发展到2015年, 30 多年的时间,但这依然不是一个十分完善的语言,可以说现在都没有一个十分完善的,不用更新了的编程语言。但是, iOS 选择了OC作为它的开发语言,这是为什么我们前期需要先来学习OC语言的原因。那其实之前我们学习的都是一些开发的必备知识,并没有接触任何的软件开发的知识。从今天开始才是真正的开始接触开发。我们知道市面上先有iOS、Android、Window Phone这三大移动平台。Windows Phone在国内是什么待遇呢?相信你们关注支付宝的人,应该听过 1% 的梗,window phone的用户要支付宝更新,结果支付宝发了一个微博说“你为什么选择 1% 的生活”,是说的它占有量不到 1%,其实我是非常喜欢微软的,我的第一个智能手机是WP7.0,我还有XBox、surface设备 。那还有Android和iOS的开发,Android是现在占有量最大的一个移动平台,你们用Android手机的人,应该新的旗舰级就还好,如果是一般的或者是以前的手机,那一定有一个反应就是,慢慢的手机变卡了。如果你接触智能手机时间比较早的话,你当时买的Android 2.3 的手机现在是都不可以升级到 4.0 以上了吧。而Android的一个变革时期就是 4.0 之后,优化好了很多。它这个硬件限制软件更新的方法,我觉得是回让他牺牲一定的用户量的,毕竟没有人希望看到自己去年买的手机,今年就不被支持了吧。但是上面我们说的事情,苹果就处理的比较好。再我们开发人员上面,Android的开发配置方面我们需要做的环境搭建,十分复杂,你光是做这个搭建开发环境的事情估计就需要半天了,但是我们这个iOS开发,到现在为止,做了什么来搭建环境啊?仅仅只是安装了一个XCode吧。实际上做开发的时候呢,其实这些都无伤大雅。综上所述呢,我们这个做iOS开发会比其他的有一定的优势。但同样,我们做这个的成本相对前两者就要高一点了,不管是买一个Mac还是安装一个黑苹果。

做iOS的开发,先要了解一下iOS这个系统的发展。从iOS 7 这之后,都是 摆脱了之前乔布斯的一个理念,不仅是从硬件方面,他的屏幕尺寸一点点的变大,还有它的整个拟物化的UI风格变成了一个扁平化的设计。这个都是现在iOS的一个发展趋势。所以我们了解他现在的一个界面的风格,也是我们以后对软件开发很重要的一块。我们可以来看看现在的一个真机上的样式。

  • 主界面

左边这个界面,就是我们现在iOS9.2最新版本的一个界面,我们看到了它上面的软件图标排列,就像是把一个个的软件放到棋盘格子里面了吧。

我们回忆一下,五子棋的棋盘是不是一个个90°的笔直的角,而我们看到这个软件的图标样式,是不是一个圆角的样子,这个已经被苹果注册了专利——圆角矩形。包括它的文件夹都是这个效果。

那我们再仔细一点看,看每一个图标的样子。是不是每一个都是很简单的图形,没有很多很炫的效果。都像一个个瓷砖贴屏幕在上面?这实际上是iOS7之后,力推的一个扁平化效果,和iOS7之前的一个拟物化效果完全不同。拟物化我们顾名思义,就是模仿现实生活中的物品来做的一个效果。比如以前的日历应用就是一本日历的样子,并且每一个都有阴影效果,显得很像现实生活中的东西一样。其实这个扁平化的历史很好玩,他是由谷歌的一个工程师提出来的概念,但是微软是第一个使用扁平化作为移动平台界面的公司,苹果呢,可以说是现在把扁平化效果做的最出色的一家公司。以至于有很多的消费者认为苹果是这个扁平化概念的提出者。这个扁平化我们要好好地记住了。

为什么我们要了解苹果现在的风格呢?其实我个人觉得最主要的一点,因为,你不能在控件列表中,找到以前的样式了,自己来做那种样式UI的话,麻烦的不是一点点。再者,苹果对按照他当前最新样式来开发的软件,只要你内容可以,那么会有一个在app store里面上内推榜的机会。这是一个推广自己软件的好机会。

  • 应用界面 

那我们再来看看这个时钟应用的内部,我们以后做的事情是来开发吧。看看这个官方的应用,我们看看一个应用由什么来组成。左上角的“编辑”和右上角的“+”号,是不是一个按钮啊?它可以被点击的,并且相应事件,是一个按钮啊。那我们再看中间有“世界时钟”几个字吧,这其实就是个字符串对不对。同样地道理,下面“北京”、“今天”、“柏林”等等这些东西都只是一些字符串吧。那在我们UI阶段,有专门放置字符串的控件,让他能够放到这个界面上面。每一个时钟,其实是个图片,我们也有专门的控件来放图片。那最底下那些东西,其实也是一些按钮了吧。所以我们可以看到,做一个软件还是很简单的啊。

那大家可以看到,iOS原生的应用都有一个固定的设计风格,并没有一个应用一个截然不同的风格吧。其实苹果不仅是对自己出得软件的界面风格有要求,他对所有在app store 上架的都要求符合现在的一个界面风格要求,保证自己整个商城的生态环境。这也是与Android最大不同的地方。那要符合这样一个设计风格,就要体现一个UI设计师的功底,和我们的关系就不是太大。但是说回来,如果一个APP的界面做的很丑,那用户也不会对这个软件买账啊,自然就发展不起来。说道这个用户,我们也讲一下app的这个商业盈利模式,像一些免费软件,它都有一些内购的东西吧,比如说每年WWDC大会上面,一些免费得软件或者游戏。 那些收费的软件就更不用说了吧,直接找你要钱的嘛。不过相比内购,我是更喜欢付费应用的。苹果现在是全球市值最高的公司,他会做一个只用来让人们方便下东西,让我们来赚钱的商城么,毕竟服务器的开销也不小!那他自己是怎么赚钱的呢?通过一个拥有高质量用户群体的app store ,拉动开发者为他开发软件,其中每一个app的下载,他就从中抽取 30%,有好的应用就会有更多的人进入到这个生态圈,更多的用户,那就会有更多好的开发者进入到这里面来, 这是一个完美的环。仅2014年一年,开发者通过app store赚取了150亿美元,其中就有45亿美元落入了apple的口袋 。现在你们可以想想这个app store 的价值。

2. UI 介绍

之前学习的都是开发的一个语言基础,不会这个编程语言,我们是没有办法在之后的工作中进行一个开发的,我们说学UI就是学习搭建一个程序的界面,如果你没有学习这个编程语言,那也是没有办法写出一个逻辑。这是我们之前为什么要学习OC的原因。那我们OC学了这么久,我们还有人想对着那个只能打印字符的控制台一辈子么?如果有的话,那你不适合做一个软件的开发,倒是可以去尝试下做一个后台的开发啊 ~ 那什么东西叫UI呢?User   Interface 这样一个词组的缩写,翻译过来“用户界面”,国外有的大学有专门的课程叫UI交互设计、UI设计等等,证明这个UI他也不是一个很简单的东西,不然也不会有这么多的课程专门来讲这个啊,但是我们对UI的使用就很简单,直接用别人总结出来的东西就可以了 ~ 但是我们要了解这个东西,怎么去使用别人的劳动成果。

我们学习UI到底是在学习什么呢?相信你们还记得OC里面我们学的Foundation框架,在UI阶段,我们也有一个框架,叫做UIKit,它也为我们封装了很多方法,可以直接进行一个操作。比方说我们可以直接使用他,来响应一个用户的点击或者触摸,可以在屏幕上显示我们想要的内容,比如一个文字或者图片,相对应的系统的相机、麦克风等等设备我们都可以进行一个调用。那相应的UIKit框架的使用,我们后面会一步一步来为大家讲解。我们之前说了iOS的一个系统架构是分成了 4 层,最下面的Core OS ,Core Service ,Media和Cocoa Touch 层,相比我们现在使用的Mac OS 系统,其实是在Cocoa框架上面封装了一层Cocoa Touch 层。也是和我们操作息息相关的一层,我们UI阶段要学习的就是Cocoa Touch 、Media层的使用。它里面包含了我们UI阶段使用最多的UIKit框架,还有地图、联系人、信息等等都是这个框架下的。还有Media层中也是我们要用到的,像播放音频视频、绘图就是Media层中来实现的。Core Service主要是 包含我们之前学的Foundation框架,Core OS 是最靠近底层的一层,我们使用的机会基本上没有了。

3. 简单 App 讲解

A. iOS 现行设备尺寸、像素规范

我们现在开发的是iOS操作系统,用的最多的就是iPad和iPhone两个设备,其实都是差不多的,我们现在就先来看看iPhone的开发

  • 如何创建一个Single View Application

还是像以前一样,我们开始新建一个工程,但是!我们不再是选OS X里面的命令行工程了,而是像上面显示的,我们找到iOS这个选项,选择他下面的Application,那我们就会看到有上面几个选项,第一个是在iPad上面会用到的,他有一个侧边栏可以选择。第二个是一个有分页效果的页面,可以左右滑动。第三个是单个页面的应用。第四个是带有一个标签栏,就像上面我们看的时钟应用,下面的按钮可以跳转到不同的一个页面。那最后一个就是Game,我就不说了。在我们学习过程中,使用最多的是第三个,也是我选中的Single View Application。

那我们像以前一样,点击Next,然后建立一个工程。这里我们先建立一个名字叫做test的Single View Application工程。                                                                                                                                                

那我们可以看到我们现在看到的不是像以前只有一个.m文件了,而是多了APPlication和ViewController这两个类,还出现了两个我们从来没有看到过的,一个Main.storyboard和一个Launchscreen.storyboard文件,我们等下会来介绍这个东西,讲这个东西有什么用。我们来选中Main.storyboard文件,看看他是什么样子。

这是一个正方形的图形,它是不是有点像一个平板的屏幕啊?比我们的手机屏幕要宽一些,长一些。storyboard顾名思义是一个故事板,storyboard就像一张白纸,你可以在上面写故事,什么意思呢?你可以在上面画任何你想要它显示的东西,我们可以使用这个工具对页面进行一个布局,想要在什么位置显示什么东西,拖相应的控件上去就行,这就是我们所谓的可视化开发。在往后的工作中,如果需要赶进度,使用这个工具,它甚至可以让界面的开发时间减少一倍。

我们先什么都不干,直接运行一次这个程序。还是和以前一样,我们按快捷键command+R。我们发现,有一个应用程序自动运行了,我们做iOS开发,你做项目,必须运行在iOS设备上面,那我们在开发阶段,使用模拟器来进行调试工作,当你项目比较完善之后,我们就使用真机来测试。那这就是一个iPhone的模拟器,我们现在看到的这个界面是一个完全空白的软件界面,内容上和我们之前看到的storyboard文件是完全一个样子。那我们默认运行的设备应该是iPhone6,在你的项目名旁边可以看到一个设备的名称,点击之后,就可以选择不同的模拟器来运行你的程序。那我们现在要记住的是一个现有的设备的尺寸,和逻辑分辨率这一栏。这对于我们日后进行一个开发是十分重要的数据。

设备

iPhone

Width

Height

对角线

Diagonal

逻辑分辨率(point)

Scale Factor

设备分辨率(pixel)

PPI

3GS

2.4 inches (62.1 mm)

4.5 inches (115.5 mm)

3.5-inch

320x480

@1x

320x480

163

4(s)

2.31 inches (58.6 mm)

4.5 inches (115.2 mm)

3.5-inch

320x480

@2x

640x960

326

5c

2.33 inches (59.2 mm)

4.90 inches (124.4 mm)

4-inch

320x568

@2x

640x1136

326

5(s)

2.31 inches (58.6 mm)

4.87 inches (123.8 mm)

4-inch

320x568

@2x

640x1136

326

6

2.64 inches (67.0 mm)

5.44 inches (138.1 mm)

4.7-inch

375x667

@2x

750x1334

326

6+

3.06 inches (77.8 mm)

6.22 inches (158.1 mm)

5.5-inch

414x736

@3x

(1242x2208->)

1080x1920

401 

那从整个界面上我们还能发现一个东西,照道理来说,这个界面应该是一个全白色的,那现在它上面多了一些数据。而且我们可以看到Carrier(运营商),有个WiFi标志。中间有一个时间,这个时间是我们电脑的系统时间,最右边是电量图标。这个东西呢,我们称之为状态栏,他在iOS7.0之前都是一个不透明的效果,是一个单独存在于屏幕上的东西,但是现在,他是透明的,怎么说呢?你可以看到他现在是一个白底黑字,你可以尝试将整个背景设置为黄色、绿色或其他的颜色,就会看到一个很明显的效果了。

   

那我们现在来尝试在界面上面,加入一些东西。我们点开main.storyboard文件,看看右下角

第一个是一些文件,比如创建类可以直接从这里拖出去就创建了一个类。

第二个是一些代码段。

第三个是一个(Object-Library)资源库,这里有很多的控件可以供我们来选择。但我们现在不会用到里面所有的控件,我们在之后的学习中会一点点的来学习。

第四个是媒体库。

B.做一个简单的加法计算器。

  • 界面的搭建

这里来尝试使用storyboard做一个简单加法计算器,上面是我们最后的成品效果图。点开main.storyboard文件,在右下方的资源库中,找到 这样一个控件。按住它,向storyboard上面拖过去,当storyboard变成蓝色之后,我们松开手,是不是上边多出了一个显示着Label字样的标签,接下来双击拖出来的这个控件,现在他就可以被编辑了,我们在上面打上“简易加法计算器”,点击一下旁边,取消这个编辑效果。可以看到它显示的样子并不是我们所希望的,那我们再次单击这个控件,这次控件上出现了 8个锚点,我们先拖动右下方这个锚点,可以看到,字一点点的可以显示出来了。我们将这个调整到合适的大小,顺便可以尝试一下其他几个锚点,看看效果。我们看效果图上面,还有“+”、“=”和“0.00”几个控件,这些都是可以使用刚才这个控件来进行实现的吧。尝试改变他们的位置和大小,让它看起来更美观。

我们再次看到右下方的资源库中,单击这个Label控件,我们可以看到旁边出现了一个对话框。那红色框起来的地方,UILabel是这个控件的类,代表什么,他是一个UILabel类控件吧,记住它,对以后代码编程的时候会很有用。

最下方还有一个可以点击的“计算”按钮,那按钮顾名思义Button吧,这里由UIButton控件来完成所需。依然是将Button拖到storyboard上面,但是我们看到并没有上图的蓝底白字的效果,打开右侧边栏,选中第四个像游标卡尺一些样的三角按钮,出现了很多选项可以让我们调整。将Text Color改为白色 ,View中的background改为蓝色 。现在就有了上面效果图的效果了吧。

同样地还有两个可以输入内容的地方,我们使用UITextField控件来完成,大家可以自己在资源库中找找。顺便试一下其他的控件都有什么用处。

  • 程序业务逻辑的创建

运行一下,可以看到现在我们的界面已经做好了。但是这个程序有任何的效果嘛?不管我们点击什么,都没有任何的反应吧。那接下来,就要给它创建他的逻辑。

那想要代码和图形能够结合起来,XCode为我们提供了一个开发界面,使我们方便操作。点击右上角的辅助编辑按钮 ,我们发现有了两个可以操作的开发界面,一个是显示的storyboard,一个显示的是代码。那这个有什么用呢?现在,按住你电脑上的control按钮,不要放手,用鼠标按住storyboard区中的“计算”按钮,向代码区拖动,有没有发现多出了一根蓝色的线,我们一直将鼠标移动到类扩展区域,看到代码区多了一根横线,这时,松开鼠标和键盘就行。

松开鼠标后,看到弹出了一个对话框

第一行Connection中有三个选项,第一个是默认的Outlet,这个中文就是出口的意思。可以想象我们有一个管道,让我们把东西从楼上运到楼下,那我们在管道的出口处等着拿东西就可以了吧~同样,想要获取到这个控件的信息,信息就从楼上运过来,我们找到这个管道的出口,是不是就可以拿到控件信息了,通过这个我们可以拿到控件属性。第二个是Action,动作,使用这个会产生一个新的方法,在响应用户交互时,会执行方法中的代码。第三个Outlet Collection,顾名思义一个属性的集合。这里我们选择Outlet。第二行的对象所属,是个不可选择的。第三行Name,要我们为这个属性或者这个方法取一个别名,这里取名calculatorButton。第四行Type中,是我们这个对象的类型,之前拖拽控件的时候我们就看到了,这里会是UIButton。最后的Storage,持有类型,这个控件在拖到storyboard上面的时候,就被这个视图有一个Strong持有,保证它存在于堆中,所以在外部,我们不需要一个Strong指针指向它。如果这个控件从视图中离开,那么,他就会被从堆中清除,这个指向控件的指针就会变为我们想要的nil,因为向nil发送一个消息,程序是不会崩溃的。大部分情况下,这里都是使用Weak。最后按下Connect按钮或者敲下回车。

原本什么都没有的类扩展中,突然就出现了一段代码。仔细看,和我们以前写类的属性的时候,十分的相像吧。但是这里多了一个IBOutlet,实际上对编译器没有任何的用处,只是告诉XCode,我和其他的东西有关联。你可以自己写一个属性,然后给他加上IBOutlet关键字,前面都会多出 这个形状。现在我们把鼠标移到这个圆上面,是不是发现,storyboard上面,“计算”这个按钮亮了起来,这就是告诉我们,这个代码已经和storyboard相关联了。

我们有输入框,要拿到输入框中的信息是不是一样需要给两个输入框拖线?还有最后用来显示结果的Label也是需要的吧。我们给他们分别取名text1,text2,resultLabel。

点击这个计算按钮,程序也应该要有反应,这里就是再次对Button拖线,但选择不再是Outlet,而是刚才说过的Action了。我们看到Action的返回类型是IBAction,这个其实就是void类型。

 1 @interface ViewController ()   2    3 //计算按钮   4 @property (weak, nonatomic) IBOutlet UIButton *calculatorButton;   5 //按钮计算方法   6 - (IBAction)calculator:(id)sender;   7    8 //第一个输入框   9 @property (weak, nonatomic) IBOutlet UITextField *text1;  10 //第二个输入框  11 @property (weak, nonatomic) IBOutlet UITextField *text2;  12   13 //结果Label  14 @property (weak, nonatomic) IBOutlet UILabel *resultLabel;  15   16   17 @end

现在我们来分析下如何编写点击按钮的方法。第一,我们要拿到输入框中的数字,UITextField中就为我们提供了相应的属性--text,使用self.text1.text,我们就可以拿到text1输入框中的内容。text2也是一样的道理。我们点到UITextField类中可以看到,text属性是一个什么类型啊?NSString吧,我们能不能直接把字符串拿来相加?明显是不可以的,使用floatValue的getter方法,我们就可以将它转化成数字了吧。那我们有了计算的结果,就要将结果显示在界面上了,UILabel也有text属性,但是NSString要怎么才能显示一个float的基础数据类型呢?要用到Foundation框架中stringWithFormat:方法了吧。然后将这个NSString赋给Label的text属性就可以了是不是?下面就是实现的代码。

1 - (IBAction)calculator:(id)sender {  2     if (self.text1.text.length != 0  && self.text2.text.length != 0) {  3         self.resultLabel.text = [NSString stringWithFormat:@"%.2f", self.text1.text.floatValue + self.text2.text.floatValue];  4     } else {  5         NSLog(@"Error, No Input");  6     }  7 } 

现在一个简单地加法计算器就做完了,这也是我们第一个iOS程序。

4.视图的生命周期

说过所有的这些简便的东西,实际上是他背后无数行代码的一个实现,那我们也可以使用代码来做。但是这一课我们不讲如何使用代码来做上面的简易计算器。我们介绍一下以后代码写在哪里。

那我们说C语言程序启动是从main函数这个入口函数进去,展开左侧边栏中的Supporting Files文件夹,main.m文件就在这里,但是我们不在这个地方写代码。说到哪里写之前,我们先看到程序的main函数,函数有入口是不是就有出口,执行完return中的函数,就return对不对,但是这里因为这个函数,导致main函数return不了。那这个函数前面两个参数是一些系统信息,第三个是一个nil,实际上是一个默认指定的UIApplication类,最后一个就是把程序的启动代理给获取到的类名,关于这个参数可以自己去研究一下。那函数实际上是帮我们创建了一个UIApplication对象和AppDelegate的对象,并将程序代理给AppDelegate对象,并且调用了AppDelegate中的方法。我们看到系统是默认帮我们创建了AppDelegate类吧,点进去,看到下面这个方法就是被代理调用的协议方法。

1 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  2     // Override point for customization after application launch.  3     return YES;  4 }

我们看这个代码,其实啊,做这个iOS的开发,其实英语好的很有优势,为什么这么说呢?didFinishLaunching这个是完成启动的意思,他在程序已经启动完之后,就会到这个方法里面来。后面我们会知道有方法,可以在程序启动之前对程序进行一些设置。我们以后写代码直接到这个方法里面写。

那还有其他的一些代理方法,我们可以来看一看。一个程序开始运行了,是不是就像激活了一个设备,设备开始运转,我们这里也有一个相同的协议方法:程序进入激活状态,是活动的。这个在程序每次在运行的时候都会调用。

//进入到活动状态  - (void)applicationDidBecomeActive:(UIApplication *)application {      // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.      NSLog(@"Become Active");  }

有激活自然有一个非激活状态,什么时候会是一个非激活的状态呢?我们双击Home键,你会发现控制台就打印了“ Resign Active”

//程序进入到非活动状态  - (void)applicationWillResignActive:(UIApplication *)application {      // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.      // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.      NSLog(@"Resign Active");  }

那我们知道,有双击Home键的操作,当然有单击Home键的操作吧。我们按一下Home键,实际上是进入了一个后台。我们可以看到,控制台这次打印了“ Resign Active”和“ Did Enter Background”。我们在执行锁屏、接电话等操作的时候都会进入到这个后台,那进入后台和非激活状态是不一样的啊。但你进入了后台,那就一定是非激活的状态。

//程序进入后台  - (void)applicationDidEnterBackground:(UIApplication *)application {      // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.      // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.      NSLog(@"Did Enter Background");  }

进入了后台,我们点击这个程序图标,还能回到这个程序,同样地一个回到前台的代理方法。

//进入前台  - (void)applicationWillEnterForeground:(UIApplication *)application {      // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.      NSLog(@"Will Enter Foreground");  }

最后我们看到,还有一个程序终止的代理方法。这个是我们退出程序的时候会调用到的一个方法,为了防止程序意外退出我们没有保存一些信息。但是这个方法只在程序运行的时候才会调用,如果我们这个程序已经进入了后台,退出程序的话,会不会调用这个方法?是不会的啊。

//程序退出  - (void)applicationWillTerminate:(UIApplication *)application {      // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.      NSLog(@"Terminate");

这些东西,我们现在先用作了解。大家现在知道,什么时候会调用什么东西就行。

今天最重要的,是我们要掌握如何使storyboard和代码间创建一个联系。可以多去自己尝试一下。那下面有一些常用的XCode快捷键,大家可以记一下

 (1)Xcode快捷键  主界面:  Command  + R   运行  Command  + .   停止运行  Command  + B  编译  Command  + H  隐藏Xcode  Command  + N  新建文件  Command  + shift + N  新建工程  Command  + O  打开文件  Command  + 数字键(1-8)  navigator栏  Command  + 数字键0  隐藏navigator栏  Command  + Y  开启控制台  Command  + option + 0  开启/隐藏utilities栏  Command + return  标准模式  Command + option + return  辅助编辑模式      代码:  Command + control + 方向键  .h/.m文件切换  Command  + [  向左缩进  Command  + ]  向右缩进  Command  + option + [  上移  Command  + option + ]  下移  Command  + Y  开启断点  Command  + \  加入断点  Command + K  清空控制台      文档:  Command  + ‘+’  放大  Command  + ‘-’   缩小  Command  + ‘=’ 适应    View Code

来自: http://www.cnblogs.com/zau11berer/p/5089138.html

</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>