Swiftの読み取り専用で計算されない変数プロパティ


126

新しいApple Swift言語で何かを理解しようとしています。Objective-Cで次のようなことをしていたとしましょう。私にはreadonlyプロパティがあり、個別に変更することはできません。ただし、特定の方法を使用すると、プロパティは論理的に変更されます。

次の例、非常に単純な時計を取り上げます。これはObjective-Cで記述します。

@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

特定の目的のために、秒、分、時間に直接触れることはできません。メソッドを使用して秒単位で増分することのみが許可されています。このメソッドだけが、インスタンス変数のトリックを使用して値を変更できます。

Swiftにはそのようなものはないので、同等のものを見つけようとしています。私がこれをすると:

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の同等物が意味をなさないのです。そうでなければ、誰かが答えを持っているなら、私はとても感謝しています;)

たぶん、アップルが約束する将来のアクセス制御メカニズムがその答えでしょうか?

ありがとう!


2
アクセス制御がその答えです。読み取り専用の計算プロパティを作成し、実際のデータにはプライベートプロパティを使用します。
レオナタン

バグレポート(機能強化)を開いて、Appleにどれほどひどく欠けているかを知らせてください。
レオナタン

1
新規ユーザーの場合:一番下までスクロールしてください
Gustaf Rosenblad 14

2
@Ethanの回答を正しいものとしてマークしてください。
ファットマン、2015年

回答:


356

次のprivate(set)ように、プロパティ宣言の前に単にを付けます。

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ながら、プロパティをprivateセットの両方、setおよびgetプライベートに。


28
ソースファイルに対してローカルに保つには「プライベート」、モジュール/プロジェクトに対してローカルに保つには「内部」。private(set)は、セットをプライベートにするだけです。Privateは、関数の設定と取得の両方を非公開にします
user965972

3
より完全にするために、最初のコメントをこの回答に追加する必要があります。
Rob Norback 2016年

現在(Swift 3.0.1)アクセス制御レベルが変更されました:「fileprivate」宣言は、宣言と同じソースファイル内のコードによってのみアクセスできます。「プライベート」宣言には、宣言の直接の囲みスコープ内のコードによってのみアクセスできます。
Jurasic

20

やりたいことを行うには、2つの基本的な方法があります。最初の方法は、プライベートプロパティと、そのプロパティを返すパブリックの計算プロパティを持つことです。

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

しかし、これは、「The Swift Programming Language」本の「Access Control」のセクションで述べられているように、別の短い方法で実現できます。

public class Clock {

    public private(set) var hours = 0

}

補足として、パブリックタイプを宣言するときは、パブリックイニシャライザを提供する必要があります。すべてのプロパティにデフォルト値を提供する場合でも、init()明示的にpublicに定義する必要があります。

public class Clock {

    public private(set) var hours = 0

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

}

これは、デフォルトのイニシャライザについて話すときに、本の同じセクションでも説明されています。


5

アクセス制御がない(つまり、呼び出し元が誰であるかによって異なるアクセスコントラクトを作成できない)ため、ここでは私が何をすべきかを説明します。

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() }
}

これは、カウンターへの直接アクセスに、あるレベルの間接性を追加するだけです。別のクラス Clockを作成し、そのカウンターに直接アクセスできます。しかし、アイデア、つまり、作成しようとしているコントラクトは、別のクラスはClockのトップレベルのプロパティとメソッドのみを使用するべきだということです。その契約を強制することはできませんが、実際にはObjective-Cでも強制することはほとんど不可能でした。


それ以来、これは変わったように見えますよね? developer.apple.com/swift/blog/?id=5
Dan Rosenstark

1
確かに、この質問/回答全体は、アクセス制御がSwiftに追加されるずっと前に書かれたものです。そしてSwiftはまだ進化しているので、答えは古くなっていきます。
マット

2

実際には、アクセス制御(Swiftにはまだ存在しません)は、Objective Cで考えているほど強制されていません。本当に必要な場合は、読み取り専用変数を直接変更できます。彼らはクラスのパブリックインターフェースではそれをしないだけです。

Swiftでも同様のことができます(コードのカット&ペーストといくつかの変更、テストしていません)。

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

これは、実際に格納されているプロパティがパブリックインターフェイスに表示されることを除いて、Objective Cにあるものと同じです。

Swiftではもっと興味深いこともできますが、Objective Cでも実行できますが、おそらくSwiftのほうがきれいです(ブラウザーで編集しましたが、テストしていません)。

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

「ユーザーは読み取り専用変数を直接変更できます」-正確にはどういう意味ですか?どうやって?
user1244109

私はObjective Cについて言及していました。ObjectiveCでは、Objective CランタイムAPIを通じてクラスに関するすべてに動的にアクセスできます。
アナログファイル

私はどういうわけかあなたがそれを迅速に暗示することを理解しました。気になった。ありがとう
user1244109
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.