|ハイブリッドOS|File System|ARM|Android|Java|制御システム|オープンシステム

 

技術者コラム

 
フォーム
 
第26回目:Swiftでオセロを作る(Part3)
2014-08-25
筆者:村田
 
こんにちは。
 
夏休み明けの1週間でしたがリハビリ完了しましたか?これからまた頑張っていきたいと思います。お盆も過ぎたのでどんどん涼しくなってくるはずですね。秋は空気が澄んでいて過ごしやすく、スポーツ、読書、食べる(!)、プログラミング(?)、色々いい季節です。個人的には体育館に行ったりするなど、少し体を動かしたいなと思うのですがどうなることか。
 
前回はオセロの盤面を表示し、タッチした場所に黒石を置けるところまで作成しました。早速続きに行きたいところですが、その前にXcodeのbeta5へ対応することにします。beta4だけで進めようかとも思いましたが、せっかくbeta5をダウンロードしたので追いかけようかと。というわけで前回までのコードをbeta5で早速コンパイルしてみます。やはりエラーが出てしまいましたorz 今回の話のネタにはなりますが:P
 
以下のようにOthelloViewのイニシャライザにrequiredという識別子を付与しなければなりませんでした。
 
required init(coder aDecoder: NSCoder!) {
    super.init(coder:aDecoder)
    ・・・
}
 
リリースノートや新しいマニュアルでrequiredについて調べてみると、以下のサンプルのようにサブクラスでイニシャライザの実装を強制する機能のようでした。
 
class SuperClass {
    required init() {
        // 何らかの初期化処理
    }
}
 
class SubClass: SuperClass {
    required init() {
        // サブクラスでinitの実装が必須
        ...
    }
}
 
ただし、サブクラスで1つ以上の何らかのイニシャライザを定義する場合にのみ強制されます。何もイニシャライザを定義しない場合はrequiredイニシャライザをサブクラスでオーバライドする必要はありません。
 
さて、requiredイニシャライザとは何なのでしょう。JavaやC++など他の言語において、抽象クラスの空っぽのメソッドをサブクラス側で実装するように強制する機能は有名です。でも、UIViewは抽象クラスではありませんし、上記のSuperClassのinitは抽象メソッドではありません。ふむ。Objective-Cを知っている方はもうお分かりかもしれませんが、requiredは指定イニシャライザのことのようです。ObjCではお作法やノウハウとして、スーパークラスの指定イニシャライザをサブクラス側でオーバーライドするようにしていましたが、Swiftではコンパイラによってこのルールが強制されることになったようです。
 
では指定イニシャライザって何なのさってことです。Objective-Cの話になってしまいますが、Swiftのrequiredの理解にも繋がりそうですので紹介したいと思います。以下にObjCで定義したクラスを使って考えましょう。
 
@interface ClassAB : NSObject
- (id)initWithA:(int)a andB:(int)b; // これが指定イニシャライザ
- (id)initWithA:(int)a;             // これは(副次的)イニシャライザ
- (void)show;
@end
 
@implementation ClassAB {
    int _a;
    int _b;
}
 
// 指定イニシャライザは全てのメンバを詳細に設定する
- (id)initWithA:(int)a andB:(int)b
{
    if (self = [super init]) {
        _a = a; _b = b;
    }
    return self;
}
 
// 副次的イニシャライザは内部では指定イニシャライザを呼び出している
- (id)initWithA:(int)a
{
    return [self initWithA:a andB:-1]; // bの初期値は-1
}
 
- (void)show
{
    NSLog(@"a = %d, b = %d", _a, _b);
}
 
@end
 
何だか適当なクラス名ですが、ClassABはNSObjectを継承したクラスです。NSObjectはObjCでのルートクラスで、JavaでいうObjectのようなものです。ClassABは_aと_bというメンバ変数を持っています。initWithA:andBというイニシャライザの実装を見てみましょう。[super init]でスーパークラス(NSObject)のイニシャライザを呼び出してから、自分のメンバを全て初期化しています。このように最も詳細に自分のメンバを設定できるメソッドを「指定イニシャライザ(designated initializer)」と言います。もう一つのinitWithAというイニシャライザは指定イニシャライザを利用して初期化処理しています。これを副次的イニシャライザと言ったりします。ポイントは「指定イニシャライザは必ず呼ばれる」ということです。初期化するのに副次的イニシャライザを呼び出したとしても、指定イニシャライザにたどり着きます。というか、そうなるようにイニシャライザを設計しましょう。
 
では、以下のmain関数から実際に使ってみます。
 
int main(int argc, const char * argv[])
{
    @autoreleasepool {
        ClassAB *objAB = [[ClassAB alloc] initWithA:100 andB:200];
        [objAB show];
    }
    return 0;
}
 
@autoreleasepoolは詳しく説明しませんがObjCでメモリを自動解放する記述です。さてshowメソッドの結果は想像できるでしょうか。「a = 100, b = 200」と表示されます。まあ普通ですね。指定イニシャライザについては一旦ここまでにします。
 
ここからちょっと遠回りな脱線です。main関数のオブジェクト生成式をもう一度見てみます。[ClassAB alloc]のallocはNSObjectのクラスメソッドで、ここで必要なサイズのメモリを確保しClassABオブジェクトを生成しています。このとき_aと_bなどメンバ変数のメモリは全て0に初期化されます。ここまではいいですか?その後、生成されたオブジェクトに対してinitWithA:andBというメソッドを呼び出して初期化しています。ところで、この初期化メソッドを呼ぶことは必須ではありません。ふむむ、つまりinit〜といったメソッドは「イニシャライザ」なんて呼ばれていますが、showメソッドと同じ普通のメソッドなのです。慣習として[[〜 alloc] init]のように続けて呼び出すことがほとんどですが必須ではありません。(なお、イニシャライザを他のメソッドと”全く同じ”と言ってしまうことは、ARCシステムを考えると語弊がありますが今回は触れません)
 
まだ脱線の続きです。えっと唐突ですが、ObjCにはコンストラクタがありません。allocを呼び出した後、明示的にイニシャライザを呼ぶことで代用していますが、これはC++のコンストラクタとは異なります。C++のコンストラクタではメモリ確保とメンバの初期化は不可分ですが、コンストラクタのないObjCでは明示的に初期化メソッドを呼んでいます。また、C++ではコンストラクタを仮想化できず(※)、コンストラクタ呼び出しは静的にディスパッチされます。それに対しObjCのイニシャライザはオブジェクトに対するメソッド呼び出しなので、継承ツリーを辿り動的にディスパッチします。なかなか面白いですね。同じC言語からオブジェクト指向を取り入れ進化した2つの言語。一方は型(クラス)に重きを置き、一方はオブジェクトに重きを置いているように感じます。このあたりの話、Simulaに影響を受けたC++と、Smalltalkに影響を受けたObjCという影響元の言語の違いなんでしょうか。SimulaもSmalltalkも勉強したことが無いので、いつか調べてみたいです。
※仮想コンストラクタを実現したければファクトリ(Abstract Factory/Factory Method)でエミュレートします
 
少し軌道を戻しましょう。先程のClassABのオブジェクト初期化ではinitWithA:andBメソッドを使いましたが、メソッドなら何でもいいです。例えばNSObjectのinitメソッドを呼び出してもいいです。
 
int main(int argc, const char * argv[])
{
    @autoreleasepool {
        ClassAB *objAB = [[ClassAB alloc] init]; // NSObjectのinitを呼ぶ
        [objAB show];
    }
    return 0;
}
 
この場合、NSObject内のメンバは正しく初期化されますが、ClassABの_aと_bは0のままです。0のままでオブジェクトとして成り立つなら特に問題はありません。しかし、_aと_bは最初に必ず-1に初期化しなければ不整合になるという場合は困りますね。C++ではコンストラクタがあるので問題になりませんが、ObjCでは上記のようなスーパークラスのイニシャライザ呼び出しが可能なので、不整合なオブジェクトが生成される可能性があるのです。どうしたらいいのでしょう。解決策は「スーパークラスの指定イニシャライザをオーバーライドすること」です。NSObjectの指定イニシャライザはinitですのでClassABでオーバーライドしてみましょう。
 
@implementation ClassAB ...
 
- (id)init
{
    return [self initWithA:-1 andB:-1];
}
 
@end
 
内部でinitWithA:andBメソッドを呼び出してそれぞれ-1に初期化するようにします。こうすることで
 
[ClassAB alloc] init];
 
と呼び出したとしても、NSObjectのinitではなくオーバーライドしたClassABのinitが呼ばれるため、正しく初期化することができます。
 
と、大分冗長に説明していますがそろそろまとめましょう。ObjC/Swiftでは必ずしも自クラスのイニシャライザが呼ばれるとは限りません。スーパークラスのイニシャライザが呼ばれるかもしれないのです。そこでスーパークラスの最も偉い?指定イニシャライザをオーバーライドし、その中でオブジェクトの不整合が発生しないように初期化する必要があるのです。ObjCではプログラマが気をつけるだけでしたが、Swiftでは指定イニシャライザをrequiredにすることでサブクラスでのオーバーライドを強制できるようにしているのですね。もちろん、メンバ変数が無い場合など、そもそも自前の初期化が必要ないクラス、つまり「イニシャライザを一つも定義していないクラス」であれば、スーパークラスのrequiredを実装していなくても怒られません。
 
なお現在のSwiftコンパイルエラーからは指定イニシャライザが何かまでは分かりません。その都度、スーパークラスやプロトコルのリファレンスを調べる必要があります。個人的にはコンパイラやfix-it機能で実装すべきrequiredイニシャライザを教えてくれたらな〜と思うのですが。
 
SwiftというかObjective-Cの話ばかりで結局オセロのコードは進みませんでしたが、今回はここまでにしたいと思います。
 
以上。
 
第25回目:Swiftでオセロを作る(Part2)
2014-08-15
筆者:村田
 
こんにちは。
 
お盆休みいかがお過ごしでしょうか。私は暑さ凌ぎも兼ねて地元福生市の図書館に通っています。図書館はエアコンが強すぎず静かで、背丈を超える本棚に囲まれるているとちょっと贅沢な気分になれるので大好きです。またこの図書館は周囲に緑があるのも嬉しいです。皆さんのお住まいの地域にはどんな図書館がありますか?どの図書館にも趣、特色があって楽しいです。そうそう川崎市武蔵小杉の駅ビルに昨年オープンした図書館に行ってきました。奇麗で蔵書も多くシャレオツでした。以前武蔵小杉に住んでいたのですが、当時オープンしていたら絶対通っていたな〜と羨ましく思いました。そんなわけで、夏休みにお時間がある方は図書館に足を運んでみたらいかがでしょうか。リラックスして贅沢な気分になれるかもしれません:-)
 
さて、オセロの続きですね。ところで早くもXcode6のbeta5がリリースされてしまいました。リリースノートを眺めてみるとSwift言語自体にも機能追加があるようでした。Optional型の使い勝手向上は分かりましたが、その他の機能はよく分かりませんでした。これまでのbetaリリースは割と頻繁にXcodeがクラッシュしていたので、なるべく新しいリリース版を使うべきですが、とりあえず今回はbeta4で進めたいと思います。
 
まず最初の目標はオセロの盤面を画面に表示させることでした。オセロの盤面は8x8マスなので2次元配列で表現するのが自然に扱えそうです。ではSwiftの配列について少し実験しましょう。ちなみにSwiftの実験にはXcode6のPlaygroundという環境を使うと楽です。「リアルタイムのREPL」という感じで、コンパイルやRunさせなくても、コードを書いた瞬間に式の評価結果が画面に表示されるという代物です。とても便利です。頻繁なクラッシュがなければね〜ボソッ(-_-)
 
Swiftの配列はオブジェクトです。配列型はArray<T>と表現します。大括弧を使って[T]とも書けます。TはIntやStringなど配列の中身の型です。
 
var array1:Array<Int> = [1,2,3]
 
Int型の配列array1を定義してみました。varは変数宣言の識別子です。var変数は書き換え可能(mutable)です。以下のように異なる配列オブジェクトを参照するように書き換えることができます。
 
array1 = [4,5,6]
 
中身も書き換えることもできます。
 
array1[0] = 9
 
次は[]を使って文字列の配列を定義してみます。こっちの[]を使った型宣言の書き方のほうが分かりやすいですね。
 
let array2:[String] = ["one", "two", "three"]
 
letは定数宣言の識別子です。書き換え不可(immutable)です。参照値を書き換えたり、中身の変更もできません。
 
array2 = ["four", "five", "six"] // compile error
array2[0] = "nine" // compile error
 
上記例ではコロンの後ろにArray<Int>や[String]と型注釈を書いてコンパイラに変数の型を教えています。Swiftでは初期値を与えれば初期値の型から型推論できるので型注釈を省略できます。
 
var array1 = [1,2,3]
let array2 = ["one", "two", "three"]
 
配列はオブジェクトなのでいくつかの便利なメソッドやプロパティを備えています。例えば配列の要素数を返すプロパティがあります。
 
array1.count
=> 3
 
あとは関数型プログラミングのリスト操作でおなじみのトリオ、map,reduce,filter関数もあります。これらは関数を引数に渡す高階関数ってやつです。Swiftではクロージャを引数に渡すことになります。map関数は配列の各要素に対して、もらったクロージャを作用させて新しい配列を作ります。元の配列から新しい配列を作るだけで元の配列は何も変わりません。
 
var array2 = array1.map({(n:Int) -> Int in
    return n * 2
})
 
array2
=> [2, 4, 8]
 
Swiftのクロージャは中括弧{}で括ります。inの前がクロージャの型です。上記例は(n:Int) -> Intなので、IntをもらってIntを返すクロージャです。inの後が処理です。もらった引数nを倍返ししています。
 
さて、ここからが面白いところです。Swiftの配列はオブジェクトですが、コピー動作時にプリミティブ値のように振る舞います。
 
var array1 = [1,2,3]
var array2 = array1
 
array1[0] = 100 // array1の内容を書き換えてみる・・・
 
array1
=> [100, 2, 3]
 
array2
=> [1, 2, 3] // array2は変わらない!!
 
ほ〜。JavaやC#のように配列をオブジェクトの参照として扱う言語とは異なります。ちょっと注意が必要ですね。これらの言語ではcopyやcloneなどのメソッドを呼び出して、明示的に新しい配列オブジェクトを生成する必要がありますが、Swiftでは不要です。配列をオブジェクトとして意識させるのではなく、「値」として扱わせるという観点は関数型プログラミングのアプローチと言えるでしょう。ただし、配列を常にプリミティブとして扱っていては不要なコピーコストが生じる可能性があります。コンパイル時にフロー解析をするのか、あるいは配列の中身の書き換えや要素数の変更時までコピーを遅延させているのか分かりませんが、いずれにせよプログラマが意識しないところでコピーコストの最小化が行われていると考えられます。なおSwiftでは文字列やディクショナリもプリミティブ値のように振る舞います。
 
文字列の例。
var str1 = "abc"
var str2 = str1
 
str1 += "def" // str1を書き換え
 
str1
=> “abcdef”
 
str2
=> “abc” // str2は変わらない
 
ディクショナリの例。
var person1 = ["age":17]
var person2 = person1
 
person2["age"] = 35 // person2を書き換え
 
person1
=> [“age”:17] // person1は変わらない
 
person2
=> [“age”:35]
 
さて話をオセロの盤面に戻します。2次元配列の中身の型はどうしましょう。各マスの取りうる状態としては黒石、白石、空きとなります。これらをオブジェクト(Enum)として表すこともできますが、今回は単純なInt値で表したいと思います。直値はやめて定数の名前ぐらいは用意しておきます。なお、Swiftはグローバルスコープやクラススコープに定義を書けるので、本記事で特に断りがなければグローバルスコープに定義を書いているものとします。クラススコープに記述する場合は、メンバ変数、メソッドなどと言うようにします。
 
let EMPTY = 0, BLACK_STONE = 1, WHITE_STONE = 2
 
盤面はメンバ変数として以下のように定義しました。Intの2次元配列です。Array<Array<Int>>と書いてもいいです。
 
class OthelloView: UIView {
    var board:[[Int]]
}
 
ついでに盤面の初期状態を定数として定義しておきましょう。中央4マスにはあらかじめ石がおいてあります。右上が黒(1)です。以下の定義をよ〜く見ると10x10マスになっています。盤面の周囲を番兵値(0)でぐるりと囲んでいます。役に立つか分かりませんが、今後のアルゴリズムにおいて配列の範囲チェックを省略することが狙いです。よくある常套手段です。
 
let initboard = [
    [0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,2,1,0,0,0,0],
    [0,0,0,0,1,2,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0],
];
 
Viewの初期化時に上記の盤面初期値をboardメンバにコピーしましょう。前回OthelloViewをStoryboardに登録して簡単な動作確認をしましたが、Storyboardに登録したViewは初期化時に以下のイニシャライザが呼ばれます。
 
class OthelloView: UIView {
    var board:[[Int]]
    init(coder aDecoder: NSCoder!) {
    }
}
 
このinitの中で盤面の初期化をします。それとNSCoderのinitは親クラスのイニシャライザを呼ぶ必要があります。
 
init (coder aDecoder: NSCoder!) {
    board = initboard
    super.init(coder:aDecoder)
}
 
盤面1マスの辺の長さ(size)と1列1行目の画面上の座標(top,left)を画面の大きさから計算します。
 
class OthelloView: UIView {
    ...
    var side:CGFloat
    var top:CGFloat
    let left:CGFloat = 0
    
    init(coder aDecoder: NSCoder!) {
        let appFrame = UIScreen.mainScreen().applicationFrame // 画面の大きさ
        side = appFrame.size.width / 8                        // 画面幅の8等分が一辺の大きさ
        top = (appFrame.size.height - (side * 8)) / 2
        ...
    }
}
 
次は盤面の描画です。オーバーライドしたdrawRectの中でグラフィックスコンテキストを使うとViewに絵を描くことができます。まずは黒丸を書くコードでちょっと練習してみましょう。
 
override func drawRect(rect: CGRect) {
    // グラフィックスコンテキストを取得
    let context = UIGraphicsGetCurrentContext()
    // 塗りつぶしの色に黒を設定
    CGContextSetFillColorWithColor(context, UIColor.blackColor().CGColor)
    // x:50, y:50 幅100,高さ100の正方形に内接する円を描く
    CGContextFillEllipseInRect(context, CGRectMake(50, 50, 100, 100))
}
 
CG〜で始まる関数群はCoreGraphicsライブラリの関数です。基本的な手順は、(1)contextを取得して、(2)contextに線の太さや色を指定して、(3)描画関数を呼ぶ、これだけです。色オブジェクトは毎回描画時に生成するとコストがかかりますので、View初期化時に生成しておきます。3色用意しました。白、黒、盤面の緑です。緑はちょっと明るい色にしました。CoreGraphicsAPIに渡す色はCGColorオブジェクトですので、UIColorからCGColorに変換しておきます。
 
class OthelloView: UIView {
    ...
    let white = UIColor.whiteColor().CGColor
    let black = UIColor.blackColor().CGColor
    let green = UIColor(red:0.6, green:1.0, blue:0.2, alpha:1.0).CGColor
    ...
}
 
ちょっと寄り道してSwiftのfor文の使い方を確認しましょう。C言語風の書き方で0〜9をコンソールに表示できます。iは書き変わるのでvarにします。
for var i = 0; i < 10; i++ {
    println(i)
}
 
次のfor-in文は、他の言語ではfor-eachとか拡張for文と呼ばれているものです。inの後はイテレーションできる配列やRangeオブジェクトを書くことができます。
 
for i in 0..<10 {
    println(i)
}
 
SwiftのRangeの書き方は、0..<10で10を含まない範囲(0〜9)、0...10で10を含む範囲(0〜10)となります。それでは以下に描画関数を一気に載せました。
 
override func drawRect(rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()
    // 各マスの枠線は白で太さ1.5ピクセルに設定
    CGContextSetStrokeColorWithColor(context, white)
    CGContextSetLineWidth(context, 1.5)
    
    for y in 1...8 {
        for x in 1...8 {
            // マスの位置からマスのrect領域を計算
            let rx = left + side * CGFloat(x-1)
            let ry = top + side * CGFloat(y-1)
            let rect = CGRectMake(rx, ry, side, side)
            // まずは盤面(正方形)を書くので塗りつぶし色を緑に設定
            CGContextSetFillColorWithColor(context, green)
            // 塗りつぶし(緑)、枠線(白)を描画
            CGContextFillRect(context, rect)
            CGContextStrokeRect(context, rect)
            
            if board[y][x] == BLACK_STONE {
                // 黒石が置いてあれば塗りつぶし色を黒にして円を描く
                CGContextSetFillColorWithColor(context, black)
                CGContextFillEllipseInRect(context, rect)
            } else if board[y][x] == WHITE_STONE {
                // 白石が置いてあれば塗りつぶし色を白にして円を描く
                CGContextSetFillColorWithColor(context, white)
                CGContextFillEllipseInRect(context, rect)
            }
        }
    }
}
 
マスの位置計算がちょっと分かりにくいかもしれません。2重のfor文は番兵を除くため1〜8で変化させます。またboard[y][x]のx,yの順番に気をつけて下さい。xのループの方がyより早く回り、左から右へ、次に上から下へマスの塗りつぶしが進むイメージです。CGFloat()の部分はIntからCGFloatへの型キャストです。ここまででオセロの初期状態がiPhoneシミュレータに表示されるはずです。
 
(文字数制限にひっかかったので次の記事に続きます・・・)
 
第25回目の続き
2014-08-15
次に画面にタッチした場所に石を置けるようにします。画面タッチのイベントハンドラはtouchesBeganメソッドですので、これをオーバライドしましょう。
 
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
}
 
touches引数の中にタッチした位置情報が入っています。touchesを解析して盤面のマス位置情報(x列,y行)に変換して返すメソッドを作りたいと思います。メソッド名はgetPosにしたいと思います。なおタッチした画面位置が盤面から外れた場所であればnilを返したいと思います。
 
ではここでSwiftのOptional型を登場させましょう。成功時にはオブジェクトを返し、失敗時にはnilを返すという関数はよくあるパターンです。関数呼び出し側では成功したかnilチェックをするわけですね。Swiftでは「値またはnil」という表現をOptional型としてカプセル化できます。Optional型は型名の末尾にクエスチョンマークを付けます。例えばInt?という型は「何らかの数値かまたはnil」ということを表す型です。以下の例でnumOfApplesはIntオプショナル型のvar変数です。3という数値やnilを代入できます。
 
var numOfApples: Int?
numOfApples = 3
numOfApples = nil
 
IntとInt?は型として異なるものです。IntからInt?への型変換は暗黙的に行われますが、逆にInt?からIntへは明示的な変換が必要です。例えばIntの値を引数にとる関数にInt?型の値をそのまま渡すことはできません。Int?型の値はnilかもしれませんのでInt型に単純に変換できないのは当然ですね。そこでInt?型の値をInt型に変換する(Int値を取り出す)必要がある場合は、Optional変数名の後にエクスクラメーションマークを付けることで取り出すことができます。
 
var apples: Int
numOfApples = 3
apples = numOfApples! // Int?からIntへ変換。生のInt値の取り出しとも言える。
 
apples
=> 3
 
もしOptional変数にnilが入っていたら、エクスクラメーションで型変換をしようとしたときに例外が発生します。nilをInt値には変換できないということです。
 
numOfApples = nil 
apples = numOfApples! // 例外発生
 
C言語をご存知の方なら、ポインタ操作に近いと感じるかもしれません。メモリアドレスとは関係ないので、あくまで操作のお作法が似ているという点においてですが。以下はC言語のポインタ操作の例です。
 
int rawApples = 3, apples;
int* numOfApples;
 
numOfApples = &rawApples; // Swiftでは numOfApples = 3 
apples = *numOfApples;    // Swiftでは apples = numOfApples!
 
上記C言語の最後の文はアスタリスクを使ったポインタの間接参照ってやつです。このときnumOfApplesというポインタ変数がNULLであればメモリアクセス例外が発生するわけです。Swiftのエクスクラメーションと似てますね。そんなわけで、オプショナル値から安全に型変換を行いたければ、事前にnilではないことをチェックする必要があります。
 
var apples: Int = 0 
if numOfApples != nil {
    apples = numOfApples! // nilでないことは保証されているので安全にInt型へ変換できる
    println("apples is \(apples)")
}
 
このような「事前にnilチェックしてからInt値への変換」というコードはあちこちに出てくるので、Swiftにはもう少しスッキリ書く記法が用意されています。
 
if let apples = numOfApples {
    println("apples is \(apples)")
}
 
letの部分はvar変数でも構いません。numOfApplesがnilの場合は型変換を行わずif文の中にも入りません。numOfApplesに値が入っている場合は、型変換してInt値をapples変数に取り出し、if文の中が評価されます。applesの型は型推論でIntであることが分かります。if let apples:Int = 〜 のように型注釈を付けてもいいです。nilチェックと型変換をifの条件部にてスッキリまとめたスマートな記法ですね。
 
余談ですがbeta5のOptional型について少し触れておきます。beta4までは「Optional値の型変換式」は左辺値(l-value)にできませんでしたが、beta5からは左辺値にすることができます。プログラミング言語における左辺値という用語は「代入式の左辺として扱えるもの」という値のことですね。例えばC言語のconst変数は左辺値ではありません。変数のアドレスも左辺値ではありません。
 
&val = 3; // こんな風には書けません
 
一度ポインタ変数にアドレスを入れ、ポインタの間接参照式にすれば左辺値にすることができます。
 
*val = 3;
 
Swiftも例で説明しましょう。beta5からエクスクラメーション式を左辺値にできるので、
 
numOfApples! = 3
 
と書けるようになりました。この例では今ひとつ嬉しさが伝わりませんね。もっと分かりやすい例が以下です。
 
numOfApples! += 3
numOfApples!++
 
上記のように書けるのはエクスクラメーション式を左辺値にすることができるからです。beta4以前では
 
numOfApples = numOfApples! + 3
 
と書いていたわけです。まあこの辺にしておきます。今後のbetaリリースでまた変わるかもしれませんし:P
 
さて本題に戻りましょう。Optional型をgetPosメソッドの戻り値に使うことで、タッチした場所が盤面上ではないことを自然に表現できるようになります。メソッドのインタフェースは以下のようになります。
 
func getPos(touches: NSSet!) -> (Int, Int)? {
}
 
引数のtouchesはtouchesBeganメソッドのtouchesをそのまま渡してもらう変数です。戻り値は盤面のマス目の位置(列,行)のタプルをOptionalで返すようにします。では引数touchesから画面上の位置(point)を取り出すところまで書いてみましょう。
 
func getPos(touches: NSSet!) -> (Int, Int)? {
    let touch = touches.anyObject() as UITouch
    let point = touch.locationInView(self)
}
 
touches.anyObject()の戻り値はAnyObject型です。asはダウンキャスト演算です。ダウンキャストとは型の互換性がない場合の強制キャストのことですね。UITouchにキャストしてlocationInViewメソッドで画面上の位置pointを取り出します。ではgetPosの続きを一気に載せてしまいましょう。
 
func getPos(touches: NSSet!) -> (Int, Int)? {
    let touch = touches.anyObject() as UITouch
    let point = touch.locationInView(self)
    for y in 1...8 {
        for x in 1...8 {
            let rx = left + side * CGFloat(x-1)
            let ry = top + side * CGFloat(y-1)
            let rect = CGRectMake(rx, ry, side, side)
            if CGRectContainsPoint(rect, point) {
                return (x, y)
            }
        }
    }
    return nil
}
 
2重for文で各マス目のrect領域を計算し、タッチ位置pointがrect領域に含まれるかチェックしています。ではこのメソッドをtouchesBeganメソッドから呼び出して黒石を置いてみましょう。
 
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
    if let (x, y) = getPos(touches) {
        board[y][x] = BLACK_STONE
    }
    setNeedsDisplay()
}
 
if文の条件式でgetPosの戻り値から安全にタプル値を取り出しています。盤面上をうまくタッチしていればif文の中に入るのでboardに黒石を置いてsetNeedsDisplayで再描画を指示します。すると非同期でdrawRectメソッドが呼ばれ、黒石を置いたところに黒丸が描画されます。
 
今回はここまでです。盤面の描画とタッチして黒石を置けるところまで作ることができました。適当に黒石を置いてみたところを参考画像に載せます。最後に一つ。Swiftの配列やOptional型は、それぞれbetaリリース中にゴタゴタと変更があった言語機能です。今後のbetaリリースでまた変わるかも知れませんので、本記事の内容についてはbeta4が前提であることに注意して頂ければと思います。
 
以上。
 
参考画像:OthelloViewをタッチして黒石を置いたところ
2014-08-15
 
第24回目:Swiftでオセロを作る(Part1)
2014-08-07
筆者:村田
 
こんにちは。
 
今回からアップルの新言語Swiftを使って何か作りたいと思います。何かと言ってますがタイトル通りオセロです。オセロについては説明不要ですね。そして現在のSwiftについてですが、6月頭のWWDC2014で発表されてからまだ2ヶ月程しか経っていませんが評価バージョンはbeta4まで改訂されています。今秋に正式リリースされる予定だそうです。すでにネットにはたくさんの解説記事がありますし、新言語にも関わらずプログラミングしていてよく分からないエラーや動作があれば、Stack Overflowで大抵解決策が見つかるという、なんともありがたい状況です。そこで、とりあえず触りながらネット検索すれば、何やら作れるんじゃないかな〜ということでオセロを作ってみたいと思います。Swift言語はOSXアプリやiOSアプリを作ることができますが、今回のターゲットはiOS(iPhone)にしました。
 
注意点として、SwiftでプログラミングするためにはXcode6が必要であり、ダウンロードするためにOSX/iOSの開発者ライセンスが必要になります。なおbeta4バージョンを前提に作っていく予定です。他のバージョンでは動かないかもしれません。実際にbeta2の頃に書いたコードをbeta3に持っていったらコンパイルエラーやランタイムエラーが出ました。アクセス制限修飾子の追加があったり、Cocoaとの一部I/Fが変わったり、配列の仕様が変わったりもしました。今秋リリースとのことですが結構大きく改訂されてますね^^;
 
ではアプリ作成を初めていきたいと思います。Xcode6のダウンロードはappleデベロッパーサイトから行うことができます。ダウンロードしたイメージファイルをダブルクリックすればインストールを開始できます。なおXcode5や他のXcode6 betaバージョンとは互いに独立しているので複数のXcodeをインストールしても問題ありません。(容量を食いますが)
 
Xcode6を起動してプロジェクトを作成して下さい。iOSのテンプレートは最もシンプルなSingle View Applicationを選択します。使用する言語にSwiftとObjective-Cが選べるのでSwiftを選びましょう。Xcodeの詳細なGUI操作については省略します:P
 
自動生成されたファイルはAppDelegate.swiftとViewController.swiftとMain.storyboardです。Objective-Cではヘッダファイル(.h)と実装ファイル(.m)が分かれていましたが、Swiftではヘッダ・実装を分ける必要はなく、.swiftファイルにクラスやら関数やらを書いていけば良いです。AppDelegateはアプリのライフサイクルのデリゲートで、ViewControllerはビューのライフサイクルのデリゲートです。これらの役割はXcode5と変わっていません。ところでObjCではテンプレートにmain.mという実装ファイルがあり、以下のようにmain関数が書かれていました。
 
int main(int argc, char * argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([XXXAppDelegate class]));
    }
}
 
Swiftの今回のiOSテンプレートを見ると、mainエントリの書かれたファイルはありません。AppDelegate.swiftをよく見ると以下のようにクラスに@UIApplicationMainという属性がついています。おそらくこれが従来main.mの中で行っていた紋切り型コードの代わりであり、AppDelegateのクラス登録を行っているのでしょう。
 
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
}
 
クラス登録は属性で行っているのは何となく分かりました。それでもエントリポイントとしてのmainはどうなってるのでしょう?GUIアプリでは明示的なmainが無くてもまあいいですが、単純なコンソールアプリではmainがあった方が分かりやすいはず、と思ってOSXのコンソールアプリのテンプレートを見てみました。おお、あるあるmain.swiftが。ファイルを開いてみると、
 
import Foundation
 
println("Hello, World!")
 
・・・これだけか。Swiftではトップレベルに実行コンテキストが書けるようです。スクリプト言語のように上から順に実行されます。なお、このようにトップレベルに式を書けるのはmain.swiftというファイルのみです。自分で追加したswiftファイルのトップレベルには式を書けません。ビルド設定のどこかに「main.swiftから実行する」って書いてあるのかな〜と探してみましたが見つかりませんでした。単純にテキスト検索しただけでは見つからないところにリンクしているかもしれません。
 
さてiOSのテンプレートに話を戻しましょう。Main.storyboardもよく見るとXcode5の頃から変わっています。Xcode5で導入されていたAutoLayout機能が必須になった感じです。おそらくiOSも様々な画面サイズのデバイスに簡単に対応できるようにするため、レイアウトを絶対配置ではなく相対配置で書くことが重要になった、ということではないかと思います。iPhone6も噂?では4.7インチ、5.5インチと画面サイズがiPhone5とは異なるようです。iOSアプリもAndroidアプリのように多様な画面サイズへの対応が必要になってきました。
 
テンプレートを眺めるのはこの辺にして、今のところ考えているオセロアプリのポイントは以下のとおりです。
・一人二役での対戦は寂しいので、一応コンピュータ対戦にする(思考アルゴリズムは無く、単純なランダム)
・副作用無しで書けそうな部分は、上書きできないlet変数を使って関数型プログラミングを意識してみる
 
最初の目標はオセロ盤面を表示させることです。2DゲームなのでSpriteKitやUIImageViewを使うことも考えられますが、衝突などのアクションやアニメーションは必要ないので、UIViewの派生クラス1個という簡単な構成で進めていきます。
 
まずOthelloView.swiftを新規作成し以下のようにUIKitをimportしてUIViewの派生クラスを作成します。
 
import UIKit
 
class OthelloView: UIView {
 
}
 
そしてstoryboardから最初に起動するViewをOthelloViewに切り替えます。記事の最後にスクショを載せてみましたが、Xcode5と手順は同じです。それとViewの描画関数内でprintデバッグを出して簡単な動作確認を行いましょう。
 
import UIKit
 
class OthelloView: UIView {
    override func drawRect(rect: CGRect) { // 描画時に呼ばれるメソッド
        println("Hello Swift!")
    }
}
 
iOS Simulatorを起動し、デバッグエリアにHello Swift!と表示されればOKです。
 
短いですが上記のSwiftコードをちょっと眺めてみます。最初の印象はどうでしょう。何らかのオブジェクト指向言語を知っていれば普通に読めちゃうと思います。Objective-Cの記法は特徴があって個人的には好きなんですが、やはりSwiftの方が理解しやすそうです。クラスや継承の書き方も普通です。最近の言語らしくオーバーライドは明示的にoverrideと記述する必要があり、「知らぬ間にオーバライド問題」を防ぎます。メソッドや関数の頭にはfuncという識別子が必要です。Swiftの宣言の特徴は先頭に識別子を書くことです。クラスはclass、変数はvar、定数はletという識別子から始まります。逆に型注釈は名前の後になります。上記例においてrectという引数名の後にCGRect型と書いていますね。他に上記例から、式や宣言の後にセミコロンを書かなくても良いことがわかります。1.宣言の最初に識別子を書く、2.型注釈は名前の後、3.セミコロンは省略可、これらはGo言語に似ている特徴と言えます。えーっと後はコメントの書き方も普通なのですぐに覚えられますね。 /*〜*/で複数行をコメントアウトできます。
 
と、まったくオセロを書く気配がありませんが、のんびりいきましょう:-)
しばらくお付き合い頂ければと思います。
 
以上
 

連載記事のソースコード

連載記事のソースコード
 
・Haskellで問題を解く(Part1〜Part4) ソースコード
・Clojureで8クイーン問題にチャレンジ(Part1〜Part5) ソースコード
・OCamlでへびゲームを作る(Part1〜Part5) ソースコード
・Swiftでオセロを作る(Part1〜Part5) ソースコード
・Processingでシューティング(Part1〜Part4) ソースコード 
Haskellでテトリス(Part1〜Part9) ソースコード
・プチコン3号(BASIC)でさめがめ(Part1〜Part3) ソースコード
・Prologでさめがめを解く(Part1〜Part6) ソースコード