苹果公司“面向协议和价值的编程” WWDC17演讲摘要

这是WWDC 2017演讲的简短表格摘要,仅重点说明要点和关键示例:

UIKit应用程序中面向协议和价值的编程– WWDC 2016 –视频– Apple Developer

在去年的面向协议的编程和具有值类型的会议上构建更好的应用程序的基础上,今年的……

developer.apple.com

您可以从WWDC视频中下载完整的示例代码:

https://developer.apple.com/library/content/LucidDreams/Introduction/Intro.html#//apple_ref/doc/uid/TP40017334

本地推理-关于功能的独立思考。 您不必了解应用程序的整个上下文即可维护各个功能。 您应该只能够应用局部推理来理解各个功能。

MVC:模型存储您的数据。 视图将显示它。 控制器在两者之​​间进行协调。

  • 使用Structs之类的Value类型与Classs之类的引用类型相比,可以促进局部推理,简化关系,并使代码更具可测试性。
  • 结构在其所有成员上赋予值语义和独立存储。
  • 在模型层中使用值类型是标准且无争议的。 但是,一旦您开始在其他地方(例如控制器)使用值类型,人们通常会犹豫不决,或者不知道该怎么做。
  • 例如,子类化一个tableview单元是有限的,因为您不能在不是单元格的其他视图中重用该代码。
  • 代替使用UITableViewCell子类,我们可以将其更改为结构并实现layout(in rect:CGRect)方法:
  • 然后,我们更新单元格子类以在上述布局结构中调用图形调用。 现在,我们还可以从其他区域调用此绘图代码,而不仅仅是表格视图单元格:
  • 通过为布局代码提供样本以进行内部布局,然后验证生成的框架,也可以更轻松地对布局代码进行单元测试。 我们不必考虑该视图使用或替代的其他特定视图功能。
  • 现在,我们可以更改布局对象以使用协议,而不是引用UIViews。 这样我们就可以将布局逻辑也应用于其他类型的对象(例如Sprite Kit对象),而不仅仅是UIView子类。 下面我们还使用“ Retroactive Modeling”使UIView和SKNode都符合我们的布局协议:
  • 协议是通过继承实现多态性的绝佳解决方案,因为您可以将相同的额外行为应用于任何不相关的类型。
  • 当前,“内容”和“装饰”都可以是任何类型的对象,并且它们不必是同一类型。 如果我们想将两个约束为相同类型,可以使用泛型通过在结构上指定所需的类型来实现。 (现在,内容和装饰都必须与传入的Child类型相同):
  • 使用泛型还可以为编译器提供有关代码正在执行的操作的更多信息,并允许我们进一步优化代码以提高性能。
  • 继承牺牲了“局部推理”,因为您必须考虑父类在做什么,它如何工作,将要更改或覆盖的内容以及如何使其与父行为一起工作。 如果要继承系统对象,那么这些对象也将非常复杂。 它使您的思维在父母和班级之间跳来跳去,系统变得更难以理解。
  • 合成意味着将许多较小的片段组合在一起,以制成较大的片段。
  • 类很昂贵,会导致堆分配。 使用类来构成视图非常浪费。
  • 结构更便宜,并且具有有价值的语义(因此具有更好的封装性)。 没有人可以修改您的副本。
  • 您可以使用以下结构为不同的布局类编写视图组成代码:
  • 但是,仍然存在更好的方法,只需简单地使布局结构也符合布局协议即可。 (从它们两个中都删除冗余方法布局):
  • 现在,我们的视图和布局都使用相同的Layout方法,将自己定位在给定的rect中是他们的责任。
  • 合成使我们能够以声明的方式构建高级布局:
  • 如果我们希望布局以正确的顺序相互嵌入视图(层叠),则每个布局都需要以正确的顺序将其子级返回给其所有者。 因此,我们可以在内容上实现get方法,并让每个实现者返回其所有视图。 relatedtype属性可确保内容视图具有相同的同类类型:
  • 现在,我们可以创建合成布局,这些布局在其下方使用特定类型的节点:
  • 但是,对于视图和节点,我们仍然有单独的布局。 理想情况下,我们不想使用这种单独的布局。 因此,除了上述内容之外,我们还可以采用Child的Content属性,并在单个布局结构中使用该属性,以指定我们将拥有的子节点类型。 因此,现在我们不必为不同类型的子对象复制所有通用的样板布局逻辑:
  • 目前,如果孩子们都拥有相同的类型,那么我们的布局就可以正常工作。 但是,如果我们还想在其他布局中组合其他布局和视图,如下所示:
  • 因此,我们可以定义一个额外的约束条件,说我们的子类型具有相同的内容。 (我们的孩子的内容必须是相同的类型)

这是我们完成的布局协议:

现在,我们的单元测试也可以更改为不使用UIViews,而使用Layout协议。 现在,即使我们的测试也与UIView完全隔离,而是使用了我们在布局协议中定义的自定义布局逻辑。

假设我们要在视图控制器中使用“返回”或撤消功能(摇动撤消)。 如果我们只是在模型中保留不同的结构,则必须为每个单独的模型结构独立实现撤消逻辑,从而增加了开销。 相反,我们可以制作一个单一的模型结构,并组成其他模型作为其一部分:

撤消堆栈的传统实现如下所示:

您可以看到我们正在手动将模型中的更改与视图中的更改匹配。 因此,这会产生错误的空间,因为随着事情的变化,很容易忘记这样做。 这会导致诸如“内部不一致异常”之类的错误。 第1节中的行数无效。”

通过这种传统实现,我们对视图控制器中分散在许多地方的状态进行了很小的更改,很容易忘记或弄乱某些东西。 相反,我们可以做的就是将每个更改隔离到一个新的Struct中。 然后只需遍历堆栈上的不同结构即可,每个结构都代表一个状态:

我们仍然需要更新UI。 现在,视图控制器实现了modelDidChange方法,该方法知道如何基于模型中的确切更改来更新UI。 我们还向撤消管理器注册了一个回调,以通知模型何时更改:

这种方法的优点是:更好的局部推理,值语义,动作顺序的独立性以及管理状态的单个位置。

管理视图控制器状态

例如,我们在视图控制器中可以有3种状态:查看梦,选择梦和共享梦。 我们在视图控制器中使用一些属性来跟踪所使用的状态。 但是添加的功能越多,就越容易失去控制,并且添加的变量太多:

在上面的示例中,这3个属性是互斥的。 每个仅以一种特定状态使用。 因此,当您设置一个时,必须清除另外两个,并且这样做容易出错,并且不能用于将来。 此外,随着复杂性的增加和引入更多功能,它倾向于变得难以维护。

枚举可用于保证这些值互斥。 现在,我们可以将它们分别存储在状态枚举中,而不必将它们直接存储在视图控制器中,而可以将它们分别存储在状态枚举中:

这是此视频中讨论的由Value类型驱动的MVC设计的高级摘要:

以及该图的更详尽的版本,包括示例代码中包含的更多功能:

总体主题:

  • 通过组合而不是继承进行定制
  • 将协议用于通用可重用代码。
  • 小型可重用的组件,易于推理和测试。
  • 利用价值语义。 如果您的值包含其他属性值,则较大的值也具有值语义。
  • 本地推理是一种通用技术,并不特定于UI编程,快速或任何快速概念。 这是您在以后的所有设计中都应该考虑的一种全新的编程思维方式。 值类型是一个非常重要的方面,它可以为您的代码启用本地推理。

您可以从WWDC视频中下载完整的示例代码:

https://developer.apple.com/library/content/LucidDreams/Introduction/Intro.html#//apple_ref/doc/uid/TP40017334