Help us understand the problem. What is going on with this article?

iOSエンジニアがKotlin触ってみた所感

More than 3 years have passed since last update.

この記事はKotlin Advent Calendar 2015の14日目の記事です。
13日目は@Kiriminさんによる2015年のAndroid開発はKotlinで決まりだったのか?でした。

ご挨拶

初めまして。表題の通り、メインはiOSでたまにAndroidもやるぐらいの人間です。
iOS開発言語としてSwiftの開発環境どんどん整備されてきている昨今、Javaで書くということ自体が足かせとなってAndroidの勉強が放置気味になっていました。
Kotlinは正直ネタ枠だと思っていたのですが、方々の勉強会でもKotlin関連のお話をよく聞くようになり、Android開発を受注したからKotlinをガッツリ使ってみたら最高だったを拝見したこと、年内にも正式リリースがされるらしいということで、ならばと門を叩いてみたので、その際に感じたことをつらつらと書いていきます。半分ポエムです。

制作物

よくある感じにAPIを叩いて、ローカルに保存、ListViewに表示するようなものを作りました。また、

あたりを使用し、なんとなく今風な構成とFull Kotlinを目指してみました。
自社のPrivateなAPIを叩いて制作してしまったため、制作物そのままをアップすることはできないのですが、ご要望があれば適当なAPIを叩くものに差し替えて、GitHubにあげます。

追記
アップしました。GitHubのレポジトリの検索 -> 保存 -> 表示というよくある感じです。
https://github.com/WU3110/KitHub

if/when and Smart Casts

今回Kotlinを勉強していて、最初にSwiftに欲しいなと思ったのはこいつです。
Kotlinのifwhenが他の言語のifswitchと異なるのは、式であるという点です。つまり、値を返せます。Swiftのswitchもだいぶ強力になりましたが、それ以上にスマートに記述できます。
例えば、TwitterライクにTweetの作成日時に応じて、〇〇秒前、〇〇分前…と作成日時が表示されるような実装を考えたときに下のように書けます。変数を定義したりメソッドを定義しなくていいので読みやすいですね。

val diff = // 現在日時と作成日時の差(秒)
textView.text = when(diff) { 
    in 0..60 -> "$diff 秒前"
    in 60..60*60 -> "${diff/60} 分前"
    in 60*60..60*60*24 -> "${diff/(60*60)} 時間前"
    else -> {
        // 適当に整形
    }
}

また、KotlinにはSmart Castsという機能があり、簡単に言えばifwhenで型チェックを通した後は、そのままその型にキャストされた状態で使えるものです。

fun smartCastDemo(x: Any?) {
    if (x != null && x is String) {
        print(x.length) // automatically cast to String
    }
}

whenと組み合わさると高い表現力を持てそうなことをわかっていただけるかと思います。
Smart Castsに関しては、Swiftでも同様にUnwrap Optionalできますね!

func unwrapOptionalDemo(x: Any?) {
    if let x = x as? String {
        print(x.characters.count)
    }

    // もしくは
    guard let x = x as? String else { return }
    print(x.characters.count)
}

スコープ関数

Kotlinのstdlibにはlet, with, run, applyの4つのスコープ関数があります。これも素敵です。
4つ関数はどれもよく似ていて使い分けが難しいのですが、この点はKotlin スコープ関数 用途まとめで、わかりやすく解説されています。
私はapplyをよく利用します。オブジェクトを初期化した後に書くプロパティに値をセットしていくというのはよくある処理だと思いますが、この時の処理を下のように書くことができます。毎度、article.~と書く必要がなく簡潔ですね。さらに、StringをDateにパースするためにSimpleDateFormat型でdateFormatを宣言していますが、こいつが名前空間を汚しません。素敵です。

// 普通に書くと
val article = Article()
article.title = "今日のおすすめ晩御飯レシピ"
article.url = "https://~"
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
article.createDate = dateFormat.parse(createDateString)

// apply関数
val article = Article().apply {
    title = "今日のおすすめ晩御飯レシピ"
    url = "https://~"
    val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    createDate = dateFormat.parse(createDateString)
}

蛇足ですが、Objective-C時代には処理ブロックの明確化と名前空間汚染対策としてこんなコードをよく書いていました。あまり見る書き方ではなかったですが。

Article *article = ({
    Article *a = Article();
    // 色々と処理
    return a;
});

interface/protocol

SwiftではProtocol Oriented Programmingというものが提唱されており、詳細は「Swift 2で提唱されているProtocol Oriented ProgrammingをWWDCセッションから学ぶ」が詳しいのでそちらをご覧いただくとして、ともかく、振る舞いの表現を継承によって実現することはあまりありません。Kotlinの流儀としてどういうものが推奨されているのかは知らないですが、抽象クラスを作るくらいならinterfaceを作る、必要ならばinterfaceにデフォルト実装を持たせるといったSwift風のスタイルはKotlinでも同様に実装できました。こういった点でも普段、Swiftを書くのと同じノリで実装ができます。

辛かったこと

言語仕様という意味で辛い部分というのはほとんどありませんでした。
辛かったのもJavaで書かれた既存のライブラリとの相性が悪い時くらいのものでしたが、IcePickやDagger等の個人的に依存しているライブラリがそのまま使えなかったのはなかなか手間でした。

JSONのデコードに利用した Moshi にも地味な辛さがありました。
MoshiはSquare社による軽量なJSONパーサです。JacksonやGsonに比べメソッド数が相当に抑えられており、Androidでは嬉しい存在です。また、単純にJSONをPOJOに変換するだけならば十分な機能を備えているかと思います。(KotlinでもPOJOって言うんですかね?)

こんな感じで定義してみました。

data class UserData (
        val id: Int,
        val name: String,
        val gender: Int,
        val introduction: String?,
        @Json(name = "created_at") val craeteDate: DateTime, 
        val children: List<ChildData>
)

Data Classはデータを保持するだけのクラスは幾つかのメソッドは機械的に生成できるという経験から生まれたクラスのようです。
具体的にはequals(),hashCode(), toString()等ですが、toString()をよしなに生成してくれるので、デバッグが楽になりました。

Primitive以外のクラスに対するConverterの作成も含め、Kotlinでも基本的な使用に問題はなかったですが、JSONにKeyが存在しなかった場合はプロパティの型を 非Optionalで宣言していても nullが入ってきました…
val location: String = ""のようにデフォルト値を設定しておきましょう。
せっかくのKotlinでぬるぽは辛い。

JSON Decoding in Swift

余談ですが、私がSwiftでJSONをデコードするときはHimotokiを用いています。
上記と同じことをこんな感じでかけます。おすすめです。SwiftyJSONはもうやめにしませんか?

struct UserData: Decodable {

    let id: Int
    let name: String
    let geder: Int
    let introducation: String?
    let craeteDate: NSDate
    let children: [ChildData]

    static func decode(e: Extractor) throws -> UserData {
        return try ArticleData(
            id: e <| "id",
            name: e <| "name",
            gender: e <| "gender",
            introduction: e <|? "introduction",
            createDate: NSDate.fromString(e <| "created_at"), // NSDate Extension
            children: e <|| "children"
        )
    }
}

まとめ

Kotlinの開発元であるJetBrain社は

100% interoperable with Java™

を謳っていますが、時折そのまま書き換えるだけというわけにもいかないものもあります。
この辺りの知見もコミュニティの人数が多いに作用するところだと思います。
まだベータ版ということもあり、「まだ早い」という印象をお持ちの方も多いでしょうが、だからこそ、今、世のSwiftエンジニアの皆さん、冬休みにでもtry Kotlinしてみませんか。

日本KotlinコミュニティのSlackグループでの、日本Kotlin界の先鋒の方々のトークは眺めているだけでも勉強になります。

勉強会も頻繁に開かれています!

本も出てます。

KazukiSaima
iOSおじさん。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした