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




