【问题标题】:Read-only access for instance property in SwiftSwift 中实例属性的只读访问
【发布时间】:2017-06-18 08:29:57
【问题描述】:

我知道private(set) 讨论过herehere,但它似乎不适用于我有具有内部属性的实例变量的情况。

我有一个实例属性,我希望它具有只读访问权限。以下代码显示了一些具体示例。

class Point {
    var x: Int
    var y: Int

    init(_ x: Int, _ y: Int) {
        self.x = x
        self.y = y
    }
}

class Sprite {
    private(set) var origin = Point(0,0)

    func incrementX() {
        origin.x += 1
    }

    func incrementY() {
        origin.y += 1
    }
}

在这里,我的意图是让Sprite 成为origin 的唯一所有者,让它成为唯一可以修改它的人,否则使origin 对公众只读。然而:

var sprite = Sprite()
sprite.origin = Point(100, 200)        // Error: setter is inaccessibie. Expected behavior.
sprite.origin.x = 150                  // No error. The origin.x will be set to 150.

显然我仍然可以通过直接修改内部xy来修改origin,但这不是我的意图。

如何使origin 真正只读?我错过了什么吗?

Point 修改为private var x: Intfileprivate var x: Int 对我不起作用,因为我希望能够让Sprite 这样的外部类修改Point

编辑:从下面的评论来看,这不是我的实际情况,因为有人认为我应该使用CGPoint。该代码作为示例,使我的问题更加具体。我实际上在另一个类中有一个复杂的类作为实例属性,而不是这个简单的Point。由于其他设计限制,我也不能将其用作struct

Edit2:下面的答案指出您不能阻止引用是可变的。这是我刚刚学到的 Swift 的一个限制,因为显然,我可以使用 Point const & 在 C++ 中实现我想要的:

class Point {
public:
    int x;
    int y;

    Point(int _x, int _y) {
        x = _x;
        y = _y;
    }

    Point & operator=(const Point & rhs) {
        x = rhs.x;
        y = rhs.y;
        return *this;
    }

    ...

};

class Sprite {

    Point origin;

public:

    Sprite()
    : origin(0,0)
    {}

    // HERE
    Point const & getOrigin() const { return origin; }

    void incrementX() {    origin.x += 1; }
    void incrementY() {    origin.y += 1; }

};

int main(int argc, const char * argv[]) {
    Sprite sprite = Sprite();
    Point const & o = sprite.getOrigin();

    cout << "o: " << o << endl;  // (0,0)
//    o.x = 10;                  // Error: Cannot assign to const-qualified type.
//    o = Point (100, 200);      // Error: no viable overlaod =.


    sprite.incrementX();

    cout << "o: " << o << endl;  // (1,0). Swift can't get this behavior with `struct Point`, but it won't prevent some writing attempts with `class Point`.
    cout << "sprite.origin: " << sprite.getOrigin() << endl;  // (1,0).

    return 0;
}

【问题讨论】:

  • 这不是“类属性”。它是一个 instance 属性。

标签: swift properties readonly access-control


【解决方案1】:

只需将Point 设为结构

struct Point {
    var x: Int
    var y: Int

    init(_ x: Int, _ y: Int) {
        self.x = x
        self.y = y
    }
}

class Sprite {
    private(set) var origin = Point(0,0)

    func incrementX() {
        origin.x += 1
    }

    func incrementY() {
        origin.y += 1
    }
}

然后再试一次

var sprite = Sprite()
sprite.origin.x = 1
error: cannot assign to property: 'origin' setter is inaccessible

顺便说一句:你为​​什么不简单地使用CGPoint 而不是定义一个新类型?

【讨论】:

  • @appzYorLife 感谢您的回复。我的代码作为示例,使我的问题更加具体。我实际上有一个复杂的类作为另一个类的属性,而不是这个简单的Point。由于其他设计限制,我也不能将其用作结构。但是,你知道为什么 Swift 在这方面对structclass 的处理方式不同吗?
  • @HuaTham:那是因为结构是值类型。请阅读thisthis
【解决方案2】:

试试这个:

class Point {
var x: Int
var y: Int

init(_ x: Int, _ y: Int) {
    self.x = x
    self.y = y
}
}

class Sprite {
private var privatePoint = Point(0,0)
var origin:Point{
    get{
        return privatePoint
    }
}
}

所以你可以这样做:

var sprite = Sprite()
sprite.origin = Point(100, 200)        // Error: setter is inaccessibie. Expected behavior.
sprite.origin.x = 150                  // No error. The origin.x will be set to 150.

【讨论】:

  • 除非我读错了,否则这似乎并不能解决 OP 的问题,它与 OP 试图避免的问题相同,
【解决方案3】:

使origin 完全成为private。唯一公开的应该是一个只读计算属性。您的公共incrementXincrementY 方法仍将按需要工作,因为它们被允许访问origin

如果 Point 是一个结构,那么这个只读属性返回 origin 就足够了,因为它将是一个副本:

var currentOrigin : Point {
    return self.origin
}

但是,如果您坚持将 Point 设为类而不是结构,那么您将 reference 出售给您已经保留的同一 Point 实例,并且您无法阻止它可变的。因此,您必须生成self.origin 的副本您自己,这样您就不会出售对您自己的私有实例的可变引用:

var currentOrigin : Point {
    return Point(self.origin.x,self.origin.y)
}

(Objective-C Cocoa 通常解决此类问题的方式是实现一个由不可变/可变对组成的类集群;例如,我们维护一个 NSMutableString 但我们提供一个 NSString 副本,所以我们的可变字符串不能在背后改变。但是,我认为你拒绝使用结构是非常愚蠢的,因为这是 Swift 相对于 Objective-C 的巨大优势之一,即它解决了你的问题有。)

【讨论】:

  • 我决定接受您的回答,因为它最接近我正在寻找的内容。据我所知,在 Swift 中可能还没有一种语言特性,可以让 reference 指向一个实例而不能修改它。但就目前而言,即使struct 没有按设计保持副本更新,它仍然不会对性能造成负担,因为 Swift 具有引擎盖下的优化,仅在绝对必要时进行复制。
  • 当我有一个合法的用例坚持使用class而不是struct时,我也不同意“愚蠢”。一旦我从struct 实例获得副本,我自然会丢失对原始副本所做的所有更新。我的观点(不是双关语)是我希望能够保留对同一个实例的引用,同时仅使该引用的某些所有者能够both 读取和写入。请参阅我更新的 OP 中的 C++ 示例。
  • C++ 传递指针(以节省时间/资源)同时防止接收者改变指向的东西的能力当然很酷。可以肯定的是,斯威夫特没有什么可比的。但是,正如我所说,Point 可以是一个结构,但 incrementXincrementY 仍然可以完全按照所写的那样工作。并且有一些方法可以传递结构的地址,以便接收者可以隐式写回原始地址(例如inout)。在我看来,这里真正的问题在于,你问的问题不是你想要回答的问题。你的例子是一个不具代表性的减少。
【解决方案4】:
class Point {
 private var x: Int
 private var y: Int

 init(_ x: Int, _ y: Int) {
     self.x = x
     self.y = y
 }

 func incrementX() {
     x += 1
 }

 func incrementY() {
     y += 1
 }

 func setX(_ value : Int){
     x = value
 }

 func setY(_ value : Int){
     y = value
 }
}

class Sprite {

    private (set) var origin = Point(0,0)

}

如果您需要获取 xy 值,您应该在 Point 类中添加 getter,如下所示:

func getX()->Int{
    return x
}

所以你可以试试这个:

let sprite = Sprite()
sprite.origin.incrementX()

let point = Point(10,20)
point.setX(12)
point.setY(100)

所以您可以将origin 设为只读,我认为您必须将xy 设置为私有。

【讨论】:

  • 我认为这不能解决 OP 的问题。这只将对 x 和 y 的直接访问转移到 setter,这仍然允许以 OP 不希望的方式修改底层 Point。
【解决方案5】:

直接暴露Point是不可能得到你想要的结果的,它是一个类,会通过引用传递。您可以使用协议仅公开您想要的 Point 部分。

protocol ReadablePoint {
    var x: Int { get }
    var y: Int { get }
}

class Point: ReadablePoint {
    var x: Int
    var y: Int

    init(_ x: Int, _ y: Int) {
        self.x = x
        self.y = y
    }
}

class Sprite {
    private var _origin = Point(0,0)
    var origin: ReadablePoint { return _origin }

    func incrementX() {
        _origin.x += 1
    }

    func incrementY() {
        _origin.y += 1
    }
}

结果:

var sprite = Sprite()
sprite.origin = Point(100, 200)        // error: cannot assign to property: 'origin' is a get-only property
sprite.origin.x = 150                  // error: cannot assign to property: 'x' is a get-only property

【讨论】:

  • 我认为“PointProtocol”应该是“ReadablePoint”。
  • 这可能只是在这个问题的上下文之外,但是:(sprite.origin as!Point).x = 5
猜你喜欢
  • 1970-01-01
  • 2010-10-11
  • 2014-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-21
相关资源
最近更新 更多