Objects and Classes

Xcode 6.0 Beta

Swift学習中。

The Swift Programming Language.epubのA Swift Tour内のObjects and Classesの項を読んだ。

クラスの定義と使用方法

クラスの定義には、キーワード class に続けてクラス名を記述し、クラス定義部を括弧 { } で括る。単純な(Objective-Cでsynthesizeするだけのような)プロパティは、定数や変数の宣言とほぼ変わらない。メソッドも、それがクラスインスタンスに結びつけられるという以外において、細かい点を除けば、関数の定義方法とほぼ変わらない。

class Shape {
    var numberOfSides = 0

    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

クラスをインスタンス化するには、newのようなキーワードは不要で、ただ単にクラス名の後ろに括弧 () をつければよい。

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

初期化と解放

インスタンスの初期化を行うには、init という特殊なメソッドを定義する。これをイニシャライザと呼ぶ。インスタンスの解放処理が必要な場合は、deinit という特殊なメソッドを定義する。initは引数を受け取ることができるが、deinitは引数を受け取ることはできないし、宣言部に括弧 ( ) を書く必要はない(書くことはできない)。

class MyObject {
    init() {
        println("init")
        // 初期化コード
    }

    deinit {
        println("deinit")
        // 解放コード
    }
}

func test()
{
    let obj = MyObject()
}

println("start")
test()
println("end")
start
init
deinit
end

継承、及び self と super

標準の基底クラス(Objective-CのNSObjectのようなもの)というものは存在しないので、クラスの作成時に、必ずしも親クラスを指定しなければならないということはない。継承を行う場合、

class サブクラス名 : 親クラス名

のように、コロンを使う。

Objective-Cと同じように、自身のインスタンスを指すために self を、親クラスのインスタンスを指すために super を使うことができる。

イニシャライザでパラメータを受け取り、selfを使って、同名のプロパティと区別する例。

class NamedShape {
    var numberOfSides: Int = 0
    var name: String

    init(name: String) {
        self.name = name
    }

    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

親クラスのメソッドをオーバーライドする場合は、override キーワード必須。

クラス継承とsuperを使って親クラスのインスタンスにアクセスする例。

class Square : NamedShape {
	var sideLength: Double

	init(sideLength: Double, name: String) {
		self.sideLength = sideLength
		super.init(name: name)
		numberOfSides = 4
	}

	func area() -> Double {
		return sideLength * sideLength
	}

	override func simpleDescription() -> String {
		return "A square with sides of length \(sideLength)."
	}
}

let test = Square(sideLength: 5.2, name: "my test square")
println(test.area())
println(test.simpleDescription())

プロパティのゲッターとセッター

クラス内にvarでプロパティを作成した場合、単純に値を取得、設定するだけのデフォルト挙動が暗黙的に与えられる。自分でカスタムのゲッターやセッターを作る場合は、プロパティごとに、get { } ブロックや、set { } ブロックを作成する。

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0

    init(sideLength: Double, name: String) {
    	self.sideLength = sideLength
    	super.init(name: name)
    	numberOfSides = 3
    }

    var perimeter: Double {
    get {
    	return 3.0 * sideLength
    }
    set {
    	sideLength = newValue / 3.0
    }
    }

    override func simpleDescription() -> String {
    	return "An equilateral triagle with sides of length \(sideLength)."
    }
}

var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
println(triangle.perimeter)
triangle.perimeter = 9.9
println(triangle.sideLength)

セッターには、デフォルトで newValue という名前のパラメータが暗黙的に渡される。
set(paramName)
として、newValue 以外の名前をつけることも可能。

class MyObject {
    var name: String {
    get {
        return self.name
    }
    set(newName) {
        self.name = newName
    }
    }
}

プロパティにはただ値を保持させるだけでよく、特別な計算が必要ない場合、だが、プロパティに値がセットされる前、または後で何か別の処理をさせたいような場合には、willSet や didSet が使える。

以下は、三角形の辺の長さを四角形の辺の長さと等しく保つクラスの例。

class TriangleAndSquare {
    var triangle: EquilateralTriangle {
    willSet {
        square.sideLength = newValue.sideLength
    }
    }
    
    var square: Square {
    willSet {
        triangle.sideLength = newValue.sideLength
    }
    }
    
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}

var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
println(triangleAndSquare.square.sideLength)
println(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
println(triangleAndSquare.triangle.sideLength)

メソッドの引数

関数とメソッドには1つ重要な違いがある。メソッドの呼び出し時には、最初のパラメータを除いて、他、すべてのパラメータに対して引数ラベルが必須である。もしメソッドの定義時に引数ラベルが明示的に作成されなかった場合、引数名と同じ名前の引数ラベルが自動的に作成される。

【引数ラベルを明示しない場合】

class Counter {
    var count: Int = 0

    // 引数ラベル指定なし
    // → 2つ目以降のパラメータに対して、暗黙で仮引数名と同じ引数ラベルがつけられる。
    func incrementBy(amount: Int, times: Int) {
        count += amount * times
    }

    func __conversion() -> String {
        return String(count)
    }
}

var counter = Counter()

// メソッドの場合、2つ目以降のパラメータには引数ラベル必須
// この点は関数と異なるところ。
counter.incrementBy(2, times: 7)

println(counter as String)

【引数ラベルを明示する場合】

class Counter {
    var count: Int = 0

    // 仮引数名とは別の引数ラベルを指定
    func incrementBy(amount: Int, numberOfTimes times: Int) {
        count += amount * times
    }

    func __conversion() -> String {
        return String(count)
    }
}

var counter = Counter()
counter.incrementBy(2, numberOfTimes: 7)
println(counter as String)

また、この章では明記されていなかったが、コード例と実際に試したところから、イニシャライザのパラメータに関しては、呼び出し時に、最初のパラメータからして引数ラベルをつけなければならないようだ。

class Counter {
    var count: Int = 0

    // この引数ラベルは省略可能。省略した場合は start が引数ラベルになる。
    init(initialValue start: Int) {
        self.count = start
    }
    
    func incrementBy(amount: Int, numberOfTimes times: Int) {
        count += amount * times
    }

    func __conversion() -> String {
        return String(count)
    }
}

// イニシャライザの場合は、メソッドと異なり、最初のパラメータも引数ラベル必須。
// var counter = Counter(0) ではコンパイルエラーになる。
var counter = Counter(initialValue: 0)

counter.incrementBy(2, numberOfTimes: 7)
println(counter as String)

オプショナル

オプショナル型としてインスタンスを使用する場合は、メソッド、プロパティ、添字の前に ? をつけることができる。この場合、以下のいずれかの挙動を取る。

・? の前の値が nil だった場合は、? の後ろはすべて無視され、式全体は nil と評価される。
・? の前の値が nil でない場合は、アンラップされた値をもとに、以降の式が評価される。

いずれの場合も、式全体の評価結果はオプショナル型になる。

// オプショナル型インスタンスを作成
// (先ほど作成したイニシャライザは削除してある)
var counter: Counter? = Counter()

// nilでないので、アンラップされて、式が評価される
println(counter?.count)	//=>0

counter = nil

// nilなので、式は評価されずに、評価結果がnilとされる
println(counter?.count)	//=>nil

アクセスコントロール

現状のXcode Beta版のSwiftでは、メンバはすべてpublicであり、privateにすることはできないが、将来的にはアクセスコントロールは実装される予定らしい。

【参考】
http://stackoverflow.com/questions/24003918/does-swift-have-access-modifiers



クラスが使えるようになると、1ステップ前進できたような気分になる。

今日はここまで。