关于objective c:Swift中的只读和非计算变量属性

Read-only and non-computed variable properties in Swift

我正在尝试用新的AppleSwift语言来解决问题。假设我以前在Objective-C中做过类似的事情,我有readonly属性,它们不能单独更改。但是,使用特定的方法,属性以逻辑方式更改。

我举个例子,一个非常简单的时钟。我会写在目标C中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@interface Clock : NSObject

@property (readonly) NSUInteger hours;
@property (readonly) NSUInteger minutes;
@property (readonly) NSUInteger seconds;

- (void)incrementSeconds;

@end

@implementation Clock

- (void)incrementSeconds {
     _seconds++;

     if (_seconds == 60) {
        _seconds = 0;
        _minutes++;

         if (_minutes == 60) {
            _minutes = 0;
            _hours++;
        }
    }
}

@end

为了一个特定的目的,我们不能直接触摸秒、分钟和小时,只允许使用一种方法一秒一秒地递增。只有此方法才能使用实例变量的技巧来更改值。

既然在斯威夫特没有这样的东西,我正试图找到一个等价物。如果我这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Clock : NSObject {

    var hours:   UInt = 0
    var minutes: UInt = 0
    var seconds: UInt = 0

    func incrementSeconds() {

        self.seconds++

        if self.seconds == 60 {

            self.seconds = 0
            self.minutes++

            if self.minutes == 60 {

                self.minutes = 0
                self.hours++
            }
        }
    }
}

这是可行的,但任何人都可以直接更改属性。

也许我在Objective-C中已经有了一个糟糕的设计,这就是为什么潜在的新的Swift等价物没有意义。如果没有,如果有人有答案,我将非常感激;)

也许苹果承诺的未来访问控制机制就是答案?

谢谢!


只需在属性声明前面加上private(set),如下所示:

1
2
3
public private(set) var hours:   UInt = 0
public private(set) var minutes: UInt = 0
public private(set) var seconds: UInt = 0

private将其保持在源文件的本地,而internal将其保持在模块/项目的本地。

private(set)创建read-only属性,而privatesetget设置为私有。


做你想做的事有两种基本的方法。第一种方法是拥有私有财产和返回该财产的公共计算财产:

1
2
3
4
5
6
7
8
9
public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

但是,这可以用一种不同的、较短的方式实现,如《Swift编程语言》一书的"访问控制"一节所述:

1
2
3
4
5
public class Clock {

    public private(set) var hours = 0

}

作为附带说明,在声明公共类型时必须提供公共初始化器。即使为所有属性提供默认值,init()也必须明确定义为public:

1
2
3
4
5
6
7
8
9
10
public class Clock {

    public private(set) var hours = 0

    public init() {
      hours = 0
    }
    // or simply `public init() {}`

}

在讨论默认初始化程序时,本书的同一节也解释了这一点。


由于没有访问控制(也就是说,您不能根据呼叫者的身份制定不同的访问合同),下面是我现在要做的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Clock {
    struct Counter {
        var hours = 0;
        var minutes = 0;
        var seconds = 0;
        mutating func inc () {
            if ++seconds >= 60 {
                seconds = 0
                if ++minutes >= 60 {
                    minutes = 0
                    ++hours
                }
            }
        }
    }
    var counter = Counter()
    var hours : Int { return counter.hours }
    var minutes : Int { return counter.minutes }
    var seconds : Int { return counter.seconds }
    func incrementTime () { self.counter.inc() }
}

这只是增加了一个间接的级别,就像以前那样,直接访问计数器;另一个类可以制作一个时钟,然后直接访问它的计数器。但是我们的想法——也就是说,我们试图达成的契约——是另一个类应该只使用时钟的顶级属性和方法。我们不能执行那个合同,但实际上在Objective-C中也很难执行。


实际上,访问控制(在Swift中还不存在)并不像您在目标C中想象的那样强制执行。如果人们真的想要,他们可以直接修改您的只读变量。它们只是不使用类的公共接口。

您可以在swift中执行类似的操作(剪切和粘贴您的代码,加上一些修改,我没有测试它):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Clock : NSObject {

    var _hours:   UInt = 0
    var _minutes: UInt = 0
    var _seconds: UInt = 0

    var hours: UInt {
    get {
      return _hours
    }
    }

    var minutes: UInt {
    get {
      return _minutes
    }
    }

    var seconds: UInt  {
    get {
      return _seconds
    }
    }

    func incrementSeconds() {

        self._seconds++

        if self._seconds == 60 {

            self._seconds = 0
            self._minutes++

            if self._minutes == 60 {

                self._minutes = 0
                self._hours++
            }
        }
    }
}

它与目标C中的相同,只是实际存储的属性在公共接口中可见。

在Swift中,您还可以做一些更有趣的事情,在目标C中也可以,但在Swift中可能更漂亮(在浏览器中编辑,我没有测试它):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Clock : NSObject {

    var hours: UInt = 0

    var minutes: UInt {
    didSet {
      hours += minutes / 60
      minutes %= 60
    }
    }

    var seconds: UInt  {
    didSet {
      minutes += seconds / 60
      seconds %= 60
    }
    }

    // you do not really need this any more
    func incrementSeconds() {
        seconds++
    }
}