Swift 方法桥接到 ObjC 时报 type cannot be represented
工程里 Swift
和 Objective-C
混编已经是常态,经常遇到 Objective-C
代码去调用 Swift
里的方法。最新的 Swift
需要手动标注 @objc
才能把方法暴露。(印象中似乎在 Swift 2.x
的时代,是默认暴露)
但经常会遇到 method cannot be marked @objc because the type of the parameter cannot be represented in objective-c
类似的错误,直觉为参数、返回值,无法从 Swift
桥接到 Objective-C
,有时候一眼就知道缺了什么,有时候则要思考半天,这里简单整理一下几种常见的情况。
环境
Xcode 15.2
Swift 5.9
Swift 结构体(Struct)
众所周知,Swift
结构体和 Objective-C
的结构体是浑然不同的事物。 Swift
倾向于值类型,会更多使用 Swift
结构体,但需要暴露给 Objective-C
的时候,就不太方便了。但基础库里内置的结构体,比如 CGPoint
等,苹果帮我们做了桥接,是可以的。比如:
1 | struct ST { |
苹果官方针对 Swift
中选择 Class
还是 Structure
有一段描述,见 Choosing Between Structures and Classes,其中一段节选:
Use Classes When You Need Objective-C Interoperability
If you use an Objective-C API that needs to process your data, or you need to fit your data model into an existing class hierarchy defined in an Objective-C framework, you might need to use classes and class inheritance to model your data. For example, many Objective-C frameworks expose classes that you are expected to subclass.
当您需要 Objective-C 互操作性时使用类(Class)
如果您使用需要处理数据的 Objective-C API,或者需要将数据模型放入 Objective-C 框架中定义的现有类层次结构中,则可能需要使用类和类继承来对数据进行建模。例如,许多 Objective-C 框架公开了您希望子类化的类。
所以,遇到需要暴露给 Objective-C
的场景时,还是选择 Class
吧,或者用 Class
做一层封装。
Swift 枚举型(Enum)
Swift
的枚举型很强大,除了能是 Int
、String
之外,还能带参数。如果用了高级的特性,也是没法暴露给 Objective-C
。如果想暴露给 Objective-C
,需要满足 Int
类型且补充 @objc
前缀,当然如果非 Int
类型 @objc
也加不上。
同时也正因为这个条件,此时带参枚举就无法使用了。
1 | // @objc enum MyEnum: String // ❌ not ok,'@objc' enum raw type 'String' is not an integer type |
Swift 基础数据类型(Int、Bool) + 可选类型(Optional)
Swift
的可选类型(Optional),有时候也会影响暴露给 Objective-C
,对于继承自 NSObject
的 引用类型,加上可选类型是没问题的,毕竟 Objective-C
也有 nil
来承接,但基础值类型,如 Int
、Bool
就没法将对应的可选类型 Int?
、Bool?
暴露给 Objective-C
了。
此时可以考虑去除可选类型,或者改用 NSNumber
类。根据是否是可选类型,Objective-C
侧展现的则是分别带有 _Nonnull
和 _Nullable
标识的入参。虽然如果对不上,默认是 warning
,不过建议还是开启 error
,并严格按照标识去处理,这同时也教育我们,Objective-C
侧对于是否可以为 nil
的标识也要做到严谨规范,才能更好的和 Swift
交互。
1 | @objc class MyClass: NSObject { |
inout
Swift
里提供了 inout
标识,对入参的修改能够返回给调用方,如果带了 inout
则不能暴露给 Objective-C
,可选的替代方案是使用 UnsafeMutablePointer
。当然自己封装成一个对象类型也是可以的。
1 | @objc class MyClass: NSObject { |
数组 Array
上文提到 Int
会被桥接成 NSInteger
,但需要注意的是 [Int]
会被桥接成 NSArray<NSNumber *>
,使用的时候务必注意。
但能够被苹果桥接的都是苹果提供的基础类型,如果是自定义的 Swift
的 Struct
则不行。
1 | struct ST { |
async
题外话,如果用到了 Swift
异步编程里的 async
等特性,其实也是可以正常暴露给 Objective-C
的,只不过会附带一个回调的 handler
,比如:
1 | @objc class MyClass: NSObject { |
小结
总体来说,Swift
自身复杂的语法特性,导致部分无法暴露给 Objective-C
。此时遇到类似 method cannot be marked @objc because the type of the parameter cannot be represented in objective-c
的报错,可以先排查下,是否是 Swift
特有的特性,比如 Swift
结构体、枚举型,并尝试补充 @objc
等标识。如果遇到困境,可以思考下,如果能暴露给 Objective-C
,在 Objective-C
那边应该是个什么形式,如果无法得出合理的结论,那应该就是问题所在了。
这里仅列出了常见部分情况,其他情况如果想到了再补充进来。