読者です 読者をやめる 読者になる 読者になる

【Swift】Appleの新言語「Swift」のリファレンスを読む(8) - Properties

注意

  • あくまでメモ書きなので細かい部分を端折りますし、色々間違ってるかもしれません。ちゃんとした内容は原文を読んでね。
  • コード例は基本的に原文からそのまま引用していますが、ちょっとした注釈をつけたり、統合したりしています。
  • SyntaxHighlighterが対応してないので微妙に読みにくいです。SyntaxHighlighterはこちらのものを使用させて頂いてます。
  • 他言語にそっくりな部分でも指摘しない。(自戒)

Properties(プロパティ)

Swiftにおけるプロパティは、C#のプロパティと大体同義です。まぁ「○○に似ている」と言い出したらキリがない言語ですし、割とモダンな言語ならアクセサつきのメンバなんて珍しくないですからサクッといきましょう。

これを定義できるのはClass、Structure、Enumerationです。構造として他に何があると言うのでしょう。Function?

Stored Properties

プロパティの最もシンプルなパターンはこんな感じです。

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}

var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8

FixedLengthRangeのfirstValue及びlengthがプロパティです。構造体なので自動でイニシャライザが提供されます。(参考

構造体FixedLengthRangeを代入する変数、rangeOfThreeItemsはvarで宣言されています。

これをletにするとプロパティの値を変更することが出来なくなります。

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// this will report an error, even thought firstValue is a variable property

ただし、これはあくまで構造体のみの話です。FixedLengthRangeがclassで宣言されていればfirstValueの値を書き換えることが出来ます。

letで宣言されているプロパティ、lengthの方はFixedLengthRangeが構造体だろうがクラスだろうが変更出来ません。

また、プロパティに対し@lazyと言うAttributeをつけることで遅延初期化することが可能です。

遅延初期化を説明する例としてはあまりに何のメリットもないコード例が載っています。

class DataImporter {
    /*
    DataImporter is a class to import data from an external file.
    The class is assumed to take a non-trivial amount of time to initialize.
    */
    var fileName = "data.txt"
    // the DataImporter class would provide data importing functionality here
}

class DataManager {
    // 遅延初期化を行う場合は必ずvarで宣言する
    @lazy var importer = DataImporter()
    var data = String[]()
    // the DataManager class would provide data management functionality here
}

let manager = DataManager()
manager.data += "Some data"
manager.data += "Some more data"
// この時点ではまだDataManager#importerは初期化されていない

println(manager.importer.fileName)
// ここで初めてDataManager#importerが初期化される
// prints "data.txt"

Computed Properties

プロパティの値が外部から呼び出されたとき、あるいは外部から変更された時の挙動を自分で定義することが出来ます。

このような計算して導き出すプロパティ(Computed Properties)は必ずvarで宣言する必要があります。これは後述するRead Onlyなプロパティであっても例外ではありません。

struct Point {
    var x = 0.0, y = 0.0
}

struct Size {
    var width = 0.0, height = 0.0
}

struct Rect {
    var origin = Point()
    var size = Size()

    var center: Point {
    get {
        let centerX = origin.x + (size.width / 2)
        let centerY = origin.y + (size.height / 2)
        return Point(x: centerX, y: centerY)
    }
    set(newCenter) {
        origin.x = newCenter.x - (size.width / 2)
        origin.y = newCenter.y - (size.height / 2)
    }
    }
}


var square = Rect(origin: Point(x: 0.0, y: 0.0),
    size: Size(width: 10.0, height: 10.0))

let initialSquareCenter = square.center

square.center = Point(x: 15.0, y: 15.0)

println("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// prints "square.origin is now at (10.0, 10.0)"

微妙に複雑ですね。

Rectが所持するプロパティ、centerは同じくRectの所持するoriginとsizeが定義されていれば計算して求めることが出来ます。また、centerとsizeが定義されていればoriginを求めることも出来ます。

27行目ではまずoriginとsizeを定義しています。この時点でsquare#centerの値を取得しようとすると、getで括られたブロックのコードが実行され、x = 5.0, y = 5.0のPointが返って来ます。

31行目ではcenterの値を書き換えています。今度はsetで括られたブロックのコードが実行され、Rect#originの値が書き換わります。後はコード例の通りですね。

上記の例ではsetterのわざわざ変数名(newCenter)を記述しているんですが、省略も可能です。省略すると自動でnewValueと言う変数が生成されます。

struct AlternativeRect {
    var origin = Point()
    var size = Size()
    var center: Point {
    get {
        let centerX = origin.x + (size.width / 2)
        let centerY = origin.y + (size.height / 2)
        return Point(x: centerX, y: centerY)
    }
    set {
        origin.x = newValue.x - (size.width / 2)
        origin.y = newValue.y - (size.height / 2)
    }
    }
}

何らかの計算を行って求めるプロパティをRead Onlyにしたい場合は、getの中身だけ記述すればOKです。

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}

let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
println("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// prints "the volume of fourByFiveByTwo is 40.0"

Property Observers

プロパティはsetが呼び出されるとその通知を受け取ることが出来ます。これは遅延初期化されるプロパティであっても可能ですし、親クラスから継承したプロパティであっても可能です。(後者は必要に応じてOverrideする必要がある。)

変更される直前に行う処理はwillSetキーワード、変更された直後に行う処理はdidSetキーワードを使います。

class StepCounter {
    var totalSteps: Int = 0 {
    willSet(newTotalSteps) {
        println("About to set totalSteps to \(newTotalSteps)")
    }
    
    didSet {
        if totalSteps > oldValue {
            println("Added \(totalSteps - oldValue) steps")
        }
    }
    }
}

let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

なお、willSet / didSetともにイニシャライザでのプロパティ初期化時には呼び出されません。

正直、わざわざ言語レベルでサポートするような機能か?って感じがします。もうちょっと洗練出来そうな気もするので今後に期待ですかね。

Global and Local Variables

スコープに関する話が物凄く抽象的に書かれています。

Computed PropertiesやProperty Observersであっても、その外部で宣言されている変数なら使うことが出来るし、内部で宣言されている変数は内部にしかスコープを持ちません。そんな話です。

まぁ、そうであってくれないと直感的に使いにくいですしね。

Type Properties

型そのものに属するプロパティ、つまり、static変数の話です。

Swiftのstatic変数は必ず初期値を与える必要があります。また、値型と参照型でちょっと記述方法が変わります。

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        // return an Int value here
    }
}

enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        // return an Int value here
    }
}

class SomeClass {
    // クラスの場合は"static"ではなく"class"と記述する
    class var computedTypeProperty: Int {
        // return an Int value here
    }
}

staticであろうとプロパティであることに変わりはないので、当然今まで説明してきたことは全部使えます。例ではcomputedTypePropertyがRead Onlyにされてますね。

プロパティへのアクセス方法も何となく見慣れたものです。「型名.プロパティ」です。

println(SomeClass.computedTypeProperty)
// prints "42"

println(SomeStructure.storedTypeProperty)
// prints "Some value."

SomeStructure.storedTypeProperty = "Another value."
println(SomeStructure.storedTypeProperty)
// prints "Another value."

その後はだらだらと「Type Propertiesの値は全インスタンスで共有される」みたいなことを示すコードが載っています。インスタンスにおける静的な変数とは何かを知っていれば特に読む必要がないので割愛します。

しかしこのクラスのType Propertiesの構文、もうちょっとどうにかならなかったんですかね。せめて全部staticで統一出来ていればよかったんですが。