KotlinとSwiftが似てるってホント？

qnote Advent Calendar 2016 14日目を担当いたします。
チャン・ジュン(日本人)です。
よろしくどうぞ

## はじめに
Kotlinしかネタがないので今回もやっぱりKotlinのお話ですが、ついにSwiftが出てきます。
ちなみに私はiOS開発初心者なのでSwift側の見解には誤りが多分に含まれている可能性がありますのでご注意ください。
調べたらできるとか多分あります。

## 比較してみよう

さて、今回はきっと誰もが通ったタイマーアプリをKotlinとSwiftでそれぞれ書いてみました。
特に違いのある部分のみを抜粋してみていこうと思います。

### グローバル変数

#### Kotlinの場合
```kotlin
//トップレベルに記述
var defaultTimerCount = 300
var timerCount = defaultTimerCount

class MainActivity : AppCompatActivity() {

	//グローバル変数として記述
    var isRunning = false
}
```

Kotlinではタイマーのカウントダウン表示に利用する`timerCount`にタイマーのカウントの初期化で使う`defaultTimerCount`を代入するという書き方ができます。

```kotlin
var timerCount = defaultTimerCount
```
※グローバル変数同士でも代入は可能です。

#### Swiftの場合
```swift
var defaultTimerCount: Int64 = 300
var timerCount: Int64 = defaultTimerCount

class ViewController: UIViewController {

    @IBOutlet weak var timerLabel: UIButton!
    @IBOutlet weak var controlBtn: UIButton!
    @IBOutlet weak var pauseBtn: UIButton!

    var isRunning = false
}
```

~~Swiftではトップレベルに記述することができませんでした。
とはいえiOS側にはトップレベルに記述する理由がないので今回は特に問題ありません。
また、グローバル変数をグローバル変数に代入できませんでした。~~
コードを修正しました。
@t-ae さんのご指摘の通り、改めて記述したところ問題なく上記コードで動きました。
ありがとうございます。

UI系のオブジェクトはiOS側にはありますが、Android側はkotlinExtensionsを利用して対応するので不要です。

### 遅延実行

#### Kotlinの場合
```kotlin
fun countDown() {
    if (!isRunning) {
        return
    }

    Handler().postDelayed({
        if (!isRunning) {
            return@postDelayed
        }

        timerCount--
        updateTimerLabel()
        if (timerCount > 0) {
            countDown()
            return@postDelayed
        }

        Handler().postDelayed({
            initTimer()
        }, 1000)
    }, 1000)
}
```
Androidの定期的な繰り返し処理は遅延実行をよく使います。
今回は`timerCount`をデクリメントして0になるまでカウントダウンメソッドを呼び続けます。
`timerCount`が0になった後再度遅延実行で`initTimer()`を呼んでいるのは0の表示後に表示が初期化されるようにするためです。

#### Swiftの場合
```Swift
func countDown() {
    if (!isRunning) {
        return
    }

    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) {
        if (!self.isRunning) {
            return
        }
        
        self.timerCount -= 1
        self.updateTimerLabel()
        if(self.timerCount > 0) {
            self.countDown()
            return
        }

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) {
            self.initTimer()
        }
    }
}
```

iOSでは遅延実行ではあまりやらないようなのですが、今回はAndroidの実装に合わせて遅延実行で実装しました。
遅延時間の設定はAndroidとiOSで異なるようです。
Androidでは単純に1秒を設定しているのに対して、iOSでは現在時間+1秒を設定します。
また、`timerCount`のデクリメントは`--`が使用不可(Swift3)なので`-=`を使用しています。
Swift3の`--`の使用不可は言語間の壁を大きくする修正のように感じますが、より安全なコーディングを考えると合理的な修正のようにも思えます。

### モーダルなダイアログを表示する

#### Kotlinの場合
```kotlin
class TimerSettingDialogFragment() : BottomSheetDialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = BottomSheetDialog(context)
        dialog.setContentView(R.layout.timer_setting_dialog)
        val listItem = arrayOf("01:00", "02:00", "03:00", "04:00", "05:00", "06:00", "07:00", "08:00", "09:00", "10:00")
        val listView = dialog.findViewById(R.id.item_list) as ListView
        listView.adapter = ArrayAdapter<String>(context, android.R.layout.simple_list_item_1,listItem)
        listView.setOnItemClickListener {
            adapterView, view, i, l ->
            defaultTimerCount = (i+1) * 60
            (context as MainActivity).initTimer()
            dismiss()
        }
        dialog.findViewById(R.id.cancel_btn)?.setOnClickListener {
            dismiss()
        }
        return dialog
    }
}
```

Androidでモーダルなダイアログを表示するには`BottomSheetDialogFragment`と`BottomSheetDialog`を利用する必要があります。
ダイアログ表示の呼び出しは下記の通りです。

```kotlin
TimerSettingDialogFragment().show(supportFragmentManager, "tag")
```

タイトルやメッセージは`AlertDialog`とは異なり、直接コード上でsetすることができなかったので、レイアウトファイルに記述しています。
リストに表示するアイテムもxmlに記述すればコードはスッキリしますが今回はベタに書いてあります。
また、ダイアログのViewの取得ではFragmentにセットされていないのでkotlinExtensionsが利用できず、気になるようであれば`BottomSheetDialog`を継承したクラスを用意することも検討する必要がありそうです。
ちなみに`AlertDialog`の表示位置を単純に下にすることは可能ですが動作的にはちょっと違ったものになってしまいます。

#### Swiftの場合
```swift
let alert: UIAlertController = UIAlertController(title: "タイマー設定", message: "時間を選択してください", preferredStyle:  UIAlertControllerStyle.actionSheet)

let action: ((UIAlertAction) -> Swift.Void) = {
    (action: UIAlertAction!) -> Void in
    var title: [String] = (action.title?.characters.split(separator: ":").map(String.init))!
    self.defaultTimerCount = Int64(title[0])! * 60
    self.initTimer()
}

for i in 1...10 {
    alert.addAction(UIAlertAction(title: String.init(format: "%02d:00", i), style: UIAlertActionStyle.default, handler: action))
}
alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel))

present(alert, animated: true, completion: nil)
```

iOSには標準でモーダルなダイアログの表示設定があるので特殊な実装の必要がありません。
クラスが別れないのでグローバル変数やメソッドを直接呼ぶこともできるのでAndroidのようにキャストが必要になったりせずスッキリしています。

### おまけ

```swift
func changeBtnText(btn: UIButton, text: String) {
    UIView.setAnimationsEnabled(false)
    btn.setTitle(text, for: UIControlState.normal)
    btn.layoutIfNeeded()
    UIView.setAnimationsEnabled(true)
}
```

iOSではLabelにアクションをつけることができなかったのでタイマーの時間表示をするラベルをボタンで実装しました。
このボタンの文言を変える時にViewがちらつくという問題が発生したので上記のコードで対応しています。


## まとめ
巷ではKotlinとSwiftは似ていると聞くことがあったのでSwift簡単に書けるんじゃないかと甘く見ていたらそれなりに痛い目にあいました。
痛い目にあった大部分はAndroidStudioとXCodeの差によるものなので、Swift自体はObjctive-Cに比べればだいぶ学習コストが低いようには感じます。
AndroidもSwiftで書けるようになるという噂も聞きますが、AndroidとiOSの思想の違いは大きいと思うのでSwiftで書けるからiOSエンジニアでもAndroidアプリが簡単に書ける！ということにはなかなかならなそうです。

## 結論
**KotlinとSwiftは似てません！**