Swift-OC混编

1.混编

  • Swift 工程中使用 Objective-C 文件;

  • Objective-C 工程中使用 Swift 文件。

本文所用 Xcode 版本:9.4.1

2.Swift中混编OC

Swift 工程中使用 OC 文件时需要依赖 桥接头文件 将 OC 中的属性、接口等暴露给 Swift。默认情况下,在 Swift 工程中首次创建 OC 文件时,Xcode 会自动提示你是否创建桥接头文件:

桥接头文件

选择创建后,工程目录下会多出一个以 “工程名-Bridging-Header.h” 命名的头文件:

swift工程中OC文件目录

可在 “Targets–>Build Settings–>Swift Compiler - General” 查看,一般不用做额外修改:

swift使用OC文件的设置

如果你忽略了 Xcode 的提示,没创建桥接头文件,没关系后面还有机会。稍后你可以按照上面的命名规则自己新建一个头文件,在 “Targets–>Build Settings–>Swift Compiler - General” 中手动设置好 “Objective-C Bridging Header” 选项的路径即可。

接下来就可以在桥接头文件 “HelloSwift-Bridging-Header.h” 中导入你想暴露的 OC 头文件了:

1
#import "OCFile.h"

这样,就可以在 Swift 工程中使用刚刚创建的 OC 类OCFile了,完整示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//OCFile.h
typedef NS_ENUM(NSInteger, Direction) {
DirectionEast,//Swift中会被编译为.east(OC中的枚举前缀在Swift中会被截断)
DirectionWest,
DirectionSouth,
DirectionNorth,
};

@interface OCFile : NSObject

@property (nonatomic, assign) int aInt;
@property (nonatomic, strong, nonnull) NSArray *anArr;
@property (nonatomic, copy) NSString *str;

- (void)instanceFunction:(Direction)direction;
+ (void)classFunction;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//OCFile.m
#import "OCFile.h"

@interface OCFile()
@property (nonatomic, strong) NSDictionary *aDic;
@end

@implementation OCFile

-(instancetype)init
{
self = [super init];
if (self) {
aDouble = 0.001;
aInte = 1;
_aInt = 2;
_anArr = @[@(123)];
_aDic = @{@"k1":_anArr};
}
return self;
}

- (void)instanceFunction:(Direction)direction
{
NSLog(@"++++call instance method");
}

+ (void)classFunction
{
NSLog(@"++++call class method");
}

@end

AppDelegate.swift中直接使用OC类,不需要再导入其头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication,
didFinishLaunchingWithOptions
launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
let ocf = OCFile()
ocf.instanceFunction(Direction.east)
OCFile.classFunction()
print("+++\(ocf.anArr)")

return true
}
}

3.OC中混编Swift

OC 工程中使用 Swift 文件需要一个命名格式为 “工程名-Swift.h” 的头文件。它是一个 OC 头文件,包含了工程 Target 里所有 Swift 代码中定义的接口、属性等。有了它 Swift 文件中标记为 openpublicinternal 的信息才能暴露给 OC 。在编译工程(command+B)后 Xcode 自动生成,不需要自己创建,也不会显示在工程目录中。下面是具体步骤:

在 OC 工程中新建 Swift 文件:“command+N -> swift File”,目录如下:

OC工程中swift文件目录

首次创建 Swift 文件时,Xcode 会提示新建桥接头文件,这里可以不创建。之后 Xcode 会自动设置好 “Objective-C Generated Interface Header Name” 选项,我们不用做什么修改:

OC中使用swift文件

自定义你的 Swift 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 继承NSObject并用@objc标记需要暴露给OC的元素
import Foundation

class SwiftFile : NSObject {
@objc var name : String?
@objc let nick : String

@objc init(name:String,nick:String) {
self.name = name
self.nick = nick
super.init()
}

@objc func swiftInstanceMethod(name:String) -> String {
self.name = name
print("name is:\(name)")
return name;
}

@objc class func swiftClassMethod(nick:String) -> String {
print("nick is:\(nick)")
return nick
}
}

在用到此 Swift 文件的地方导入 “工程名-swift.h” 头文件即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "AppDelegate.h"
#import "ASDF-Swift.h" //导入 swift 文件对应的 OC 版头文件

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
SwiftFile *sf = [[SwiftFile alloc] initWithName:@"swift" nick:@"sf"];
sf.name = @"Swift4.1";
[sf swiftInstanceMethodWithName:@"Hello"];
[SwiftFile swiftClassMethodWithNick:@"newNick"];

return YES;
}
@end

command + B 编译之后,command + 点击这个ASDF-Swift.h就能看到其中的内容了。里面是 Xcode 帮我们生成的 SwiftFile.swift 的 OC 版头文件,内容很长,重要内容摘录如下:

1
2
3
4
5
6
7
8
@interface SwiftFile : NSObject
@property (nonatomic, copy) NSString * _Nullable name;
@property (nonatomic, readonly, copy) NSString * _Nonnull nick;
- (nonnull instancetype)initWithName:(NSString * _Nonnull)name nick:(NSString * _Nonnull)nick OBJC_DESIGNATED_INITIALIZER;
- (NSString * _Nonnull)swiftInstanceMethodWithName:(NSString * _Nonnull)name SWIFT_WARN_UNUSED_RESULT;
- (nonnull instancetype)init SWIFT_UNAVAILABLE;
+ (nonnull instancetype)new SWIFT_DEPRECATED_MSG("-init is unavailable");
@end

注意事项

  • 可访问性

OC 中使用 Swift 文件时,Swift类需要继承NSObject并用@objc标记需要暴露给OC的元素;并且工程名-Swift.h文件中只能将 Swift文件中标注为openpublicinternal的类、属性和接口等暴露给 OC,而fileprivateprivate标记的信息对 OC 不可见。Swift 中默认的访问控制修饰符是internal

  • 版本变更

Swift 3.x 中类只要继承了 NSObject,编译器会隐式地为所有public的属性或方法添加@objc标注,这样就能把 Swift 的属性、函数等暴露给 OC;但 Swift 4.x 中继承自 NSObject 的类不再隐式添加@objc,需要手动标注。

  • 继承问题

Swift类可以继承自 OC 类,但反过来 OC 类不能继承 Swift 类。

  • 宏定义

Swift中使用 OC 中的时,可以将简单的宏定义成全局常量,复杂的宏可以将其定义成函数。

  • Swift独有的特性

将 Swift 代码导入 OC 后,你可以访问在 Swift 类或协议中使用@objc标记的任何对象,只要该对象与 OC 兼容。部分 Swift 独有的特性不能在 OC 中使用,包括:泛型、元组、非Int类型的枚举、可变参数、函数嵌套等。


相关参考:

#©Apple


Swift-OC混编
https://davidlii.cn/2018/09/20/swift-mix.html
作者
Davidli
发布于
2018年9月20日
许可协议