日落果

日落果的随手记

twitter
github

Kotlin Native 實戰 -- 兩種方式獲取 iOS 設備型號

需求與嘗試#

今天在折騰 Kotlin Multiplatform 遇到了一個簡單的需求,獲取 iOS 設備型號。

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

這裡的 model 屬性的介紹如下:

可能的型號字符串示例為 “iPhone” 和 “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 在互調用這方面上心點

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。