You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

iOS 15.4模拟器运行时触发CMM - 'Symbol not found'错误,寻求编译阶段忽略该类错误的方案

iOS 15.4模拟器运行时触发CMM - 'Symbol not found'错误,寻求编译阶段忽略该类错误的方案

这个问题我之前也碰到过,核心原因其实是:虽然你的运行时代码分支不会走到iOS16+的逻辑,但编译阶段链接器会尝试解析所有代码里引用的符号,而iOS15的SDK根本不存在UIWindowSceneGeometryPreferencesIOS这个类,所以直接触发了dyld符号找不到的错误。下面给你几个实用的解决办法,都是我亲测有效的:

方案一:动态获取类,绕开编译时硬绑定

把直接创建UIWindowSceneGeometryPreferencesIOS的代码改成动态查找类的方式,这样编译时不会强制依赖这个类的符号:

actual fun setPortraitOrientation() {
    val systemVersion = UIDevice.currentDevice.systemVersion
    val majorVersion = systemVersion.split(".").firstOrNull()?.toIntOrNull() ?: 0
    
    if (majorVersion >= 16) {
        val windowScene = UIApplication.sharedApplication.connectedScenes.firstOrNull() as? UIWindowScene
        // 动态通过OC runtime获取类,避免编译时直接引用
        val preferencesClass = objc_getClass("UIWindowSceneGeometryPreferencesIOS") as? KClass<*>
        preferencesClass?.let { cls ->
            val preferences = cls.constructors.first().call() as NSObject
            preferences.setValue(UIInterfaceOrientationMaskPortrait.toLong(), forKey = "interfaceOrientations")
            windowScene?.performSelector(
                NSSelectorFromString("requestGeometryUpdateWithPreferences:errorHandler:"),
                preferences,
                { error: NSError? ->
                    println("Error updating orientation: ${error?.localizedDescription}")
                }
            )
        }
    } else {
        UIDevice.currentDevice.setValue(UIInterfaceOrientationPortrait.toLong(), forKey = "orientation")
    }
}

这种方式利用Objective-C的Runtime特性,运行时才去查找类和方法,编译阶段不会触发符号检查,完美适配iOS15环境。

方案二:用Kotlin的可用性注解标记代码

把iOS16+的逻辑抽成单独的函数,并用Kotlin的@ObjCName注解标记它的可用性,让编译器自动处理符号的可选绑定:

import kotlin.native.ExperimentalObjCRefinement
import kotlin.native.ObjCName

actual fun setPortraitOrientation() {
    val systemVersion = UIDevice.currentDevice.systemVersion
    val majorVersion = systemVersion.split(".").firstOrNull()?.toIntOrNull() ?: 0
    
    if (majorVersion >= 16) {
        setPortraitOrientationIOS16()
    } else {
        UIDevice.currentDevice.setValue(UIInterfaceOrientationPortrait.toLong(), forKey = "orientation")
    }
}

@OptIn(ExperimentalObjCRefinement::class)
// 标记这个函数仅在iOS16及以上可用
@ObjCName(name = "setPortraitOrientationIOS16", availability = "iOS 16.0")
private fun setPortraitOrientationIOS16() {
    val windowScene = UIApplication.sharedApplication.connectedScenes.firstOrNull() as? UIWindowScene
    windowScene?.requestGeometryUpdateWithPreferences(
        UIWindowSceneGeometryPreferencesIOS().apply {
            this.interfaceOrientations = UIInterfaceOrientationMaskPortrait
        },
        errorHandler = { error ->
            println("Error updating orientation: ${error?.localizedDescription}")
        }
    )
}

通过availability参数告诉编译器这个函数依赖iOS16的API,编译iOS15目标时会自动将其标记为可选符号,不会触发链接错误。

方案三:条件编译分离不同版本代码

如果是Kotlin Multiplatform项目,还可以通过条件编译把iOS16+和旧版本的代码完全分开,编译时根据目标版本自动排除不兼容的代码:

首先拆分函数:

actual fun setPortraitOrientation() {
    val systemVersion = UIDevice.currentDevice.systemVersion
    val majorVersion = systemVersion.split(".").firstOrNull()?.toIntOrNull() ?: 0
    
    if (majorVersion >= 16) {
        setPortraitOrientationIOS16()
    } else {
        setPortraitOrientationLegacy()
    }
}

@OptIn(kotlin.native.ExperimentalNativeApi::class)
@Target(AnnotationTarget.FUNCTION)
annotation class IOS16OrLater

// 标记这部分是iOS16+专属代码
@IOS16OrLater
private fun setPortraitOrientationIOS16() {
    val windowScene = UIApplication.sharedApplication.connectedScenes.firstOrNull() as? UIWindowScene
    windowScene?.requestGeometryUpdateWithPreferences(
        UIWindowSceneGeometryPreferencesIOS().apply {
            this.interfaceOrientations = UIInterfaceOrientationMaskPortrait
        },
        errorHandler = { error ->
            println("Error updating orientation: ${error?.localizedDescription}")
        }
    )
}

private fun setPortraitOrientationLegacy() {
    UIDevice.currentDevice.setValue(UIInterfaceOrientationPortrait.toLong(), forKey = "orientation")
}

然后在build.gradle.kts里配置条件编译规则:

kotlin {
    ios {
        binaries {
            framework {
                // 针对iOS16+的编译目标
                iosX64("ios16") {
                    compilations.main.defaultSourceSet {
                        languageSettings.optIn("IOS16OrLater")
                    }
                }
                // 针对iOS15及以下的编译目标
                iosX64("ios15") {
                    compilations.main.defaultSourceSet {
                        languageSettings.prohibit("IOS16OrLater")
                    }
                }
            }
        }
    }
}

这样编译不同iOS版本的目标时,会自动过滤掉不兼容的代码,从根源上避免符号找不到的问题。

备注:内容来源于stack exchange,提问作者Alyona

火山引擎 最新活动