Swift日本語チュートリアル ~ 後編(Apple公式ドキュメントSwift Tour±α)

f:id:knj4484:20140613214723p:plain

この記事は、

  • プログラミング経験がある人が
  • Swiftを初めて勉強するときに、
  • インストールから始めて、
  • iPhoneアプリ開発の第一歩を踏み出す

ことを目的としたチュートリアルの後編です。

Swift日本語チュートリアル ~ 前編(Apple公式ドキュメントSwift Tour±α) - こんにゃくマガジンと併せてApple公式ドキュメント
The Swift Programming Language: A Swift Tour相当(翻訳ではないです)±αな内容になっています。

前編の内容は、開発ツールのインストール、開発ツールの基本的な使い方、Swift言語の変数、定数、制御構文、オプショナル値、関数の定義と呼び出し、SwiftでのiOSアプリ開発はじめの一歩についてです。

原稿をGitHubで管理することにしました。間違いの訂正や古い情報の更新はプルリクエストして頂けると助かります。
knj4484/hateblo-xavier · GitHub

クラスの定義 - init/deinitと継承

クラスはコード中で常に参照渡しされる(構造体との重要な違い)

class FoodMenuItem {
    var discountRate: Int? = 0
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func discountMessage() -> String {
        return "No discount"
    }
}

class Pizza: FoodMenuItem {
    var sauce: String
    
    init(sauce: String, name: String) {
        self.sauce = sauce
        super.init(name: name)
        discountRate = 5
    }
    
    override func description() -> String {
        return "Pizza \(name) is discounted \(memberDiscountRate)%"
    }
}
  • initializerを定義するにはinitを使う
  • deinitializerを定義するために、deinitを使う
    • オブジェクトのメモリ破棄の前になにかクリーンアップする場合に必要となる
  • サブクラスの定義は、クラス名のあとにコロンで区切ってスーパークラスの名前を置く
  • スーパークラスの実装をオーバーライドしたサブクラスのメソッドは、overrideと書く

クラスの定義 - setter/getter

class MenuItem {
    var price: Int
    let taxRate = 1.08
    
    var taxedPrice: Int {
    get {
        return price * taxRate
    }
    set {
        price = newValue / taxRate
    }
    }
}

class PizzaBeerSet {
    var pizza: Pizza {
    willSet {
        if (2000 < pizza.price + beer.price)
            pizza.discountRate = 10
        }
    }
    }
    var beer: Beer {
    willSet {
        if (2000 < pizza.price + beer.price)
            pizza.discountRate = 10
        }
    }
    }
    init(pizza: Pizza, beer: Beer) {
        self.pizza = pizza
        self.beer = beer
    }
}
  • プロパティーにgetterやsetterを持たせることが出来る
  • setterの中で、新しい値には暗黙の名前newValueがある
    • setの後の括弧中に明示的に名前を与えることも出来る
  • didSetで指定したコードは、新しい値をセットする直前に実行される
  • willSetで指定したコードは、新しい値をセットした直後に実行される

構造体

構造体はコード中で常にコピー渡しされる(クラスとの重要な違い)

struct PastaDrinkSet {
    var pasta: Past
    var drink: Drink
    func description() -> String {
        return "set of \(past.description()) and \(drink.description())"
    }
}
let recommendSet = PastaDrink(pasta: .Three, drink: .Beer)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
  • 構造体を定義するには、structを使う
  • クラスと比較して、
    • 共通点:プロパティー、メソッド、initializer、拡張できる、プロトコル *1 を実装できる
    • 相違点:継承できない、deinitializerがない、実行時の型キャストがない

列挙

enum PastaType: String {
    case Tomato = "tomato"
    case Cream = "cream"
    case GarlicOil = "garlic oil"
    func sauceName() -> String {
        switch self {
        case .GarlicOil: // selfの値がPastaTypeであることが既知なので、短縮形の.GarlicOilによって列挙値が参照されている
            return self.toRaw()
        default:
            return self.toRaw() + "sauce"
        }
    }
}
let calbonaraType = PastaType.GarcliOil // 定数に明示的な型がないので、フルネームによって列挙メンバーが参照されている
let VongoleRossoTypeRaw = PastaTape.Tomato.toRaw()
  • 列挙を定義するにはenumを使う
  • 列挙はそれに紐付いたメソッドがある
  • 列挙の生の値として、文字列と整数、浮動小数点数が使える
    • 生の値の型がIntの場合、最初の値が指定されていれば、残りの値は順に割り当てられる
  • 生の値と列挙値を変換するためには、toRawfromRawを使う
  • 列挙のメンバーの値はそれ自体の値があり、生の値の単なる別表記ではない
  • 意味のある生の値がない場合には、それらを書かなくてよい
  • enumの値を書くときはフルネームと短縮形がある
    • 短縮形は値の型が既知であるときはいつでも使える

ジェネリクス

C++のテンプレートやJavaジェネリクスのようなもの

func lastItem<ItemType>(items: Array<ItemType>) -> ItemType {
    return items[items.count - 1]
}
lastItem(["Newton", "New York", "Snow White"])
lastItem([4, 6, 8, 9])

class SequencePair<T1, T2 
                   where 
                   T1: Sequence, T2 : Sequnce,
                   T1.GeneratorType.Element == T2.GeneratorType.Element> {
    items1 : itemsType1
    items2 : itemsType2

    public commonItems() -> Array<T1.GeneratorType.Element> {
        var itemExistsInItems1 = Dictionary<T1.GeneratorType.Element, Bool>()
        for item in items1 {
            itemExistsInItems1[item] = true
        }
        for item in items2 {
            itemExistsInBoth[item] = true
        }
        return itemExistsInBoth.keys
    }
}
  • クラスや列挙、構造体、関数、メソッドジェネリクスなものを作ることができる
  • ジェネリック関数やジェネリック型を定義するには、定義する名前に続けて<>の中に型を書く
  • 要件のリストを指定するために型名の後にwhereを書く
    • 例)
      • プロトコルを実装するための型を要求すること
      • 二つの型が同じであることを要求すること
      • クラスが特定のスーパークラスを持つことを要求すること
  • 単純な場合は、whereを省略して、コロンの後にプロトコルやクラス名を書くだけで良い
    • つまりは同じである

Web上のテキストコンテンツを取得する

let url = NSURL(string: "http://api.tiqav.com/search/random.json")
let request = NSURLRequest(URL: url)
let response = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)
let response_body :String = NSString(data: response, encoding: NSUTF8StringEncoding)
        
println(response_body)
  1. NSURLでURLオブジェクトを作る
  2. NSURLRequestでリクエストを作る
  3. NSURLConnection.sendSynchronousRequestでリクエストを送る
  4. レスポンスをStringとして取得する

Web上の画像データを取得する

let imageURL = NSURL(string: "https://devimages.apple.com.edgekey.net/swift/images/swift-hero.png")
let imageData = NSData(contentsOfURL: imageURL)

ちょっと進んだSwiftでのiPhoneアプリ開発

画像URLを入力してその画像を表示するアプリを作ってみます。
ついでに少しだけデバッグ手法も使います。

  1. Xcode 6 betaを起動する
  2. メニュー:File > New > Project
  3. テンプレート選択ダイアログ: ①左枠iOS直下のApplication > ②右枠のSingle View Application > ③Next+ オプション選択ダイアログ
    • Project Name: 本当はなんでもよいがとりあえずMy2ndSwiftApp
    • Organization Name:適当に入れて下さい
    • Organizatin Identifier:適当に入れて下さい
    • Language : Swiftを選択
    • Device : とりあえずiPhoneを選択
  4. 保存場所ダイアログ:どこでもよいがとりあえず左側のDocumentをクリック > Create
GUIパーツの配置
  1. 左カラムのMain.storyboardをクリック
    • 真ん中カラムにiPhoneの画面的なものが出てくる
  2. 右カラム一番下の検索ボックスにtextと入力 > TextFieldを真ん中カラムのView Controller内左下にドラッグ&ドロップ
  3. 右カラム一番下の検索ボックスにbutと入力 > Buttonを真ん中カラムのView Controller内右下にドラッグ&ドロップ
  4. + 右カラム一番下の検索ボックスにimaと入力 > Image Viewを真ん中カラムのView Controller内上の方にドラッグ&ドロップ
GUIパーツとコードの結びつけ
  1. ウインドウ右上に四角いアイコンが6個あるうちの左から2個目(マウスオーバーするとShow the assistant editorとなる)をクリック
    • 画面が分割され、左側はiPhone画面的なもの、右側はコードエディターになる
  2. controlを押しながらiPhone画面的なものにあるUIImageViewをドラッグ > 右側コードのclass定義の内側一番下にドロップ > 出てきたダイアログのNameにimageViewと入力 > connectクリック
  3. controlを押しながらiPhone画面的なものにあるText Fieldをドラッグ > 右側コードのclass定義の内側一番下にドロップ > 出てきたダイアログのNameにtextFieldと入力 > connectクリック
  4. controlを押しながらiPhone画面的なものにあるButtonをドラッグ > 右側コードのclass定義の内側一番下にドロップ > 出てきたダイアログで下記入力 > connectクリック
    • connection : Actionを選択
    • Name : showImageと入力
    • Argument : Noneを選択
    • その他はいじらずにそのまま
Swiftコードの記述
  1. viewDidLoad内に追加するコード
        textField.text = "https://devimages.apple.com.edgekey.net/swift/images/swift-hero.png"
  1. showImage内に追加するコード
        let imageURL = NSURL(string: textField.text)
        println("start fetch: " + textField.text)
        let imageData = NSData(contentsOfURL: imageURL)
        println("finish fetch: " + textField.text)
        imageView.image = UIImage(data: imageData)
動作確認
  1. デバッグのことも考えてコンソールを表示:メニューからView > Debug Area > Active Console
  2. ウインドウ左上の再生ボタンを押す
    • ビルドが成功するとiOSシミュレーターが起動する
    • ビルドが失敗すると、エラーになったところが赤く表示されるので直しましょう
  3. (面倒でなければtext fieldの画像URLを書き換えて)Buttonを押す
    • 画像が表示されましたか?
    • Xcodeのコンソールにprintlnしたメッセージが表示されましたか?

コマンドラインSwiftシェルを使う

~/.bash_profileに下記を追加しておく

PATH=/Applications/Xcode6-Beta.app//Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:$PATH

Terminalを起動して、swiftコマンドを動かす

$ swift  -sdk $(xcrun --show-sdk-path --sdk macosx)
(略)
Welcome to Swift!  Type :help for assistance.
  1> 

Swiftプロンプトで何か実行してみる

  1> println("Hello Swift!")
Hello Swift!
  2> 

control + D で終了

-integrated-replオプションというのもあり、同じような使い方が出来る(違いはなんだろう?)

$ swift -integrated-repl -sdk $(xcrun --show-sdk-path --sdk macosx)
Welcome to swift.  Type ':help' for assistance.
(swift) for i in 1..3 {
          println(String(i))
        }
1
2

コマンドラインSwiftファイルを処理する

-iオプションでコンパイル結果をそのまま実行

$ swift -i foo.swift

swiftコマンドにファイル名を渡して実行ファイルを作る

$ swift foo.swift 
$ ./foo

コマンドライン引数を使う

for i in 1..C_ARGC {
    let arg = String.fromCString(C_ARGV[Int(i)])
    println(arg)
}
$ swift -emit-executable try-arg.swift 
$ ./try-arg aaa bbb
aaa
bbb

編集後記

今までネット上の技術ドキュメントに関しては完全なROMでしたが、
少しずつでも皆さんに恩返しをして行かなければと思っていたところ、
今回、よい機会を得てこのチュートリアルをポストすることが出来ました。
至らない点も沢山あると思いますが、よろしくお願いします。

ところで、最近みなさんが欲しいものは何でしょうか?私はこの二つが気になっています(特に下のやつ)。

*1:抽象クラスやインターフェースのようなもの