应用技术
适配、升级、混编

混编、适配

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.h
命名冲突Swift 有命名空间,ObjC 没有在 Swift 调用时加模块前缀:ModuleName.ClassName
Category 与 ExtensionCategory 运行时添加;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

参考资料