web-dev-qa-db-ja.com

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

新しい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に相当するものが意味をなさないのかもしれません。そうでない場合、そして誰かが答えを持っている場合、私は非常に感謝するでしょう;)

たぶん未来アクセス制御メカニズム Appleによって約束された答えですか?

ありがとう!

119
Zaxonov

次のように、プロパティ宣言の前に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プロパティを作成し、privatesetgetの両方をプライベートに設定します。

346
Ethan

あなたがしたいことをする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 class Clock {

    public private(set) var hours = 0

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

}

これは、デフォルトの初期化子について説明するときに、本の同じセクションでも説明されています。

20
elitalon

アクセス制御がないため(発信者が誰であるかによって異なるアクセスコントラクトを作成できないことを意味します)、ここで私がやることは次のとおりです。

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

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

5
matt

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

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++
    }
}
2
Analog File