为什么使用委托和协议而不是仅仅在Swift中传递实例?

时间:2014-07-25 16:37:03

标签: delegates swift protocols

我试图在Swift中的视图之间传递变量,并遇到了相当抽象的协议和委托概念。

然后我尝试在第二个视图中存储对第一个视图的引用,并直接调用函数。这似乎有效:

SCREEN 1

class Screen1: UIViewController {

    var myName = "Screen1"

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    //
    // checking if the segue to screen 2 is called and then passing a reference
    //
    override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
        if segue.identifier == "screen2Segue"{
            let vc = segue.destinationViewController as Screen2
            vc.storedReference = self
        }
    }

    func getName() -> String {
        return myName
    }
}

SCREEN 2

class Screen2: UIViewController {

    var storedReference:Screen1!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    func testReference() {
        // calling a function on the stored reference to screen 1
        var str = storedReference.getName()
        println("Leaving screen 2, going to " + str)
    }
}

我的问题:这段代码有什么问题?如果您可以直接传递引用,为什么要使用委托和协议?

也许是相关的:视图何时被取消初始化并被全新的视图实例取代?我打电话给' getName()'在旧的实例上?

5 个答案:

答案 0 :(得分:11)

协议对于将实现与接口分离很有用,这有助于提高代码的可重用性,可理解性和可测试性。

例如,您可能希望将项目存储在某种类型的列表中。 List的一些可能实现包括基于阵列的实现和基于节点的(链接列表)实现。如果您要声明一个名为List的协议并且具有实现该协议的类ArrayListLinkedList,则需要使用列表的任何内容(作为参数传递给方法的变量,属性等)可以使用List作为变量类型,并且能够在不关心列表是ArrayList还是LinkedList的情况下运行。您可以更改使用的类型或它们的实现方式,对于使用它们的任何内容都无关紧要,因为只有协议中声明的公开接口才可见。

协议对于模拟多重继承之类的东西也很有用,因为类可以从超类继承,也可以实现一个或多个接口。 (例如,蝙蝠既是哺乳动物又是翅膀,所以它可以表示为Bat类继承自实现Mammal协议的Winged类。

委托模式使用协议将一些职责委托给另一个对象,这对于代码分离和可重用性特别有用。例如,iOS中的UITableViewDelegate协议允许UITableView通过委派另一个对象来处理事件来对单元格选择等内容做出反应。数千个应用程序中的数百万个对象可能已经使用过这种方法,但没有Apple的开发人员实现了UITableViewUITableViewDelegate对实现协议的对象的了解。

通过在视图控制器之间直接传递引用,您强制第二个完全依赖于第一个。如果您希望更改应用程序的流程以便可以从其他位置访问第二个视图控制器,则会强制您重写该视图控制器以使用新的原点。如果您使用协议,则不必对第二个视图控制器进行任何更改。

答案 1 :(得分:6)

基本的设计原则是不再暴露任何设计。通过传递参考,你将暴露整个对象。这意味着其他人可以调用其任何功能并访问其任何属性。并改变它们。这不好。除了让其他人以可能没有预期的方式使用对象之外,如果你试图在将来改变对象并发现它打破了使用你不想要的东西的其他人,你也会遇到问题。所以,不要暴露任何你不需要的东西总是一个好主意。这是代表和协议的目的。它使对象完全控制所暴露的内容。更安全。更好的设计。

答案 2 :(得分:3)

我认为你并没有完全理解协议是什么。

我总是说协议就像合同一样 实现某些协议的委托对象承诺它可以执行委托者不能做的事情。

在现实世界中,我的房子管子有问题 我(委托人)打电话给管道工(代表)来解决它。水管工承诺(通过合同)能够组合它。承诺是协议。我不在乎他是怎么做的,只要他这样做。

但这些合同不仅对代表团有用 我正在写一个食品订购应用程序。由于它有一个菜单,它需要在其中显示项目 我可以使用基本继承并编写一个类MenuItem,所有子类都必须继承。
或者我写了一个协议来表达:«无论你是什么对象,只要你履行这份合同我们就有了交易»。这允许我创建许多不同的类或注释类别中的现有类,虽然我没有多重继承的工具。

实际上我同时做了两件事:我编写了符合协议的协议MenuItem和类MenuItem。现在我可以使用简单继承或使用不从类MenuItem继承的类。

Objective-C中的代码(抱歉:我仍在转换到Swift)

@protocol MenuItem <NSObject>

-(NSString *)name;
-(double) price;
-(UIColor *)itemColor;

@end


@interface MenuItem : NSObject <MenuItem>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) double price;
@property (nonatomic, strong) UIColor *itemColor;

@end

#import "MenuItem.h"

@implementation MenuItem

-(id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if (self) {
        self.name = [decoder decodeObjectForKey:@"name"];
        self.price = [decoder decodeDoubleForKey:@"price"];
        self.itemColor = [decoder decodeObjectForKey:@"itemColor"];
    }
    return self;
}

-(void)encodeWithCoder:(NSCoder *)encoder
{
    [encoder encodeDouble:self.price forKey:@"price"];
    [encoder encodeObject:self.name forKey:@"name"];
    [encoder encodeObject:self.itemColor forKey:@"itemColor"];
}


@end

Apple为NSObject使用相同的架构:有一个协议和一个类NSObject。这允许继承自类NSObject的未完整的类作为NSObject。一个着名的例子:NSProxy


在您的情况下,Screen1承诺能够理解由详细视图控制器Screen2发送的消息。这些允许解耦:可以使用任何理解Screen1协议的对象。此外,它还有助于维护一个理智的对象树,因为我们不必进行循环导入。但总的来说,你必须记住委托者(Screen2)必须对它的委托保持弱引用,否则我们有一个保留圈。


当然是UITableView的一个重要例子:
表视图对象知道有关渲染它的单元格,处理滚动等等的一切。但是编写它的工程师现在不可能想要你的表视图。这就是为什么他介绍了一个让你有机会创造合适细胞的代表。由于他不知道你的数据是什么样的,他还介绍了数据源 - 它与委托完全一样:你将被要求提供所需的所有数据信息。

答案 3 :(得分:2)

这主要是一个意见问题,所以这个问题可能应该被关闭,但我认为整个开发者社区都在就此达成一致,所以无论如何我都会回答它。

Software Architecture中的一个重要概念(代码结构的设计)称为Separation of Concerns。基本原则是,您应该将代码必须分解为仅具有一个不同目的的小组件。这些组件中的每一个都应该能够主要独立存在,而不必担心除了需要直接与之交互的组件之外的其他组件。

这有助于代码重用。如果您设计的独立于大多数(如果不是全部)其他组件的小组件,您可以轻松地将其插入代码或其他应用程序的其他部分。以UITableView为例。通过使用委托模式,每个开发人员都可以轻松创建表视图并使用他们想要的任何数据填充它。由于该数据源是一个单独的对象(单独关注数据),您可以将相同的数据源附加到多个表视图。想想iOS上的联系人列表。您将希望以多种方式访问​​相同的数据。您可以根据需要多次重复使用不同表视图的数据源,而不是始终重写加载特定数据并以特定方式显示的表视图。

这也有助于您的代码的可理解性。开发人员很难对应用程序的状态保留太多东西。如果将每个代码组件分解为小的,明确定义的职责,开发人员可以分别理解每个组件。他们还可以查看组件,并对其执行的操作做出准确的假设,而无需查看具体实现。对于小型应用程序而言,这并不是什么大问题,但随着代码库的增长,这变得非常重要。

通过传入对第一个视图控制器的引用,您将使第二个视图控制器完全依赖于第一个视图控制器。您不能在另一个实例中重用第二个视图控制器,并且其工作变得不那么清晰。

分离关注点还有很多其他好处,但我相信这些是两个引人注目且重要的问题。

答案 4 :(得分:0)

我认为后者的问题源于单个类的多次重用。

例如,一个名为CustomTableViewCell的自定义UITableViewCell。假设您有A类和B类,它们都具有tableViews,并且都希望使用CustomTableViewCell作为其单元格。您现在有两个选择。您是否愿意:

A。为CustomTableViewCell使用一个名为CustomTableViewCellDelegate的委托/协议。在CustomTableViewCell类中声明一个名为“ delegate”的对象,该对象将实现所提到的协议并对其进行调用,无论它调用的是哪个类

B。为CustomTableViewCell中的每个类(A类,B类)声明一个对象,以便您可以保存对每个类的引用。

如果您需要对多个类使用CustomTableViewCell,那么我认为您知道应该采用哪个选项。从软件体系结构的角度来看,在CustomTableViewCell中为不同的类声明多个对象将是一件痛苦的事情。