【问题标题】:Swift Protocol get only settable?Swift 协议只能设置?
【发布时间】:2015-06-01 12:46:09
【问题描述】:

为什么我可以这样做而没有任何错误:

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。请参阅下面的答案。

标签: ios swift protocols getter-setter


【解决方案1】:

苹果在"Swift Programming Language (Swift 3)"中声明:

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

因此,以下五个 Playground 代码 sn-ps 均有效:

示例 #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 代码 sn-ps 不是有效:

示例 #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
    }
}

【讨论】:

  • 这太棒了。你的例子5是不是有点误导?虽然fullName 不能从属性级别设置...该类的用户可以通过初始化直接设置它。
  • 很好的解释,但它需要在私有可访问性方面修复示例,否则编译器会抛出错误。
【解决方案2】:

根据official documentation:

一致性类型可以通过多种方式满足 getter 和 setter 的要求。如果属性声明同时包含 get 和 set 关键字,则符合类型的类型可以使用存储的变量属性或可读可写的计算属性(即同时实现 getter 和 setter 的属性)来实现它。但是,该属性声明不能实现为常量属性或只读计算属性。 如果属性声明仅包含 get 关键字,则可以将其实现为任何类型的属性。

【讨论】:

  • 奇怪的斯威夫特! Get 和 Set 意味着 getable 和 settable 一个 Get only 意味着做你想做的事? :)
  • 虽然我认为这很混乱,因为从我的角度来看,在协议中使用 get 或 get/set 声明几乎没用,我将在 ModelDto 结构声明中使用私有(集合),例如:私有(集合) var objectId: Int
  • 这也很有趣,这个问题被两次否决了;)虽然如果你问我,它描述了一个非常奇怪的行为。
  • @ArturPajonk,我看不出这种行为有什么奇怪的。您的结构ModelDto 确实有objectId 的getter 和setter。它符合DataTransferObject 协议,具有getter,但只要您直接使用ModelDto,它就会暴露一个setter。所以你可以随意调用它。如果您希望隐藏您的二传手,您可以将 ModelDto 转换为 DataTransferObject
  • 即使我也不觉得这很奇怪。这就是协议的作用。他们定义了必须实施的规则。 {get} 表示符合条件的东西必须有一个 getter。它可能有也可能没有二传手。
【解决方案3】:

考虑以下几点:

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,所以我们不知道底层实现有一个可设置的属性。我们只知道协议中声明的 gettable 属性。

简而言之,您已经声明 ModelDto 有一个 get/set 变量,所以如果 Swift 确实不让您设置它,那确实会很奇怪。拥有 get only 变量将取决于您通过协议引用对象或将 ModelDTO 上的 objectId 更改为 let 变量。

编辑:为了解决您对为什么允许ModelDto 具有可设置变量的困惑。这与ModelDto 允许具有协议中定义的功能之外的其他功能相同。 Getter 和 setter 实际上只是函数,因此需要 getter 的协议并不排除实现也具有 setter。在 Objective C 中也是如此。协议是描述性的,而不是限制性的。

【讨论】:

    【解决方案4】:

    我是在一般意义上回答这个问题。

    在回答问题之前,您必须知道get 和set 是什么意思。

    (如果你来自 Objective-C 世界:)get 表示 只读,也就是说我可以知道动物的腿数。我不允许设置它。 get & set 一起表示 readWrite 即我可以知道动物的重量,同时我也可以设置/更改动物的重量

    下面的例子。

    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'
    }
    

    你不能隐瞒,因为你答应提供二传手……

    【讨论】:

    • 谢谢亲爱的 :)
    【解决方案5】:

    您在代码示例中看到的行为称为成员隐藏。成员隐藏发生在面向对象的语言中,当新成员声明为与继承的成员具有相同的名称或签名时,因此具有: var objectId: Int 在您的结构实现中,您实际上是在创建一个名为 objectId 的新成员并隐藏从协议继承的属性。

    为了遵守结构和协议之间的约定,objectId 可以声明为:

      let objectId: Int = 1
    

    或

    var objectId: Int {
            get {
                return 1
            }
        }
    

    【讨论】:

      【解决方案6】:

      在您的类中,您创建一个名为objectId 的存储属性。在您的协议中,您指定该属性需要一个 getter——这是它的唯一要求。

      如果您希望它成为计算机属性,就像您期望的那样,您需要使用以下内容声明 objectId:

      var objectId: Int{ return (someNumber) }
      

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

      【讨论】:

      • 如果是这样,protocols中的gettable和settable定义完全没用,不是吗?
      • @ArturPajonk 如果您同时指定了 getter 和 setter,那么您的属性 必须 有一个 setter。否则,它可以是。
      猜你喜欢
      • 2023-04-06
      • 2018-05-21
      • 1970-01-01
      • 2016-05-13
      • 1970-01-01
      • 1970-01-01
      • 2019-11-23
      • 1970-01-01
      • 2017-06-27
      相关资源
      最近更新 更多