iOS オールスターズ2でお話しした資料です! Swift らしさってなんだろう、そんなところを API デザインガイドラインと Swift 標準ライブラリの表現に着目して 7 つほど紹介してみました。あくまでも "指針" なので『そういう風に考えていくのね』みたいに捉えて、そこからは "自分らしい" 言葉を紡いでいってくれたらいいのかなって思います。
iOS オールスターズ2でお話しした資料です! Swift らしさってなんだろう、そんなところを API デザインガイドラインと Swift 標準ライブラリの表現に着目して 7 つほど紹介してみました。あくまでも "指針" なので『そういう風に考えていくのね』みたいに捉えて、そこからは "自分らしい" 言葉を紡いでいってくれたらいいのかなって思います。
Event : Visual Studio Users Community Japan #1
Date : 2019/09/14
ソフトウェア/サービス開発において最も後回しにされるものの代表が「パフォーマンスの向上」です。C#/.NET の最大の武器は開発生産性ですが、C# 7.0 以降はパフォーマンス向上のための機能追加が多数行われています。いくつかのポイントを押さえることで実装時からより高速なコードを書くことができるようになります。
このドキュメントでは、そんなポイントとなる箇所をふんだんにお届けします。
13. コード例 (冒頭に出てきた data class を value class に移植)
@JvmInline // JVM バックエンドをターゲットにする場合のみ必要
value class MailAddress(private val text: String) {
companion object {
private val PATTERN = "(メールアドレスの正規表現 )".toRegex()
}
init {
if (!text.matches(PATTERN)) throw Exception("「${text}」は不正な値です。")
}
}
基本的には `data` を `value` に書き換えばOK (簡単!)
14. value class は Kotlin1.5 で stable になった新機能
機能概要
● 一言でいえば、data class のサブセット。以下の制約がある
○ 単一のプロパティしか持てない
○ mutableなプロパティを持てない ( var を使ったプロパティが宣言できない )
○ 参照の比較はできない ( === を使った比較ができない )
● value class に依存したメソッド名はメソッド名がマングリングされる
@JvmInline
value class UInt(val x: Int)
// コンパイル後、関数は compute-<hashcode>(int x) となる (<hashcode> は7桁の英数字になる)
fun compute(x: UInt) { }
参考: https://kotlinlang.org/docs/inline-classes.html / https://star-zero.medium.com/kotlin%E3%81%AEvalue-class-27e865696f35
15. value class は Kotlin1.5 で stable になった新機能
機能概要 (続き)
● コンパイル時にできる限りプリミティブ型としてバイトコードを生成するので data classより高速
○ 但し以下のような場合はプリミティブ型への最適化はされない
@JvmInline
value class Foo(val i: Int) : I
fun <T> asGeneric(x: T) {} // ジェネリクスとして引数を設定する場合
fun asInterface(i: I) {} // インターフェイスとして引数を設定する場合
fun asNullable(i: Foo?) {} // Nullableなプロパティとして引数を設定する場合
val f = Foo(42)
asGeneric(f)
asInterface(f)
asNullable(f)
参考: https://kotlinlang.org/docs/inline-classes.html / https://star-zero.medium.com/kotlin%E3%81%AEvalue-class-27e865696f35
18. (注意点1) JSONシリアライズ
例えば、jackson を使用して以下の User クラスをシリアライズすると
プロパティ名がマングルされてしまいます。
// kotlin
value class UserID(val userId: String)
value class UserName(val userName: String)
data class User(val userId: UserID, val name: UserName)
// JSON
// 注意: プロパティ名がマングルされている
{
"userId-abc1234": "abc123",
"name-def5678": "foo"
}
20. (注意点2) mockito が対応していない
value class を使用したオブジェクトをmockitoで使用できません。
value class UserID(val userId: String)
// モック
val someClass = Mockito.mock(SomeClass::class.java)
Mockito.doNothing().`when`(someClass.doSomething(isA(UserID::class.java)))
fun <T> isA(type: Class<T>): T {
Mockito.isA(type)
return null as T
}
// エラーメッセージ
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "UserID.unbox-impl()"
because the return value of "isA(java.lang.Class)" is null
21. (注意点2) mockito が対応していない
解決策としては mockk を使用すれば良い (GitHub issue)
value class UserID(val userId: String)
// モック
val someClass = mockk<SomeClass>()
every {
someClass.doSomething(UserID(any()))
}