混编、适配
Objective-C 与 Swift 混编
基础机制
桥接原理
Swift 与 Objective-C 通过 Clang 模块(module)和桥接头(Bridging Header) 实现互通。
- Swift 编译器自动生成
-Swift.h 头文件,将 Swift 暴露给 Objective-C。 - Objective-C 文件通过 Bridging-Header.h 将要暴露给 Swift 的头文件导入。
运行时差异
- Swift 主要是静态派发(Static Dispatch);除 @objc 修饰外不会注册到 ObjC runtime。
- Objective-C 基于动态派发(Dynamic Dispatch),通过 runtime 的消息发送机制调用方法。
命名空间差异
- Swift 有模块级命名空间;
- ObjC 无命名空间,常通过类名前缀区分(如 NS、UI)。
工程配置
Bridging Header 设置
- 新增 Swift 文件后,Xcode 会提示是否创建 Bridging Header。
- 路径:Build Settings → Swift Compiler - General → Objective-C Bridging Header
暴露 Swift 给 ObjC
- 确认 Defines Module 设置为 Yes;
- Swift 类需继承 NSObject,并加上 @objc;
- ObjC 文件中可 #import "
-Swift.h" 调用 Swift 代码。
头文件引用规则
<ProjectName>-Swift.h不能手动加入 bridging header;- 只能在 .m 文件中引入,不能放在 .h 中(否则循环依赖)。
互相调用
1. Objective-C 调用 Swift
@objc class MySwiftClass: NSObject {
@objc func greet() {
print("Hello from Swift")
}
}// ObjC 中使用
#import "YourProjectName-Swift.h"
MySwiftClass *obj = [MySwiftClass new];
[obj greet];要点:
- Swift 类需继承自 NSObject
- 方法需加 @objc
- 若需支持 KVO 或 Method Swizzling,加 @objc dynamic
2. Swift 调用 Objective-C
// ObjC 文件
@interface MyObjcClass : NSObject
- (void)sayHi;
@end// Swift 文件
let obj = MyObjcClass()
obj.sayHi()要点:
- 在 Bridging-Header.h 中引入需要的 ObjC 头文件;
- Swift 可直接调用;
- 可用 NS_SWIFT_NAME() 优化命名风格。
3. 值类型(Struct、Enum)限制
- Swift 的 struct、enum 不能直接暴露给 ObjC;
- 需要封装成继承自 NSObject 的类或通过桥接转换。
常见坑点(高频陷阱题)
| 问题 | 原因 | 解决方式 |
|---|---|---|
| 模块循环导入 | Swift 导 ObjC,ObjC 再导 Swift | 不要在 .h 文件中引入 |
| 命名冲突 | Swift 有命名空间,ObjC 没有 | 在 Swift 调用时加模块前缀:ModuleName.ClassName |
| Category 与 Extension | Category 运行时添加;Extension 编译期扩展 | 不可在 Extension 中新增存储属性 |
| 单元测试混编报错 | Target 配置不一致 | 为测试目标单独设置 bridging header |
面试追问示例
Q: 为什么 Swift 类默认不暴露给 ObjC?
A: 因为 Swift 强类型、静态派发设计,不想被 ObjC runtime 干扰;仅显式 @objc 才暴露。
Q: 如何让 Swift 属性支持 KVO?
A: 使用 @objc dynamic 修饰。
@objc dynamic var name: String = ""Q: Swift 如何调用 Objective-C Category 方法?
A: 直接在 Bridging-Header.h 导入 Category 的头文件。
Q: Swift 泛型类能否被 ObjC 使用?
A: 不能。ObjC 无真实泛型,仅编译期语法糖。
Q: 为什么有些 ObjC API 在 Swift 中不可见?
A: 被标记 NS_SWIFT_UNAVAILABLE 或宏条件编译过滤。
适配暗黑模式
颜色适配
iOS 13 之前 UIColor 只能表示一种颜色,从 iOS 13 开始 UIColor 是一个动态的颜色,它可以在 LightMode 和 DarkMode 拥有不同的颜色。 iOS 13 的 UIColor 增加了很多动态颜色,我们来看下用系统提供的颜色能实现怎么样的效果。
// UIColor 增加的颜色
@available(iOS 13.0, *)
open class var systemBackground: UIColor { get }
@available(iOS 13.0, *)
open class var label: UIColor { get }
@available(iOS 13.0, *)
open class var placeholderText: UIColor { get }
...
view.backgroundColor = UIColor.systemBackground
label.textColor = UIColor.label
placeholderLabel.textColor = UIColor.placeholderText如何自己创建一个动态的 UIColor 呢?
@available(iOS 13.0, *)
public init(dynamicProvider: @escaping (UITraitCollection) -> UIColor)这个方法要求传一个闭包进去,当系统从 LightMode 和 DarkMode 之间切换的时候就会触发这个回调。这个闭包返回一个 UITraitCollection 类,我们要用这个类的 userInterfaceStyle 属性。
@available(iOS 12.0, *)
public enum UIUserInterfaceStyle : Int {
case unspecified
case light
case dark
}现在我们创建两个 UIColor 并赋值给 view.backgroundColor 和 label,代码如下:
let backgroundColor = UIColor { (traitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return UIColor.black
} else {
return UIColor.white
}
}
view.backgroundColor = backgroundColor
let labelColor = UIColor { (traitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return UIColor.white
} else {
return UIColor.black
}
}
label.textColor = labelColor图片适配
打开 Assets.xcassets 把图片拖拽进去,然后我们在右侧工具栏中点击最后一栏,点击 Appearances 选择 Any, Dark 或者 Any, Light, Dark。

获取当前模式 (Light or Dark)
if traitCollection.userInterfaceStyle == .dark {
// Dark
} else {
// Light
}监听模式变化
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
// 适配代码
}
}改变当前模式
overrideUserInterfaceStyle = .dark
print(traitCollection.userInterfaceStyle) // dark参考资料