Swift协议只能设置?

问题描述 投票:31回答:6

为什么我可以做到这一点没有任何错误:

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定义),为什么我还要使用它们呢?

ios swift protocols getter-setter
6个回答
24
投票

根据official documentation

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


46
投票

苹果公司在"Swift Programming Language (Swift 3)"说:

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

因此,以下五个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"

Apple还声明:

如果协议要求属性可获取和可设置,则不能通过常量存储属性或只读计算属性来满足该属性要求。

因此,以下两个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
    }
}

2
投票

考虑以下:

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变量将依赖于您通过协议引用对象或将objectId上的ModelDTO更改为let变量。

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


1
投票

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

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

var objectId: Int{ return (someNumber) }

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


1
投票

我正在回答这个问题的一般意义。

在解决这个问题之前,你必须知道getset是什么意思。

(如果你来自Objective-C世界:) get意味着readOnly,那就是我被允许知道动物的腿数。我不被允许设置它。 getset一起意味着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'
}

你不被允许隐藏,因为你答应提供一个二传手......


0
投票

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

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

  let objectId: Int = 1

要么

var objectId: Int {
        get {
            return 1
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.