记一次 swift 运行时的大坑

这里每天分享一个 iOS 的新知识,快来关注我吧

前言

最近在开发的时候用到了参数化协议类型(parameterized protocol types),简单来说就是在协议上使用泛型,比如我们熟知的 Identifiable 协议:

swift 复制代码
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public protocol Identifiable<ID> {
    associatedtype ID : Hashable
    var id: Self.ID { get }
}

其中 ID 就是这个协议的泛型,在 swift 5.7 中增加了 some 关键字来解决带泛型的协议作为函数参数时必须使用泛型的问题,这在之前的文章有讲到过。

any 和 some 关键字的区别

同时在 swift 5.7 开始也支持了对参数化协议类型的隐式转换,比如,现在可以直接通过 as? any Protocol<Type> 的方式来做转换,今天要讲的一个坑就是跟这个问题相关的。

问题复现

最简单的复现代码如下:

swift 复制代码
struct Thing: Identifiable {
    let id = "thing"
}

let thing: any Identifiable<String> = Thing()

_ = thing as? any Identifiable

以上代码可以正常编译,但是在 iOS 16 以下会发生崩溃,这就是我遇到的坑,明明可以编译通过,却在某些机器上崩溃。

正确的做法应该是把 as? 改成 _ = thing as? any Identifiable<String>,但这时候会看到 Xcode 报错:

arduino 复制代码
Runtime support for parameterized protocol types is only available in iOS 16.0.0 or newer

问题分析

我查了一些资料,这个问题最早在 Xcode 14 Beta 2 上,最早的报错提示是:

arduino 复制代码
Runtime support for parameterized protocol types is only available in iOS 99.0.0 or newer

这显然是一个不正确的提示,后来 Xcode 的开发人员在 Beta 3 将报错提示改成了 iOS 16.0.0 以上,并给出了一些解释,对参数化协议的运行时转换仅限于 iOS 16 以上,因为它需要运行时支持,否则不可用。

那为什么 _ = thing as? any Identifiable 会在 iOS 16 以下崩溃呢? 首先这种转换语法在 iOS 16 以下是合法的,因此编译时能正常通过,但是 iOS 16 以下的运行时并不支持转换 Identifiable<String> 这种类型,所以就直接 Crash 了。

解决方法

解决方法是尽量避免这种类型的转换,其实在日常的开发中很少遇到类似的需求,不过如果真的避不开,也可以通过 some 关键字来做一层中转,比如上边的代码,可以写成:

swift 复制代码
struct Thing: Identifiable {
    let id = "thing"
}

let thing: any Identifiable<String> = Thing()

var thing1: (any Identifiable)?
//        _ = thing as? any Identifiable<String>
test(thing)
// 改为 some
func test(_ p: some Identifiable) {
    thing1 = p
}
print(thing1)

这样在 iOS 15 上就不会崩了。前面的文章讲过,some 其实跟泛型一样,所以代码也可以这样写:

swift 复制代码
let thing: any Identifiable<String> = Thing()

var thing1: (any Identifiable)?
test(thing)
func test<T: Identifiable>(_ p: T) {
    thing1 = p
}
print(thing1)

这里每天分享一个 iOS 的新知识,快来关注我吧

本文同步自微信公众号 "iOS新知",每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!

相关推荐
2501_915909061 小时前
原生 iOS 开发全流程实战,Swift 技术栈、工程结构、自动化上传与上架发布指南
android·ios·小程序·uni-app·自动化·iphone·swift
2501_915106321 小时前
Comodo HTTPS 在工程中的部署与排查实战(证书链、兼容性与真机抓包策略)
网络协议·http·ios·小程序·https·uni-app·iphone
2501_915909062 小时前
苹果软件混淆与 iOS 代码加固趋势,IPA 加密、应用防反编译与无源码保护的工程化演进
android·ios·小程序·https·uni-app·iphone·webview
2501_916007472 小时前
苹果软件混淆与 iOS 应用加固实录,从被逆向到 IPA 文件防反编译与无源码混淆解决方案
android·ios·小程序·https·uni-app·iphone·webview
Zender Han7 小时前
Flutter 实现人脸检测 — 使用 google_mlkit_face_detection
android·flutter·ios
大熊猫侯佩7 小时前
月球矩阵日志:Swift 6.2 主线程隔离抉择(下)
swift·编程语言·apple
大熊猫侯佩7 小时前
月球矩阵日志:Swift 6.2 主线程隔离抉择(上)
swift·编程语言·apple
2501_916008898 小时前
iOS 26 性能分析深度指南 包含帧率、渲染、资源瓶颈与 KeyMob 协助策略
android·macos·ios·小程序·uni-app·cocoa·iphone
HarderCoder10 小时前
Swift 并发深度指南:非结构化任务与分离任务全解析
swift
HarderCoder11 小时前
Swift 6 新关键字 `sending` 深度指南——从 `@Sendable` 到 `sending` 的进化之路
swift