日落果

日落果的随手记

twitter
github

Kotlin Native 实战 -- 两种方式获取 iOS 设备型号

需求 & 尝试#

今天在折腾 Kotlin Multiplatform 遇到了一个简单的需求,获取 iOS 设备型号。

查找 Apple 官方文档,找到了 UIKit.UIDevice.

这里的 model 属性的介绍如下:

Possible examples of model strings are ”iPhone” and ”iPod touch”.

很明显并不符合获取具体型号的要求,于是继续 Google,在 strackoverflow 找到了 这篇回答,其中高赞回答中,大多使用到了 utsname 或 sysctlbyname,其实两种方式具体实现是一样的,一个简单一个复杂的区别。

使用 Kotlin Native 复刻#

方法一(简单)#

首先来看 Objective-C 的实现,非常简单,声明出 systemInfo 再调用 uname 传入 systemInfo 的指针,最后获取 systemInfo.machine 的内容转为 NSString。

#import <sys/utsname.h> // import it in your header or implementation file.

NSString* deviceName()
  struct utsname systemInfo;
  uname(&systemInfo);
  return [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
}

那我们就可以很方便的转换为 Kotlin Native 的写法,首先声明一个 memScoped,因为类似于 alloc 这样的内存操作必须要在 memScoped 里完成:

@OptIn(ExperimentalForeignApi::class)
    fun getDeviceInfo() {
        return memScoped {
            // todo
        }
    }

使用 alloc<T> 声明一个类型为 utsname 的变量,然后将指针传入 uname:

@OptIn(ExperimentalForeignApi::class)
    fun getDeviceInfo() {
        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()
        }
    }

方法二(麻烦)#

第二种方法对应的 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 size,然后再调用一次 sysctlbyname 拿到了 hw.machine 的值,最后通过 stringWithCString 转为了 NSString。

按照惯例,先声明一个 memScope:

@OptIn(ExperimentalForeignApi::class)
    fun getDeviceInfo() {
        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,参考方法一中的方法,调用 sysctlbyname 并将 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 已经 Stable 了,希望 JetBrains 在互调用这方面上心点

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。