日落果

日落果的随手记

twitter
github

Kotlin Native 実践 -- iOS デバイスのモデルを取得する2つの方法

要求と試み#

今日は Kotlin Multiplatform をいじっていて、iOS デバイスのモデルを取得するという簡単な要求に直面しました。

Apple の公式ドキュメントを調べて、UIKit.UIDeviceを見つけました。

ここで、model プロパティの説明は次のようになっています:

モデル文字列の例としては、"iPhone" や "iPod touch" などがあります。

明らかに、具体的なモデルを取得する要件には合致していません。そのため、引き続き Google で検索し、この回答を見つけました。高評価の回答では、utsname または sysctlbyname を使用することが多く、実際には 2 つの方法は同じですが、1 つは簡単で、もう 1 つは複雑です。

Kotlin Native を使用した再実装#

方法 1(簡単)#

まず、Objective-C の実装を見てみましょう。非常に簡単で、systemInfoを宣言し、unamesystemInfoのポインタを渡し、最後に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 です。実機の場合はモデルコードが表示されます):
image

iOS デバイスを持っていないため、実機でのテストはできません(

批判#

Kotlin Native はまだ改善の余地がありますが、Kotlin Multiplatform はすでに安定しています。JetBrains には相互呼び出しについてもっと注意を払ってほしいです。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。