Swift协议只有get属性和可设置属性吗?

43

为什么我可以毫无错误地这样做:

var testDto = ModelDto(modelId: 1)
testDto.objectId = 2

当我定义这个时:

protocol DataTransferObject {
    var objectType: DtoType { get }
    var parentObjectId: Int { get set }
    var objectId: Int { get }
    var objectName: String { get set }
}

struct ModelDto: DataTransferObject {
    var objectType: DtoType
    var parentObjectId: Int
    var objectId: Int
    var objectName: String

    init(modelId: Int) {
        self.objectType = DtoType.Model
        self.objectId = modelId
        self.parentObjectId = -1
        self.objectName = String()
    }
}

如果我的协议中的定义大多被忽略(getter,setter定义),我为什么还要使用它们呢?


你在这里漏掉了非常重要的东西。你的协议没有被考虑,因为你没有引用它。你选择直接引用ModelDto而不是通过DataTransferObject引用。请参见下面的答案。 - Patrick Goley
6个回答

63

苹果在"Swift编程语言文档"中指出:

如果协议只需要属性可读取,那么任何类型的属性都可以满足要求,如果这对你自己的代码有用,属性也可以是可设置的。

因此,以下五个Playground代码片段都是有效的:

示例#1:常量属性

protocol FullyNamed {
    var fullName: String { get }
}

struct Duck: FullyNamed {
    let fullName: String
}

let scrooge = Duck(fullName: "Scrooge McDuck")
print(scrooge.fullName) // returns "Scrooge McDuck"

例子 #2:变量属性

protocol FullyNamed {
    var fullName: String { get }
}

struct Duck: FullyNamed {
    var fullName: String        
}    

var scrooge = Duck(fullName: "Scrooge McDuck")
print(scrooge.fullName) // returns "Scrooge McDuck"

scrooge.fullName = "Scrooge H. McDuck"
print(scrooge.fullName) // returns "Scrooge H. McDuck"

示例 #3:计算属性(只读)

protocol FullyNamed {
    var fullName: String { get }
}

struct Duck: FullyNamed {
    private var name: String
    var fullName: String {
        return name
    }
}

let scrooge = Duck(name: "Scrooge McDuck")
print(scrooge.fullName) // returns "Scrooge McDuck"

示例 #4:计算属性(获取和设置)

protocol FullyNamed {
    var fullName: String { get }
}

struct Duck: FullyNamed {
    private var name: String
    var fullName: String {
        get {
            return name
        }
        set {
            name = newValue
        }
    }
}

var scrooge = Duck(name: "Scrooge McDuck")
print(scrooge.fullName) // returns "Scrooge McDuck"

scrooge.fullName = "Scrooge H. McDuck"
print(scrooge.fullName) // returns "Scrooge H. McDuck"

例子 #5:private(set) 变量属性

/* Duck.swift located in Sources folder */

protocol FullyNamed {
    var fullName: String { get }
}

public struct Duck: FullyNamed {
    public private(set) var fullName: String
    
    public init(fullName: String) {
        self.fullName = fullName
    }

    public mutating func renameWith(fullName: String) {
        self.fullName = fullName
    }
}

/* Playground file */

var scrooge = Duck(fullName: "Scrooge McDuck")
print(scrooge.fullName) // returns "Scrooge McDuck"

scrooge.renameWith("Scrooge H. McDuck")
print(scrooge.fullName) // returns "Scrooge H. McDuck"

苹果公司也指出:

如果协议要求一个属性既可读又可写,则该属性的要求不能通过常量存储属性或只读计算属性来实现。

因此,以下两个Playground代码片段不是有效的:

示例#1:常量属性

protocol FullyNamed {
    var fullName: String { get set }
}

struct Duck: FullyNamed {
    let fullName: String
}

let scrooge = Duck(fullName: "Scrooge McDuck")
// Error message: Type 'Duck' does not conform to protocol 'FullyNamed'

示例 #2:计算属性(仅获取)

protocol FullyNamed {
    var fullName: String { get set }
}

struct Duck: FullyNamed {
    private var name: String
    var fullName: String {
        return name
    }
}

var scrooge = Duck(name: "Scrooge McDuck")
// Error message: Type 'Duck' does not conform to protocol 'FullyNamed'

示例 #3:计算属性(只读)

protocol FullyNamed {
    var fullName: String { get }
}

struct Duck: FullyNamed {
    var fullName: String {return "Scrooge McDuck"}
    
    init(fullName: String) {
        self.fullName = fullName 
  // Error Message Cannot assign to Property: "FullName" is get only
    }
}

这太棒了。你的第五个例子有点误导人吧?虽然fullName不能从属性级别设置...但是类的使用者可以通过初始化直接设置它。 - mfaani
讲解得很好,但需要在示例中修正私有访问权限,否则编译器会报错。 - zeytin

32
根据官方文档所述:
获取器和设置器的要求可以通过符合类型以多种方式来满足。如果属性声明包括get和set关键字,符合类型可以使用存储变量属性或可读写的计算属性(即实现getter和setter)来实现它。但是,该属性声明不能作为常量属性或只读计算属性实现。如果属性声明仅包括get关键字,则可以将其实现为任何类型的属性。

5
奇怪的 Swift!Get 和 Set 意味着可获取和可设置,而 Get 只是意味着你想做什么就做什么? :) - Artur Pajonk
虽然我认为这样做有些混乱,因为在我的观点中,在协议中使用get或get/set声明几乎没有用处,但我会在ModelDto结构的声明中使用private(set)来定义objectId变量,如下:private(set) var objectId: Int - Artur Pajonk
看到这个问题被投了两次反对票,我觉得很有趣;) 尽管在我看来它描述了Swift中的一种非常奇怪的行为。 - Artur Pajonk
2
@ArturPajonk,我在这个行为中没有看到任何奇怪的地方。您的结构ModelDto既没有objectId的getter也没有setter。它符合DataTransferObject协议,因为具有getter,但只要您直接使用ModelDto,它就不会公开setter。所以你可以自由调用它。如果您希望隐藏setter,则可以将ModelDto转换为DataTransferObject - igrek
4
就算我不觉得这很奇怪,这就是协议的作用。它们规定了必须实现的规则。{get} 表示符合规则的对象必须有一个 getter 方法,setter 方法可以有也可以没有。 - BangOperator
@ArturPajonk get 意味着这个值 必须 存在。实际上,这就是它的全部含义。它不会对符合类型应用任何限制。然而,如果你有 set,那么对符合类型将应用限制。我强烈建议您查看下面 Imanou 的答案。它很棒。 - mfaani

4
请看以下内容:

var testDto = ModelDto(modelId: 1)

在这里,变量testDto的类型是ModelDto。已知ModelDto有一个可变变量var objectId: Int。您可以自由地修改objectId,因为您是通过ModelDto接口访问对象而不是仅可获取协议接口的方式来访问对象。

请尝试以下操作:

var testDto: DataTransferObject = ModelDto(modelId: 1)
testDto.objectId = 2 // compiler error

以上示例不应该编译通过。因为testDto的类型仅为DataTransferObject,我们并不知道底层实现是否有可设置的属性。我们只知道在协议中声明的可获取属性。

简而言之,你声明了ModelDto具有可获取/设置的变量,所以如果Swift不允许你进行设置,那就相当奇怪了。只有可获取的变量将依赖于您通过协议引用对象或更改ModelDTO上的objectId为一个常量变量。

编辑:为解答关于为何ModelDto可以具有可设置变量的困惑。这与ModelDto可以拥有协议中未定义函数的方式是一样的。Getter和setter实际上就是函数,因此,在协议要求getter的情况下,实现也可以具有setter。Objective C也是一样的。协议是描述性的,而不是限制性的。


3
我会以通用的方式回答这个问题。
在回答这个问题之前,您必须知道什么是“get”和“set”。
(如果您来自Objective-C世界:)“get”表示“只读”,也就是说,我被允许知道动物有几条腿。我不被允许设置它。“get”和“set”一起表示“可读写”,即我可以知道动物的重量,同时也能够设置/更改动物的重量。
以下是一个示例。
protocol Animal {
    var weight : Int { get set }
    var limbs : Int { get }
}

如果你只有getter方法,想要隐藏setter方法(使用private (set)),那么不会出现错误……这很可能就是你想要的,也是必须这样做的方式!

很可能是你想要的:

class Cat : Animal {
    private (set) var limbs: Int = 4 // This is what you intended, because you only have get requirements...and don't want any conforming type to be able to set it ie don't want others do catInstance.limbs = 22
    var weight: Int = 15

}

var smallCat = Cat()
smallCat.weight = 20 // Good!

// attempting to set it will create an error!!!
smallCat.limbs = 5 // Error: Cannot assign to property: 'limbs' setter is inaccessible

可能不是你想要的:

class Panda : Animal {
    var limbs: Int = 4 // This is OK, but it kinda defeats the purpose of it being a get only
    var weight: Int = 200   
}

var littlPanda = Panda()

littlPanda.weight = 40 // Good
littlPanda.limbs = 30 // NO Error!!! Likely unintended

使用{get}仍然需要进行一些额外的工作,而编译器并不会告诉你...您必须添加private (set)以实现预期的行为。


如果您的属性有setter并且尝试隐藏setter,则实际上会看到错误。

class Dog : Animal {
    private (set) var limbs: Int = 4
    private (set) var weight: Int = 50  // Error: Setter for property 'weight' must be declared internal because it matches a requirement in internal protocol 'Animal'
}

你不能隐藏,因为你承诺提供了一个setter(设置器)...


谢谢你,亲爱的 :) - Mohamed Raffi

2
您所看到的代码示例行为被称为成员隐藏。在面向对象语言中,当使用相同名称或签名声明新成员时,就会发生成员隐藏现象,因此通过在结构体实现中添加var objectId: Int,您实际上创建了一个名为objectId的新成员,并隐藏了从协议继承的属性。
为了遵守结构体和协议之间的契约,objectId可以声明为:
  let objectId: Int = 1

或者
var objectId: Int {
        get {
            return 1
        }
    }

1
在你的类中,你创建了一个名为objectId的存储属性。在你的协议中,你指定该属性需要一个getter——这是它唯一的要求。
如果你希望它成为计算属性,就像你期望的那样,你需要用以下方式声明objectId
var objectId: Int{ return (someNumber) }

如果没有闭包来计算值,那么默认情况下它是一个存储属性。


如果这是真的,那么协议中的可获取和可设置定义就完全没有用了,不是吗? - Artur Pajonk
@ArturPajonk 如果你同时指定了getter和setter,那么你的属性必须有一个setter。否则,它可以是任意一个。 - erdekhayser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接