Swift/オブジェクト指向

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動

型エイリアス[編集]

typealias Long = Int64

列挙型[編集]

何かのグループに属する名前の一覧をリストアップして宣言したい場合、列挙型(enum 型)が使える。「列挙体」ともいう。

基本[編集]

構文

enum 列挙体名 {
    case 列挙子名1
    case 列挙子名2
    case 列挙子名3
}

何のためにenumがあるかというと、たとえばswitch文で条件分岐する際、もしenumが無いと各条件に相当する数値をプログラマーが考えなければならず、プログラマーごとに数値のつけかたが違っていたりするとコード品質のバラつきにつながるなど非効率であるし、そのため後任者などの第三者がコードを読解しづらくなる(条件の数値がマジックナンバーになる)。もし順番を入れ替えたり、あるいは新規の条件を挿入する場合、もし enum が無いと条件の数値を割り振りしなおしになってしまう。enumによって、そういう問題を減らせる。


Swiftに限らず、enumでは、「列挙子名」が、個々のリスト名のことである。上記の構文中にcaseが見えるが、enum の宣言文自体はなんの条件判定も行わない(C言語など別言語のenumでも、enum自体では条件判定を行わない)。よって別途、if文やswitch文などによる条件判定が実装の際には必要になる。

プログラミング用語やプログラマーによっては、「列挙子」と呼ぶ代わりに「列挙定数」とも言う。


Swiftのenumはたとえば下記のように使う。

コード例

// 方角を列挙
enum Compass {
    case N
    case S
    case E
    case W
}

var direction = Compass.W


if (direction == Compass.N){
	print("北です");
}

if (direction == Compass.S){
	print("南です");
}

if (direction == Compass.E){
	print("東です");
}

if (direction == Compass.W){
	print("西です");
}

表示結果

西です

ひとつの列挙体の宣言の中で、列挙子名に重複があるとエラーになる。たとえばcase Wが2つあるとエラーになる。このような仕組みにより、一意性が保証される。つまり、ひとつの列挙体の中で同じ名前の列挙子は二つ無い。

上述のように、列挙体の各要素にアクセスするには、列挙型名.列挙子名となる。

条件判定などの際、列挙体の各要素へのアクセスが必要になるので、よってアクセス用の変数も別途宣言する必要があるので、たとえば上記コードのように

var direction = Compass.W

というような行が必要になる。


Swift の列挙体において、列挙体を格納した変数には、特に値を指定しない限りは、print などの際には列挙子の名前が表示される。

コード例

// 方角を列挙
enum Compass {
    case N
    case S
    case E
    case W
}

var direction = Compass.S
print(direction)

表示結果

S

関連値[編集]

列挙子に追加的に値を定義することは出来るが、その場合、列挙型のほうに型(Int または String など)を併記しないといけない。また、値の定義で等号 = を使う際、等号の左右には半角スペースがひとつ以上、必要である。コンパイラなどの事情だろう。

なお、下記コードの「北」など、要素に関連づけさせた追加情報の値のことを、英語で associated value といい、和訳で「関連する値」だとか「関連値」などという。

定義した値へのアクセスには enum変数.rawValue を使う。

コード例

enum Compass : String {
    case N = "北"
    case S
    case E
    case W
}

var direction = Compass.N
print(direction)
print(direction.rawValue)

表示結果

N
北

値が定義されていない場合、rawValue は上述のように列挙子名にアクセスするので、値が未定の部分は追加しないでそのままにすることもできる。

また、関連値は追加情報なので、もとの列挙子名はそのまま残る。

enumにおいて値の型を整数型で宣言した場合、特に値を上書きしない限りは、列挙子の宣言順に 0,1,2,・・・の値がそれぞれの列挙子の割り当てられる。

コード例

enum Compass : Int {
    case N 
    case S
    case E
    case W
}

var direction = Compass.N
print(direction)
print(direction.rawValue)
print()

direction = Compass.S
print(direction)
print(direction.rawValue)
print()

direction = Compass.E
print(direction)
print(direction.rawValue)
print()

表示結果

N
0

S
1

E
2

入れ子[編集]

他のプログラミング言語には見られない、Swift の enum の大きな特徴・利点として、下記のように enum の中に別のenumを入れて宣言することが可能です。このため、リストアップしたものを、共通点ごとに分類することができます。

一般に、入れ子のことをネスト(nest)と言います。つまり、Swiftのenumはネストできます。

コード例

enum animal {
    enum cat {
        case persian
        case himalayan
    }

    case dog
}
var x = animal.cat.persian
print(x)

表示結果

persian

内側のenumの前には、case は不要です。むしろ、内側のenum宣言の前にcaseがあるとエラーになります。つまり、caseが必要なのは、末端の列挙子の前だけです。

また、上記コードのように、同列の列挙子どうしをenum化するかどうかは、そろえる必要はありません。たとえば、dogについては上記コードでは、何もグループ化していません。

このため、要素のenumグループ化を始める際に、けっして要素すべてをいっぺんにグループ化する必要はなく、自分が現段階でグループ化しやすそうな要素だけをグループ化できるので、グルーピング作業を開始しやすい利点があります。たとえば上記コードの動物の例なら、ネコの品種には詳しくてもイヌの品種に詳しく無い場合でも、イヌはそのまま case dog だけにしておいて、ネコの品種だけをenumで細分化することができます。

この仕様のため、Swiftのenumのネストでは、分業もしやすい利点があります。つまり、イヌの品種のenum化については別途、イヌに詳しい人に任せることも出来ます。


case himalayan などの末端の列挙子ではなく、catという小分類そのものにアクセスしたい場合は、下記のように.selfプロパティを使います。

コード例

enum animal {
    enum cat {
        case persian
        case himalayan
    }

    case dog
}
var x = animal.cat.self
print(x)

表示結果

cat


Swift の enum のネストのさらに優れている点として、下記コードの case dog のようにまだグルーピングされておらず放置されている要素にも、.selfプロパティは使用可能であり、case dog の場合は.selfプロパティでは「dog」を返します。このおかげで、グルーピングの終わった要素と終わってない要素に対して、共通のコードを使いまわしできます。

コード例

enum animal {
    enum cat {
        case persian
        case himalayan
    }

    case dog
}

var x = animal.dog.self
print(x)

表示結果

dog


Int 型で宣言したenumがネストされた場合、rawValue で caseの値を読み取ろうとしても、エラーになる。数値の割り当てでは、ネストされた部分はスキップして、ネストされていない部分にだけ値を割り当てる仕組みである。

コード例

enum animal  : Int{
    enum cat {
        case persian 
        case himalayan
    }

    case dog 
    case bird    
}

var x = animal.dog
print(x.rawValue) 

var y = animal.bird
print(y.rawValue)

表示結果

0
1

上記のように enum のネスト部分のcaseは数値化されないため、配列との連動は困難である。

enum に enum をくみこむ[編集]

enumに、他のenumをくみこむ事が出来る。下記コードのようになる

コード例

enum nobird {
    case cat
    case dog
}

enum animal {
    case a(nobird)
    case bird
}

var x = animal.a( .dog)
print(x)

print(animal.bird)

表示結果

a(sample.nobird.dog)
bird


ただし、Int型で定義してみても、rawValueでの数値取得が、知られていない。このため配列との連動に問題がある。

また、enumを継承したりなどは、現状では特に知られていない。サポートは現状、期待しないほうが良いだろう。

クラスと構造体[編集]

インスタンスの初期値[編集]

Swift のクラスや構造体では、インスタンスの生成時までに原則、初期値を用意しないといけない。具体的な値の存在しないインスタンスにアクセスすることによるエラーを防止するため、例外的に整数変数や文字列変数などの単純な場合を除いて、配列やクラスなどでは原則、Swiftでは初期値を与えて宣言される事が仕様的に好まれている。(このため、C++、C#的にインスタンス時に構造体の型だけ用意して、あとから値を代入しようとしても、Swiftでは困難、ないしは不可能なことになる。もし JavaScript のような事実上の型の無い言語なら、空白の文字列""を暫定的な初期値として置いて、あとから値を変更するという回避策が使えるのだが、しかしSwiftは型のある言語なので、その回避策は使えない。文字列型を整数型や浮動小数型に変更するコードをいちいち書くのは手間である。型変換の命令もあるが、そこまでするぐらいなら、Swift以外の言語を使うか、設計方針を見直すほうがよいだろう。)

struct TestStruct {
    var num:Int

    init (num:Int) {
        self.num = num
    }
}

var struct1 = TestStruct(num: 3)

print("struct1.num = \(struct1.num)") // struct1.num = 4


クラスや構造体の定義の時点で初期値をすでに与えて入れば、インスタンスの生成時には初期値を指定する必要は無い。なお、この場合、イニシャライザがあるとエラーになるので、下記のようにイニシャライザは除去する。

struct TestStruct {
    var num:Int = 7 // 区別のため数値変更

}

var struct1 = TestStruct()

print("struct1.num = \(struct1.num)") // struct1.num = 7


Swiftでは初期値が無いとインスタンス生成が困難なので、どうしても初期値を決められない事情がある場合は、とりあえず数値を格納するインスタンスなら、たとえば年齢なら「-99」歳みたいにありえない数字を入れて区別するなどの方法がある。

だが、数学みたいにすべての範囲を数を扱いたい場合、このような方法では回避できない。その場合はSwiftではなく別の言語で開発するの良いだろう。

クラスと構造体の違い[編集]

クラスは参照型である一方、構造体は値型である。

class TestClass {
    var num:Int

    init (num:Int) {
        self.num = num
    }
}

var class1 = TestClass(num: 1)
var class2 = class1
class1.num = 2 // class1とclass2は同じオブジェクトを指す参照であるため、この代入は両方に影響する。
print("class1.num = \(class1.num)") // class1.num = 2
print("class2.num = \(class2.num)") // class2.num = 2
struct TestStruct {
    var num:Int

    init (num:Int) {
        self.num = num
    }
}

var struct1 = TestStruct(num: 3)
var struct2 = struct1
struct1.num = 4 // struct2はstruct1からコピーされた別の構造体であるため、この代入はstruct2には影響しない。
print("struct1.num = \(struct1.num)") // struct1.num = 4
print("struct2.num = \(struct2.num)") // struct2.num = 3

原則として、構造体メソッド内では自身のプロパティの値を変更することはできない。変更の必要がある場合は、メソッドの定義にmutatingキーワードを指定する必要がある。この場合、暗黙的にプロパティの値の再代入として処理される。

struct AnotherTestStruct {
    var num:Int

    init (num:Int) {
      self.num = num
    }

    mutating func increment() {
      self.num += 1
    }
}

var struct1 = AnotherTestStruct(num: 3)
print("struct1.num = \(struct1.num)") // struct1.num = 3
struct1.increment()
print("struct1.num = \(struct1.num)") // struct1.num = 4

プロパティ[編集]

ストアド・プロパティ[編集]

プロパティはletまたはvarを使って、ローカル定数/変数と同じように定義できる。
ストアド・プロパティは他言語のインスタンス変数に近いが、遅延初期化や値の監視など、より高機能である。

class SomeClass {
    var val1 = 0 // 宣言時に初期化する場合は、型推論により型を省略できる
    var val2: Int // 宣言時ではなくイニシャライザで初期化する場合は、型を明示する

    let val3 = 0
    let val4: Int

    init() {
        // 宣言時に初期化せずにイニシャライザ内で初期化してもよい
        val2 = 0
        val4 = 0
    }
}
let obj = SomeClass()
print(obj.val1)
obj.val2 = 1
//obj.val3 = 1 // letで宣言したプロパティには再代入不可
遅延ストアド・プロパティ[編集]

ストアド・プロパティは定義の前にlazy修飾子を付けることで、初めてアクセスされる時まで初期化の実行を遅らせることができる。

class HeavyClass {
    init() { print("HeavyClassをインスタンス化") }
    func method() { print("HeavyClassのメソッド") }
}

class SomeClass {
    lazy var heavy: HeavyClass = HeavyClass()

    init() { print("SomeClassをインスタンス化") }
    func method() { print("SomeClassのメソッド") }
}

var obj = SomeClass()
obj.method()
obj.heavy.method()  // ←ここで初めてheavyプロパティが初期化される

// 実行結果:
// SomeClassをインスタンス化
// SomeClassのメソッド
// HeavyClassをインスタンス化
// HeavyClassのメソッド
コンピューテッド・プロパティ[編集]

ゲッター/セッターを介してアクセスするプロパティを定義できる。他言語の一般的なプロパティに近い。

import Foundation

class Square {
    var sideLength: Double

    var area: Double {
        get {
            return sideLength * sideLength
        }
        set {
            sideLength = sqrt(newValue)
        }
    }

    init (sideLength: Double) {
        self.sideLength = sideLength
    }

    public var description: String {
        return "sideLength=\(self.sideLength)"
    }
}

let sq = Square(sideLength: 5)
println(sq.area) // 25.0

sq.area = 36
println(sq.sideLength) // 6.0
型プロパティ[編集]

一つの型のインスタンス全てで共有するプロパティ。他言語でいうクラス変数に近い。

クラスではclassキーワードを、構造体ではstaticキーワードを使って宣言する。

class MyClass {
    class var computedTypeProperty: Int {  }
}

struct MyStruct {
    static var storedTypeProperty = 42
    static var computedTypeProperty: Int {  }
}
サブスクリプト[編集]

配列と同じように、添字を使って要素にアクセスできる。

class CyclicSequence<T> {
    var content:Array<T>

    init (_ content:Array<T>) {
        self.content = content
    }

    subscript(index:Int) -> T {
        return content[index % content.count]
    }
}

var cycSeq = CyclicSequence<Int>([1, 4, 2, 8, 5, 7])

for i in 0..<10 {
    let item = cycSeq[i]
    print("cycSeq[\(i)] = \(item)")
}
弱い参照(weak)と非所有参照(unowned)[編集]

いずれも、ARCによって保持が必要な参照としてカウントされない参照。Optional型として宣言するか否かに違いがある。

import Foundation

class SomeClass {
    weak var wkStr:NSString? // weak … Optional型として宣言する
    unowned var uoStr:NSString // unowned … Optional型として宣言できない
    init (str: NSString) {
        wkStr = str
        uoStr = str
    }
}

var obj1 = SomeClass(str: "hello")
print("obj1.wkStr : \(obj1.wkStr)") // "hello"は保持されないためnil
// print("obj1.uoStr : \(obj1.uoStr)") // 実行時エラー。unownedではnilを表現できない
print("-----")

var str = "hello" as NSString
var obj2 = SomeClass(str: str)
print("obj2.wkStr : \(obj2.wkStr)")
print("obj2.uoStr : \(obj2.uoStr)")
print("-----")

str = "goodbye" as NSString
print("obj2.wkStr : \(obj2.wkStr)") // 元の値"hello"が解放されたため nil 
// print("obj2.uoStr : \(obj2.uoStr)") // 実行時エラー。unownedではnilを表現できない

/* 実行結果:
obj1.wkStr : nil
-----
obj2.wkStr : Optional(hello)
obj2.uoStr : hello
-----
obj2.wkStr : nil
*/

イニシャライザ[編集]

指定イニシャライザと簡易イニシャライザ[編集]

初期化が必要なプロパティのすべてが初期化されることを保証するイニシャライザのことを、指定イニシャライザ(designated initializer)と呼ぶ。

それに対して、プロパティの初期化を他のイニシャライザに委譲するイニシャライザを、簡易イニシャライザ(convenience initializer)と呼ぶ。簡易イニシャライザからは最終的に指定イニシャライザが呼び出されるようにしなければならない。

class Color {
    var red, green, blue: Int // 指定イニシャライザでの初期化が必要

    // 指定イニシャライザ
    init(red: Int, green: Int, blue: Int) {
        self.red = red
        self.green = green
        self.blue = blue
    }

    // 簡易イニシャライザ → 指定イニシャライザ
    convenience init(white: Int) {
        self.init(red: white, green: white, blue: white)
    }

    // 簡易イニシャライザ → 簡易イニシャライザ → 指定イニシャライザ
    convenience init() {
        self.init(white: 0)
    }
}

var brown = Color(red: 0xA5, green: 0x2A, blue: 0x2A)
var gray = Color(white: 0x80)
var black = Color()
全項目イニシャライザ[編集]

構造体の場合のみ、イニシャライザを定義しなかった場合は全てのプロパティ初期化のためのイニシャライザが自動的に実装される。

struct Point {
    var x:Int
    var y:Int
}

var p = Point(x:2, y:3)
失敗可能イニシャライザ[編集]
extension Color {
    convenience init? (name: String) { // 初期化に失敗しnilを返す可能性があるため、init? とする
        switch (name) {
            case "black":   self.init()
            case "white":   self.init(white: 0xFF)
            case "red":     self.init(red:0xFF, green:0x00, blue:0x00)
            case "green":   self.init(red:0x00, green:0xFF, blue:0x00)
            case "blue":    self.init(red:0x00, green:0x00, blue:0xFF)
            default:        return nil // nilはイニシャライザでreturnできる唯一の値である(成功時は何もreturnしない)
        }
    }
}

var color = Color(name:"orange") // nil

デイニシャライザ[編集]

class Person {
    var name: String

    init (name: String) {
        print("Hello, \(name)!")
        self.name = name
    }

    deinit {
        print("Goodbye, \(name)!")
    }
}

var person = Person(name: "Tom")
person = Person(name: "Mike")

/* 実行結果:
Hello, Tom!
Hello, Mike!
Goodbye, Tom!
*/

継承[編集]

class ColoredSquare: Square {
    var color: Color

    init (sideLength: Double, color:Color) {
        self.color = color
        super.init(sideLength: sideLength)
    }

    public override var description: String {
        return "sideLength=\(self.sideLength) color=(\(self.color.red), \(self.color.green), \(self.color.blue))"
    }
}

var coloredSquare1 = ColoredSquare(sideLength: 1.0, color:Color(white: 255))
print("coloredSquare1 : \(coloredSquare1.description)")

coloredSquare1.color = Color(red:255, green:0, blue:0)
print("coloredSquare1 : \(coloredSquare1.description)")

var square2 = Square(sideLength: 2.0)
print("square2 : \(square2.description)")

/* 実行結果:
coloredSquare1 : sideLength=1.0 color=(255, 255, 255)
coloredSquare1 : sideLength=1.0 color=(255, 0, 0)
square2 : sideLength=2.0
*/
var square1 = coloredSquare1 as Square // Square へのアップキャストは必ず成功する
print("square1 : \(square1.description)")

var coloredSquare2 = square2 as? ColoredSquare // ColoredSquare へのダウンキャストは失敗する可能性がある(この場合は失敗する)
print("coloredSquare2 : \(coloredSquare2?.description)")

var coloredSquare1_2 = square1 as? ColoredSquare // coloredSquare1 は元々 ColoredSquare なので、成功する
print("coloredSquare1_2 : \(coloredSquare1_2?.description)")

/* 実行結果:
square1 : sideLength=1.0 color=(255, 0, 0)
coloredSquare2 : nil
coloredSquare1_2 : Optional("sideLength=1.0 color=(255, 0, 0)")
*/

エクステンション(拡張)[編集]

// 先述のColorクラスにメソッド追加
extension Color {
    var description:String {
        return String(format:"#%02X%02X%02X", self.red, self.green, self.blue) // カラーコードを文字列で返す
    }

    func complement() -> Color { // 補色
        return Color(red: 0xFF - self.red, green: 0xFF - self.green, blue: 0xFF - self.blue)
    }
}

var orange = Color(red:0xFF, green:0xA5, blue:0x00)
print(orange.description)
var navyblue = orange.complement()
print(navyblue.description)

// 実行結果:
// #FFA500
// #005AFF
// 先述のColorクラスを、Equatableプロトコルに準拠するように拡張
extension Color: Equatable {
    static func == (lhs: Color, rhs: Color) -> Bool {
        return (lhs.red == rhs.red) && (lhs.green == rhs.green) && (lhs.blue == rhs.blue)
    }
}

var black1 = Color()
var black2 = Color(red:0 , green:0 , blue: 0)

if black1 == black2 {
    print("same color")
} else {
    print("different color")
}

// 実行結果:
// same color

ジェネリック[編集]

ジェネリックタイプ[編集]

自分用のジェネリックタイプを定義できる。 それには、カスタム、構造体、列挙型等が配列や辞書と同じ様にどんなタイプとでも動く。

struct Stack<T> {
    var items = [T]()
    mutating func push(item: T) {
        items.append(item)
    }
    mutating func pop() -> T {
        return items.removeLast()
    }
}
var stackOfStrings = Stack<String>()
stackOfStrings.push(item:"一")
stackOfStrings.push(item:"二")
型束縛[編集]

型パラメータに指定できる型を、プロトコルによって制限する例。

struct EquatableStack<T: Equatable> {
    var items = [T]()
    mutating func push(item: T) {
        items.append(item)
    }
    mutating func pop() -> T {
        return items.removeLast()
    }
    func has(item: T) -> Bool {
      return items.contains(item) // 型TはEquatableプロトコルに適合しているため、containsメソッドが使用可能
    }
}
var stackOfStrings = EquatableStack<String>()
stackOfStrings.push(item:"一")
stackOfStrings.push(item:"二")

print(stackOfStrings.has(item:"一")) // true
print(stackOfStrings.has(item:"三")) // false

ジェネリック関数[編集]

以下の例は、自由な型の2つの値を交換する関数である。 Tは仮の型名で実際にはどんな型でも動く。

func swapTwoValues<T>(inout a: T, inout b: T) {
  let temporaryA = a ; a = b ; b = temporaryA
}

var someInt = 3 ,  anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
 
var someString = "hello" ,  anotherString = "world"
swapTwoValues(&someString, &anotherString)