CS193P:使用Swift Lecture 2 Notes开发iOS应用程序

什么是MVC?

  • MVC基本上不适合我们将应用程序分为3个不同的阵营,即Model,ViewController
  • 模型与您的应用程序做什么有关。 它与应用程序的显示或在屏幕上绘制的方式无关。
  • 对于计算器应用程序, 模型可能是进行计算的部分。
  • 控制器模型的显示方式或在屏幕上的显示方式有关(UI逻辑)。
  • 视图是您的控制器的奴才。 这些是Controller用来在屏幕上放置诸如按钮,标签,表格等之类的东西以显示Model中的内容以及从用户那里获取输入以更新Model的东西。

知道如何将代码分为这三个阵营是一回事,但难题的关键在于它们之间的通信。 也就是说,什么是允许的,什么是不允许的。

  • Controller必须完全控制Model ,并且可以随时与它对话。 它需要完全访问权限,因为它的工作实际上是向用户展示此模型中的内容,并根据用户输入来更新模型。
  • Controller需要能够使用其奴才,但是它想显示Model
  • 在大多数情况下, 控制器视图之间的连接是通过插座进行的 。 在Xcode中,它显示为@IBOutlet 。 例如,我们在计算器中用于显示标签控制器用来显示Model中执行的数字和计算结果的插座。
  • 模型是独立于UI的,而View是完全UI的。 他们绝对不应该互相交谈。

视图可以与控制器对话吗? 是的,没有。

  • View的问题在于其中的所有奴才都有通用对象,例如UIButtonUILabel 。 它们是由Apple几年前创建的,对您正在构建的应用程序(在本例中为计算器)一无所知。 但是,有时需要将UI中的更改传达给Controller 。 然后, ViewController的通信方式必须“盲目”和“结构化”。
  • 沟通是盲目的 ,因为视图中的对象不知道它们正在与哪些类进行交谈。 例如,按钮对CalculatorViewController一无所知。
  • 之所以构成这种通信,是因为由于不了解对象的两端,因此它们必须以定义良好的预定义方式进行通信。

视图可以与Controller进行通信的结构化方式有哪些?

  • Target-Action:这非常简单,因为Controller可以通过定义方法将目标挂起。 @IBAction。 然后,当View要与Controller对话时, View会调用此方法 制作touchDigit函数时,我们在计算器应用程序中使用了“ 目标:操作 ”通信方式,因为我们希望按下按钮以调用计算器中的方法并告诉我们按下了哪个按钮。

有时, 视图需要传达比“被感动”更复杂的东西。 例如,作为通用View仆从的ScrollView可能需要告诉Controller某人开始滚动,或指示某人放大了特定的缩放比例。 也就是说,它想通知控制器,因为控制器可能希望对此做出反应。 放大或缩小时, 模型可能会更改。 此外,也许ScrollView需要确保做某事是否可以。 可能需要问控制器一个问题:“我现在应该允许垂直滚动吗?”。 我们有很多消息涉及到诸如MinsWillDid之类的小词 ,这些小仆们想问控制器。 这种沟通形式是通过我们所谓的代表来完成的

  • 委托在这里是一个恰当的用语,因为View的仆从基本上将某些特殊职责委托给了Controller
  • 协议并不真正知道该类,但它确保与我们进行通信的对象将响应一组特定的方法或属性。 然后,控制器注册以侦听这些方法。 我们将了解有关此委托模式的更多信息,所以现在不要担心它是否有点混乱。
  • 视图和控制器之间存在一种特殊的通信类型,因为视图不能也不应该拥有它们显示的数据。 例如,假设我们有一台iPod Touch,上面有数千首歌曲。 我们是否应该让基本只在列表中显示我的数据的通用tableView成为我所有音乐的所有者? 从性能的角度来看,这将是疯狂的,更不用说同步问题了。 因此,如果View不能拥有数据,我们该怎么办?
  • 好吧,View基本上将使用另一个协议,称为dataSource,但是这次的问题与应该,将会和确实存在的问题不同。 它将询问诸如以下问题:此数据中有多少项? 给我这个位置的项目或索引等。因此,每次说一个tableView需要数据时,它都会询问Controller,后者反过来几乎总是转过来询问Model。 这样可以进行一些不错的优化。 由于在我们的屏幕范围内,tableView只能显示大约10个数据项,因此在我们手机上的数千种音乐中,它仅需要10个数据项。 然后,当我们滚动时,它会不断询问更多数据,仅保留当前显示的数据。

与控制器通讯的模型又如何呢?

  • 因此,模型无法直接与我们的Controller通信,但是如果Model中发生更改,会发生什么情况,我们是否不想更新Controller,以便此更改可以反映在View中。 例如,假设我删除了Model中的其中一首歌曲,那么Controller应该知道,以便该歌曲不会显示在tableView中。
  • 我们建立机制的机制是创建一种类似于广播电台的机制。 然后播放诸如“嘿,关于我的某些改变!”之类的信息。 然后,控制器将其调谐到此广播电台,当它听到广播时,询问模型,因为控制器与模型之间的通信是一条不错的绿线,所以发生了什么变化。
  • 现在,当我们构建应用程序时,我们不仅在构建一个巨型MVC。 我们可以拥有一系列彼此通信的MVC,并且彼此之间通过View连接的MVC。
  • 例如,假设我们正在构建一个日历应用程序。
  • 这些不同的视图中的每一个都是三个不同的MVC,但是它们的视图相互连接,以使Year MVC连接到Month MVC,Month MVC连接到Day MVC。
  • 请注意,让不同的MVC共享同一模型,并使模型与其他模型进行通信是完全可以的。

我们不想要的是:

目前,这已经为MVC做好了准备。 让我们重新开始创建计算器应用程序,使其适合或符合我们刚刚学习的MVC设计范例。

  • 回想一下我们在计算器上离开的地方,它是在控制器中执行运算或计算的。 由于这些操作与UI无关,因此我们应将这些类型的功能放入Model类中。 创建一个新的Swift文件,并将其命名为CalculatorBrain。
 进口基金会 
  • 由于这将成为我们的模型类,因此我们导入Foundation,而不是UIKit。
  • 如果在导航器中查看,就会看到我们有一个Model: CalculatorBrain ,View: Main.storyboard和Controller: ViewController。
  • 现在,我们需要考虑我们的模型及其公共API是什么。 API代表应用程序编程接口 。 API是用于构建应用程序软件的一组子例程,定义,协议和工具。

应用程序编程接口–维基百科
在计算机程序设计中,应用程序编程接口(API)是一组子例程定义,协议,以及… en.wikipedia.org

  • 因此,我们需要仔细考虑我们的模型允许其他人(例如Controller)执行的操作。
  • 我们可以选择创建一个类,但是可以选择使用结构数据结构作为计算器的模型。
  • 在Swift中,Structs比其他语言功能强大得多。 实际上,到目前为止,我们所看到的大多数数据结构(如StringDouble,Array,Dictionary等)都在实现为Structs的内部。
  • Swift中的结构和类之间有两个主要区别。 类具有继承,而结构则没有。 因此,如果您要构建一个可能需要是子类才能扩展的对象,那么您可能希望使其成为一个类。 另一个区别是类存在于堆中,并且我们有指向它们的指针。 结构不存在于堆中,并且通过复制将它们传递出去。 即,类通过引用传递,而结构通过值传递。 这是一个很大的差异。
  • 可能有人认为,由于这些值是通过值传递的,因此将它们复制到周围的效率非常低,但是Swift编译器为此进行了优化,并且这些值类型基本上是写时复制的

写时复制–维基百科
写入时复制(COW),有时也称为隐式共享或屏蔽,是一种资源管理技术,用于… en.wikipedia.org

  • 回到我们的公共API的功能问题。 我们知道某种方式我们将需要执行操作或计算,因此我们将为此提供一个功能。
  func performOperation(_符号:字符串){ 
  } 
  • 我们将一些数学符号作为我们正在执行的操作。
  • 现在,如果要执行操作,则必须要执行操作数,因此我们需要另一个函数来设置操作数。 让我们将整个计算器与Doubles一起使用,因此让操作数为Double类型。
  func setOperand(_操作数:Double){ 
  } 
  • 请注意,我们使用_作为我们的外部参数,因为在调用此函数时我们不希望像这样:
  setOperand(operand:5.0) 

这样称呼它更有意义

  setOperand(5.0) 
  • 前者相当冗长,但是从后者中读取得更简洁,从函数名称中可以很明显地看出5.0是我们的操作数。
  • 考虑一下,我们还需要获得这些操作的结果。 以类似Swift的方式,让我们创建一个计算属性以获取结果。
  var结果:Double { 
  } 
  • 有人认为我们需要担心的是,我们不希望其他任何事情影响我们的结果。 我们如何使它只读? 好吧,就像currentTitle一样,在按钮上我们可以使其设为get
  var结果:Double { 
得到{

}
}
  • 如果我们考虑一下计算器的内部结构,就会知道计算器在计算时会累积其答案,因此让我们创建一个捕获此运行结果的变量。 其他人应该无法访问此属性,因此请将其设为私有。
 私人无功累加器:双 
  • 请注意,即使我们尚未初始化变量,也不会像类一样收到没有初始化错误消息。 这是因为Structs自动获得一个初始化器,该初始化器将初始化其所有未初始化的变量。
  • 让我们考虑一下该累加器。 当用户首次启动计算器时,累加器将处于“未设置”状态,因此使其成为可选的Double是有意义的。
 私人var累加器:双倍? 
  • 如果我们有此内部累加器,我们如何使用它来实现我们的功能?
  • setOperand基本上只是将累加器设置为操作数,因为当您设置一个新的操作数时,它将替换我们累加器中的所有内容。 为了得到结果,我们只返回累加器。 我们确实有一些错误。 就是说“自我”,这意味着我们的CalculatorBrain是不可变的,并要求我们通过在函数的开头添加mutating来对其进行修复。
  • 这带来了结构和类之间的另一个区别。 每当我们有改变结构本身值的方法时,就必须将该方法标记为变异。
  • 我们确实有另一个错误。 这是因为结果是Double,而累加器是可选的。 那么我们是否应该像这样解开包装呢?
 返回累加器! 
  • 绝对不! 在许多情况下,我们的累加器将处于未设置状态,展开未设置的可选设置将使程序崩溃。 计算器首次启动时,累加器将处于“未设置”状态。 另外,当我们执行5 * 3运算时,当我们输入5 *时,累加器将保持未设置状态,直到达到3,然后将其设置为Set并提供结果。
  • 我们还应该使result为Optional,因为如果有人在我们可以评估操作之前要求答案,我们就不应该返回结果。 例如,如果用户要求5 *,那么我们将无法为他们提供结果。
  var结果:加倍?  { 
得到{
返回累加器
}
}