本字幕由志愿者义务贡献，采用许可协议
知识共享 署名-非商业性使用-相同方式共享 3.0 美国
Stanford University. >> Welcome to Stanford CS193P,
欢迎参加斯坦福 CS193P 课程
Developing Applications for iOS. This is Lecture Number 2,
iOS 应用程序开发。今天是 2017 年秋季学期
fall of 2017. So today, I'm gonna spend the first 15 or
的第二节课。今天我要先花十五到
20 minutes talking about Model-View-Controller,
二十分钟的时间讲 Model-View-Controller
this design paradigm that I told you we always have to do
这个设计模式，我说过我们总是需要
when we develop for iOS. And then, we're gonna apply
在开发 iOS 时使用这个设计模式。然后我们把
Model-View-Controller to our Concentration app, so
模型-视图-控制器 运用到翻牌游戏里
you're gonna get to see the kinda the concepts first,
你会先了解下这个概念
then we get to see it in action. All right,
然后我们看实际的应用。那么
Model-View-Controller, what is it? It's essentially a way
模型-视图-控制器 究竟是什么呢？它是一种
we're gonna divide up all the objects in our system into
在我们的系统里把对象分成三个阵营
three camps. one camp, this blue camp, is the Model camp.
的方法。一个是这个蓝色的模型（Model）
That's a UI independent set of objects that is
这是不依赖于 UI 的一系列对象，这代表了
the what of your app? So for our Concentration game, it's
你的程序究竟能做什么。对于我们的翻牌游戏
the part of our app that knows how to play Concentration.
就是程序里知道翻牌游戏规则的那部分
It knows how to match cards, take them away,
它知道怎么匹配，移走卡片
it knows when to flip cards, it knows all that stuff.
知道什么时候翻牌，知道所有这些
It's all the kind of knowledge about the Concentration
它知道所有翻牌游戏相关的的信息和规则
game but nothing about how it appears on screen. That
但是并不知道这是怎么显示在屏幕上的
is all part of the Controller camp's responsibility.
那个就属于控制器（Controller）的职责了
So the Controller camp is the how
控制器阵营的是负责
your Concentration game appears on screen.
你的翻牌游戏如何显示到屏幕上
So Model is what is your app about, and
所以模型是代表程序是做什么的
the Controller is how it shows up on screen.
控制器是程序是怎么显示到屏幕上的
And the View camp is your Controller's minions.
然后视图（View）阵营就是控制器的下属
These are very generic UI elements, like UI button,
它们是通用的 UI 组件，比如 UIButton
the UI view controller even UI label.
甚至 UIViewController，还有 UILabel
All those kind of generic things, UI things,
所有这些通用的 UI 组件
that the Controller has to communicate with the Model to
在控制器与模型交流过后
get your game, whatever is going on your app onto the UI.
得到了游戏一类的信息，就会用视图显示出来
So the View is generic minions of the Controller. So
所以视图是通用的，控制器的下属
those are the three camps. Now, MVC is really all about
这就是模型-视图-控制器三个阵营，简称 MVC
managing the communication between these camps, right?
但最重要的是规范这三个阵营之间的通讯
You put the objects in these camps, and
你把对象分到这三个阵营里了
they have to kind of obey certain rules when they talk
那它们之间交谈就要遵守一定的规则
to each other. So, I've drawn road signs up here.
所以我这里用道路交通标线来表明
You see the little road signs that are kind of approximating
看到这个道路交通标线了吗？它们表示了
what kind of communication is allowed between the various
这几个阵营之间被允许的沟通
camps. And let's look at it all in detail. So
让我们来仔细看看
the Controller, talking to the Model,
所以这个控制器可以访问模型
that's a fully dashed white line going in that direction.
向这个方向的是一条白色的虚线
You can cross over anytime you want, big green arrow.
任何时候都可以穿过去，我用绿色箭头表示
The Controller can talk to the Model all it wants.
这个控制器可以随便和模型交流
It has to be able to because it is the Controller's job to
它必须要能这样做，因为这是控制器的工作
present this what the thing is to the user, so it has to be
就是把模型展示给用户，所以必须要
able to access the Model. So that's a big old green arrow,
能够访问模型，所以这是个肯定可以的绿色箭头
and this is the Controller talking to the Model.
控制器是可以访问模型的
Pretty much unlimited communication to the Model's
访问模型基本没有限制
publicly available functionality. What about this
只要是公开可用的功能都行。那这边呢？
direction? Well, similarly here, its minions,
这边也是类似的，视图是下属
the Controller has to be able to control its minions, so
控制器需要能控制它的下属，所以
it's a pretty much a wide open green arrow that way as well,
这也是可以的，我这个方向也用绿色箭头表示
and you've already seen a green arrow from
而且你已经看到了这样的“绿色通道”了
the Controller to the View in our Concentration.
在翻牌游戏里，从控制器到视图
It was called an outlet. We had an outlet to the flip
就是我们的出口（outlet），我们有一个到
count label, and of course we can talk to the flip count
flipCountLabel 的出口，当然我们可以告诉 flipCountLabel
label and say anything we want to it to get it to say what we
说我们想要让它在 UI 上显示
want in the UI. That is the Controller's prerogative.
我们想要的内容。这就是控制器的特权
So you can see the controller can talk to everybody
你看到了，所以控制器能和所有人交谈
pretty much all at once. What about some of the other kind
基本都可以。那其他阵营之间的
of communication? What about the Model talking
通讯呢？比如模型
directly to the View? Okay, that's pretty much impossible.
直接访问视图呢？好吧，这个基本是不可能的
Why is that impossible? Two reasons.
为什么不可能呢？有两个理由
One, the Model is UI independent, and the View only
其一，模型与 UI 是无关的，而视图
has UI things in it, so there's absolutely no way a UI
只负责显示 UI。所以一个无关 UI 的阵营
independent thing could talk to such UI dependent things
不可能去访问这个只管 UI
like the View. Another reason is that these View things
的视图。另一个原因是视图
are generic objects, like a button or a slider. How could
都是通用的，比如按钮或是滑杆
a button have any idea what a Concentration game is about?
一个按钮怎么可能知道翻牌游戏是干什么的？
No way. There's just no way it would know that, it's generic.
不可能。按钮是通用的，它不可能知道翻牌这个游戏
So there's never any communication between these
所以这两个之间是不可能沟通的
two, I never wanna see you having any communication
我不希望看到你在作业里面
between these camps in any of your homeworks or whatever.
让这两个阵营之间沟通
That's why it's a double yellow line there.
这就是为什么这里是双黄线
No crossing over. All right, that's an easy one. What about
不能跨过去。好，这个简单
the View talking back to the Controller? This is probably
那视图翻过去通知控制器呢？这个可能
the most interesting of the communication pathways here.
是最有趣的交流的方法了
The View can speak to its Controller, of course it,
视图是肯定可以通知控制器的
it kind of has to, like when a button is clicked or whatever,
某种意义上这是必须的，比如“按钮被按下”一类的
but when it communicates,
但是它们要交流的话
this communication has to be blind and structured. It
这种交流必须要是某种标准化的匿名通讯机制
has to be blind because these are generic view objects.
说它是“匿名的”是因为视图是通用的对象
The UI Button doesn't know anything about a Concentration
UIButton 不知道有关翻牌
game controller, so when it's talking to the Controller,
这个游戏的控制器，所以当它通知控制器的时候
it doesn't really know that it's a Concentration game
它是不知道那是翻牌游戏的
controller and it's structured in that
控制器。说它是有标准的
since we're gonna have this communication going on,
是因为我们要让这个通讯成立
a generic object has to think a little bit ahead about how
一个通用的对象要提前考虑好
it might wanna communicate with this Controller object.
要如何和控制器对象沟通
So, you already know one structured, blind way for
你们已经见过了一个视图使用的
your View to communicate, and that's target action.
标准化的匿名通讯，那就是 target action
When we Ctrl dragged and created the method touchCard.
当我们按住 control 拖拽，创建了一个方法 touchCard
That's target action. And all the Controller has to do is
这就是目标对象操作。而控制器所要做的
kinda hang a target on itself. That's to say, it creates
就是把自己设为目标对象，也就是创建
a method like touchCard. And then UI button and
一个 touchCard 这样的方法，然后 UIButton
other things, they can get this action, and
一类的控件就能得到这个操作方法
every time the button is pressed,
然后每次按下按钮
they just call the target. This is a very,
按钮就调用目标对象的方法。这是一种非常
very simple kind of blind structure communication. But
非常简单的标准化匿名通讯
sometimes you need more complicated communication,
但有的时候你需要更复杂的通讯
like you have a more complicated generic view
比如你有更复杂的通用 UI 组件
item like, let's say a scroll view. A scroll view is
比如一个 scroll view，滚动视图能够
scrolling around on some image or something like that, and
四处移动来查看一张图片一类的
it might need to tell the Controller,
它可能需要告诉控制器
I scrolled to the end. Am I allowed to scroll down here?
我滚动到最底部了，我还能继续往下么？
Can I scroll vertically or horizontally?
我移动的方向是竖着还是横着？
It kinda wants to talk to Controller as it's
它想要在完成任务期间
working to do its job. And we do that with these kind of
和控制器保持沟通。为了实现这个，我们会用
predefined methods that the scroll view defines as part of
滚动视图规定好的一些方法，这些方法
what's called its delegate. So its delegate is just a var in
属于 delegate，代理的一部分。代理就是一个变量
scroll view that, and this var will have some object in it.
滚动视图里的一个变量，这个变量存储了一个对象
And all we know about this object is that it responds to
对于这个对象，我们所知道的只有
a certain number of messages. Most of these messages
它能够响应一定数量的某些方法。大部分这些方法
start with the words will, should, or did.
都会以 will, should 或 did 开头
Like, I will scroll to here, should I scroll over here? I
比如我将要（will）滚动到这里，是否能够（should）滚动到那里？
did scroll down to here. Those are classic delegate methods.
我已经（did）滚动到这里了，这些就是典型的代理的方法
And the Controller, using a mechanism called protocols,
然后控制器通过 protocol，协议
which we'll talk about next week,
这个我们下周讲
is able to tell the scroll view,
就能告诉滚动视图
I'm your delegate, and all the scroll view will know
我是你的代理，然而滚动视图只知道
is that it implements these will, should, and did.
我实现了这些 will，should，did 方法
It doesn't know anything about it. It doesn't know its class,
滚动视图不知道其他我的信息，不知道究竟是哪个类
doesn't know that it's has to do with Concentration game
不知道是否是我们的翻牌游戏
obviously, it knows nothing. It just knows that
显然什么也不知道，只知道
the Controller will implement that. So
这个控制器实现了代理方法
we'll see delegation in about two weeks when we start using
我们会在大约两周之后看到代理模式，就是当我们
more complicated UI objects. Now, another important thing
开始使用更加复杂的 UI 对象的时候。另外一个重要的是
to remember in the MVC model is that views,
记住在 MVC 模式里，视图
these generic things,
这些通用的视图
cannot own the data they're displaying. In other words,
不能自己提供它们显示的数据，也就是说
they're not going to have the data they're displaying as
它们不能写死显示的数据，然后作为
part of their instance variables. Now why is this?
实例变量的一部分（伴随每个对象）。为什么呢？
Well, imagine that the View is showing your entire iPod music
让我们现象一下你展示整个 iPod 音乐库
library. And let's say you have 50,000 songs in there.
比如说我们里面有五万首歌
There's no way, it would make absolutely no sense to have
我们不可能，而且也根本没有理由
a list view or something, some generic view that lists
让某个列表视图，某种展示某个列表的通用视图
things, to bring all 50,000 of those things in there. So
存储五万首歌在里面
instead, it uses this same kind of protocol mechanism to
取而代之，应该使用类似的某种协议
have another set of special messages,
让另一些特殊的方法
and they are messages like data, give me the data at, or
这些方法包括给我在这个地方的数据
how many items are there? And the Controller implements that
或者是一共有多少个项目要显示，然后控制器实现这些方法
so we can talk to the Model and get the data for the View.
控制器通过访问模型来获取视图需要的数据
So, for example, table view, which is a big scrolling list
比如列表视图（table view），就是个很长的滚动列表
kind of generic view item, that's in iOS.
这个是 iOS 提供的通用视图组件
When it's scrolling around on all your iPad music things,
当你在来回滑动查看 iPad 音乐库内容的时候
it's just asking for the ones it's currently showing. Right,
它就会去请求正在显示的那些歌曲
there's 50,000. It's only scrolling around to
一共有五万首，来回滑动的话
showing maybe 10 at a time. And so it's just asking
每次大概只会显示十首，它就是去让
the controller give me the next ten,
控制器给它接下来的十首
give me the ten here, and
给我这里的十首歌，然后
the controller turns around to the model,
控制器就转过去问模型
which is probably a nice fast SQL database or something, and
可能是很快的 SQL 数据库一类的
grabbing the data, and handing it off to the view. Okay, and
抓取这些数据，然后传递给视图
this uses the same mechanism where the table view again
这种方法同样的，列表视图
doesn't know anything about this as being an iPod
也是不知道这些数据来源，不知道是不是 iPod
music app, it just knows that it's the data provider, and
音乐程序，只知道它能提供数据
we call this kind of delegate the data source. All right, so
我们称这种代理为数据源（data source）
you'll see that as well, and both data source and delegate,
之后你也会看到。数据源和代理
very similar it's just kind of a different set of methods,
这两个很类似，只是是两套不同的方法
and these methods are of course dependent on the kind
当然这些方法取决于具体的
of UI element, they're not a preset list, depends on what's
UI 组件，并不是固定的，取决于
going on in that UI element. So that's the kind of
那个 UI 组件是干什么的。所以这就是
communication the view can have with the controller,
视图到控制器的通讯
it's structured, it's kind of predefined, things like that.
它是有标准的，提前制定好的
Because of all this communication going on in this
因为这个方向的所有这些通讯
direction we say the controller's job in an MVC is
我们说控制器在 MVC 里的作用
to interpret and format the models information for
是把模型的信息翻译成某种格式
the view. That's its primary purpose.
提供给视图，这就是它主要的目的
It kind of also goes the other way,
反方向它也要处理
It interprets user interaction in the view for the model.
把视图里的用户交互翻译成模型里的数据
It's the interpreter back and forth.
它负责当来回通讯的翻译
It's the center of all communication here.
是所有交流的中心
What about the model? Can the model talk to its controller?
那模型呢？模型能通知控制器吗？
Obviously not directly. Because the model is UI
肯定不是直接的，因为模型是与 UI
independent and the controller is fundamentally UI dependant,
无关的，而控制器根本上讲是取决于 UI 的
so it can't do it directly. But there is a mechanism for
所以不能直接沟通。但是有一种方法
the model to communicate, for example, if some data changes
让模型能够在比如数据发生改变的时候发出通知
and it wants any UIs that are interested out there
好让任何其他对此感兴趣的 UI
to update. And the way it does that is with a model that I
更新，它实现的这种方法我
call radio station model, and the model essentially
把它叫做电台模式。基本上就是模型
starts broadcasting on a certain known radio station.
在某个已知的电台上开始广播
And the controller up there it's just going to tune in and
而上面的控制器就会收听这个电台
when it hears oh something's changed on the models radio
然后听到模型电台说，发生改变的时候
station then it's gonna use its big green arrow,
那控制器就用它的“绿色通道”
to go talk to the model and get the date of the change, or
来访问模型，然后获得改变了的数据
whatever. So this is a radio station model. In iOS
之类的。所以它用的是类似于电台的机制。iOS 里
it's called notifications, or KVO, Key Value Observing, and
这叫做通知（notification），或者键值监听（KVO，Key Value Observing）
we'll talk about those in a few weeks as well. So there is
这个我们几周之后也会讲。所以我们有
a way from the model to kind of broadcast, wow things
一种方法让模型来广播说
are changing. Now, some people have asked can a view tune in
这些发生了改变。有些人问视图能够收听
to a radio station? A View could only really tune into
某个电台吗？真要说的话，视图只能收听
a controller radio station like another UI thing, cuz
控制器的电台，或者某种 UI 相关的东西
a view is fundamental of UI, but even that's pretty rare.
因为视图是 UI 的基础，但即使有的话也是很罕见
Usually the radio stations are pretty much for
通常电台是用来让
the model to communicate hey something's happening in your
模型用来交流说你的数据发生了变动
data or whatever. This, MVC, a collection of MVC, is
这类的。这个 MVC，这个模型，视图，控制器的集合
generally only used to control one screen on the iPhone, or
一般只会用来控制 iPhone 上的一个界面
on the iPad. Maybe it can control little sub-place,
或者 iPad 上的一个界面。可能可以控制某个附属的界面
like maybe on an iPad, maybe one MVC controls one space,
比如像在 iPad 上，可能一个 MVC 控制一个界面
and another MVC controls another place, and
另一个 MVC 控制另一个界面
possibly a third MVC controls another space, but
然后可能第三个控制另一个界面，但是
you would never have more than one screen, in an iPhone,
但是在 iPhone 上你不会有多个界面
controlled by a single MVC. So the MVC kind of goes with
是由一个 MVC 同时控制的。所以 MVC 还会
a grouping of UI. Usually one screen, one iPhone screen
把 UI 分组，通常是一个界面，一个 iPhone
size worth of stuff. So most apps have tons of screens.
屏幕那么多的内容 。所以大部分程序都有很多个界面
You got your settings, you got all the different features in
你有设置界面，不同的功能
your app, tons and tons of screens going on. So
你的程序里有很多很多的界面
how do we build an app out of multiple MVCs?
那我们如何用多个 MVC 来构建一个程序呢？
Multiple MVC apps look like this. I have a bunch of MVCs
多个 MVC 的程序看起来是这样的。我有很多个 MVC
upright. All those purple things
所有这些紫色的
are all the controllers, and when one MVC wants to interact
是所有的控制器。当一个 MVC 想要和
with another MVC like right here, you see this right here,
另一个 MVC 交互的话，比如，你看这里这个
it always treats those other MVCs as part of its view. So
它总是把那个 MVC 当作是它的视图
these three MVCs down here are part of the view of this MVC.
所以下面这三个 MVC 是这个 MVC 的视图
So it has to talk to them in a blind and structured way.
所以它需要用标准化的匿名方式交流
These kind of act like generic, reusable components,
这些就像是通用的，可重用的组件
and we're gonna see how that works when we
我们将会看到这个是怎么运作的
start talking about multiple MVC apps, a week from Monday.
周一之后过了一周我们就会开始讲多个 MVC 的程序
Now the main thing that we wanna do here is not build our
注意我们讲主要的目的是让你不要编写
multiple MVC apps like this, where there's just green
一个像这样的多个 MVC 的程序
arrows talking everywhere, and the reason we don't want this,
绿色箭头到处都是。我们不想要这个的原因是
it is impossible to debug and find out what is going on
我们不可能调试这个，然后找出问题所在
inside our app, because if something changes in the UI,
在我们的程序里，如果 UI 发生了改变
we don't know which controller was doing it, or
我们不知道是哪个控制器干的
what model gave the data. We're just totally lost. So by
或者是哪个模型提供的数据。我们会毫无头绪
grouping them into these nice MVCs each screen on the phone
所以通过把它们分组成为很好的 MVC，每个手机上的界面
is very well contained and understandable, debugable,
都是隔离开的，而且方便理解和调试
manageable. Everybody got all that? So we're
是可管理的。大家都清楚了吧？所以我们
not gonna do things this way. This is do not do it this way.
不会先这样随意交流，千万不要这样做
Okay so the demo that I'm gonna do.
所以我要演示的是
We're going to do our concentration game.
我们会接着完成翻牌游戏
I'm just gonna create the model and
我会创建一个模型，然后
we're gonna hook the model up into it.
把模型整合进来
Along the way we're gonna learn all these other things.
同时我们会学习其他这些知识点
Again, this is a slide you go look at after the demo to make
同样的，这张幻灯片是给你们之后看的
sure you learn these things.
用来确认你收否学习到了这些
I won't get back to the slides. So once again,
我不会再回来讲幻灯片。那么
what's coming up, don't forget that Friday section at 11:30,
之后的安排，不要忘了周五的课是十一点半
on Friday, about Xcode and debugging, and then next week
星期五是关于 Xcode 和调试，然后接下来一周
we're gonna talk about Swift and some other iOS things, and
我们会讲 Swift 和其他一些 iOS 的内容
we're gonna do it all in with the Concentration app as
我们都会用翻牌游戏
a little bit like our demo land.
把它作为做演示的地方
I'll show you, talk to you about something, some slides,
我会讲解某些内容，给你看些幻灯片
and then we'll go to Concentration.
然后我们到翻牌游戏里
I'll show you how it actually looks. All right, here we are,
我给你看看实际上是什么样的。好，我们到 Xcode 里
back exactly where we were at the end of Monday. We've got
回到我们星期一下课的时候所在的地方。我们有
our view controller here. We're all ready along the way
我们的视图控制器，我们已经在运用
of MVC. Right here, in this story board, that's our V,
MVC 的路上了。这里我们的 storyboard 就是 V(iew)
our view, and right here, this is our C our controller. Okay,
我们的视图。然后这里就是我们的 C(ontroller)，控制器
so this is our controller, and this is our view.
所以这是我们的控制器，这是我们的视图
So we need the M. So let's go make the M right now. So
我们需要 M(odel)，那我们现在就创建这个模型
how do we make a new class or whatever in Xcode.
那在 Xcode 里我们如何创建一个新的类呢？
We go to file > new > file. That's how you create a new
我们的菜单栏的 File > New > File...，这就是
Swift file, or whatever, and when you do that, it's gonna
我们创建类似于新 Swift 文件的方法。当你点击之后，它会
offer you a lot of different kinds of iOS files you want,
提供很多种你可能想要的，有关 iOS 的不同种类的文件
but really these are the two that are most interesting.
但我们最关心的是这两个
This is if you wanna create a subclass of
这个是如果你想创建一个子类
a Coco Touch class, of an iOS class, like a subclass of
继承自 iOS 的 Cocoa Touch 框架的某个类
another view controller or something like that.
比如创建一个继承 UIViewController 的子类
But here we're talking about the model, non-UI, so
但我们现在是要模型，和 UI 无关，所以
we're going to pick a blank, totally blank Swift file.
我们选择空白的，完全空的 Swift 文件
This is asking the name, we always name the files that we
这里问文件的名字，我们总是把文件
are creating in Swift. We name it after the most
创建的 Swift 文件，依据
important class that's gonna be in that file. Now I'm gonna
文件里最重要的类命名。那我要
call my model, my main model class, Concentration,
把我的模型，主要的模型类叫做 Concentration
because it is the thing that implements the game
因为模型实现了游戏
Concentration, so it deserves the name Concentration, so
翻牌，所以应该用游戏英文名 Concentration 命名
that's what I'm gonna call it. By the way, I'm not gonna
所以这就是我给它的名字。顺便一提，我不会
put it, you see this group, this is the top level project,
把它，你看这里有个 Group，现在是项目的最顶层
you really wanna put it in the folder one level down.
你应该把它放在下一层的文件夹里
It's the same folder where your view controller it's
和你的视图控制器放在同一个文件夹里
a better place to put it than at the top level.
放那里比放项目最顶层要好
It'll work if you put it in the top level, but
放在最顶层也是可以的
it just looks nicer to put it down here. All right so
但是放在这下面会好看些。好
I'm gonna create this Swift file, it creates it for us.
我点 Create 创建这个 Swift 文件，Xcode 已经创建好了
To make it full screen here. Notice that all it says is
我把它全屏显示。注意这里唯一的代码是
import Foundation. Not import UI kit. This is not a UI file.
import Foundation 而不是 UIKit，因为只是和 UI 无关的文件
This is a model totally UI independent.
这个模型是和 UI 不沾边的
So I'm gonna make my class here.
我要在这里定义我的类
I'm gonna call it Concentration. Concentration.
我要把它叫做 Concentration
Okay, is my class, and when ever I build a new class,
好，这是我的类。每当我定义了一个新的类
I always wanna think about what its public API is.
我总是要考虑它的 public API，公开的应用程序接口
How many people know what the phrase API mean?
有多少人知道 API 是什么意思？
Well, almost nobody, okay. So, API stands for
好吧，基本没人知道。所以 API 是
Application Programing Interface. It's just a list of
Application Programing Interface 的缩写，其实就是
all the methods and instance variables in that class and
这个类所有的方法和实例变量的列表
the public API is all the instance variables and methods
而公有的应用程序接口就是所有实例变量和方法
that you're going to allow other classes to call. So it's
你允许其他类访问的那部分。所以
basically how you use this class, and next week we'll
基本上就是你能如何使用这个类，然后下一周我们
talk about how you actually make things private and
会讲如何让这些接口变为私有（private）的
public, but today we're not going to worry about that, but
或是公开（public）的，但我们今天不操心这个
I'm going to basically design my public API.
但我会先设计我的公开的应用程序接口
Now why, when I kind of design my vars and funcs right off
那为什么我要在一开始就设计我的变量和方法呢？
the bat here, And the reason is because to do that I have
原因是要能够设计 API 的话，我需要
to get the essentials of what is it that this thing does and
理解这个东西本质上能做什么，以及
how are people gonna use it. And
人们如何使用它。这样
that makes me think clearly as I go in to my design here. So
我设计的时候就能思路清晰。因此
I recommend doing this for
我建议你这样做
any class that you design. So a concentration game.
在设计任何类的时候都这样。那对于翻牌游戏
Remember, we had it up here? What are the essentials of it?
就是我们摆在这里的这个，它的本质是什么？
Well, one essential is it has some cards. That card,
它最基本的就是有一些卡片
those cards are arrays of cards or something,
那些卡片可以用一个卡片的数组来表示
not quite sure what, some kind of arrays of card. So,
我还不是很确定，某种类似于存放卡片的数组
that's a fundamental part of the Concentration game for
这就是翻牌游戏的基础
sure. What about, what can you do in the Concentration game?
那么你可以在翻牌游戏里做什么？
The only thing that you're allowed to do as a user is
玩家唯一能做的一件事就是
flip the cards over, right, choose cards. That's all you
把一张卡翻过来，对吧？选择一张卡翻面
can really choose cards. All the matching and
你能做的就是翻牌。对于配对
all that stuff is kind of internal implementation of
这些都是我们内部的实现
the Concentration game. From user's perspective,
翻牌游戏自己处理的。对于玩家而言
you're just touching on it. So I'm gonna need some func
你只是点一张卡片。所以我需要某个方法
that let's me choose a card. Okay, and
能让我选择一张卡片
the argument to this could be either a card, one of whatever
这个方法的参数，要么是张卡片，某个
this thing is which we're gonna define in a second, but
这个类的实例，Card 这个类我们等会儿定义
I'm actually gonna make it be a little more flexible,
但我实际会让它更灵活
I'm gonna make it be the index into this array. So when you
我接受这张卡在这个数组里的索引
choose a card, I'm gonna let you choose it by index
所以当你选择一张卡，我会让你选择它的索引
and that's just to be a little more flexible to different
这样可以更灵活地让不同的
kinds of UI's that might want to do this.
用户界面来实现这个功能
They might be index based. It's really not that big of
这些 UI 可能是基于索引的。这其实影响不大
a deal cuz you could always just look up the index in this
因为你总是可以通过索引查找
array all the time.
数组里的元素
But it's a little easier to subscript an array and
但确实把索引作为下标（subscript）访问数组元素
find something than to go the other way around,
比在数组中找到某个元素要简单点
do index of like we did last time. So that's it actually.
要比用上次的 index(of:) 简单。好了，这就完了
This is all, this is the entirety of the public API
这就是所有的，我们全部公开的应用程序接口
of my Concentration game. Could not be simpler.
我的翻牌游戏就这样，不能再简单了
Now the only thing here is we gotta define Mr.Card. So
但现在我们要做的就是定义 Card 这个代表卡片的类
we're gonna go file>new>file and create another Swift file.
我们还是 File > New > File... 创建另一个 Swift 文件
This one I'm gonna call card. Okay,
这个我把它叫做 Card
put it in the same place as everything else there.
把它和其他的放在一起
All right, again, this is not a UI thing.
好，同样这不是 UI 相关的
This is part of my model. So I have two things in my model.
这也是我模型的一部分。所以我的模型有两个部分
The Concentration game and this card. What's really
一个是翻牌游戏，一个是卡牌
interesting here is I'm going to make card to be a struct.
很有趣的是我要用 struct 关键字定义 Card
Not a class. Now what's the difference
而不是 class。那有什么区别？
between a struct and a class? Let's go ahead and
struct 和 class 有什么区别？让我们
get the concentration up here in the same time.
把 Concentration 类也同时显示出来
Can use my manual thing here. We've got card on the left and
我能够用 Manual 手动选择它。我们 Card 在左边
structure on the right. One's a class, one's a struct. Now
然后 Concentration 在右边。右边的是 class，左边是 struct
in a lot of other languages like C, a struct is just kinda
在很多像 C 语言一类的语言，结构体（struct）
this little thing that holds a little bit of data.
就是某种存储少量数据的类型
It's nothing really that big a deal, but in Swift,
不是什么重要的东西。但 Swift 里
structs and classes are almost exactly the same.
结构体和类基本是一样的
They have methods, they have vars, very very similar. So
它们都可以有方法，有变量，非常相似
what is the difference between the two?
那这两个之间的区别是什么？
There's two major differences. There's some minor
主要有两个区别。有些小的
differences we'll run across as we going over this, but
区别我们等会儿会过一遍
there's two major differences between a struct and a class.
但是结构体和类两个最主要的区别
This is very important to understand this.
理解这个是非常重要的
Number one, struct, no inheritance.
第一，结构体没有继承
So you have no inheritance in struct that makes structs
所以结构体不能继承。这让结构体
a little simpler. Because if you have inheritance, you have
稍微简单了些。因为如果有继承
to kind of worry about all the things you're inheriting and
你必须要考虑继承的那些东西
what that might mean for you. The struct has no inheritance
考虑那对你来说意味着什么。结构体没有继承
so it's a little simpler than a class. Number two, and
所以它比类稍微简单些。第二
the most important difference, is that structs are value
也是最重要的区别，结构体是值类型
types and classes are reference types.
而类是引用类型
What does that mean? A value type,
那是什么意思？值类型
when you pass it as an argument, put it in an array,
当你把它作为参数传入函数，把它放进数组
even assign it to another variable, it gets copied.
甚至把它赋值给另一个变量，它都会被拷贝
This is very important to understand. It gets copied.
理解这个是非常重要的。它会被拷贝
And why is it so important that you understand this?
为什么你理解这个是非常重要的呢？
Why don't you just avoid structs?
那你为什么不干脆避免使用结构体呢？
Because you can't avoid structs in iOS.
因为你开发 iOS 是没法避免结构体的
Arrays are structs, ints are structs, strings are structs,
数组是结构体，Int 整数是结构体，String 字符串是结构体
dictionaries are structs, these are all structs.
Dictionary 字典是结构体，这些都是结构体
When you pass them around in your code,
当你把它们在代码之间传递的时候
they're getting constantly copied.
它们总是会被复制
Now you might be kinda like, whoa wait a second,
那你现在可能会想，等等
that is gonna be incredibly inefficient. No, because Swift
那会非常低效率。并不会，因为 Swift
is super smart about when it passes these things around.
非常聪明，在把这些结构体四处传递的时候
It doesn't copy all the bits of all the things of
它不会把所有这些的每个比特都复制
the array when you pass it. It passes it in a way so that it
当你把数组传入的时候并不会这样。它传递的方法是
only has to make copies, actual copies, when someone
只会在需要实际进行拷贝，也就是有人
modifies it. That's called a copy-on-write semantics, and
修改了它的值才拷贝。这叫做写时复制（Copy-On-Write）
that's the way Swift implements these value types.
这是 Swift 实现这些值类型的方法
So structs are value types. Classes are reference types.
所以结构体是值类型，类是引用类型
What's a reference type?
那什么是引用类型？
That's what you're used to in other languages.
那是你在其他语言里熟悉的
The thing lives in the heap. You got pointers to it. When
它们被存储在堆（heap）中，你保留指向它的指针
you pass it around, you're not passing the thing around.
当你传递它的时候，你不会把对象四处传递
You're just passing pointers to it.
你传递的是那个指针
And so you might have a whole bunch of code that
所以你可能有很多代码
has pointers to the same object. So
都拥有指向统一个对象的指针
you see the difference there?
你看出区别了吗？
So, structs are gonna take some getting used to because
确实要花些时间才能适应结构体
you're not used to when you pass things,
因为你还不适应，在传递的时候
it makes a copy. But you're going to see
它会拷贝这种机制。但你会看到
it provides an awesome semantic that you can really
它提供的这种很好的机制，你其实可以
use to your advantage. We're even gonna see that here
让它为你服务。我们甚至能够在这里看到
in a small way in our example, but you'll definitely start
在这个程序里它的体现。但写时复制，你肯定会
seeing it when you start using arrays and dictionaries and,
在开始使用数组和字典的时候看到
such, stuff like that. Okay, structs, card here,
或者使用类似的东西的时候。好，Card 结构体
what does a card need?
它需要什么？
Well same thing, let's think about its essentials.
同样的，让我们来考虑它的本质是什么
Certainly, a card can either be face up or not, so and
肯定有记录一张卡是否是面朝上的
it probably always starts face down, let's say.
多半不是，我们决定开始的时候是面朝下的
A card can be matched or not, probably starts out definitely
一张卡片可以被匹配或者不匹配，大概我们需要明确地以
unmatched. You know a card also needs a unique identity.
不匹配开始。你知道一张卡片还需要一个唯一的身份
Because we're playing a matching game and if we can't
因为我们正在玩一个匹配游戏，如果我们不能
tell the identity of the card, we can't match it against
分辨出卡片的身份，我们将无法使它与
another card. So we need some kind of identifier or
另一张卡片匹配。所以我们需要一种识别符
something, which really could be any type.
之类的东西，这可以是任意类型的
It could be a string or whatever. I'm gonna make
它可以是一个 String 或其他东西。我将
an Int because it's really easy to make a unique Int.
用一个 Int 因为创建一个唯一的 Int 是非常简单的
So, we definitely need that. Now,
所以，我们一定需要它
some of you might thinking, oh,
你们可能有一些人会想
okay a card also needs the emoji that's on it.
一张卡片也需要一个 emoji 在上面
Like the pumpkin or the ghost. And very importantly,
比如南瓜或者幽灵。非常重要的是
no it doesn't. This card is UI independent.
它并不需要。因为卡片是与 UI 不相关的
So there's no way it can have emojis or jpeg images or
所以它不可能拥有一个 emoji 或者一个 jpeg 图片 或者
anything like that, that's all how you display the cards.
其他类似的东西，那都是关于你如何展示这些卡片
This is just how the cards behave, how the game works. So
而这仅是这些卡片如何表现，这个游戏如何运行
you would never have an emoji in here, in this model. We're
所以在这个模型里绝不会有一个 emoji 变量
in the model here not the UI. Very important to understand.
我们是在模型里而不是 UI 里。这是你们需要理解的一件非常重要的事情
All right, so now we've got basically all of our API and
好的，现在我们基本上得到了所有的 API
our model here. Let's get rid of some these errors. You see
和模型。让我们把一些错误除去
that we still have this error class concentration has no
你看我们仍有这个错误 Concentration 类没有
initializers, a very common error that we're used to.
构造器，一个非常常见的错误
That's because this var is never initialized, so
那是因为这个 var 还没有初始化
how do we create an array of cards, okay?
我们该如何创造一个卡片的数组呢？
So this is now you're learning how to create an instance
现在你将学习如何创造一个结构或者
of a struct or of a class. It's exactly the same.
类的实例。它们是完全一样的
And the way we do that is I'm just gonna say equals
我们要做的方式仅仅是添加一个等号
open parentheses, close parentheses.
左括号，右括号
So this is just a type. It could be int or string.
它仅仅是一个类型。可是是一个 Int 或者 String
It happens to be an array of cards. And I'm just putting
这里碰巧是一个卡片的数组。而且我仅仅是在后面
open parentheses, close parentheses after it. And
添加了左括号和右括号
I could actually be putting various arguments in here. And
我还可以将各种参数放在里面
these correspond to those inits I was telling you
它们与我之前告诉过你的那些 init 函数有关
about. Remember last time, I said class could have
记得上一次，我说过类可以拥有
an initializer, it's a special method called init,
一个构造器，它是一个叫做 init 的特殊方法
it can have whatever arguments you want.
它可以拥有任意你想要的参数
You could have multiple inits. Array has an init with
你还可以有很多个 init 方法。Array 拥有一个 init 方法
no arguments and what it does is it creates an empty array.
它没有参数，它的作用就是创建一个空数组
So the array exists, but there's no cards in it.
所以这个数组就存在了，但是里面没有卡片
There's nothing in it. There are other, and you can go look
里面没有任何东西。还有一些其他的 init 方法，你可以
in the documentation, for other inits that array has.
查阅文档，一个 Array 的其他 init 方法
It has an init, for example, let's you reserve capacity for
举个例子，它有一个 init 方法，让你预定它的容量
a certain amount for performance reasons if you're
这是为了一些性能原因，如果你
pretty sure you know how big there's going to be array.
非常确信这个数组将有多大
You can create an array from another array and
你可以从另一个数组里创建一个数组
it'll copy over the items from that other array,
它会从那个数组里复制物品
so there are other inits, but the most common array init
所以这里有其他的 init 方法，但是最常见的
is this one where you just do nothing.
就是这个，你不需要做任何事情
Now by the way, we're using this kind of easier to
顺便说一下，我们会这样做让语法变得
understand syntax, but we would almost certainly use
更容易理解，但是我们大部分情况下会用
this syntax. Array of Card. So I showed you last time.
这种语法。卡片的数组。我上次给你们展示过
So I've got an empty array of cards to work with right here.
所以我得到了一个空数组来操作
I've got no warnings. So now, it's time to go
没有警告，所以现在，是时候
back to our controller and try to start using this model. So
回到我们的控制器尝试使用这个模型
were doing MVC were going back to our controller. So
我们正在使用 MVC 设计模式，现在返回我们的控制器
I'm gonna back over here. I'm gonna go to my ViewController.
所以我要返回这里，我要去到我的 ViewController
let's have the Concentration on at the same time here. So
让我们同时把 Concentration 文件也放在这里
Actually, let's go,
现在，让我们开始吧
we've got Concentration,
我们得到了 Concentration 文件
I'll make it small, there it is, and our ViewController,
我把它缩小一点，这样子，在我们的视图控制器里
so how am I gonna use my model in this controller? Well,
我该如何在这个控制器里使用我的模型
the first thing I wanna do is make that big green arrow,
我想做的第一件事就是制造这个绿色通道
the green arrow that points from my controller to my
它从我的控制器指向
model. And I'm gonna do that by creating a var
我的模型。所以我将做这件事，通过创建一个 var
in my controller, and call it game. And it's gonna be of
在我的控制器里，然后把它称为 game（游戏)。然后它的类型是
type Concentration. There's my big green arrow. I can send
Concentration（翻牌游戏）。这就是我的绿色通道，我可以发送
any messages I want to game, like, I can get at its cards.
任何我想要的消息给 game，比如，我可以得到它的 cards (卡片数组)
I can choose a card, whatever. and I'm ready to go.
我可以选择一个卡片，等等。现在可以继续进行下去
Now, look at the error I get, our favorite error,
注意看我得到的这个错误，我们最喜欢的错误
Class ViewController has no initializers, because this
ViewController 类没有构造器，因为它还没有
is not initialized. How are we gonna initialize this?
初始化。我们将如何初始化它
Let's see, we wanna do something similar to what we
让我们看看，我们要做一些类似于对待这个数组一样
did with this array over here. Let's try that, let's do
的事情。让我们试一下，让我们
Concentration,this is probably not gonna work, right?
写下 Concentration, 这可能不太会正常运行，对吧？
Because we didn't create an init with no arguments for
因为我们没有为 Concentration 创建一个没有参数的 init 方法
Concentration. But it did work!
但是它成功了！
How could this possibly work? The answer here is classes,
它怎么可能正常运行？答案是类
and Concentration is a class, get a free init with no
Concentration 是一个类，它自动得到了一个没有参数的
arguments as long as all of their vars are initialized.
构造器，条件是只要它所有的变量都被初始化了
And this only has one var, it's initialized, so
因为它只有一个变量，且已经被初始化，所以
Concentration got a free init. We're initialized.
Concentration 自动得到了一个 init 方法。我们初始化了它
We don't actually need this type. Remember why?
实际上我们不需要这个类型，记得为什么么？
Type inference, obviously Swift can figure out from that
类型推断，显然 Swift 能够从这行代码中分辨出
line of code the game is of type Concentration, we don't
game 是 Concentration 类型，我们
need to put that in there. But this was fun and nice that we
没有必要把它放到这里。但是有趣并且美好的是
got this free initializer, but actually, it's no good.
我们得到了这个默认构造器，但是实际上，它并不优雅
It's not good enough. Because when we create a Concentration
它不足够优雅。因为当我们创建一个翻牌
game, we gotta think how many cards there are. Because
游戏的时候，我们将会思考游戏中有多少卡片。因为
the Concentration game has to kinda load up this array over
翻牌游戏会加载这个
here with all the cards that it's gonna use,
拥有所有卡片的数组，然后使用它
and we can't assume that a Concentration game only has 12
我们不能假设一个翻牌游戏只有12张卡牌
cards like we had on the board. Or only has four cards,
就像我们在面板上的那样子。或者只有四张卡片
like our current UI here has. We actually need to create our
像我们现在的 UI。我们实际上需要创建一个
own init. So let's go, again, make some more space over
我们自己的 init 方法。让我们开始吧。再一次，让这里的空间
here. And add an init to our Concentration class.
大一点。然后在我们的 Concentration 类里添加一个 init 方法
Init. Now again, we get to have any arguments we want.
现在，我们需要得到我们想要的参数
And what we need to create our game is we need to know
创建我们的游戏所需要的是
the number of pairs of cards. And that's gonna be an Int.
卡牌对的数量。它会是一个 Int
So this is the initializer that people are gonna
所以这就是人们将要用来创建一个翻牌游戏
have to use to create a Concentration game.
所需要的构造器
Cuz we need this. So how are we gonna implement this init?
因为我们需要它。那么我们该如何实现这个构造器呢？
We need to create this many pairs of cards and
我们需要创建一个储存卡牌对数量的属性
put them in here. Let's try and create one card.
然后把它放到里面。让我们尝试创建一个卡片
Let card =, maybe we'll get lucky and
let card =, 大概我们比较幸运，我们可以直接
we can just say card. We got, that worked for
写 Card。写好了，我们写好它让它为 Concentration
Concentration. Didn't work. Why didn't that work?
工作。它并不成功。这是为什么？
Because if you look over here to our card.
因为如果你从这个地方查看我们的 Card 文件
Let's bring up Mr. Card here. The card,
让我们把 Card 拿过来
it has something that has to be initialized there.
它有一些还未被初始化的东西
Card is a struct. Now, structs get a free initializer
Card 是一个结构体，而结构体也有一个自动获得的构造器
as well, but it's different than a class.
但是它和类的并不同
So this is another difference between structs and classes.
这是结构体和类的另一个不同点
Struct, the free initializer they get, initializes all of
结构体自动获得的逐一成员构造器会初始化它
their vars, even if they're already pre-initialized,
所有的变量，即使它们已经有了默认值
like isFaceUp and isMatched. Watch this, okay,
比如 isFaceUp 和 isMatched。看这个，好吧
I'm gonna just type card. Open parenthesis,
我将敲下 Card, 左括号
I'm gonna let Xcode show me the initializer, and
我会让 Xcode 展示给我构造器
you can see that it just initializes every single one.
你可以看到它把每一个变量都初始化了
So I could do isFaceUp false, isMatched false,
所以我可以把 isFaceUp 写成 false, isMatched 写成 false
the identifier, I guess I'm gonna have to make up some
至于 identifier, 我猜我要添加一些
identifier for this thing to do it.
identifier 来做这件事情
So I could initialize a card like that, cuz I get a free
之所以我能够像这样子初始化一个 card 对象，是因为我得到了
initializer. Classes never get this kind of free initializer.
逐一成员构造器。类从来没有这种构造器
They don't get the free initializer where they,
它们没有这种为所有变量都初始化的
you can initialize all their vars.
的构造器
That's purely a struct thing. But this is bogus, because
这完全是结构体拥有的东西。但是它有一个缺点
these are, start out false, so why, when I'm initializing,
因为它们是以 false 开始的，所以为什么当我初始化的时候
do I have to say again they're false? If I want to get rid of
我还要再写一遍它是 false 呢？如果我想要摆脱这些
those, and to get rid of those, I have to add
麻烦，我必须自己添加
an initializer here. So I'm gonna have an initializer that
一个构造器。所以我会在这里添加一个
just takes the identifier, which is an Int.
只有 identifier，一个 Int 类型参数的构造器
And then I wanna set this identifier right here equal to
我想要设置这里的标识符等于
this identifier right here. We've
这里的这个标识符。我们已经
got a couple of problems with that, we're trying to do that.
遇到了两个问题，我们要尝试解决它
One is this certainly can't be right. Identifier equals
其中这个问题一定是不对的。Identifier 等于
identifier? That, that's just weird. One thing also notice
identifer? 那太奇怪了。有一件需要注意的事情是
that in both of my inits, I didn't do an external name and
在我的两个 init 方法里，我并没有写参数标签和
an internal name. You notice that?
参数名称。你们注意到了么？
I only did one, which means both the external name and
我只写了一个名字，它意味着参数标签和参数名称
the internal name is the same. So one thing I could do to fix
都是一样的。所以可以修复这个问题的一个方法就是
that is make the internal name be different, like i.
让参数名称不同，例如 i
And then I could say identifier = i.
这样子我可以说 identifer = i
You see? This is the external name,
懂了么？这是参数标签
I'm using it here when I call this init.
当我调用这个 init 方法时，我在使用它
And this is the internal name, which I'm using inside here.
这是参数名称，我在方法内部使用它
And now it knows that this identifier means that one. But
现在它知道了这个 identifier 意味着那一个
you know what? This is kinda gross. First of all,
但是你知道么 这是有点粗野的。首先
I hate variable names like i. i is a bad variable name.
我讨厌像 i 这样子的变量名字。i 是一个很不好的变量名字
I just don't wanna have it here. But I can't really think
我就是不想要它出现在这里。但是我真的想不出
of another one here that's any better than identifier.
一个比 identifier 更好的名字
That's, identifier is a good name there. So
identifier 在这里是一个足够好的名字。所以
I actually want it to be same internal name and
我想要它同时是参数标签和参数名称
external name. Now, inits are the one method that
现在，init 通常是一个拥有
usually has the same internal name and external name. Most
拥有相同的参数标签和参数名称的方法
functions don't, but inits tend to. They don't have to,
大部分函数都不是，但是 init 倾向于是这样子。它们不一定都是这样
but they tend to. So, now I'm back to saying this, but
但是它们倾向是这样子。所以，现在我回头再写成这个样子
it's not, I can't really distinguish between these two,
但是我完全不能分辨它们两者
so how can I distinguish between two, these two?
所以我该如何分辨这两个呢？
This is the parameter. This is my identifier on myself.
这个是实参，这个是我的标识符
Well, I can say, self. So, self., means my identifier.
所以，我可以写，self. 所以 self. 意味着我的标识符
This card's identifier. So now it knows we want this one,
这张卡牌的标识符。所以现在它知道我们想要的是这个
and so this one is this one. That's kinda cool.
这个对应这个。这是蛮酷的
We got out of having that going on. All right, so
我们成功地让它能够继续。好的
let's go back over here again.
让我们再一次返回这里
We're able to create one card, great, worked good.
我们已经能成功的创建一张卡牌的实例
But we have to specify the identifier for the card. So
但是我们必须明确它的标识符
this really doesn't matter what the identifier is,
标识符具体是什么真的不重要
as long as it's unique. So I'm gonna create a for loop. So
只要它是唯一的就好。所以我要创建一个 for 循环
pay attention, here's how you create a for loop in Swift.
请注意，看你将如何用 Swift 创建一个 for 循环
It's a little different than other languages. It starts out
和其他语言相比，这有一点不同
the same. For identifier, this is just for some variable. But
它们开始是一样的。for identifier 这仅仅是 for 跟上一些变量，但是
you don't say equals zero, less than 20, i plus plus,
你并不写成 for i = 0; i < 20 ; i ++ 这样子
we don't do any of that. Instead we use the word, in,
我们完全不这样子写。相反，我们使用关键词 in
and then this right here can be anything in Swift that is
然后后面的地方可以是 Swift 里的任意一个序列
a sequence. And a sequence in Swift means anything where you
Swift 中的序列代表着你可以从一个它某一个位置开始
can start somewhere and go to the next one, go to next one,
然后去到下一个位置，再下一个位置
go to the next one, and that's what a for loop does.
再下一个位置，这就是一个 for 循环所做的事情
It starts somewhere, and it goes to the next one, and
它从某个位置开始，然后到达下一个位置
goes to the next one, goes to the next one. So
再下一个位置，再下一个位置。所以
what are some examples of sequences in Swift?
Swift 里序列的例子有什么呢？
Arrays? Okay, obviously array, you could start at the first
数组？是的，显然在数组里，你可以从第一个元素
element, and go to the next one, go to the next one,
开始，然后去到下一个元素，再下一个元素
till you get to the end.
直到你到达结束位置
String, a string is a sequence. First character,
一个字符串，一个字符串也是一个序列。第一个字符
go to the next character, go to the next character,
去到下一个字符，再下一个字符
next character. The one I'm gonna use here is a really
再下一个字符。我现在要在这里使用的
cool sequence called a countable range. So
是一个非常酷的序列叫做 CountableRange
a countable range is a range, in other words, it has a start
所以 CountableRange 是个 Range，换句话说，它有一个开端
and an end, and it's countable. In other words,
和一个结尾，并且它是可数的。换句话说
it knows how to count through it and go to the next space.
它知道如何穿过它计数然后到达下一个地方
And there's special syntax in Swift because it's so
这是 Swift 中一个特殊的语法因为
common to wanna make a countable range. And
创建一个 CountableRange 实在是太常见了
it goes like this. The start of the countable range,
它是这样子运行的。CountableRange 以
..<, which means from 0 up to and
..< 开头，意味着从零开始直到
not including the number of pairs of cards.
numberOfParisOfCards 但不包含它为止
So this is a for loop that is going to go through,
所以这是一个 for 循环，它将会遍历
why is that not cutting? I didn't select it,
为什么这个没有被剪切？我没有选中它
I guess? Cut, there we go, paste. It's gonna go through
我猜？剪切，好的，粘贴。它将会遍历
the number of pairs of cards. Now, there's another kind of
numberOfParisOfCards。现在，这里有另一种
countable range creator here, which is .. Dot,
创建 CountableRange 的方法，它是 ...
that means zero to here, including this one.
它意味着从零到这个地方，并且包含它
So, here, if I wanted to use that one, I would say one dot,
如果我想要使用这个，我会写成从一开始
dot, dot cuz I only want this many cards. So, that makes
点点点，因为我只需要这么多卡片。所以，它就成了
a countable range. In fact, if you look at this thing's type,
一个 CountableRange。事实上，如果你查看这个东西的类型
it'll be countable range. Okay, so now I'm going through
它会写着 CountableRange。好的，所以我会遍历
and creating a card here, but I need another card.
然后创建一个卡牌，但是我需要另一张卡牌
So I could say matchingCard = the same thing, right?
所以我会写成 matchingCard =，后面是相同的内容，是吧？
Create another card with the same identifier. Makes sense,
创建另一张有着相同标识符的卡牌。这听起来讲的通
right? Now I have two cards that match that I can add to
对吧？现在我有了两张匹配的卡牌所以我要添加到
my bunch of cards up here.
我的卡牌数组里面
I always need two cards that match,
我总是需要两张能够匹配的卡片
but you know what's amazing is I actually don't even need to
但是你知道惊喜的地方在于，实际上我甚至不需要
do this right here. I can just do this,
在这儿做这件事情。我可以仅仅这样做
because when you assign a struct, which card is
因为当你将一个结构体赋值，Card 是一个结构体
a struct, to another variable, it copies it. So
赋值给另一个变量的时候，它拷贝了值。所以
matchingCard is a copy of card. Just by assigning it,
matchingCard 是 card 的一个拷贝。仅仅通过赋值给它
it makes it a copy. Now I have a card and a copy of it. So
其实是创建了一份拷贝。现在我有了一张卡片和它的一个拷贝
I can say cards.append,
所以我可以写成 cards.append
that's how you add something to an array, append the card,
那是你如何添加一些东西到一个数组上的方法。添加这张卡片
and then cards.append the matching card, matchingCard.
然后 cards.append 跟上匹配的卡片即 matchingCard
So now I've added these two matching cards to my array.
所以现在我已经添加了这两张正在匹配的卡片到我的数组中
And I'm going to do that number of pairs of cards
在循环里，我将一共进行 numberOfPairsOfCards 次这样子的事情
times. I've got my cards,
我就得到了我的卡牌数组
however I don't even need this matching card.
然而，我甚至都不需要这个匹配卡片
I can get rid of that, and instead just say append(card).
我可以去除它，用 append(card) 代替
Because putting things in an array or taking them out also
因为把东西放到数组里或者从里面拿出来也是
copies the card. There are actually three cards involved
复制卡片。这里其实涉及到了三张卡片
here. This one I create, the copy that gets put in here,
我创建的那张，复制到这里的那张
and another different copy that gets put here. So
和另一张不同的复制到这里的那张。所以
understand that when you pass these structs around,
理解这个原理，你就会明白，当你传递结构体的时候
you are copying them.
你其实是在拷贝它们
Now later when we turn one of these cards face up,
之后当我们把其中一张卡片朝上翻转的时候
the copy will actually be a real copy and
那份拷贝是一份真的拷贝的卡片
only one of them so it's not a pointer to the same card and
只会有一张卡片翻转过来，所以它并不是一张卡片的一个指针和
memory, it's actually two different cards. By the way,
内存地址，它们是两张不同的卡片。另外
another cool way we can do this different syntax is I can
我们能用另一种酷的语法来做，我可以写成
say cards, which is an array of cards,
cards 它是一个卡牌数组
+= another array of cards, with those two cards.
+= 另一个包含这两张卡牌的卡牌数组
Plus equals works with arrays. You can add arrays together
+= 这种语法在数组里同样适用。你可以把数组们相加到一起
and this, putting this card in this array and
这里，你把这个 card 放到这个数组里，然后
this card in this array copies it. And then we put it in
把这个拷贝的 card 加到这个数组里，然后我们把它放到这儿
there, and in effect this array also gets copied
实际上这个数组也得到了拷贝
cuz array is a struct. All right, so we got that,
因为数组就是一种结构体。好的，现在我们进行到了这里
this is all going quite well. The only thing I don't really
所有事情的进行都很顺利。唯一我不太喜欢
like about this is it doesn't really seem right to me that
的事情，看起来不太对的事情是
the concentration game has to pick the identifiers for
翻牌游戏不得不为卡片选择标识符
the cards, because the concentration game does not
因为翻牌游戏不需要
care what the identifiers are.
关心标识符具体是什么
All it cares is that they're unique. So really,
它只关心它们是不是唯一的。所以
I don't wanna do this. I want to get this out of here, and
我不想要这样子做。我想要把它从这里分离
have the card figure out its own unique identifier. There's
让卡牌来想出它自己的唯一标识符
no reason the card could just like pick 1 billion and 7.
一张卡片没有理由选择像十亿零七这样子的数字
And that's its, as long as that's unique and
所以这就是标识符，只要它是唯一的，只要它永远也不会
it never gives out another card with 1 billion and 7,
将十亿零七分配给另一张卡牌
then it should be unique. That's what I do.
那它就是唯一的，这就是我要做的事情
I want this init to not have to take this identifier. But
我想要这个 init 方法省略这个标识符参数。但是
if this init over here doesn't take the identifier, then we
如果这里的 init 方法没有这个参数，那么我们
gotta figure out how to create a unique identifier here. So
要想出如何创造一个唯一的标识符
how are we gonna do that? Well, I'm going to teach you
我们该如何做呢？好吧，我将要教给你
another cool Swift thing, which is I'm going to create
Swift 另一件酷的事情，我要创建另一个
a special kind of method. It's a static method.
特殊的方法。一个静态方法
It's a same syntax func but it has static before it, and
它有着相同的函数语法只不过在前面有一个 static 关键词
it's going to get a unique identifier. It's gonna return
它将产生得到一个唯一的标识符。它会返回
an int that's gonna be unique. And every time I call it,
一个唯一的整数。并且每一次我调用它的时候
it's gonna return me another unique identifier.
它会返回给我另一个唯一标识符
It's just gonna return some unique identifier.
它会返回给我某一个唯一的标识符
We're gonna have to make it work. Now, what is a static
我们必须让它工作。现在，静态
function all about? A static function is a function,
函数到底是什么呢？一个静态函数是一个函数
even though it's in the card class, you can't send it
即使它在 Card 类里面，你也不能用一个 card 实例
to a card. A card does not understand this message.
调用它。一个 Card 实例不能理解这条消息
What understands this message is the type card.
理解这条消息是 Card 类
You send it to the type itself.
你把它发送给类本身
So you can think of it kind of like a global function,
所以某种程度上你可以把它看做一个全局函数
a utility function or something that's just tied to
一个工具函数或其他与这个类
the type. Does it make sense? You don't send it to a card.
关联的东西。这样说的通么？你不会将它发送给某一张卡片
You can't ask a card for a unique identifier.
你不能询问一张卡片要一个唯一标识符
You ask the card type. So, and that's what static means.
你要询问这个卡片类本身。所以，这就是静态的含义
So here when I want to call this function,
所以我这里在调用这个函数时
I send it to the type card.getUniqueIdentifier.
我把它发送给类 Card.getUniqueIdentifier
That's getting it from the card type. Now how am I gonna
这就是它的语法。现在我改如何实现
implement this get unique identifier? Well,
这个得到唯一标识符的功能呢？
I'm gonna have a static var, cuz you can have those too.
我要拥有一个静态变量，因为你也可以这样子做
So that's a variable that's stored with the type,
所以这里有一个变量，它随着类而储存
not with each individual card. These are stored with
而不是单独的一张卡牌实例。这些变量是随着每一个
each individual card, this is stored with the type.
卡牌实例而储存的，这个是随着类而储存
And I'm gonna call this my identifierFactory,
我会把它命名为我的 identifierFactory（标识符工厂）
I'm just gonna start out with = 0. And then in here,
我会以等于零开始。在这里
what I'm going to say is Card.identifierFactory += 1 to
我会写 Card.identifierFactory += 1
make it a new unique identifier. And then I'm gonna
来生成一个新的唯一标识符。然后我会返回
return Card.identifierFactory. So now I'm returning a unique,
Card.identifierFactory。所以现在，我正在返回一个唯一的标识符
every time you call this, it makes a unique int,
每一次你调用它，它都会生成一个唯一的整数
cuz it starts at zero and makes a unique int each time.
因为它从零开始，每次都会加一，生成一个新的唯一的数字
Now, what's interesting is I don't really need to say Card
现在，有趣的地方在于，我真的不太需要写成 Card.
dot because I'm in a static method, so I can access my
这样子因为我正位于一个静态函数里，所以我能直接调用
static vars without the Card. So that's kinda cool.
静态变量而不需要使用 Card. 。这是蛮酷的
So I just want to take this little detour to show you how
所以我仅是想用这一点点兜圈子的方法向你们展示
you can do these nice utility methods or whatever,
如何使用这些类型中很棒的的工具方法
utility vars on a type. So now let's go back over to
和工具变量。所以现在让我们返回我们的
our concentration, over here cuz it's got a warning, let's
Concentration，因为这里我们有一个警告，让我们
see what this warning is all about. It says immutable value
看看这个警告是关于什么的。它写着 不可变值
identifier, this, was never used.
identifer, 这个，从没被使用过
It says consider replacing with underbar or removing it.
它说考虑把它替换成下划线或者移除它
Well, we can't remove it because it's the control
好吧，我们不能移除它因为它是
variable of our for loop. But we can replace it with
我们 for 循环的控制变量。但是我们可以把它替换为
underbar. Underbar in Swift means kind of ignore this or
下划线。Swift 中，下划线意味着某种忽略或者
I don't really care what this is cuz I'm not gonna use it.
我并不十分在乎一件事情是什么，因为我将不会使用它
And we've actually used this before, do you remember?
并且实际上之前我们已经使用过这个语法了，你们记得么？
Back over here in our ViewController, touchCard,
返回我们的 ViewController，touchCard 操作
it's external name was underbar. Which means don't
它的参数标签就是一道下划线，这意味着当你调用它的时候
give an external name when you call this because remember
并没有给它一个参数标签，因为
touchCard's kind of an Objective-C thing.
touchCard 是有点像 Objective-C 一样的东西
This target action, and so it didn't have external name and
这个目标操作，同样也没有参数标签
so we just said, hey,
所以我们说过
I don't want an external name. Similar kind of thing here in
我们不想要一个参数标签。与它相似，在
Concentration, we're doing a for loop, we still want to do
Concentration 里，我们正在进行一个 for 循环，我们依然想要
this, this many times. But we don't care what identifier is
进行这个循环很多次，但是我们并不关心这个标识符是什么
cuz we don't use it in here anymore. Got that? Okay,
因为我们再也不会使用它了。明白了么？好的
the last thing we need to do here when we're initializing,
最后一件事情我们需要做的是，当我们初始化的时候
it's kind of left to do, is to shuffle the cards. cuz if we
剩下来要做的，就是洗牌。因为如果我们
don't shuffle the cards, then they're always going to be in
不洗牌，它们将总是
the same order all the time. It would be really easy to
位于相同的顺序。那会让游戏
play the game. I'm gonna leave that for your homework.
变的非常简单。我会把它留作你们的家庭作业
Your job is gonna be shuffle the cards there. It's gonna
你们的任务就是洗牌。它将会
require you to understand, again, this value type arrays,
要求你去再一次理解，这个值类型数组
or value types, etc. It's a pretty good exercise to
或值类型。它是一个非常好的练习
do that, so I'm gonna leave that for
所以我会把它留给你
you. We've solved the whole initialization problem. So
我们已经解决了初始化的问题。所以
now we can go back over to our view controller over here. And
现在我们要返回我们的 ViewController
find it. There it is. And Concentration
找到它，它在这儿。并且 Concentration
right here needs to specify the number of pairs of cards.
需要明确卡牌对的数量
numberOfPairsOfCards, and what number do we put here?
numberOfParisOfCards，我们要放什么数字到这里
Right, I guess we could put four.
好吧，我猜我会放四到这里
We know we have four cards in our UI.
我们知道我们的 UI 已经有了四张卡牌
But what if I wanted to add more buttons?
但是如果我想要更多的按钮呢？
Now I gotta come back here and change this four?
现在我要返回去然后更改这个数字四
Why don't we just count the number of card buttons,
为什么我们不直接数出卡牌按钮的数量
remember? Card buttons, all those buttons are in there,
记得么？cardButtons 所有的按钮都在这里
cardButtons.count. That's how many cards there are, and
cardButtons.count。那个数字就是这里有多少卡牌
we'll just divide by two. All right, make sense?
然后我们仅要把它除以二。好的，讲得通了吧？
Let's so you can see this a little better.
让我们这样子，让你可以看起来更好一点
I'm just gonna take the card buttons and divide by two. So
我仅要写上 cardButtons 然后除以二
if I have four card buttons, that's two pairs, obviously.
所以如果我有四个卡片按钮，那就是两对，显然
Actually I should be a little careful.
事实上我需要更认真一点
If I had an odd number of cards,
如果我有奇数个卡片的话
I probably wanna round up. So if I had three cards I'd need
我可能要将它取整。所以如果我有三张卡片，我就需要
two pairs so I have four total cards.
两对牌，所以一共我有四张卡片
I'll never be able to match that third card but
我将永远不能匹配第三张卡片，但是
at least the game will have enough. Now, this is the right
至少我们的游戏拥有足够的数量正常进行。这是要做的正确的事情
thing to do but I got a very serious error here. Okay,
但是我得到了一个非常严重的错误。好吧
let's look at it. It says, cannot use Instance member
让我们看一下它。它写着，在这里不能使用实例成员
card buttons, right here, within a property initializer.
cardButtons，在一个属性构造器里面
Oh yeah, look, var game that's a property and
是的，看，game 变量是一个属性并且我正在
I'm initializing it, that's a property initializer.
初始化它，那是一个属性构造器
It says property initializers run before self is available.
它写着，属性构造器在 self 可用之前运行
Oh, remember I told you that in Swift you have to
记得我告诉过你们，在 Swift 中你必须
completely initialize something before you can use
在使用任何东西之前
anything in it, before you can access any of its bars,
完全初始化它，包括你使用它的任何属性
call any of its functions, anything.
调用它的任何函数，任何事情
Well that obviously we're not fully initialized yet
这里很明显的我们还没有完全初始化它
because we're in the process of initializing game. And
因为我们正在初始化 game 实例的过程中
game is the one that's initialized so
game 就是那个正在被初始化的实例所以
we got a catch-22 here. How the heck are we gonna do this,
我们得到了一个相互抵触的窘境。我们要如何做呢
where one depends on the other? Where one var depends
在这里一个依赖着另一个？一个变量依赖者另一个
on another. There's a couple of ways to address this.
这里有几种方式来解决这个问题
But I'm gonna show you kind
但是我要向你们展示
of a cool one which is lazy. If you make a var lazy,
一种蛮酷的方式，lazy 关键词。如果你懒加载一个变量
that means it doesn't actually initialize until someone grabs
这意味着它将不会被初始化，直到有人想要抓取它
it. Until someone tries to use it. As soon as someone tries
直到有人想要使用它。一旦有人尝试使用
to use game, then it's going to initialize it.
game，它才会被初始化
Now by definition, because of that very same catch 22, no
现在通过定义，因为这个相互抵触的窘境
one can try and use game until this is fully initialized.
没有人可以尝试使用 game 实例，直到它被完全初始化
So we win, it works perfectly. And lazy count as,
所以我们赢了，它完美运行。而且是 lazy 关键词让这个
this var initialized. Counting that game of getting going
变量成功初始化的。让这个 game 能够成功发展到
this way, so it's awesome. There is one restriction
这里，它太棒了。不过这里有一个关于
about lazy though, that's not so nice.
lazy 的限制，这不太好
Which is, it can not have a didSet. If you try
那就是，你不能使用 didSet 语法。如果你尝试
to add a didSet to it, like we did here with flipCount,
给它添加一个 didSet，就想我们对 flipCount 做的那样子
remember that? It's, you're gonna get an error here,
记得么？如果添加了，你就会得到一个错误
this is clash declaration blah blah blah, but basically what
这是一个崩溃的声明 等等等，但是基本上它
it's saying here is you can't use property observers with
所说的就是你不能够在一个懒加载的变量上使用
a lazy var. So if you need to use a property observer there,
属性观察者。所以如果你需要在某个地方使用属性观察者
you need to find out every time the game changes,
你需要找出每次 game 改变的地方
you're going to have to do a different way.
你将以一种不同的方式来做
What are the other ways you can do it, by the way?
顺便说一下，你可以用什么其他的方式呢？
Well, you're going to
你将会在
learn next week that there are methods that are called after
下周学习到一些方法，它们会在所有的
all these outlets get fired up. The system will call
出口接通后。系统将会调用一个
a method and in there you can initialize your game.
方法，在那里你可以初始化你的 game 实例
And maybe in the mean time you make it an optional or
可能你想要把它设置成一个可选类型或者
maybe even an implicitly unwrapped optional.
甚至是一个隐式解析可选类型
A little hint to you there. But you won't need that for
这是给你一点小的提示，不过你的家庭作业
your homework though. You'll be able to do this for
不需要它。你用这个就可以完成
your homework. Okay, so it's good. Now we have the game,
你的家庭作业。好的，它很棒。现在我们有了 game
the big green arrow. There our controller is talking to our
有了绿色通道，在那里我们的控制器正在向模型通信
model. What do we need to do next?
我们接下来需要做什么？
Let's go back and look at our Concentration API again, see
让我们再一次返回查看我们的 Concentration API
what we need to do. It seems like there are two things,
看我们需要做什么。它看起来有两件事情
we have used this part of the API. We got these two left, so
我们已经使用了一部分的 API。我们还剩下两个，所以
let's do this one, chooseCard. We got to tell
让我们来处理这个，chooseCard。当一张卡片被选中的时候
the concentration model when our card is chosen.
我们要告诉这个 concentration 模型
And we know exactly when that is in our controller.
并且在我们的控制器中，我们知道确切的时间
It's right here in touch card. Whenever a button calls touch
它就在 touchCard 方法里。每当按钮调用 touchCard
card, we know a card's being touched.
我们就知道点击了某张卡片
And instead of doing all this flip card with emoji choices
然后取代 flipCard(withEmoji: 选中的表情)
thing right here, I'm just gonna tell my game hey,
这个方法，我用 game.chooseCard(at:)
choose this card. Okay?
来告诉 game 说选这张卡片
So instead of handling it myself, I'm letting the model
不是我自己控制器来处理，而是让模型
handle it. So, it's the one that's figuring it out.
来处理它，让模型来实现逻辑
But there's something interesting to note here,
但很有趣的是，我们注意
is that when I tell the model choose this card,
当我让模型选择这张卡的时候
it might change the game might change. And in fact, I hope it
它是可能会发生改变的，游戏的状态会发生改变
does change cuz it's supposed to be doing matching and
而我也希望这样，因为它应该去匹配
all kinds of stuff. So it's gonna change. So now,
等等，所以游戏的状态会改变
we have to update our view from the model, okay?
那我们就需要从模型获取数据更新视图
Our view is now a little bit out of sync with the model,
我们现在的视图就没更上模型的改变了
because when we chose this card,
因为当我们选择这张卡片的时候
that could have caused the game to change.
游戏的状态可能会发生变化
So we need a method like updateViewFromModel or
所以我们需要一个类似于 updateViewFromModel 的方法
something like that. Some kind of func down here,
来用模型更新视图，在下面实现这样一个方法
func updateViewFromModel. And what's that gonna do?
func updateViewFromModel 要做什么呢？
That's gonna use the other part of this API right here.
它会使用 API 里的另一个部分
It's gonna look at all the cards and
它会去查看所有的卡片
make sure all our card buttons match, right?
然后确保按钮和卡片是保持一致的
Whether they're face up, whether they're matched,
它们是否是面朝上的，是否已经配对了
all that business. We look in the game,
等等这些。我们去 game 模型里
find out, and make sure our card does match. All right,
查看这些卡的状态，然后确保按钮是正确的
so how do we implement this updateViewFromModel?
好，那我们怎么实现 updateViewFromModel 呢？
Well I wanna go through all the card buttons and
我要遍历所有的卡片的按钮
look at the game and set it up appropriately. So I could do
然后对照 game 模型，然后正确地设置按钮
another for loop where I say for button in my cardButtons,
我可以用一个 for 循环，也就是 for button in cardButtons
okay? And just go through and now each button will be if we
对于 cardButtons 里的每个按钮，这个 button
look at the type of this it's going to be a UI button. See?
如果我们查看它的类型，会是 UIButton，看到了吗？
Because this is a sequence of buttons. Isn't that cool?
因为这是一系列按钮。这个可以吧？
All right? But I'm actually not gonna do that.
是吧？但我不会这样做
I'm gonna do something else because I need to look up this
我会选择用另一种方法，因为我要用
button's index in this card thing so I'm gonna do my for
按钮的索引找到这个 card 里对应的卡片，因此
loop by index. In the card buttons array which I could do
我选择循环索引。要遍历 cardButtons 数组，那我可以
with 0 dot dot less than cardButtons.count, okay.
用 0..<cardButtons.count
Everybody understand this countable range right here, but
大家都理解这个 CountableRange 吧？
I'm not gonna do that either I'm gonna show you another
但是我也不会这么做。我要给你展示另一种方法
way, cardButtons.indicies, okay. cardButton.indicies
用 cardButtons.indices，cardButtons 的索引
Indices is a method in array. Okay, that returs
indices 是数组的一个方法（注：变量）
you a countable range of all the indexes into the array.
给你一个由数组所有索引组成的可数的区间
In fact, if I option click on indices, look at it's type,
事实上如果我们按住 option 点击 indices 看它的类型
Countable range of int. Okay? So,
它是 Int 构成的 CountableRange
that's a kind of a cool way to go through all the indices.
这是一种遍历索引的有趣的方式
And now that I have the Indices,
现在我有了索引
I can say let button equal our cardButtons at that index.
我可以赋值 button 为 cardButtons 在那个索引的按钮
And let the card equal the game's cards
然后 let card = game 里 card 数组
at that index. So this is awesome. I've got the button.
在那个索引的卡片。很好，我得到了那个按钮
I've got the card. I just have to make them match up.
我也得到了卡片。我只有需要将它们同步
So I'm going to say, if the card is faced up,
所以我要判断，如果卡片是面朝上的
then I'm gonna do my face up UI, which is what,
那我就显示正面的 UI，也就是
that's this one. With the white background there.
这一个，白色为底色的这个
So let's do that. Otherwise else, if it's faced down,
让我们就把这段代码移过来。否则朝下的话
I want the orange background thing right here, that there.
那我就用这个橙色底色的这部分代码
And now I don't even need flip card,
现在我们甚至不需要 flipCard 方法了
let's get rid of flip card. Everybody,
让我们把 flipCard 方法删掉。大家
see what I did ther? I just made it so
看到我干了什么吧？我刚才让
that the button matches the card. There's one other thing,
按钮显示了对应的卡片。还有一件事
by the way, cards can be matched. They have is matched.
我们可以配对卡片，有 isMatched 变量
Remember, when we put that in the card, is matched. So
还记得我们 Card 里的 isMatched 吗？
I need to handle that. And the way I'm gonna do that,
我需要处理这个。而我实现的方法是
is I'm gonna make matched cards be clear, so
我要让配对了的卡片透明
that I can't see them. If the card is matched,
这样我就看不见它们了。如果已经配对了
I'm just gonna take it out of the UI by making it background
我就把它们“移出”UI，就通过让背景色
be clear instead of orange, when it's matched and
变为透明，而不是橙色。如果是配对了
face down. If it matches and still faced up, I don't want
而且朝下就这样。如果还是朝上的话，我不想
it to be clear, I want people to see you gotta a match.
让它变透明，我想要让人们看到它们配了对
But once it turns back face down in Match I don't want it
但只要它们配了对翻过去了的话，我不想让它
to show. So the way I'm gonna do that is by doing this,
显示出来。所以我要做的是
card.isMatched. Matched. Question mark.
card.isMatched ? 透明 : 橙色
Either this thing or this. Does everyone know this
配对就透明，否则用橙色。是否大家都知道这个
question mark colon? We have it in C and other languages as
?: 这个三元运算符？在 C 等其他语言里也有这个
well. So here instead of orange I want it to be clear.
所以如果是配对了的话，我不用橙色，改用透明
How do you get clear? Well if you go to any color
我们怎么才能得到透明色？如果你用任何颜色
chooser, if you look at the bottom it says opacity. That's
选择器，然后看最下面的 opacity
how transparent the color is. So that's fully opaque, fully
也就是颜色的不透明度。这便是完全不透明
transparent. In other words, clear. So if the cards match,
这是完全透明。如果卡配对了
I'm gonna make it clear. Now, there's one other error here.
那我就让它们变透明。但这里有个错误
It's this, emoji. Now, this emoji used to be something we
是这个 emoji。本来这个我们是从
grabbed out of this emoji choices based on the index and
用基于索引选中 emojiChoices 里的 emoji 来
all that. I'm gonna postpone implementing that here by,
实现的。我准备等会儿再来实现这个，通过
it's calling a function emoji for card. And
调用函数 emoji(for: card) 来实现
this is gonna be a function I'm gonna do down here.
这会是我在下面实现的一个函数
Let's scroll this up a little bit so
我们向上移动下
you can see it, func emoji for card which is a type card, is
好让你看清。方法 emoji(for card:) 参数是 Card 类型
gonna return an emoji and I'm gonna return question mark for
-> String 会返回一个表情。我返回问号
now. We just returned question mark,
作为我们临时的表情符号
we're gonna deal with taking these emoji choices up here.
我们等会儿会处理上面这个 emojiChoices
We're gonna put these emoji choices and eventually,
我们会用这些 emojiChoices，而最终
we're going to pick one of these emojis. Randomly,
我们会随机选择这里的某个表情
and put it on the card, and actually I have a few more
然后放到卡片上。而事实上我还准备了些
emojis here, little more Halloween emojis.
更多的和万圣节相关的的表情
So we're gonna pick one of these many emojis right here,
所以我们要从这里选某个表情
and put it on the card, but for
让后放到卡片上。但现在
now, I'm just gonna do question marks. Cuz I really
我就用问号代替，因为我
wanna get back to my UI and make sure I haven't broken
想回到 UI 里去确认我没有
anything with all this MVC machinations. Let's go back
因为 MVC 搞砸任何部分。让我们回到
to our model and do something when a card is chosen. So
模型里，然后在选中卡片之后做些处理
all I'm gonna do, eventually we gotta make it match. But
最终我们要进行配对，但我要做的就是
all I'm gonna do now is have the model flip the card over.
我现在要做的就是让模型把牌翻个面
The model's just gonna flip the card over for now, so
现在模型就是把卡翻个面，所以
here we are in chooseCard in our model. And so
我们到模型的 chooseCard 方法里，然后
I'm gonna say, if the card at that index is face up,
判断如果在那个索引的卡牌是面朝上的
then I'm going to set the card at that index to
那我就让那个索引的卡片
be isFaceUp = false, and else, I'm going to make this true.
的 isFaceUp 属性为 false，否则就设为 true
True, so I'm just gonna flip the card over, everyone
所以我这就让卡片翻个面，大家
believe me that that flips the card over? Yes, just gonna,
都认为这能让卡翻面吧？是的，就这样
if it's face up, it goes face down, if it's face down,
如果朝上，就朝下了；否则就是朝下的
face up, so that's it. So now when we run our app,
现在就朝上了，就这样。现在让我们运行我们的程序
there's no emoji, everything's gonna be a question mark, but
没有表情，所有的都会是问号，但是
the card should flip over. And this is the entirety of our
这个卡片会翻个面。这就是我们全部的
UI, here's our controller, that's all there is.
UI 了。这是我们的控制器，就这样了
And here's our model over here, plus we have the card,
然后这是我们的模型，再加上我们的 Card 卡片类
which is just keeping track of isMatched and
用 isMatched 来记录是否配对
doing that identifier. So let's take a look,
和保存 identifer 标识符。那让我们来看看
let's run this, make sure we haven't broken anything.
让我们运行这个，确保我们没有搞砸任何部分
It's always nice when you make MVC and you divide it up,
当你用 MVC 的时候总是可以很好的有分工合作
try to make your model to just do something simple.
不过尽量先让你的模型实现些简单的功能
Just so you can make sure that your MVC is actually working.
来确保你的 MVC 确实是可用的
Here we go, sure enough, it appears to be flipping these
显示出来了，而且确实在把这些
cards over. Okay, excellent, so all we need,
卡片翻面。很好，所以我们需要的
we have two things left to do. One, get emoji on these cards,
就只有这两个了。一个是让表情显示到卡片上
two, make it actually match, make it be a real game.
第二个是让它们配对，实现这个游戏
One thing is a controller thing, the emoji,
显示表情是控制器负责的
another thing is the model. So let's go to our controller
而另一个是模型负责的，所以让我们到控制器
right here, and do this emoji thing. Now in teaching you and
就这里，然后实现显示表情。为了教学目的
showing you this emoji thing, I'm gonna show you and
在实现显示表情的同时，我会展示给你
teach you how to use a dictionary. A dictionary,
并教你如何使用 Dictionary，也就是字典
it's very important to struct,
这是个很重要的结构体
does everybody know what a dictionary is?
大家都知道什么是字典吗？
Like a hash table, it's just a data structure where you can
它就是个哈希表（hash table）这种数据类型，你可以
look something up and get a value for a certain thing.
通过键查找，然后获得某个值
So we're gonna use a dictionary,
所以我们要使用个字典
and our dictionary looks like this, I'm gonna call it emoji,
然后我们的字典是这样的，我把它叫做 emoji
and its type is gonna be a dictionary.
然后它的类型是 Dictionary，字典
Now, dictionary is also a generic type like array, but
注意，字典和数组一样都是泛型，但
you specify the type both of the key, which I'm gonna
你需要同时声明键（key）的类型，这个我会
have be a int, cuz it's gonna be a card identifier, and
用 Int 类型，因为这会是我们卡片的标识符，还要
the value, which is a string, an emoji.
声明值（value）的类型，也就是 String 类型存储表情
So this emoji dictionary, I'm going to look up the card
所以这就是存储表情的字典，我会通过查找卡片
identifiers to get the emoji that goes on that card.
的标识符来获取显示到卡片上的表情
Everybody got that, gotta understand that before we move
大家都明白吧？我往下讲之前大家得明白
on here. So how do I create one of these dictionaries,
那我怎么创建一个字典呢？
exactly the same as I did with array.
其实是和数组一样的
I'm just gonna do open parentheses,
我就用做小括号
close parentheses, which creates an empty dictionary.
右小括号来创建一个空字典
So this is a dictionary of ints mapped to strings, but
这就是个把 Int 映射到 String 的字典，但是
it's empty. Now down here in emoji for
它是空的。在下面 emoji(for: card)
card I'm just gonna look in this dictionary and
里我就在这个字典里查找
get a card. So let's say let chosen emoji equal, and
然后得到卡片（的表情）。 我们赋值 chosenEmoji，选中的表情
here's how you look something up in a dictionary. You say
然后这是你查找字典的语法，就用
the name of the dictionary, open square bracket,
字典的名字，左中括号
the thing you wanna look up. And this had better be an int,
然后查找用的键，这里最好是 Int 类型的
because this is a dictionary that looks up
因为这是个字典，它查找的
ints and gives you back strings, and this is gonna be
是 Int 类型的键，然后返回 String 类型的值
a string. If I alt-click, option-click on this,
这会是个字符串。如果我按住 alt/option 点击它
is that going to be a string? Obviously not, or I wouldn't
它的类型会是 String 吗？肯定不是，否则我就不会
be asking that question. Let's see what this chosen emoji is,
问你们这个问题了。让我们看看 chosenEmoji 是什么类型的
you think it would be a string, oh, what is that? What
你认为它会是 String，但你看看它其实是什么？
type is that, anyone want to venture, I heard it out there
那是什么类型，有谁大胆说一下？我听到了
whispered, an optional. It returns an optional, why is it
悄悄地说那是可选类型，它返回的是可选类型，为什么
returning an optional instead of returning a string? Well of
它不返回 String，而是返回可选类型 Optional 呢？
course, because when we look something up in a dictionary,
当然，因为我们在字典里查找的时候
it might not be in there.
可能它不在里面
The thing we looked up isn't necessarily in there, and
我们查找的键不一定在字典里面
if it's not there, we're gonna get back not set,
如果不在里面，我们就会得到缺省值
the optional not set. If it is in there, we're gonna get
空的可选类型。如果在里面的话，我们就会得到
the optional set, and it's going to be a string as its
有值的可选类型，然后就有个 String 字符串作为
associated value, because of course, a string is what's in
它的关联值。当然了，因为 String 是字典里的
the dictionary. Everyone, understand that looking
值的类型。大家要明白
something up in the dictionary returns an optional.
在字典里查找的时候会返回可选类型
By the way, see this kind of thing,
顺便一提，这个 Dictionary
you know how we have that special array syntax,
大家还记得数组特殊的语法糖吗？
open square bracket, type that's in the array.
中括号里写数组元素类型
We have the same thing for dictionary,
同样字典也有语法糖
it looks like this, open square bracket.
它看起来是这样子的，左中括号
Open square bracket, the key,
左中括号，键的类型
colon, the value type. So that's,
英文冒号，值的类型（，右中括号）。就这样
dictionaries and strings are the most common things we're,
字典和字符串（和数组）是我们最常用的
data structures we're using. So we have special syntax for
数据结构，所以我们有特殊的语法糖
both of them, so that's what declaration of a dictionary.
来定义它们。所以这就是定义字典的方法
That's exactly the same as the thing I just blanked out,
这和我替换掉的是等价的，也就是
dictionary, int, string.
Dictionary
So we could, since we know this returns,
所以我们可以，既然我们知道它返回
this thing is an optional, we could do if let here. But
这个是可选类型，我们可以用 if-let，但是
I'm gonna show you a different way to deal with optional. So
我要给你展示处理可选类型的另一种方法
you know how to do exclamation point, which we definitely
你知道如何用感叹号，但我们这里肯定
don't wanna do here. Because, for example,
不想要这么做，因为，比如
this dictionary starts out empty, so if we do exclamation
这个字典一开始是空的，如果我们用感叹号
point here, it's gonna crash every time, so that's no good.
那每次运行程序就会崩溃，所以这是不行的
And we could do if let, that would be safe, but I'm gonna
我们可以用 if-let，那会很安全，但是我
show you another way that we often deal with something that
要给你展示另一种方法，我们经常用来处理
might be optional. Is that we just check to see,
可能为可选类型的值，那就是我们看
if this thing does not equal nil, then we can exclamation
如果不是 nil 的话，那我们就能用感叹号
point it. I can return this thing exclamation point, and
强制解包它。我可以返回这个加上个感叹号，而且
it's safe because I checked to make sure it's not nil first.
这是安全的，因为我已经检查确保过这不是 nil 了
So that's another way to deal with optionals, just check and
所以这是我们处理可选类型的另一种方法，就判断
make sure it's not nil, and
确保不是 nil 之后
then you can exclamation point it. And then the else,
就用感叹号强制解包。然后否则的话
I guess if we can't find the emoji in the dictionary, we'll
我想如果我们不能在字典里找到这个表情，我们
just return question mark, so we'll kinda give up and
就返回个问号，我们放弃尝试
just show question marks. Now, this code is so common to want
然后就显示个问号。其实这个代码很常见，我们想
to get something that's an optional, and
得到某个可选类型，如果
if it's not, if it's set, then use it. But if it's not set,
它不是空的话，就用它的关联值，否则如果缺省值的话
do some other well-defined thing like this, but you can
我们就使用另一个值，就比如这个问号。所以这个你可以
write this with a special operator, looks like this.
用另一个特殊的运算符，它长这样的
Return, return, this thing right here.
你 return 返回这里的这个
But if it's nil, question mark, question mark,
如果是 nil 的话，就用 ?? 运算符
return a different thing. So this is return this, but
以此返回另一个默认值。这个就是，有值就返回这个，否则
if it's nil return this. Very common,
如果它是 nil 的话就返回这个，非常常见
that's exactly the same code as this, exactly the same. So
这和我们这段代码是等价的，完全一样，所以
we do not need that, everyone got that syntax,
我们不需要那些代码，大家都理解这个语法吧？
it's very common to do this. So we're awesomely looking
实现这个很常见，所以我们顺利地
up the card identifier in our emoji dictionary and
在 emoji 字典里通过 card 的 identifer 标识符查找
returning emoji, hopefully. But we never put anything
然后最好返回了某个表情，但是我们没有放进去
in that dictionary, so it's always gonna return a question
任何东西到那个字典里，所以它总是会返回问号
mark, so how do we put something in the dictionary?
那我们怎么向字典里添加键值对呢？
Well, I'm gonna put them in the dictionary on demand,
我会在有需要的时候再把它们添加到字典里
as they are used. So every time someone asks me for
也就使用它们的时候再放进去。所以每次有人让我
the emoji in a card, I'm gonna check and
提供某张卡的表情，我就去看
see. If this emoji for that card is currently nil,
如果这个卡的 emoji 是 nil 话
then I'm gonna put an emoji in the dictionary for that card.
那我就把选一个表情对应那个按钮放到字典里
So I'm kind of just in time loading up of this dictionary,
我就及时准备好字典
now, how am I gonna do that? I'm going to take one of these
那我要怎么做呢？我需要选这里的一个
at random, and put it in this dictionary, so let's get
随便选一个放到字典里，那我们生成个
a random index. I'm gonna let randomIndex equal something,
随机的索引。我要赋值 randomIndex 为某个随机索引
I'm gonna use this nice Swift function called
我要用 Swift 里的一个很好的函数，叫
arc4random_uniform.
arc4random_uniform 函数
So arc4random_uniform is a pseudo-random number
arc4random_uniform 是个伪随机数
generator, and it generates a random number between 0 and
生成器，它会生成一个随机数，在 0 到
this upper bound. Do you see it says __upper_bound,
这个上限，__upper_bound 之间的随机数
which is an unsigned int, 32 bit integer.
它是以个 UInt32，无符号 32 位整数
It will generate a random number between 0 and that,
它会生成个 0 到那个上限之间的随机数
not inclusive of that number. Which is exactly what I want,
但不包括上限。而这正是我想要的
where the upper bound is, how many things are in this array?
因为那个上限就是这个数组里有多少个元素
Cause I want an index into this array between zero and
因为我想要索引这个数组里在 0 到
however many things are in the array minus one. So here,
数组元素个数减一区间某个地方的元素
the upper bound,
所以这个上限
I'm just gonna say emojiChoices.count.
我就用 emojiChoices.count
Now this is great, but it doesn't work. I get an error,
这很好，但不能直接用，我得到了个错误
and what does the error say? It says cannot convert value
那个错误说什么？它说不能转换
of type int to unexpected argument type unsigned 32 bit
Int 类型为要求的 UInt32 类型
int. arc4random totally works only with unsigned ints.
也就是 arc4random_uniform 只能用无符号整数
Whereas this array's count is an int. Not an unsigned int.
而这个数组的 count 是个 Int 整数，不是无符号的 UInt
And Swift never does automatic type conversion never.
而 Swift 又从来不会自动转换类型
It does never automatically converts from into an unsigned
它从来不会把有符号的 Int 转换为无符号的整数
enter from into a double. You have to explicitly convert it.
也不会转换为 Double；你必须要明确的去转换
And so how do you convert types in Swift?
那在 Swift 里怎么转换类型呢？
This is why I'm showing you this so
这就是为什么我要给你展示这个
that you'll know how to convert types in Swift
这样你就知道怎么在 Swift 里转换类型了
you have to create a new thing and
你想要构造一个新的类型的实例
use the initializer of that new thing to create one. So
那就要用那个类型的构造器来创建
here, I wanna create a UInt32, an unsigned int 32. So I have
所以这里，我要创建个 UInt32，无符号 32 位整数，那我
to call uint32 initializer to create a uint32. Luckily,
就要用 UInt32 的构造器来构造个 UInt32。幸运的是
uint32, which is a struct, by the way, just like Int,
UInt32，顺便一提 UInt32 和 Int 一样是结构体
just like string, just like array, just like dictionary,
就像 String 字符串, Array 数组，Dictionary 字典
just like card. These are all structs.
和 Card 卡片一样都是结构体
It has an initializer that takes an int.
UInt32 正好有个构造器的参数是 Int
Exactly what I want, okay? So I can create a UInt32 by
正是我想要的，所以我构造一个 UInt32
passing it an int emojiChoices.
通过给它 emojiChoices.count 这整数
That's why it's suggesting this fix right here.
这就是它这里建议的解决方案
So I'm gonna do that, fix. You see what it did? UInt32,
所以我就点这个 Fix。看到它干了什么吗？UInt32
open parentheses. The argument to its init is an Int.
左小括号。这个构造器的参数是 Int 类型
It knows how to do that. Now we're not there yet.
它知道如何去转换。但还没完
Because look at the return value here, randomIndex.
因为你看它的返回值，这个 randomIndex
It's also an unsigned integer, and that's no good.
它也是无符号整数了，这不好
Because I want to use it as an index into this array. And
因为我想要用它索引这个数组，而
we know that indexes are not unsigned ints,
我们知道索引不是无符号整数
although they probably should be. They are ints. Okay,
虽然理论上都不是负数，但它们是 Int 类型的
just for mostly for backwards compatibility issues.
应该是为了向后兼容
So I need to convert this whole thing to an int, luckily
所以我需要把它转换为 Int，幸好
int Has an initializer that will take an unsigned int and
Int 有个构造器接受无符号整数
turn it into an int. Okay, so now I have random index here.
然后转换为 Int。好，我现在有了随机的索引
It's an int, it's suitable for indexing into this array.
它是个 Int，可以用来索引数组
It's a random index in there. So to get it, I can say emoji
它是这里的某个随机的索引。那我们就来得到这个表情，我可以用
sub card.identifier. Now to put something in A dictionary.
emoji[card.identifer]。我们要把值放到字典里这个地方
You just say equals and the thing you want. Emoji,
就用 = 后面接要放的值就可以了，也就是
choices, sub, random, index. But I'm not, this is not quite
emojiChoices[randomIndex]。但我并不想，这并不完全
what I'm gonna do. This would work kind of.
是我想做的。这个某种意义上说是可以用的
The only problem with this is, I could get two identifiers
但这个的问题是，我可能会得到两个不同的标识符
that use the same emoji. So instead of just grabbing
它们会使用同一个表情，所以不是从数组里取得
something out here, I'm gonna remove it. Okay, when I use
那个表情，我会移除那个表情。当我用
one of these emojis, I'm going to remove it out of here so
这里的某个表情的时候，我要把它从这里移走
I never use it again. So to do that, I'm going to use
这样我就不会再次使用它了。那要这样的话，我就
a different method in array than subscripting called
不用数组下标，而是用另一个方法，叫做
remove at. And I'm just gonna remove at the random index.
remove(at:)，我就移除在 randomIndex 的表情
So remove at returns the thing it removed. And so it's gonna
remove(at:) 方法会返回移除的元素，所以它会
pick one of these emojis out of here and return it.
移除这里的某个表情，然后返回它
And I'm gonna put it in the dictionary. Everybody got
然后我把它添加到字典里。大家懂了吧？
that? Now there's only one other thing here,
那我们还有要处理的是
which is what if emojiChoices.count is zero.
如果  emojiChoices.count 是零
In other words, what if I've used all the emoji
换句话说，我把所有的表情都用完了
in emojiChoices, I've pulled them all out of there?
我把 emojiChoices 里的全部都移除了怎么办？
That's going to cause a problem. ran, arc4random can't
那会是个问题。arc4random_uniform 不能
take zero because it goes from zero To that number minus 1.
接受 0 作为参数，因为它返回 0 到参数减一之间的数
And it's an unsigned int, so it can't be a minus number.
而且是无符号的整数，所以不能是负数
So we're gonna have to protect against that case.
所以我们要避免这种情况
So I'm gonna say if emojiChoices.count
所以我判断如果 emojiChoices.count
is greater than 0, then I can grab
要是大于零的话，那我就移走
an emoji out of there and use it. But if
并使用里面的某个表情，否则
it's less than that, if it's equal 0 then I can't do this.
如果小于等于零，那我就不这样
And then I'm stuck with question mark down here. By
那就放个问号进去
the way cool thing in Swift, you see how we have back to
顺便说个 Swift 很酷的地方，看到我们这里有
back ifs here. You can put back to back ifs on the same
两个相邻的 if 语句，你可以把两个合并到一行
line by just separating them with comma, okay,
只要用英文逗号分隔 if 的判断就可以了
then you don't need the embedded ifs right there.
这样你就不需要嵌套 if 语句了
And it's nice especially when things are related, like look
在两个 if 语句相关的时候这会显得非常好。你看看
at how this cut line of code reads. If the emoji for
这行代码多么清晰，如果表情里对应
this card identifier is not set and
这个卡片标识符的表情不存在，而且
if we have emojiChoices, then go get one, All right?
我们还有可以选择的表情，那就去获取一个，是吧？
Okay, so let's see if this works. Let's go and run and
好，让我们看看这能不能用。让我们运行然后
see if we're grabbing a random emoji out of there and putting
看我们是否随机从这里选择了个表情然后放
them in one of our four cards. It's pseudorandom by the way,
到我们这四张卡上了。顺便一提这是伪随机数
so you're gonna see that it's not super random.
所以你会看到它并不是很随机
But anyway, all right, so let's look.
但管他呢。好，我们来看一看
Oh yeah, look, we got a random emoji. Oh there's another one,
哦，太好了，我们得到了个随机的表情。哦，这是另一个
little candy, hopefully these two are the same, yay!. Okay
是个糖果。希望这两个是一样的。是的！好
so it's working. Okay it's looking it up by identifier,
我们的程序可以用，它在字典里通过标识符
picking emoji and using that emoji for that identifier
找到表情，然后把那个表情显示到所有那个标识符
on all cards. And hopefully it's random. Let's stop again.
对应的卡牌上。为了验证是不是随机的，让我们再试一次
So we've got the devil guy, and the candy here.
我们这里是魔鬼和糖果
Cross your fingers that pseudo random generator does it,
我们期望伪随机数生成器没问题
let's see. Uh oh, devil guy, oh we got the cat,
我们来看看。哦，这是魔鬼。哦，这是猫
the scared cat or whatever that is, the screaming cat. So
受惊吓的猫一类的，尖叫的猫
we're getting random, this looks like it's working.
所以我们得到的是随机的，它可以用
So we've got our UI fully functional here.
所以我们的 UI 是正常工作的
So the only thing that's left to do now
那我们唯一剩下的就是
is make this thing play Concentration and
让它能实际玩翻牌游戏
that's purely a model thing. So to make
那完全是模型负责的，所以要
that work I'm going to bring up my model. And
实现这个，我们把模型显示出来
I'm only going to put code here in my model.
我只会向模型添加代码
Right now it flips cards over. We'll stop that.
现在它只会把牌翻过来，我们删掉它
Instead we're gonna have it play the game. And
取而代之，我们让它能够玩这个游戏
as part of implementing this, I'm gonna use an optional.
而实现这个的其中之一，我会用到可选类型
Because I know you're all still except for
因为我知道你们所有人，除了
this guy who asked this great question. You're all still not
这个问了个好问题的同学，都还不太
quite sure I understand the optional thing.
理解可选类型
So here's a chance, case where I'm gonna use an optional
所以这就是个我会用可选类型的例子
as part of my Fundamental semantic implementation
用可选类型实现这个方法的基础语义
of this method. So here we go.
那我们开始吧
The first thing I'm gonna do when a card is chosen is
当选中一张卡片之后我要做的第一件是
I'm gonna ignore a card that's already been matched. So
我要忽略已经配对了的卡片，也就是
if you choose a card that's on the board that's already been
如果你选中了桌面上已经
matched to another card, I'm just gonna ignore it. So
配对了的另一张卡的话，我就直接忽略它
I'm gonna say if the card that you chose, card of the index,
所以我用 if 判断如果选中的卡，cards 里在索引 index 的卡
isMatched. Then I'm not gonna do this, so
如果 isMatched，配对了的话，那我就什么也不做
I'm gonna say if it's not Matched then do it,
所以我要判断如果没有配对，那就这样做
if it is Matched then don't do this. This notch,
如果配对了就不这样做。这个感叹号
just like any other language means not, the opposite of
和其他语言一样表示“非”，相反的
this Bool. I'm gonna ignore all Matched cards.
布尔值。我会忽略所有配对了的卡片
Now what am I gonna do? There's three cases here
现在我要干什么呢？现在有三种情况
that can happen. One, no cards are face up. If no cards
可能会发生。第一种，没有卡片面朝上。如果没有卡片
are face up when I choose a card it just flips it over.
面朝上的时候我选了一张卡，那就把它翻过来
That's all it does. Another option is two cards are face
这就是它要做的。另一种可能性是有两张卡朝上
up, either matching or not matching. If that's true,
可能配对可能没配对。如果是这种情况
when I choose another card it needs to flip those cards face
当我再选一张卡的时候，那就需要让那两张卡
down cuz I'm starting a new match now. The third option is
面朝下，因为我要开始新的一轮配对。第三种可能是
there's one card face up and I choose some other card.
有一张卡面朝上，然后我选了另一张卡
Now I need to match. I need to see if they match.
那现在我要尝试配对，看看是否是匹配的
So those are the three options. So I want to
所以就是这三种情况，那么我想要
really keep track of the option where there's one card
记录的情况是有一张卡
face up because that's where I really have to do the work.
面朝上，因为我需要实际地做额外的工作
I have to try and see if they match. So
我需要去尝试看看是否配对了
I'm going to create a var to keep track if there is one and
我要创建一个变量记录是否有一张
only one card face up.
而且仅有一张卡朝上
I'm going to call it indexOfOneAndOnlyFaceUpCard
我把它叫做 indexOfOneAndOnlyFaceUpCard
and of course its an index so its going to be an int.
既然是索引的话，那自然就是 Int 类型了
Now whats the value of the indexOfOneAndOnlyFaceUpCard if
那 indexOfOneAndOnlyFaceUpCard 的值是什么？
no cards are face up? What's the value of that if two
特别是当没有卡朝上的时候？如果两张卡朝上的时候
cards are face up? Ah, interesting.
它的值又是什么？非常有趣
I think this wants to be optional. You see why, because
那我认为这应该定义为可选类型。你是知道原因的，因为
in those cases where there's not one index of a one and
在这两种情况下，我们没有一张且仅有一张朝上的卡
only face card, then this going to be not set,
的索引，因此这就会缺省值
cause there is not one and only face card. So this
因为没有唯一的一张朝上的卡，所以这个
is a great use of optionals right here. So all the time in
是使用可选类型的好地方。所以每当
my game, when there's one and only one face up card, this
我游戏里有一张且仅有一张朝上的卡片，这个
going to tell me the index of it so I can match against it.
就会告诉我它的索引，让我来尝试和它配对
And all the other times this is gonna be nil and
其他的时候，这就是 nil
I'm gonna know I don't have to do any work. I don't have to
我会知道我不需要做任何的工作，我不需要
do any matching because there's no card to match
去尝试配对，因为没有卡片可以用来配对
against. So what does the code look like to use this?
那使用它的代码长什么样？
I'm going to say if I can let a match index,
我判断如果可以让 matchIndex
which is a local variable, equal this index of one and
这个局部变量赋值为 indexOfOneAndOnlyFaceUpCard
only face up card right here, then I have something to
就是这里的这个，那我就有尝试匹配的对象了
match. By the way, I wanna make sure that that match
顺便一提，我要确保那个配对的
index of that thing is not equal to the card you chose.
卡片索引和选择的卡片是不一样的
If there's only, only, if there's one and
如果这里只有
only one face of card and you chose that,
只有那张朝上的卡，然后你再选了它一次
it's going to ignore that. You have to choose a different
我会选择忽视它。你必须要选张不同的
card. This inside this curly brace right here is, you know,
卡。在这对大括号里面，你知道的
check if cards match. And then outside right here is what?
判断卡片是否配对。而在外面呢？
Either no cards or
要么没有卡片
two cards are face up, so I can't match. So
要么有两张卡片朝上，我没法配对，所以
in that case I'm gonna need to turn the cards face down and
那种情况我就要把牌翻到背面
have the card you chose be the only face up card.
然后让你选的卡成为唯一朝上的卡
And then I'm gonna set it to be the index of the one and
然后赋值给 indexOfOneAndOnlyFaceUpCard
only face up card.
让它成为仅有的那张朝上的卡
Let's do all that. Let's check if the cards match,
让我们开始做吧。首先检查是否卡片是配对的
really easy if the cards match index its identifier
这是非常简单的，如果要配对的卡片的标识符
equals the cards of the index you chose. This is choose card
等于你选择的卡片的标识符，index 就是 chooseCard 这个函数里面的
at index. If they match, then I'm gonna mark them matched.
实参。如果它们是匹配的，那么我将要标记它们为成功匹配
Cards of match index is matched and
待匹配的卡片修改为成功匹配
the cards that you chose is matched.
你选择的卡片也是成功匹配
So I matched them, excellent. Now, even if they don't match,
所以如果我匹配了它们，是很棒的。现在，如果它们不匹配的话
what happens when I chose that second card right there? Two
当我选择第二张卡片的时候，这里要发生什么事情呢？
things happen, one, I gotta flip up the card you chose.
两件事情发生，首先，我将要把你选择的卡片翻到正面
Cuz you chose a card that was face down, right there so
因为你选择的那张卡片之前是朝下的，在这里
I'm gonna say cards sub index is...
我会写成，那张卡片的
Is face up is true, cuz you chose a card,
isFaceUp 属性为 ture，因为你选择了它
I'm flipping it up of course. And number two and
当然我就会把它翻到正面。其次
most importantly the index of the one and only face up card
也是最重要的是，indexOfOneAndOnlyFaceUpCard
equals nil. Because now there are two matched cards,
赋值为 nil，因为现在这里有了两张匹配的卡片
two, matched or unmatched, two face up cards. So
不，是两张匹配或者不匹配的，正面朝上的卡片
there is not a one and only face up card. So it's nil, so
所以并不是有且只有一张朝上的卡片。所以它的值为 nil
it's perfectly legal to to set an optional to nil. So that's
并且设定一个可选类型的值为 nil 是完全有效的
what we do if the cards match, that's all we have to do.
那就是我们要做的如果卡片匹配的逻辑，必须去做的全部的逻辑
Now here we either have no cards or
在这里我们要么没有卡片朝上
two cards are face up. In this case,
或者有两张卡片朝上。在这种情况下
I'm gonna turn all cards face down. Now they might already
我会先把所有卡片翻到背面。它们可能已经
all be face down but so it's a little of wasted work.
全部翻到背面，这只不过这做了一点点多余的工作
I'm just gonna go for flip down index in, what?
我要写 for flipDownIndex in
In my cards' indices. Cards.indices,
cards.indices。Cards 的 indices 属性
that's all, that's a countable range. Remember,
那是一个 CountableRange，记得么
this is a countable range right here, of all the indexes
在这里它是一个 CountableRange，范围是
of the flip, in my cards. So for each one of the cards,
我所有的卡片。所以对于每一张卡片
I'm gonna say card at the flipDownIndex.isFaceup= false.
我会写 位于 flipDownIndex 的那张卡片的 isFaceUp 属性为 false
I just turned every single card on the board face down.
我仅仅是把桌面上每一张卡片都翻到了背面
But now you've chose a card, so I'm gonna turn that card
但是既然你已经选择了一张卡片，我就要把你选择的那张
that you just chose back to face up. .isFaceUp = true and
翻到正面，isFaceUp 设为 true
of course since I just turned all the cards face down and
当然因为我刚刚把所有的卡片都翻到了背面
turned one card faced up, what is the index of the one and
而且只把一张卡片翻到了正面，那么 indexOfOneAndOnlyFaceUp 的
only face up card? It's this index. I just turned it
那张卡片的值是什么呢？它就是这个 index。我刚刚
faced up so, by definition it's the only card.
把它翻到正面，所以通过定义，它就是唯一的卡片
And this ladies and gentlemen what do we got here? Oh this
我们现在得到了什么？噢，这里应该是
is cardsindex.identifier got to compare the identifiers.
cards[index].identifier 来对比那个 identifier
This is the entirety of the concentration game logic,
这就是翻牌游戏整个的逻辑
that's it, that's all that's necessary.
这就是了，这是所有必要的逻辑
And can you see how having this optional
你能够看出如何通过这里的可选类型
right here kinda made it very simple and straightforward?
令整个逻辑十分的简洁和直白么？
Because it's easy for me to track the card I want it to
因为对我来说，定位我想要的那张要匹配的卡片一直是
match against all the time. And it was easy for
非常简单的。并且对我来说
me to tell whether there was one to match it against or
判断出这里是否有一张要匹配的卡片也是简单的
not, all contained in one little variable that had a lot
所有的逻辑都包含在一个小小的变量里面
of information it it. That's it, let's go see if our
它有着太多的信息。这就是了它了。让我们看看是否
app is working. It should just work, I mean once you make
应用可以运行。它应该可以运行，我的意思是一旦你实现好了
the model, do what its suppose to do the UI doesn't care.
模型，实现了它应该做的事情， UI 并不会在意
It's just presenting what the model has, so
它只是展示模型的内容，所以
it should just work. So let's try it here. All right,
它应该可以正常运行。让我们试一下
we've got this, the cat, oh, there's the screaming face.
好的，我们得到了这个，那只猫，喔，那里有一张尖叫的脸
Now what should happen when I click another card?
现在如果我点击另一张卡片应该发生什么？
It should flip them all face down.
它应该把所有的卡片都翻到背面
It didn't match, this didn't match, so
因为它并没有匹配，没有匹配，所以
it should flip these all face down. Turn the new one face
所以它应该把所有的卡片都翻到背面。把新的卡片
up, and now that optional's gonna be set. So
翻到正面，然后那个可选类型将会有值
let's try it. Sure enough it turned them all face down and
让我们试一下。当然它把它们都翻到了背面
turned this one up. Now what happens if I choose a match?
然后把它翻到了正面。现在如果我选择一张能够匹配的卡片会发生什么？
Now they've both been marked matched. Now the next time I
现在它们全部都会被标记为成功匹配。下一次
click on a card, it's gonna turn them face down.
我点击一张卡片的时候，它们就会翻到背面
They're gonna be both face down and matched, so
它们将会全部翻到背面并且成功匹配，所以这里会有
they're gonna get a clear background and
一个透明的背景
we're not gonna be able to see them, watch.
我们将不会再看到它们，看
Now that the game is done. Now in your homework you're gonna
现在这个游戏就完成了。在你们的家庭作业里，你们将要
add a new game button which is what you would press at this
添加一个按钮，当你按下的时候会开始一局新的游戏
point. I won't play another game. Last thing
我不会再玩另一场游戏。最后一件事情
to do here and we have time luckily is to add more cards.
要做的，幸运的是我们有时间添加更多的卡片
Because four cards is kind of a boring game so
四张卡片的游戏是有点无聊的
let's go here to our UI. We've got our UI. I made my
让我们去到我们的 UI。我们得到了我们的 UI
buttons here be 80 points wide which I kinda recommend you do
我让这里的按钮变成 80 像素点宽，我推荐
for your homework. Because 80 points wide,
你们的家庭作业也这么多。因为 80 像素点宽
they fit really nice four across okay? Let's put them
可以非常好的符合四张卡片的排布，好么？让我们
up,now I'm gonna use the little blue lines to help me
把它们放上去，我会利用这条蓝线
place them here. If you use the blue lines look,
帮我放置它们。如果你使用这些蓝线，看
four of them at 80 points each fit right across. And
四个 80 像素点的恰好横向排布
then I'm gonna make some more. I'm gonna select them and
我还要做更多事情。我要选中它们
copy paste. Use the blinds, the blue lines paste some
然后复制粘贴。利用这些像百叶一样的东西，这些蓝线
more, let's have 12 of them here.
复制了更多，现在我们有了12张卡片
And I need to make sure that everything is connected so
我需要确定每个都连接上了
let's look at like touch card. Hey, they're all sending touch
让我们可以看一下 touchCard。它们都会发送 touchCard 消息
card, great. How about card buttons? When we copy and
棒极了。至于 cardButtons 呢？当我们复制粘贴的时候
paste it doesn't put them into that card buttons outlet
它并不会把它们放到 cardButtons 出口数组里
array of buttons, so we need to do that.
所以我们需要做这个
So I'm just going to do ctrl+drag.
我要按住 Ctrl，拖拽
Now if you really had 12 cards or more than 12 cards, in real
如果你真的有12张或更多的卡牌，在实际操作中
life you'd probably wouldn't use an outlet collection.
你可能不会使用一个 出口集合
You would probably go in your code and go find all these
你可能会去到你的代码里面，发现所有的事情
things, you could probably do it all in one or
事实上，你可能会用一到两行代码
two lines of code actually. But you really haven't learned
搞定全部的事情。但是你还没有
enough from me about how the view hierarchy works and
从我这里学到足够多的关于视图嵌套是如何工作的，和有关的
all these things to do that. So for your homework
全部事情。所以对于你的家庭作业来说
you're just gonna have to be doing some control dragging
你将做一些拖拽的控制，就像我做的
like I am to hook them all up. They're all hooked up,
把它们关联起来。它们都关联好了
they're all hooked up. Our flip count label's hooked up.
都关联好了。我们的 flipCount 标签已经关联好了
Our UI looks all set. I haven't changed anything but
我们的 UI 看起来都设置完成了。除了那个我还没改动任何东西
that. I didn't go change any code anywhere. Okay, I just
我并没有改动任何的代码。好的，我仅仅
put more buttons in my UI. And because I built this nice,
在我的 UI 里放置了更多的按钮。并且正因为我搭建了这个好的
flexible UI that can really handle any number of cards,
灵活的界面，以至于它真的可以应付任何数量的卡片
this should just work. Here we go,
它应该可以工作。让我们开始
let's try. We got candy, apple,
试一下，我们得到了糖果，苹果
no match. I have bat, no match.
没有匹配。我得到了蝙蝠，没有匹配
How about pumpkin? Oh candy I think I remember candy,
南瓜如何？噢！糖果，我认为我记得糖果的位置
was it maybe right here? Yes,
它是在这里么？是的
woo ho we found the candy it was right next to each other.
哇，我们找到了糖果，它们就在彼此的旁边
Let's try something else. Okay how about this one, apple,
让我们试试其他东西。这个怎么样，苹果
no.,no match. Oh, apple oh, this is a pretty easy game.
没有匹配。噢，苹果，噢，这是一个相当简单的游戏
I don't know why everyone thinks Concentration's so
我不知道为什么每个人都认为翻牌游戏很
hard, look at that. Okay,
困难，看那个啊。好的
all done. Okay so why is this game so easy?
所有都完成了。所以为什么这个游戏如此简单呢？
Well, because you guys have not done your homework yet,
因为你们还没有完成家庭作业
which if you remember back here Concentration we have
如果你记得在 Concentration 文件里，我们还有
this little to do. To do by the way is a special comment.
这点小事情要做。顺便说一下，ToDo 是一个特殊的注释
If you look at the top line of your thing right here, which
如果你看向 Xcode 顶部那里，它
shows you which file you're choosing. It also shows you
展示给你正在选中的文件是哪一个。它还展示给你
within that file with all your methods and properties and
这个文件里所有的方法和属性以及
if you put //to do, it shows up. See shuffle the cards.
如果你写了一个 ToDo 的注释，它也会出现。看 shuffle the cards
All right, see you on Monday. >> For
好的，我们周一再见
more, please visit us at Stanford.edu.
>> 更多课程详见 stanford.edu
