要求と試み#
今日は Kotlin Multiplatform をいじっていて、iOS デバイスのモデルを取得するという簡単な要求に直面しました。
Apple の公式ドキュメントを調べて、UIKit.UIDeviceを見つけました。
ここで、model プロパティの説明は次のようになっています:
モデル文字列の例としては、"iPhone" や "iPod touch" などがあります。
明らかに、具体的なモデルを取得する要件には合致していません。そのため、引き続き Google で検索し、この回答を見つけました。高評価の回答では、utsname または sysctlbyname を使用することが多く、実際には 2 つの方法は同じですが、1 つは簡単で、もう 1 つは複雑です。
Kotlin Native を使用した再実装#
方法 1(簡単)#
まず、Objective-C の実装を見てみましょう。非常に簡単で、systemInfo
を宣言し、uname
にsystemInfo
のポインタを渡し、最後にsystemInfo.machine
の内容を NSString に変換します。
#import <sys/utsname.h> // ヘッダーファイルまたは実装ファイルでインポートしてください。
NSString* deviceName() {
struct utsname systemInfo;
uname(&systemInfo);
return [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
}
これを Kotlin Native の形式に簡単に変換できます。まず、memScoped
を宣言します。なぜなら、alloc
などのメモリ操作はすべてmemScoped
内で行う必要があるためです。
@OptIn(ExperimentalForeignApi::class)
fun getDeviceInfo(): String {
return memScoped {
// todo
}
}
alloc<T>
を使用して、utsname 型の変数を宣言し、ポインタを uname に渡します。
@OptIn(ExperimentalForeignApi::class)
fun getDeviceInfo(): String {
return memScoped {
val systemInfo = alloc<utsname>()
uname(systemInfo.ptr)
}
}
次に、systemInfo.machine の型がCArrayPointer<ByteVar>
であることに気づきます。つまり、char *
です。では、Kotlin の String に変換するにはどうすればよいでしょうか?Kotlin Native には次のような拡張関数が用意されています:
fun CPointer<ByteVar>.toKString(): String
その名前からもわかるように、CPointerを Kotlin の文字列に変換するための関数です。
CArrayPointer<T> = CPointer<T>
なので、この拡張関数を使用してchar *
からString
に変換することができます。最終的なコードは次のようになります:
@OptIn(ExperimentalForeignApi::class)
fun getDeviceInfo(): String {
return memScoped {
val systemInfo = alloc<utsname>()
uname(systemInfo.ptr)
systemInfo.machine.toKString()
}
}
方法 2(手間がかかる)#
2 番目の方法に対応する Objective-C の実装は次のようになります:
+ (NSString *)getModel {
size_t size;
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
char *model = malloc(size);
sysctlbyname("hw.machine", model, &size, NULL, 0);
NSString *deviceModel = [NSString stringWithCString:model encoding:NSUTF8StringEncoding];
free(model);
return deviceModel;
}
簡単に分析すると、ここで sysctlbyname を呼び出して char のサイズを取得し、さらに sysctlbyname を呼び出してhw.machine
の値を取得し、最後に stringWithCString を使用して NSString に変換しています。
慣例に従って、まず memScope を宣言します:
@OptIn(ExperimentalForeignApi::class)
fun getDeviceInfo(): String {
return memScoped {
// todo
}
}
先ほどの方法と同じように、最初の sysctlbyname の呼び出しを実装します:
val sizePtr = alloc<size_tVar>()
sysctlbyname("hw.machine", null, sizePtr.ptr, null, 0u)
非常に簡単です。次に、allocArray
を使用して、sizePtr
の長さの固定長配列を宣言します。これは先ほどのchar *model = malloc(size)
に対応します:
val model = allocArray<ByteVar>(sizePtr.value.toInt())
次に、sysctlbyname
を直接呼び出し、方法 1 のコードを参考にして、model
を Kotlin の文字列に変換します。最終的なコードは次のようになります:
@OptIn(ExperimentalForeignApi::class)
fun getDeviceInfo(): String {
return memScoped {
val sizePtr = alloc<size_tVar>()
sysctlbyname("hw.machine", null, sizePtr.ptr, null, 0u)
val model = allocArray<ByteVar>(sizePtr.value.toInt())
sysctlbyname("hw.machine", model, sizePtr.ptr, null, 0u)
model.toKString()
}
}
なぜ私たちのコードには free がないのかと思うかもしれません。それはmemScoped
の役割です。Kotlin Native はメモリを自動的に管理し、model を自動的に解放するため、私たちが心配する必要はありません。
テスト#
Xcode の iOS シミュレータで実行し、正しい結果が得られました(仮想マシンは x86_64 です。実機の場合はモデルコードが表示されます):
iOS デバイスを持っていないため、実機でのテストはできません(
批判#
Kotlin Native はまだ改善の余地がありますが、Kotlin Multiplatform はすでに安定しています。JetBrains には相互呼び出しについてもっと注意を払ってほしいです。