yofer张耀琦的专栏

数学之法,世界之道

Cocoa设计模式(iOS常用设计模式) Cocoa Design Patterns

Cocoa设计模式

Cocoa环境的许多架构和机制都能够有效地使用设计模式:抽象设计可以解决特定环境中的重复问题。 本文描述了Cocoa中设计模式的主要实现,主要关注模型(Model) - 视图(View) - 控制器(Controller)和对象建模。 本章的主要目的是让您更深入地了解Cocoa的设计模式,并鼓励您在自己的软件项目中利用这些模式。

什么是设计模式?

设计模式是设计的模板,它可以在特定的上下文中解决一般的、重复出现的问题。它是一种抽象工具,在建筑、工程和软件开发等领域都很有用。下面的部分总结了设计模式是什么,解释了为什么它们对于面向对象设计很重要,并讨论了一个示例设计模式。

一个问题的解决方案

作为开发人员,您可能已经熟悉面向对象编程中的设计模式概念。 他们首先由Erich Gamma,Richard Helm,Ralph Johnson和John Vlissides(通常被称为“四人帮”)设计模式:可重用面向对象软件的元素进行了权威性的描述和编目。 这本书最初发表于1994年,不久之后还有其他书籍和文章,进一步探讨和阐述了面向对象系统中的设计模式。

设计模式的简洁定义是“对上下文中的问题的解决方案”。让我们通过反向解释这个短语来解析这个问题。 上下文是该模式适用的反复出现的情况。 问题是你在这个上下文中试图达到的目标,以及上下文所带来的任何约束。 解决方案就是你所追求的目标:通用的上下文设计,达到目标并解决约束。

设计模式将具体设计结构的关键方面抽象出来,这已经被证明是有效的。 该模式有一个名称,并标识参与该模式的类和对象及其职责和协作。 它还列出了后果(成本和收益)以及该模式可以应用的情况。 设计模式是特定设计的一种模板或指南; 从某种意义上讲,具体的设计就是一种模式的“实例化”。 设计模式并不是绝对的。 在如何应用它们方面有一定的灵活性,通常诸如编程语言和现有体系结构的东西可以决定如何应用模式。

设计的几个主题或原则影响设计模式。 这些 设计原则是构建面向对象系统的经验法则,如“封装不同系统结构的各个方面”,“编程是为了接口而不是实现”,它们表达了重要的见解。 例如,如果您隔离系统中各部分并将其封装起来,那么它们可以独立于系统的其他部分而变化,特别是如果您为它们定义了与实现细节无关的接口。 您稍后可以更改或扩展这些可变部分,而不会影响系统的其他部分。 因此,您可以消除依赖性并减少部件之间的耦合,从而使系统变得更加灵活和易于更改。

在编写软件时,设计模式是一个重要的考虑因素。 如果您在程序设计中找到,适应和使用模式,那么程序(包括它所包含的对象和类)将更具可重用性,可扩展性和更容易随着未来需求的变化而改变。 而且,基于设计模式的程序通常比不考虑设计模式的程序更加优雅和高效,因为它们只需要更少的代码就能实现相同的目标。

举例:命令模式(Command Pattern)

“四人帮”的大部分书籍都是由一个设计模式目录组成的。 它按范围(类或对象)和目的(创建,结构或行为)对目录中的模式进行分类。 目录中的每个条目讨论设计模式的意图,动机,适用性,结构,参与者,合作,后果和实现。 其中一个条目是 命令模式(一种对象行为模式)。

本书陈述Command模式的意图是“将请求封装为一个对象,从而允许您使用不同的请求参数化客户端,排队或记录请求,并支持可撤销操作”。模式将发送消息的对象与接收和评估这些消息的对象分隔开。 消息的发起者(客户端)通过将特定接收者上的一个或多个动作绑定在一起来封装请求。 封装的消息可以在对象之间传递,放置在队列中或以其他方式存储以用于稍后的调用,并且动态地修改以改变接收器或消息参数。 下图显示了该模式的结构图。

对于一个熟悉Cocoa的开发者来说,命令模式的这个简短的概述可能会敲响一个钟声。 该模式完美地描述了Foundation框架中的一个类,其目的是封装消息: NSInvocation。 正如该模式的意图状态,其目的之一是使操作可撤销。 调用对象用于Cocoa设计中 撤销管理以及分布式对象,这是一个进程间通信的体系结构。 命令模式也描述(虽然不太完美)Cocoa的target-action机制,其中用户界面控制对象封装了用户激活时发送的消息的目标和动作。

在其框架类,语言和runtime,Cocoa已经为您实现了很多编目设计模式。 (这些实现在下面有描述。)通过使用设计模式的“现成”适应性,可以满足您的许多开发需求。 或者你可以根据你的问题,它的上下文来需要一个你自己的全新的基于模式的设计。 重要的是在开发软件时要有意识的注意模式,并在适当的时候在设计中使用它们。

Cocoa如何改变设计模式

可以在OS X和iOS版本中找到适用于Cocoa的设计模式。 基于模式的机制和体系结构在Cocoa框架和Objective-C 的 runtime 和语言中很常见。 Cocoa经常把自己独特的旋律放在一个模式上,因为它的设计受语言能力或现有体系结构等因素的影响。

本节包含大多数设计模式的摘要,这些设计模式是在“ 设计模式:可重用面向对象软件的元素”中编目的。 每个部分不仅总结了模式,还讨论了Cocoa的实现。 只列出Cocoa实现的模式,以下各节中的模式描述都与特定的Cocoa上下文有关。

Cocoa设计模式的实现有多种形式。 以下部分中描述的一些设计(如协议和类别)是 Objective-C语言的特性。 在一些情况下,“模式实例”是在一个类或一组相关类(例如,类集群和单例类)中实现的。 在另一些情况下,模式适应是一个主要的框架体系结构,例如响应者链。 一些基于模式的机制几乎可以“免费”获得,而另外一些机制则需要做一些工作。 即使Cocoa没有实现一个模式,当情况需要时,你自己可以这样实现; 例如,对象组合(Decorator模式)通常比继承类的行为更好。

两个设计模式保留给后面的部分,模型 - 视图 - 控制器(MVC)和对象建模。 MVC是一种复合或聚合模式,这意味着它基于几种目录模式。 对象建模在“四人帮”目录中没有对应,而是源于关系数据库的领域。 然而MVC和对象建模也许是Cocoa中最重要,最普遍的设计模式,在很大程度上它们是相互关联的模式。 它们在包括绑定,撤销管理,脚本和文档体系结构在内的多种技术的设计中起着至关重要的作用。

抽象工厂(Abstract Factory)

抽象工厂模式提供了一个接口,用于创建相关或依赖对象的族,而不指定具体的类。 客户与从工厂获得的具体对象的任何具体细节分离。

类集群

类集群是一种将公共抽象父类下的许多私有具体子类组织在一起的体系结构。 抽象父类声明了创建其私有子类实例的方法。 父类根据调用的创建方法分配适当的具体子类的对象。 每个返回的对象可能属于不同的私有具体子类。

Cocoa中的类集群只能生成其数据存储可能因环境而异的对象。 Foundation框架具有NSStringNSDataNSDictionaryNSSetNSArray对象的类集群。 公共父类包括这些不可变类以及互补的可变类NSMutableStringNSMutableDataNSMutableDictionaryNSMutableSetNSMutableArray

使用和限制

如果要创建集群表示类型的不可变或可变对象,则可以使用类集群的公共类之一。 对于类集群,在简单性和可扩展性之间进行权衡。 一个类集群简化了一个类的接口,从而更容易学习和使用这个类。 但是,创建类集群的抽象父类的自定义子类通常比较困难。

适配器

适配器设计模式将类的接口转换为客户期望的另一个接口。 适配器让类能够协同工作。 它将客户从目标对象的类中分离出来。

协议 (Protocols)

一个 协议是一种语言级(Objective-C)功能,可以定义适配器模式实例的接口。 协议本质上是一系列与类无关的方法声明。 (在Java中, 接口与协议是同义的)。如果你想要一个客户端对象与另一个对象进行通信,但是对象的不兼容接口使得这个接口变得困难,你可以定义一个协议。 另一个对象的类然后正式采用该协议,并通过实现该协议的一个或多个方法来“符合”该协议。 该协议可能要求符合类实现其一些方法,并可能使其他的实现可选。 然后客户端对象可以通过协议接口将消息发送到另一个对象。

协议创建了一组独立于类层次结构的方法声明。 它们可以在协议一致性和类继承的基础上对对象进行分组。 NSObject方法 conformsToProtocol:允许您验证对象的协议从属关系。

Cocoa有非正式协议以及正式协议。 非正式协议是NSObject类中的一个类别,因此可以使任何对象成为该类别中任何方法的潜在实现者。 非正式协议中的方法可以有选择地实现。 非正式协议是OS X中委托机制实施的一部分。

请注意,协议的设计并不完全匹配适配器模式的描述。 但是它实现了这个模式的目标:允许具有不兼容接口的类一起工作。

使用和限制

您主要使用一个协议来声明一个接口,如果他们想要进行通信,那么层次上无关的类将被预期符合。 但是你也可以使用协议来声明一个对象的接口,同时隐藏它的类。 Cocoa框架包括许多正式的协议,使定制的子类能够与特定的目的进行通信。 例如,Foundation框架包含NSObjectNSCopyingNSCoding协议,这些协议都非常重要。 AppKit协议包括 NSDraggingInfoNSTextInput ,和 NSChangeSpelling。 UIKit协议包括 UITextInputTraitsUIWebViewDelegate ,和 UITableViewDataSource

正式的协议隐式地要求符合类来实现所有声明的方法。 但是,他们可以用@optional指令标记单个方法或方法组,并且符合类可以选择执行这些方法。 他们也很脆弱; 一旦你定义了一个协议并使其可用于其他类,将来对其进行的更改(除了附加的可选方法外)可能会破坏这些类。

责任链

责任链设计模式通过给予多个对象一个机会来处理请求,从而将请求的发送者与其接收者分离。 该模式将接收对象链接在一起,并沿着链传递请求,直到对象处理它为止。 链中的每个对象处理请求或将其传递给链中的下一个对象。

响应者链

应用程序框架包括称为响应者链的体系结构。 这个链由一系列响应者对象组成(也就是继承对象) NSResponder 或者,在UIKit中, UIResponder ),一个事件(例如,一个鼠标点击)或动作消息被传递(通常)最终被处理。 如果给定的响应者对象不处理特定的消息,则将消息传递给链中的下一个响应者。 响应者对象在链中的顺序通常由视图层次结构决定,从层次结构中的低级响应者到高层响应者的进程,最终在管理视图层次的窗口对象,窗口对象的委托,或全局应用程序对象。 响应者链上的事件和行动消息的路径是不同的。 应用程序可以拥有尽可能多的响应者链,因为它具有窗口(甚至是本地层次的视图)。 但是一次只能有一个响应者链,即与当前活动窗口关联的响应者链。

AppKit框架对错误处理也实现了类似的响应链。

iOS注意: UIKit实现的响应者链不同于AppKit。 如果一个视图是由一个 UIViewController 对象,视图控制器成为链中的下一个响应者(从那里事件或动作消息传递到视图的超级视图)。 另外,UIKit本身不支持文档体系结构; 因此响应者链中没有文档对象或窗口控制器对象。 iOS中也没有错误处理应答链。

视图层次结构与响应者链紧密相关,调整复合模式的设计 。 动作消息(来自控制对象的消息)基于target-action机制,它是命令模式的一个实例。

使用和限制

当您使用Interface Builder或以编程方式为程序构建用户界面时,您可以免费获得一个或多个响应者链。响应者链与视图层次结构紧密相关,您在创建视图时会自动获取对象窗口的内容视图的子视图。 如果您将自定义视图添加到视图层次结构中,它将成为响应者链的一部分。 如果您实施适当的NSResponderUIResponder方法,则可以接收和处理事件和操作消息。 作为窗口对象或全局应用程序对象的委托的自定义对象(AppKit中的NSApp )也可以接收和处理这些消息。

您也可以通过编程方式将自定义响应者注入到响应者链中,并且可以通过编程方式操纵响应者的顺序。

命令

命令设计模式将请求作为对象进行封装,从而使您可以使用不同请求参数化客户端,排队或记录请求,并支持可撤销操作。 请求对象将特定接收者上的一个或多个动作绑定在一起。 命令模式将发出请求的对象与接收和执行该请求的对象分开。

调用对象

一个实例 NSInvocation 类封装了一个 Objective-C消息。 一个调用对象包含一个目标对象,方法选择器和方法参数。 您可以动态地更改调用对象调度的消息的目标及其参数; 一旦调用被执行,您也可以从对象中获取返回值。 使用单个调用对象,您可以重复调用目标和参数具有多种变化的消息。

创建一个NSInvocation对象需要一个NSMethodSignature对象,它是一个封装了与方法的参数和返回值有关的类型信息的对象。 NSMethodSignature对象又是从方法选择器创建的。 NSInvocation的实现也利用了Objective-C runtime 的功能。

使用和限制

NSInvocation对象是分布式对象的编程接口,撤销管理,消息转发和定时器的一部分。 您还可以在类似的上下文中使用调用对象,您需要从接收消息的对象中分离发送消息的对象。

分布式对象技术用于进程间通信。

Target-Action 机制

该 target-action机制使控件对象(即按钮,滑块或文本字段等对象)能够将消息发送到另一个可以解释消息并将其作为特定于应用程序的指令处理的对象。 接收对象,或者 目标,通常是一个自定义控制器对象。 消息命名为 动作消息 - 由选择器确定,一个方法的唯一运行时标识符。

在AppKit框架中,控件拥有的单元对象通常封装了目标和动作。 当用户点击或以其他方式激活控件时,控件从其单元中提取信息并发送消息。 (菜单项还封装了目标和动作,并在用户选择时发送一个动作消息。)目标动作机制可以基于选择器(而不是方法签名)工作,因为动作方法的签名在按照惯例AppKit总是相同的。

在UIKit中,目标操作机制不依赖于单元格。 相反,控件会将目标和操作映射到控件上可能发生的一个或多个多点触控事件。

使用和限制

创建Cocoa应用程序时,可以通过Interface Builder应用程序设置控件的操作和目标。 因此,您可以让控件启动自定义行为,而无需为控件本身编写任何代码。 动作选择器和目标连接存档在nib文件中,并在nib被取消存档时恢复。 您也可以通过发送控件或其单元格setTarget:setAction: messages来动态更改目标和动作。

用于OS X的Cocoa应用程序可以使用目标操作机制来指示自定义控制器对象将数据从用户界面传输到模型对象,或者在模型对象中显示数据。 Cocoa绑定技术避免了为此目的使用目标动作的需要。 看到 可可绑定编程主题 关于这项技术的更多信息。

Controls和cell不保留其目标。

复合

复合设计模式将相关对象组合成树结构来表示部分 - 整体层次结构。 该模式可以让客户统一处理个别对象和对象组合。

Composite模式是Model-View-Controller聚合模式的一部分

查看层次结构

View ( NSView 要么 UIView 对象)在一个窗口内部结构化成一个 视图层次。 在层次结构的根部是一个窗口( NSWindow 要么 UIWindow 对象)及其内容视图,一个透明视图,填充窗口的内容矩形。 添加到内容视图中的视图成为其子视图,并成为添加到视图中的所有视图的超级视图。 除了内容视图外,视图还有一个(也是唯一的) superview和零或任何数量的 子视图。 你认为这个结构是遏制:父视图包含它的子视图。 图4-2显示了视图层次结构的视觉和结构方面。

视图层次结构是在绘图和事件处理中起作用的结构体系结构。 一个视图有两个边界矩形,它的框架和边界,这些矩形会影响视图的图形操作。 框架是外部边界; 它将视图定位在其超级视图的坐标系中,定义其大小,并将绘图剪辑到视图的边缘。 边界(内部边界矩形)定义视图自身绘制的曲面的内部坐标系。

当窗口系统要求窗口准备显示时,要求超级视图在其子视图之前进行渲染。 当你发送一些消息到一个视图 - 例如,一个请求视图重绘自己的消息 - 消息传播到子视图。 因此,您可以将视图层次结构的分支视为统一的视图。

响应者链也使用视图层次来处理事件和动作消息。

使用和限制

无论是以编程方式还是使用Interface Builder将视图添加到其他视图,都可以创建或修改视图层次结构。 AppKit框架自动处理与视图层次关联的所有关系。

装饰器

装饰设计模式动态地将额外的责任附加到对象上。 装饰器为扩展功能提供了子类化的灵活替代方案。 与子类化一样,修饰器模式的适应性允许您在不修改现有代码的情况下合并新的行为。 装饰器包装了它们扩展行为的类的一个对象。 它们实现与它们所包装的对象相同的接口,并在将任务委托给包装对象之前或之后添加它们自己的行为。 Decorator模式表达了这样的设计原则:类应该是可以拓展的,而不需要修改类。

常规注释

装饰者是一个对象组合模式,非常鼓励这样做。 但是,Cocoa提供了一些自己的类和机制(在下面的部分讨论)是基于装饰模式。 在这些实现中,扩展对象并不完全复制它所包装的对象的接口,并且这些实现使用不同的技术来进行接口共享。

Cocoa在其几个类的实现中使用了Decorator模式,包括NSAttributedStringNSScrollView ,和 UIDatePicker 。 后两个类是复合视图的例子,它将其他视图类的简单对象组合在一起,并协调它们的交互。

代理 (Delegation)

代理是一种机制,通过这种机制,宿主对象将一个弱引用(意味着它是一个简单的指针引用,而不是 retained)嵌入到另一个对象中,在需要输入任务时定期向代理发送消息。 宿主对象通常是一个“现成的”框架对象(比如 NSWindow 要么 NSXMLParser 对象)正在寻求完成一些事情,但只能以一种通用的方式来实现。 代理几乎总是一个自定义类的实例,与主机对象协调工作,在任务的某些点提供程序特定的行为( 见下图 )。 因此,委派可以修改或扩展另一个对象的行为,而无需进行子类化。

代理,就一个对象委托给另一个对象的简单意义而言,是面向对象编程中的常用技术。 然而,Cocoa以一种独特的方式实现了代理。 一个主机类使用一个 正式协议或一个 非正式协议来定义委托对象可以选择实现的接口。 非正式协议中的所有方法都是可选的,正式的协议可以声明可选的方法,允许委托只实现协议中的一些方法。 在尝试向其委托发送消息之前,主机对象决定是否实现该方法(通过respondsToSelector:消息)以避免运行时异常。

Cocoa框架中的一些类也向它们的数据源发送消息。 数据源在所有方面都与代理相同,除了意图是向主机对象提供数据以填充浏览器,表视图或类似的用户界面视图。 与委托人不同,数据源也可能需要实现协议的某些方法。

代理不是Decorator模式的严格实现。 主机(delegating)对象不包装它想要扩展的类的一个实例; 事实上,这是另一回事,委托人正在专门委托框架类的行为。 除了由框架类声明的代理方法,也没有接口的共享。

Cocoa的代理也是模板方法模式的一部分。

使用和限制

代理是Cocoa框架中的一个常见设计。 AppKit和UIKit框架中的许多类将消息发送给代理,包括 NSApplicationUIApplicationUITableView ,和几个子类 NSView 。 Foundation框架中的一些类,例如NSXMLParserNSStream ,也维护代理。 除非委托方法不允许您实现您的目标,否则应始终使用类的代理机制,而不是继承类的子类。

尽管可以动态更改代理,但一次只能有一个对象成为代理。 因此,如果您希望多个对象同时通知特定的程序事件,则不能使用代理。 但是,您可以为此使用通知机制。 只要委托实现框架类声明的一个或多个通知方法,代理就会自动从其委托框架对象接收通知。

在AppKit中代理对象不保留代理或数据源。

分类(Categories)

一个 category是Objective-C语言的一个特性,它使您能够将方法(接口和实现)添加到类中,而无需创建子类。 在程序范围内,类的原始方法和类别添加的方法之间没有运行时差异。 类别中的方法成为类类型的一部分,并由所有类的子类继承。

和代理一样,类别并不是对装饰者模式的严格适应,实现了意图,而是采取了不同的途径来实现这个意图。 按类别添加的行为是编译时工件,并不是动态获取的。 而且,类别不会封装正在扩展的类的一个实例。

使用和限制

Cocoa框架定义了许多类别,其中大多数是非正式的协议。 他们经常使用类别来分组相关的方法。 您可以在您的代码中实现类别来扩展不带子类的类或将相关方法分组。 但是,你应该知道这些警告:

  • 不能将实例变量添加到类中。
  • 如果您覆盖了该类的现有方法,那么您的应用程序可能会表现出不可预测性。

外观(Facade)

外观设计模式为子系统中的一组接口提供统一的接口。 该模式定义了一个更高层次的接口,通过降低复杂度并隐藏子系统之间的通信和依赖关系,使子系统更易于使用。

NSImage

NSImage 类 ,AppKit框架提供一个统一的接口用于装载和使用图像,可以是基于位图(例如在JPEG,PNG,TIFF或格式)或基于向量的(例如在EPS或PDF格式)。NSImage可以保持相同的图像的一个以上表示;每个表示是一种 NSImageRep 对象。 NSImage自动化选择一种表现,对于一个特定类型的数据和对于给定的显示装置是合适的。它也隐藏了图像操作和选择的细节,使客户可以互换使用许多不同的底层表示。

用途和限制

由于NSImage支持的图像就是几个不同的表现,一些请求的属性可能不适用。例如,以像素的颜色请求图像,如果底层图像表示是基于矢量和设备无关,则不起作用。

迭代器

Iterator设计模式提供了一种方法来顺序访问聚合对象的(即,集合)元件,而不暴露其底层表示。迭代器模式转换用于访问和遍历从集合本身集合的元素添加到迭代器对象的责任。迭代器定义了一个接口,用于访问集合元素和跟踪当前的元素。不同的迭代器可以执行不同的遍历策略。

枚举器

Foundation框架的NSEnumerator类实现了迭代器模式。抽象NSEnumerator类的私人、具体的子类返回枚举对象,顺序返回集合中的对象给客户,有各种类型-数组,集合,词典(值和键)。

NSDirectoryEnumerator是远亲类。这个类的实例递归枚举在文件系统中目录的内容。

用途和限制

集合类如NSArrayNSSet,和NSDictionary包括返回适当集合类型的枚举方法。所有枚举器以相同的方式工作。发送nextObject消息,当退出循环时,枚举器对象nil返回,而不是集合中的下一个对象。

您还可以使用快速列举访问集合中的元素; 这种语言功能是快速枚举。

中介者模式(Mediator)

中介者模式定义了如何封装一组互动对象的对象。中介者促进由称呼对方明确地保持对象的松散耦合,并可以独立地改变它们之间的相互作用。因此,这些对象可以保持更多的重用。

此模式的一个“中介对象”集中在一个系统中对象之间复杂的通信和控制逻辑。这些对象告诉中介对象当其状态的变化,反过来,从介体对象请求作出响应。

AppKit框架控制器类

模型 - 视图 - 控制器设计模式给在一个面向对象的系统(例如应用)中的对象分配角色。它们可以是模型对象,其中包含应用程序的数据和操作这些数据; 它们可以是view对象,可以显示数据和响应用户的动作; 或者它们可以是控制器对象,作为所述模型和视图对象之间的中介。控制器对象适合中介者模式。

在Cocoa中,控制器对象可以是两种通用的类型: 中介控制器或 协调控制器。中介调解的控制器查看对象和模型对象之间的数据在应用程序的流程。中介控制器通常是NSController对象。协调控制器执行集中通信和控制逻辑用于应用,作为代理对框架对象和作为行动的消息的目标。他们是典型的NSWindowController对象或自定义NSObject对象子类。因为他们是如此高度专业化的特定程序,以协调控制器往往不被重用。

抽象类NSController和AppKit框架中它的具体子类是Cocoa绑定技术的一部分,它会自动同步包含在模型对象和显示和查看对象编辑的数据的一部分。例如,如果用户在text field编辑字符串,绑定技术传达变化,通过一个中介控制器到绑定模型对象的相应属性。所有的程序员需要做的就是正确设计自己的模型对象,并使用Interface Builder,建立一个程序的视图,控制器,和模型对象之间的绑定。

Interface Builder库中具体的公共控制器类的实例是可用的,因此是高度可重用。他们提供的服务,如选择和占位符值的管理。这些对象执行以下具体功能:

  • NSObjectController 管理一个模型对象。
  • NSArrayController管理模型对象的数组,并保持一个选择; 它也可以让你的对象从array中添加和删除对象。
  • NSTreeController 使您能够以分层树结构中添加,删除和管理模型对象。
  • NSUserDefaultsController提供了一个方便的接口首选项(用户缺省值)系统。
用途和限制

通常使用NSController的对象作为中介控制器,因为这些对象是为了应用程序查看对象和模型对象之间的通信数据来设计的。要使用中介控制器,您通常从Interface Builder的库拖动对象,指定模型对象的属性键,并建立使用Interface Builder的信息窗口的绑定窗格视图和模型对象之间的绑定。您也可以继承NSController或它的一个子类来获得更专业的行为。

可以在任何一对对象之间都建立绑定,只要这些对象遵守NSKeyValueCodingNSKeyValueObserving非正式协议。但为了NSController的优势,最好是通过中介控制器进行绑定。

协调控制器集中应用程序的通信和控制逻辑:

  • 维护outlets模型和视图对象(outlets是保持连接或引用其他对象的实例变量)
  • 通过目标-动作响应view对象的用户操纵
  • 作为一个代理由框架对象发送的消息

通常做上述所有的连接,outlets,target-action,delegates,在Interface Builder,他们存档在应用程序的nib文件。

View Controllers in UIKit

iOS中运行的应用程序经常使用一个模型和导航用户界面的设计,用于呈现屏幕上的应用数据模型。一个应用程序可以具有导航栏和工具栏,这些对象之间的是应用数据的当前视图。用户可以点击工具栏上的按钮来选择一个模式,导航栏上的点击按钮,并且在当前视图点击控制遍历模型(数据)对象的层次结构;每个级别上的中央视图显示更多的细节。在这个层次的结尾往往是用户可以检查或编辑的项目。

UIViewController继承的视图控制器。UIViewController是一个抽象类,可以继承管理特定视图。UIKit框架还提供用于管理导航栏和工具栏对象子类: UINavigationControllerUITabBarController。如下图所示,一个tab-bar控制器可以管理许多导航控制器,这反过来又可以管理一个或多个视图控制器,每个具有其相关联的view对象的步骤。除了管理视图(包括重叠视图),一个视图控制器指定了显示在导航栏中的按钮和标题。

备忘录模式(Memento)

备忘录模式捕捉和外部化对象的内部状态,而不违反封装-使得对象可以恢复到这种状态后。备忘录模式保持关键对象外部的重要状态。

归档

将对象存档在一个程序中,随着这些对象的属性(属性和关系)到档案,可以存储在文件系统或过程或在网络之间传输。以字节存档了一个程序的对象图,保留对象的身份和他们之间的关系的一种体系结构无关的流。因为一个对象的类型及其数据存储对象解码字节流通常实例化的对象使用原来的编码相同的类。

使用和限制

一般来说,你想保存一些程序中想要保存状态的对象。模型对象几乎都属于这一类。写一个对象的档案进行编码和解码,通过解码从归档中读取那个对象。编码和解码是你使用NSCoder对象执行的操作,最好使用密钥归档技术(你要调用的方法NSKeyedArchiverNSKeyedUnarchiver类)。被编码和解码的对象必须符合NSCoding协议;该协议的方法被调用时归档

属性列表序列化

属性列表是一个简单的,使用下面类对象结构序列化的对象图:NSDictionary, NSArray, NSString, NSData,NSDate, NSNumber.这些对象通常被称为属性列表对象。几个Cocoa框架类提供的方法来将这些属性列表对象和定义数据流记录的对象及其层次关系的特殊格式的内容。这个NSPropertyListSerialization类提供的类方法,可以从XML格式或优化的二进制格式,序列化属性列表对象。

使用和限制

如果在对象图中的对象是简单的,属性列表序列化是一个灵活的,便携的,和足够的手段来捕捉和呈现对象及其状态。然而,这种形式的序列化有其局限性。它不保存对象完整的标识,只是一般的类型(数组,字典,字符串,等等)。因此,一个对象从属性列表恢复可能是一个与原来类不同的类。这是一个问题,当一个对象的可变性可以不同。属性列表序列化也不跟踪,对象中多次引用的对象,可能会导致多个实例在反序列化,在原来的对象图的一个实例。

Core Data

Core Data是Cocoa框架,定义了管理对象图,使他们持续的架构。正是这第二能力对象持久化使Core Data适应备忘录模式。

核心数据的设计也严重的影响了模型-视图-控制器和对象建模模式。

使用和限制

Core Data在企业应用的发开中非常有用,模型对象的复杂图必须定义,管理,和透明的归档和未归档和数据存储。
核心数据是特别有用的企业应用中的模型对象的复杂图必须定义,管理,和透明的归档和未归档和数据存储。Xcode开发环境包括项目模板和设计工具,来减少需要创建核心数据应用的两种类型的编程工作,那些基于文档和那些不是基于文档的数据。Interface Builder应用还包括可配置的库中的核心数据结构对象。

观察者(Observer)

观察者设计模式定义了对象之间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的通知并自动更新。观察者模式本质上是一个发布和订阅模型,它的主体及其观察员是松散耦合。观察和观察对象之间不需要知道对方就可以发生通信。

通知(Notifications)

Cocoa通知机制实现了一个基于观察者模式的消息一对多广播。在一个程序中的对象添加自己或其他对象的列表,有一个或多个通知观察者,其中每一个是由全局的字符串识别(通知的名字)。要通知其它对象,观察对象创建一个通知对象和推送到通知中心的对象。通知中心决定一个特定的通知观察者,通过消息将通知发送到他们。该方法通过通知消息调用必须符合一定的单参数签名。该方法的参数是通知对象,其中包含通知名,观察对象,和一个包含任何补充信息字典。

推送通知是一个同步程序。发布对象不重新控制到通知中心有广播通知所有的观察者。对于异步行为,你可以在通知队列中通知;控制立即返回到发布对象,当通知到达队列顶部时通知中心再广播。

定期通知,这些通过通知中心的广播只是进程内。如果你想给其他进程广播通知,您可以使用分布式通知中心及其相关的API。

使用和限制

你可以因为各种各样的原因使用通知,。例如,可以广播通知改变用户界面元素的显示信息,基于在程序的其他地方的某一事件。或者你可以用通知的方式确保目标文档中的文档窗口关闭前保存它们的状态。通知的目的是告知其他对象程序事件使他们能够作出适当的反应。

但对象接收通知的反应只有在已经发生的事件。这是和代理的显著差异。代理有机会拒绝或修改由委托对象提出的操作。另一方面,观察对象不能直接影响即将进行的操作。

通知类都是NSNotification(通知对象),NSNotificationCenter(推送通知和添加观察员),NSNotificationQueue(队列化通知),和NSDistributedNotificationCenter。许多Cocoa框架类发布通知后,任何对象都可以观察。

Key-Value Observing

KVO是一种机制,允许当其他对象的具体特性的变化时对象被通知。它是基于NSKeyValueObserving 非正式协议。观察到的属性可以是简单的属性,一个一关系,或一对多的关系。在模型视图控制器模式下,键值观察尤为重要,因为它使视图对象模型对象的观察通过控制器层的变化。因此,这是Cocoa的一个必不可少的绑定技术的组成部分。

Cocoa提供了一个默认“自动”的实施NSKeyValueObserving方法:给所有符合对象的属性的观察能力。

使用和限制

KVO是类似的通知机制,但在一些重要的方面不同。在键值观察没有中央的对象,提供所有观察员变更通知。相反,变化通知直接发送到观察对象。键值观察也直接关系到具体对象的属性值。另一方面,通知机制,更广泛的关注程序事件。

对象参与键值观察(KVO)必须KVO兼容即遵守一定的要求。对于自动观测,这需要符合要求的关键值编码(KVC依从性)和使用KVC的依从性的方法(即,访问器方法)。键值编码是一种机制(基于相关的非正式协议)自动获取和设置对象属性的值。

你可以通过禁用自动化KVO通知观察者通知,使用方法NSKeyValueObserving非正式的协议和相关的类执行手动通知。

代理(Proxy)

代理设计模式提供了一种替代,或占位符,为另一个对象来控制访问其他对象。使用这个模式来创建一个代表或代理,对象,控制访问另一个对象,这可能是远程的,复杂的创建,或需要保护。这种模式的结构类似于装饰模式却有不同的用途;装饰给对象添加行为,而代理控制对对象的访问。

NSProxy

NSProxy类定义了对象作为其他对象的代理对象的接口,甚至对象还不存在。一个代理对象通常转发消息给它代表的对象,但它也可以响应消息,通过加载对象转变为它或代表。虽然NSProxy是一个抽象类,它实现了NSObject协议和其他基本方法的根对象的预期;事实上,一个层次结构的基类,如NSObject

具体的NSProxy子类能够完成既定目标的代理模式,如昂贵的对象或作为安全哨兵懒实例化对象。NSDistantObject,基础框架中一个NSProxy具体子类,实现了一个远程代理,对于透明的分布式消息。NSDistantObject对象是分布式对象结构的一部分。作为对其他进程或线程对象的代理,它们有助于使这些线程或进程的对象之间的通信。

NSInvocation 对象,这是命令模式的一种适应体,也是分布式对象体系结构的一部分

使用和限制

Cocoa只有在分布式对象采用NSProxy对象。这个NSProxy对象是具体的实例,是具体子类NSDistantObjectNSProtocolChecker。不仅可以使用分布式对象为进程消息(在相同的或不同的计算机)也可以用它来实现分布式计算和并行处理。如果你想使用代理对象的其他用途,如昂贵的资源或安全的创造,你要实现你自己的具体的NSProxy子类

接待员(Receptionist)

前台设计模式是一种混合模式。虽然它没有出现在“四人帮”的书,它结合了命令,备忘录的元素,和本文中描述的代理设计模式。这也是蹦床模式(Trampoline pattern)的一个变种(其中还没有出现在书中);在这种模式中,一个事件最初是由一个蹦床对象接收,这样叫因为它立刻反弹,或重定向,处理到目标对象的事件。

当你需要反弹继续处理另一个执行上下文,可以通过前台设计模式。当你观察一个通知,或者实现一个块处理程序,或响应一个消息,你要确保你的代码在适当的执行上下文中执行,可以实现前台模式改变所必须做的工作,执行上下文。前台模式,在反弹任务数据处理之前,甚至可以执行一些过滤或合并输入数据。例如,可以收集数据分批进入,然后在区间调度这些批次做其他处理。

一种常见的情况,接待员模式是有用的键值观察。对一个模型对象的属性的值的变化是通过KVO通知传达给观察者。然而,一个模型对象的变化可以在后台线程上发生。这个结果在一个线程不匹配,因为一个模型对象的状态变化通常会导致更新用户界面,这些都必须在主线程中发生。在这种情况下,你想重定向KVO通知主线程。

前台设计模式的实践

KVO通知调用由观察者实现的observeValueForKeyPath:ofObject:change:context:方法。如果改变的属性发生在辅助线程,该observeValueForKeyPath:ofObject:change:context:在同一个线程执行的代码。在这个模式中有中央的对象,前台接待员,作为一个线程的中介。下图说明,接待对象被分配作为一个模型对象的属性的观察。前台实现observeValueForKeyPath:ofObject:change:context:将收到的通知在辅助线程上的另一个执行上下文的主要操作队列,在这种情况下。属性变化时,接待员收到一个KVO通知。接待员立即增加一块操作的主要操作队列;块包含指定的更新用户界面的适当代码。

定义一个接待员类具有元素需要添加本身作为一个观察者的属性,然后将一个KVO通知到一个更新的任务。因此,它必须知道它的观察对象,该对象的属性,这是观察,什么更新任务执行,什么队列执行它。下面代码显示初始声明RCReceptionist类及其实例变量。

@interface RCReceptionist : NSObject {
    id observedObject;
    NSString *observedKeyPath;
    RCTaskBlock task;
    NSOperationQueue *queue;
}

RCTaskBlock实例变量是下面这样的block对象

typedef void (^RCTaskBlock)(NSString *keyPath, id object, NSDictionary *change);

observeValueForKeyPath:ofObject:change:context: 方法的参数是相似的,参数类声明单例类工厂方法,rctaskblock对象是一个参数:

+ (id)receptionistForKeyPath:(NSString *)path
        object:(id)obj
         queue:(NSOperationQueue *)queue
          task:(RCTaskBlock)task;

它实现了该方法分配传入的值来实例化被接待员对象创建的实例变量,对象为模型对象属性的一个观察者

+ (id)receptionistForKeyPath:(NSString *)path object:(id)obj queue:(NSOperationQueue *)queue task:(RCTaskBlock)task {
    RCReceptionist *receptionist = [RCReceptionist new];
    receptionist->task = [task copy];
    receptionist->observedKeyPath = [path copy];
    receptionist->observedObject = [obj retain];
    receptionist->queue = [queue retain];
    [obj addObserver:receptionist forKeyPath:path
             options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:0];
    return [receptionist autorelease];
}

注意代码复制的block对象而不是retaining它。因为bock可能是在栈上创建的,它必须被复制到堆,,KVO通知时它得存在于内存中。

最后,observeValueForKeyPath:ofObject:change:context:方法参数的实现:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
        change:(NSDictionary *)change context:(void *)context {
    [queue addOperationWithBlock:^{
        task(keyPath, object, change);
    }];
}

这个代码在给定的运行队列进行简单的查询任务,通过被观察对象的task block,为改变属性的关键路径,和字典包含新value。任务是封装在一个NSBlockOperation对象执行任务队列。

客户端对象提供的block代码,更新用户界面创建一个接待员对象时,如下面代码,请注意,创建的接待对象时,客户端传给在运行队列的数据block被执行,在这种情况下的主要操作队列。

RCReceptionist *receptionist = [RCReceptionist receptionistForKeyPath:@"value" object:model queue:mainQueue task:^(NSString *keyPath, id object, NSDictionary *change) {
            NSView *viewForModel = [modelToViewMap objectForKey:model];
            NSColor *newColor = [change objectForKey:NSKeyValueChangeNewKey];
            [[[viewForModel subviews] objectAtIndex:0] setFillColor:newColor];
        }];

单例(Singleton)

单例设计模式确保一个类只有一个实例,并提供一个访问它的全局访问点。这类跟踪它的唯一实例,可以确保没有其他实例可以被创建。单例类是适当的情况下,对于提供一个全球性的资源是有意义的一个对象。

框架类

几个Cocoa框架类是单例。他们包括NSFileManager,NSWorkspace,NSApplication以及在UIKit中,UIApplication。一个进程是限制这些类只有一个实例。当客户端请求一个实例的类,它获取一个共享实例,这是建立在第一次请求时懒创建的。

使用和限制

使用通过单例类返回的共享实例,和使用非单例类的一个实例没什么不同,只是你无法copying、retaining或者releasing它(有关方法重新实现零操作)。如果情况需要可以创建你自己的单例类

模板方法

模板方法设计模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类。模板方法模式让子类重新定义算法中的某些步骤而不改变算法的结构。

重写框架方法

模板方法模式是一个基本的Cocoa设计,而且面向对象框架是很普遍的。在Cocoa的模式让一个程序把自己的自定义组件为一个算法,但框架组件决定何时需要它们。Cocoa类的编程接口通常包括由子类重写方法。在运行时,框架调用这些所谓的通用方法在某些点的任务执行。一般的方法提供了一种用于自定义代码提供程序特定的行为和数据的任务执行和协调的框架类结构。

使用和限制

利用Cocoa改编的模板方法模式,你必须创建一个类的子类并重写这些方法,框架调用插入应用程序特定的输入方法是执行。如果你写自己的框架,你应该包括在设计模式。

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zyq522376829/article/details/78791721
个人分类: iOS
想对作者说点什么? 我来说一句

COCOA设计模式

2016年01月02日 39.37MB 下载

cocoa设计模式中文版

2013年12月25日 39.37MB 下载

没有更多推荐了,返回首页

不良信息举报

Cocoa设计模式(iOS常用设计模式) Cocoa Design Patterns

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭