oc和swift相互调用

 

可以。OC 和 Swift 混编主要分两个方向:

1. Swift 调用 Objective-C:通过桥接头文件。
2. Objective-C 调用 Swift:通过 Xcode 自动生成的 `项目名-Swift.h`。

## 一、Swift 调用 Objective-C

假设有一个 OC 类。

`Person.h`:

```objc
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

- (instancetype)initWithName:(NSString *)name;
- (void)sayHello;

@end

NS_ASSUME_NONNULL_END
```

`Person.m`:

```objc
#import "Person.h"

@implementation Person

- (instancetype)initWithName:(NSString *)name {
    self = [super init];

    if (self) {
        _name = [name copy];
    }

    return self;
}

- (void)sayHello {
    NSLog(@"Hello,我是 %@", self.name);
}

@end
```

### 1. 创建桥接头文件

如果在 Swift 项目里第一次创建 `.m` 文件,Xcode 通常会询问:

```text
Would you like to configure an Objective-C bridging header?
```

选择:

```text
Create Bridging Header
```

然后会生成:

```text
项目名-Bridging-Header.h
```

在里面导入 OC 头文件:

```objc
#import "Person.h"
```

### 2. Swift 直接调用

```swift
let person = Person(name: "小明")
person.sayHello()

print(person.name)
```

不需要在 Swift 文件里再写:

```swift
import Person // 不需要
```

因为桥接头已经将 `Person.h` 暴露给整个 Swift Target。

### 3. 没有自动创建桥接头怎么办?

自己新建一个头文件:

```text
MyApp-Bridging-Header.h
```

内容:

```objc
#import "Person.h"
#import "NetworkManager.h"
#import "LegacyViewController.h"
```

然后打开:

```text
Target
→ Build Settings
→ Objective-C Bridging Header
```

填写桥接头相对于项目目录的路径,例如:

```text
MyApp/MyApp-Bridging-Header.h
```

注意不要填写绝对路径。

---

## 二、Objective-C 调用 Swift

Swift 类需要能暴露给 Objective-C。

```swift
import Foundation

@objcMembers
final class UserService: NSObject {

    var userName: String = ""

    func login(name: String, completion: @escaping (Bool) -> Void) {
        userName = name
        print("登录用户:\(name)")
        completion(true)
    }

    func logout() {
        userName = ""
    }
}
```

这里有两个重点:

```swift
NSObject
```

让 Swift 类进入 Objective-C 运行时体系。

```swift
@objcMembers
```

自动将该类中兼容 OC 的成员暴露给 Objective-C。

### 在 `.m` 文件中导入自动生成的头文件

```objc
#import "MyApp-Swift.h"
```

然后直接调用:

```objc
UserService *service = [[UserService alloc] init];

[service loginWithName:@"小明"
            completion:^(BOOL success) {
    NSLog(@"登录结果:%@", success ? @"成功" : @"失败");
}];

[service logout];
```

`MyApp-Swift.h` 不是真实存在于源码目录里的文件,而是 Xcode 编译时自动生成的。

它的名字取决于 Target 的模块名称,不一定等于工程文件名。可以在这里查看:

```text
Target
→ Build Settings
→ Product Module Name
```

假设值是:

```text
MyApplication
```

那么就导入:

```objc
#import "MyApplication-Swift.h"
```

## 三、只暴露个别 Swift 方法

如果不想整个类都使用 `@objcMembers`,可以分别添加 `@objc`:

```swift
final class PaymentManager: NSObject {

    @objc var amount: Double = 0

    @objc func pay() {
        print("支付 \(amount) 元")
    }

    // 只给 Swift 使用
    func loadSwiftOnlyData() async throws -> Data {
        Data()
    }
}
```

OC 调用:

```objc
PaymentManager *manager = [[PaymentManager alloc] init];
manager.amount = 99;
[manager pay];
```

如果希望 OC 看到一个更自然的方法名,可以指定 selector:

```swift
@objc(loginWithUserName:)
func login(userName: String) {
    print(userName)
}
```

OC:

```objc
[service loginWithUserName:@"小明"];
```

## 四、哪些 Swift 写法不能直接给 OC 调用?

Objective-C 无法直接理解一部分纯 Swift 特性,例如:

- `struct`
- Swift 风格的 `enum`,尤其是带关联值的枚举
- 泛型方法和泛型类型
- `async/await`
- 元组
- Swift-only protocol
- 一些复杂的闭包类型
- 默认参数
- 方法重载在映射后可能产生冲突

例如下面不能直接暴露:

```swift
struct User {
    let name: String
}

func request<T>() -> T {
    // ...
}
```

通常需要包装成 OC 能理解的类型:

```swift
@objcMembers
final class OCUser: NSObject {

    let name: String

    init(name: String) {
        self.name = name
        super.init()
    }
}
```

### async 方法的兼容包装

Swift:

```swift
func fetchUser() async throws -> String {
    "小明"
}
```

给 OC 包一层 completion:

```swift
@objc
func fetchUser(
    completion: @escaping (NSString?, NSError?) -> Void
) {
    Task {
        do {
            let name = try await fetchUser()
            completion(name as NSString, nil)
        } catch {
            completion(nil, error as NSError)
        }
    }
}
```

## 五、OC 枚举与 Swift

推荐使用 `NS_ENUM`:

```objc
typedef NS_ENUM(NSInteger, LoginState) {
    LoginStateUnknown,
    LoginStateLoading,
    LoginStateSuccess,
    LoginStateFailure
};
```

Swift 会把它识别为枚举:

```swift
let state: LoginState = .success
```

Swift 枚举想暴露给 OC,需要使用 `Int` 原始值:

```swift
@objc enum PaymentState: Int {
    case pending
    case success
    case failure
}
```

OC:

```objc
PaymentState state = PaymentStateSuccess;
```

## 六、Block 和 Swift 闭包

OC:

```objc
typedef void (^LoginCompletion)(BOOL success, NSString * _Nullable message);

- (void)loginWithCompletion:(LoginCompletion)completion;
```

Swift 调用时会转换成闭包:

```swift
manager.login { success, message in
    print(success)
    print(message ?? "")
}
```

Swift 暴露给 OC:

```swift
@objc
func loadData(
    completion: @escaping (NSData?, NSError?) -> Void
) {
    // ...
}
```

OC:

```objc
[manager loadDataWithCompletion:^(NSData *data, NSError *error) {
    // ...
}];
```

## 七、一个好记的关系

```text
Swift → OC
桥接头:项目名-Bridging-Header.h
桥接头里 import OC 的 .h
```

```text
OC → Swift
自动头:ProductModuleName-Swift.h
Swift 类继承 NSObject,并使用 @objc 或 @objcMembers
```

常见报错排查:

- 检查文件的 `Target Membership` 是否勾选。
- 检查 `Product Module Name`,不要凭项目显示名称猜 `xxx-Swift.h`。
- 桥接头只导入 `.h`,不要导入 `.m`。
- `项目名-Swift.h` 通常只在 `.m` 中导入,不要随意放进公共 `.h`。
- Swift 类即使写了 `public`,也不代表 OC 能调用;仍需兼容 Objective-C 运行时并使用 `@objc`。

 

posted @ 2026-07-28 18:36  黄增松  阅读(7)  评论(0)    收藏  举报