对象,数据和方法
是时候讲一些编程的理论了,是的,你逃不掉这个环节的。
Swift是所谓的面向对象编程语言,这意味着你所用到的所有东西都牵扯某种对象。我已经提到过几次,app是由对象组成,它们之间会互相传递消息。
当你写一个app时,你将会使用由系统提供给你的对象,比如UIKit中的UIButton,并且你也会自己写属于自己的对象,比如view controllers。
那么对象到底是什么呢?把它想象为你程序中的一个功能模块。
程序员们喜欢将相关的功能组合成一个对象。在一个成熟的app中你会使用许多不同类型的对象(几十甚至几百个)。
即使你现在这个小小的app也包含了若干不同的对象。一个是你目前为止花了最多时间在上面的ViewController。还有Hit Me按钮也是一个对象,提醒窗口当然也是一个对象。并且哪些你在提醒窗口中添加的文本——“Hello World”和“This is my first app!”同样也是对象。
这个app还有一个对象叫做AppDelegate,虽然在本节课中我们忽视了它(如果你好奇的话,可以打开这个文件看看里面的内容)。这些被称为对象的东西无处不在。
一个对象中可以同时拥有数据和功能:
你之前在view controller上添加的Hit Me按钮就是一个关于数据的例子。当你将按钮拖拽到storyboard中时,它实际上就成为了这个view controller的一部分数据。数据会包含一些东西。在这个例子中,view controller包含button。
你添加的用于响应按钮的showAlert动作就是一个关于功能的例子。功能会做一些事情。
这个按钮本身也有自己的数据和功能。按钮的数据就是例如其标签的文本及颜色(文本就是Hit Me,其颜色可以设置改变)、在屏幕上的位置、它的宽和高等等。按钮的功能就是:它可识别用户的点击并且做出响应触发一个动作。
这种给一个对象提供功能的东西通常被称之为方法(method)。其他编程语言也许会将其称为“过程”,“子程序”或者“函数”。你也会在Swift中见到函数这个术语;一个方法(method)可以简单的理解为属于某个对象的函数。
你的showAlert动作就是一个关于方法的例子。你可以将其称之为方法是因为它以func(function的简写)开头,并且名称后面跟随有一对括号:
如果你看看ViewController.swift的其他部分,你可以看到好几个其他的方法,比如viewDidLoad()和didReceiveMemoryWarning()。
这些目前不用太操心;只是为了你方便Xcode自动将它们放上去了。这些特定的方法经常被view controller使用,所以你很可能会在某些时候用到它们,那时你再去将它们的内容填满。
关于方法的概念也许有点绕,我们来图示一下:
简单翻译一下
//关于“你的”一个对象,其中有开party(func throwParty ())这个方法
func throwParty() {
clean the room //打扫房间
put on music //放点音乐
steve.buyIceCream(10) //调用了一个关于“史蒂夫(Steve)”的对象中的一个buyIceCream(买冰淇淋)的方法
........
}
//关于“Steve”的对象中有一个func buyIceCream(howMany Int)——买冰淇淋的方法,这个方法包含一个整数参数,用于表示买几个冰淇淋
func buyIceCream(howMany Int){
.......
go to the store // 去商店
get howMany cones of ice cream //买howMany个冰淇淋
pay for them //付款,付款后返回
.......
}
你(或者说叫做“你”的这个对象)想要办一个party,但是忘了买冰淇淋。幸运的是,你邀请了正好住在便利店旁边的名为“Steve”的对象。party上是不能没有冰淇淋的,所以在你准备party的过程中,你抽空给Steve发了个消息,让他带一些冰淇淋过来。
之后电脑切换到对象Steve上,并且执行了其中buyIceCream()的方法中的命令,从第一条开始,一条条执行完毕。
当这个方法结束后,电脑回到了你的throwParty方法并且继续做其中剩余的事情,所以你和你朋友就可以吃到Steve买回来的冰淇淋了。
Steve对象也有自己的数据。当他去商店前他必须有钱。在商店里他用钱这个数据和另一个更为重要的数据冰淇淋做了数据交换!这个交易结束后,他带着冰淇淋数据来到你的party。(假如他在路上将冰淇淋吃完了,那么你的程序就BUG了)。
“发送消息”听起来涉及很多东西,但是实际上没有那么多。这其中并不涉及“信鸽”或者邮递员。电脑仅仅是简单的从throwParty()方法跳到buyIceCream()方法,然后再跳回来。
也经常会有人用“调用方法”或者“唤醒方法”来替代“发送消息”,但是它们完全是一样一样的:电脑跳转到你调用的方法,当它执行结束后,电脑就会回到之前离开的方法。
需要记住的重要事情是对象拥有功能(买冰淇淋的那些步骤)和数据(钱和冰淇淋)。
对象可以互相间查看数据(无论如何在某种程度上,Steve也许不会批准你都看他的钱包里面)并且互相要求执行对方的方法。这就是你的app工作的方法。
添加剩余的控件
你的app已经有一个按钮(button),现在你需要将其他剩余的控件添加上去。下面是添加好的屏幕界面的样子,对各个控件都做了注释:
如你所见,我在标签(label)中设置了预占值(比如,“999999”)。这样可以使你在实际使用它们之前轻易的观察到标签的长宽,看看是否符合布局。比如分数标签(Score)可以是一个很大的值,所以你最好留够足够的空间给它。
试着自己从对象库(Object Library)里拖拽出这些控件,重现这个屏幕。你需要一些按钮(Buttons),标签(Labels),和一个滑条(Slider)。你可以从上图中大概看看每样东西大概多大,位置怎么摆放。一开始做的不太好没事!
可以用属性检查器(Attributes inspector)调整这些控件的设置。你可以在Xcode的窗口右手边的面板中看到这个检查器:
⚠️:要调整某个控件的参数,请确保先选中这个控件再点击属性检查器。
检查器展示了当前选中控件的各种方面的内容。例如,可以让你变更标签的背景色或者按钮中文本的字体大小,这就是属性检查器。你已经见识过了展示按钮动作的链接检查器。当你进一步精通构造界面的时候,你将会使用全部的这些检查器配置你的控件。
这里的(i)按钮实际上就是一个标准按钮,只是它的类型在属性检查器里被设置为“信息灯”:
同样用属性检查器可以配置滑条(Slider)。它的最小值应该是1,最大值为100,并且当前值为50.
当你做完以后,你应该有12个用户界面元素在你的屏幕上了:一个滑条(Slider),三个按钮(buttons)和一堆标签(labels)。完美!
运行这个app,并且到处点点看看。目前这些控件基本干不了什么(除了最初的那个可以弹出一个提醒窗口的按钮外),但是至少你可以拖拽滑条玩玩。
你可以把不涉及任何编程的条目从你的工作清单上勾掉了!世界马上就要产生变化了,因为你从此刻开始不得不写Swift代码让这些控件去做点事情了。
滑条(Slider)
你工作清单上下一项要做的事情应该是:“用户点击Hit Me按钮之后读取滑条上的值”
如果,在你混乱的界面构造器上,你没有偶尔的断开之前的Hit Me按钮和showAlert动作,你就可以顺利的修改你的app,使得在弹出的提醒窗口上展示滑条的值。(如果你断开了这一链接,那么你需要先把它们重新链接上。)
想一想你是如何将识别用户点击按钮的响应动作添加到视图控制器上的?你可以对滑条做同样的事情。这个新的动作将在任何用户拖动滑条上的浮标的时候执行。
添加这个动作的步骤和你之前做过的大致相同。
首先,打开ViewController.swift文件并且将以下代码添加到最下面一个花括号的前面。
@IBAction func sliderMoved(_ slider: UISlider) {
print("The value of the slider is now: \(slider.value)")
}
第二步,打开storyboard并且按住ctrl将滑条拖向纲要面板上的View Controller(黄色圆圈的那个)。放开鼠标并且在弹出小窗口中选择sliderMoved。完毕!
来理一下思路,纲要面板就是界面建造器画布上左手边的部分。它展示了storyboard上的视图层级。这里你可以看到View Controller中包含一个白色小方块图标的view(就是名字叫做view的那个),这个view包含了你添加的按钮及标签等次级视图(按钮和标签等控件也是一种视图(view))。
记住,如果这个面板在你的电脑上不可见的话,点击底部的这个小图标就可以显示出来了:
当你链接滑条时,确保是拖拽到了View Controller(黄色圆圈图标的那个),不是小白方块图标的那个view。如果你看不到这个黄色圆圈图标,就点击View Controller Scene左边的三角箭头,把它展开。
如果一切顺利,这个动作就和滑条值变动的事件链接好了。着意味着这个sliderMoved()方法可以在滑条被左右拖动的时候调用起来。
你可以选中滑条并且在链接检查器中确认链接是否成功了:
⚠️:你有没注意到sliderMoved这个方法和之前的showAlert有什么不同,sliderMoved有一个参数叫做slider(sliderMoved后面的括号里的内容),在后面我们会具体学习如何使用参数。
运行app并且拖拽滑条试试。
你一开始拖拽,Xcode就会在底部打开一个新的窗口,叫做调试区域(Debug area),窗口中会展示如下信息:
如果你滑动滑条到最左边,你应该看到显示的值为1,而最右边,值为100.
这个print()函数对观察app的运行情况非常有帮助。它的全部作用就是把文本信息写到调试区域(Debug area)。在这里,你用它来确认滑条和动作是否已经链接好了以及当滑条移动到时候,你是否可以跟踪滑条上的值。
在添加新的功能前,我经常使用print()函数确认app目前是否运行正常。实现这个目的打印一条消息到调试区域是最快并且最简单的方法。
⚠️:你也许会在调试区域看到一大串其他的消息。这些消息是由UIKit框架和iOS模拟器输出的,你完全可以忽视它们,它们不代表任何隐患。
字符串(Strings)
把文本放入你的app,你需要用到一种叫做“字符串”的东西。这些是你最近使用过的字符串:
"Hello, World"
"This is my first app!"
"Awesome"
"The value of the slider is now: \(slider.value)"
前三个是在做提醒窗口UIAlertController用到的;最后一个是在print()中用到的。
这样的一组文本被称为字符串是因为你可以将文本想象为字符的序列,就像一条绳子上穿着的小珠子一样:
使用字符串这件事会贯穿你写app的整个过程,所以在学完本系列的几个教程后,你会称为使用字符串的大师。
简单的将文本放在双引号中就可以创建一个字符串了。在其他的一些语言里也有使用单引号的,但是在Swift中,则必须使用双引号。而且必须是英文半角的双引号,不能是汉字或者全角模式下的双引号。
例举一下
//这是Swift中标准的字符串
"I am a good string"
//这些事错误的情况
'I should have double quotes' (应该使用双引号)
''Two single quotes do not make a double quotes'' (两个单引号并不能构成一个双引号)
“My quotes are too fancy” (不能使用汉语模式的双引号)
@"I am a Objective-C string" (这是Objective-C语言的字符串,不是Swift的)
任何在字符串中被\(...)包围的东西叫做插入值,我们在print()函数中使用过,"The value of the slider is now \(slider.value)". 把\(...)想象为一个预占符"The value of the slider is now X",这个X也就是\(slider.value)会被滑条上实际的值替换掉。
插入值在Swift中的使用非常频繁。
下面介绍一下变量
使用print()打印信息到调试区域在开发app的过程中是非常有用的,但是它对实际的用户是一点作用都没有,因为用户看不到这些信息。
让我们改进一下这个按钮动作的方法使滑条上的值可以显示在弹出的提醒窗口里。那么你怎么才能把滑条上的值添加到showAlert里呢?
当你在sliderMoved()里读取滑条上的值时,因为滑条的动作的方法已经结束了,所以这个值也随时消失了,无法读取。这个问题的解决方案也很简单,直到玩家点击Hit Me按钮之前,你把这个值存下来就可以了。
幸运的是,Swift可以很好的实现这个意图:使用变量(variable)。
打开ViewController.swift并且在顶部,就在class ViewController这一行的下面添加如下代码:
var currentVlaue: Int = 0
你现在在view controller对象上添加了名叫currentValue的变量。
现在代码看起来应该是这个样子(我省略了方法中的内容):
import UIKit
class ViewController: UIViewController {
var currentVlaue: Int = 0
override func viewDidLoad() {
...
}
override func didReceiveMemoryWarning() {
...
}
@IBAction func showAlert() {
...
}
@IBAction func sliderMoved(_ slider: UISlider) {
...
}
}
把变量都写在方法的上面是一种习惯。并且每一行都缩进两个tab或者四个空格,使用哪一种主要看个人喜好,我个人其实比较喜欢空格。(其实Xcode会自动帮你完成缩进,你可以改变自动缩进的参数,选择菜单Xcode->Preferences,然后在弹出窗口中选择Text Editing,然后选择Indentation tab标签页,就可以看到自动缩进的设置了)
还记得我说过一个view controller或者一个对象(Object),可以同时拥有数据和功能吗?showAlert()和sliderMoved()方法就是功能部分,而变量currentValue就是数据部分。
变量可以允许app存储某些东西。把变量看作是一个独立数据的临时存储容器。这里有各种类型以及尺寸的容器,适用于所有种类的数据。
你不能仅仅是把东西塞进容器然后就把它忘了。实际上你会频繁的用新的值替换容器中的旧物件。当你的app需要改变存储的东西时,你需要把旧的值拿出去,并且把新的放进来。
以下是关于变量的所有要点:它们可以变化。例如,每次滑条移动到新的位置后,你需要更新currentValue的值。
存储容器的大小以及可以存储哪种变量是由数据类型(简称为类型)决定的。
你指定了变量currentVlaue的类型为Int,这意味着这个容器只能存储负20亿到正20亿之间的整数。Int(整型)是最常用的类型之一,此外还有很多其他的类型,你甚至自定义类型。
变量和小孩子的积木块差不多:
核心是要某个形状的积木装到与之对应的容器中去。积木的类型决定了积木的形状。
你可改变每个容器里的内容。比如,你可以拿出蓝色的方块然后放入红色的方块,只要它们都是方块。
但是你不能把方块放倒圆形的容器里去:值的数据类型和变量的数据类型必须一致。
我说过变量是一种临时的存储容器,那么它可以存储多久呢?与肉类和蔬菜不同,你保存的时间再长变量也不会变质,变量可以无限期待保存其中的值,直到你放一个新的值进去,或者直到你摧毁这个容器为止。
每一个变量都有它的寿命(也称为变量的作用范围),这完全依赖于你把变量定义在程序的那个位置。在这个例子里,currentValue的寿命和其属主view controller一样长。它们的命运交织在一起。
在这个例子里,只要app运行则view controller和currentValue就一直存在。直到app结束运行,它们才被毁掉。很快,你将会见识到一种短命的变量(被称为局部变量的那个)。
理论暂时告一断落,我们现在来让变量为我们工作。
打开ViewController.swift,并且用下面的内容替换sliderMoved()方法中的内容:
@IBAction func sliderMoved(_ slider: UISlider) {
currentVlaue = lround(slider.value)
}
你移除了打印语句,并且替换为下面这一句:
currentVlaue = lround(slider.value)
这里会发生什么?
你之间见过slider.value,就是滑条当前时刻位置的值。这个值的范围是1-100,它可能是个带小数点的值。而currentValue是你刚创建的变量的名字。
把一个新的值放进变量,你只需要简单的这样做就可以了:
variable = the new value //变量名 = 新的值
这叫做分配,你分配新的值到一个变量。这里你把代表滑条位置的值放进变量currentValue里。
很简单,但是lroundf是个什么鬼?回忆一下,滑条上的值可能是个小数。之前你应该在滑条滑动时通过print()在调试区域打印的结果看到过。
然后,如果你让玩家猜测数值时精确到小数点后几位,那么这个游戏未免太虐了。几乎不可能猜对。
只用整数的话就会公平些了。这就是为什么我们把currentValue定义为Int,因为它只存储整数。
你用lroundf()函数将slider.value四舍五入为整数先,然后在将这个整数存储到currentValue。
⚠️:方法与函数
你已经见识过了,方法可以提供实际的功能,此外函数也是给你的app提供实际功能的一个途径。函数和方法是一种聚合单元,swift就是用它们把多行代码整合为一行代码的。(比如你只用了一行就对slider.value进行了取整,如果自己写取整语句的话就需要写很多行)。两者之间的区别是,函数不属于特定的对象,而方法属于。换句话说,方法就是函数,这就是为什么它们都用func定义,除了你使用方法时必须有一个存在的对象,而函数则可以随时随地的调用。Swift非常庞大且实用的函数库。lroundf()就是其中的一个,并且在本节课中你还会用到几个。顺便说一下,print()也是函数。函数和方法的名称总是跟随着一对括号并且其中可能有一个或多个参数。
现在把showAlert()改成下面的样子:
@IBAction func showAlert() {
let message = "The value of the slider is: \(currentValue)"
let alert = UIAlertController(title: "Hello World", message: message, preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
present(alert,animated: true,completion: nil)
}
let message这一行时新添加的,同时也注意一下其他两个地方的小改动。
之前,你创建并且展示了提醒窗口UIAlertController,只是这一次提醒窗口的消息变成了:“The value of the slider is:X”,这里的X被currentValue变量的值(1到100之间的整数)替代了。
假设currentValue的值为34,这就意味着滑条的位置大概在三分一略靠左的位置。新的那行代码会将"The value of the slider is: \(currentValue)"替换为“The value of the slider is: 34”,并且把它放进新的名为message的对象中。
之前的print()做的事情和这个类似,只是它是将结果打印在调试区域上。然而现在那你并不希望把信息打印在调试区域而是把它展示在提醒窗口中。这是为什么你要告诉UIAlertController,你现在使用新的字符串message作为展示内容。
运行app,拖动滑条并且点击按钮。现在提醒窗口应该展现滑条实际位置的值了。
酷~,你使用了一个变量currentValue,存储了一个特定的数据——被取整后的滑条的位置数据,因此这个数据现在可以在app上的任何地方应用了,在这里应用这一数据的是提醒窗口的消息文本。
如果你没有移动滑条,再次点击按钮,提醒窗口上展示的值则不会发生改变。变量会永远的保存它的值,直到你放一个新的值进去。
你的第一个BUG
目前这个app有一个小小的问题,也许你早就注意到了。让我们来重现一下这个问题:
点击Stop按钮,彻底关闭app,然后点击Run按钮再次运行。这时不要去动滑条,直接点击Hit Me按钮。
你会看到提醒窗口的消息写到:“The value of the slider is:0”。但是此时滑条的浮标很明显位于中间位置,所以目前的值应该是50。你发现了一个BUG!
练习:想一想为什么在这个特定场景下显示的值会是0,在app刚运行时,不动滑条,直接点击Hit Me按钮。
答案:线索是只有你不动滑条的前提下才会发生这种现象。如果你不动滑条,那么sliderMoved()则永远不会给你传递消息,因为sliderMoved()是对应滑条的动作,不动滑条,这个方法永远不会运行,则你永远不会把滑条的值传递给变量currentValue。然而currentValue的默认值是0,所以你会看到这一现象。
为了解决这个bug,把currentValue的默认值改为50:
var currentValue: Int = 50
现在currentValue的初始值是50了,和滑条的初始位置保持一致。
运行app,确认下bug是否已经解决了。
本小节的代码可以在02 - Slider and Variables目录中找到。(随书附件请大家尽量支持正版,我就不提供了)