OC & Swift 混编

导语

 

  本文主要简单描述了在 OC & Swift 混编工程中,两者是如何相互调用的,顺便实现在 Swift 中获取类的属性。

环境

 

macOS Sierra 10.12.4
Xcode 8.3.1
Swift 3.0

流程

Swift 调用 OC

 

  在 OC 编程语言编写的源工程中,当创建 Swift 文件时,根据提示创建好桥接文件。在桥接文件中有如下注释

Use this file to import your target’s public headers that you would like to expose to Swift.

  通过在该文件引入头文件可以顺利实现在 Swift 中调用 OC 方法。

OC 调用 Swift

 

  查看 Build Settings ,其中有一项 Swift Compiler - General 如下图所示

  其中 Objective-C Bridging Header 字段即为桥接文件。下面的 Objective-C Generated interface Header Name 字段就是 OC 中调用 Swift 类时需要引入的头文件。该字段的值的规则为 工程名-Swift.h

1
2
3
4
5
6
// 引入头文件
#import "Daily_modules-Swift.h"

// 使用Swift文件

[[TouchIDViewController alloc] init];

按住 进入 TouchIDViewController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@import UIKit;
#endif

#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch"
#pragma clang diagnostic ignored "-Wduplicate-method-arg"
@class NSBundle;
@class NSCoder;

SWIFT_CLASS("_TtC13Daily_modules21TouchIDViewController")
@interface TouchIDViewController : UIViewController
- (void)viewDidLoad;
- (void)didReceiveMemoryWarning;
- (nonnull instancetype)initWithNibName:(NSString * _Nullable)nibNameOrNil bundle:(NSBundle * _Nullable)nibBundleOrNil OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
@end

在运行时工程的 objc-runtime-new.h 头文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct objc_class : objc_object {
...
class_data_bits_t bits;
...
}

struct class_data_bits_t {
...
bool isSwift() {
return getBit(FAST_IS_SWIFT);
}
...
}

// class is a Swift class
#define FAST_IS_SWIFT (1UL<<0)

可以了解到 Swift 编写的类也就会被运行时处理。

Swift 的类的名称

  比如当前工程的 _TtC13Daily_modules21TouchIDViewController 如何来的。

前缀 _TtC
工程名的长度: 13
工程名: Daily_modles
类名的长度: 21
类名: TouchIDViewController

1
2
3
4
5
6
7
8
9
10
11
12
13
extension NSObject {
// create a static method to get a swift class for a string name
public class func swiftClassFromString(className: String) -> AnyClass! {
// get the project name
if let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String? {
// generate the full name of your class (take a look into your "YourProject-swift.h" file)
let classStringName = "_TtC\(appName.lengthOfBytes(using: String.Encoding.utf8))\(appName)\(className.lengthOfBytes(using: String.Encoding.utf8))\(className)"
// return the class!
return NSClassFromString(classStringName)
}
return nil;
}
}

上面是一个获取 Swift 类的 extension

Swift 版获取类的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class func getPropertiesInfo(cls:AnyClass?, recursive:Bool) -> Any! {

var clsM = cls
var glist = [String]()
var outCount:UInt32 = 0;

repeat {
let properties:UnsafeMutablePointer<objc_property_t?> = class_copyPropertyList(clsM, &outCount);
for index in 0..<outCount {
let property = property_getName(properties[Int(index)])
let result = String(cString: property!)
glist.append(result)
}
clsM = clsM?.superclass()
print(clsM! == NSObject.self);
}while (clsM! != NSObject.self && recursive)

return glist
}

只能获取 public / open 访问范围的属性 + OC 原有的属性

总结

Swift 的注释中如下的一段话

/// Accessing Objective-C Methods and Properties
/// ============================================
///
/// When you use AnyObject as a concrete type, you have at your disposal
/// every @objc method and property—that is, methods and properties
/// imported from Objective-C or marked with the @objc attribute. Because
/// Swift can’t guarantee at compile time that these methods and properties
/// are actually available on an AnyObject instance’s underlying type, these
/// @objc symbols are available as implicitly unwrapped optional methods and
/// properties, respectively.

  这一点和参考链接中提到的表达是有关的

  纯Swift类的函数调用已经不再是Objective-c的运行时发消息,而是类似C++的vtable,在编译时就确定了调用哪个函数,所以没法通过runtime获取方法、属性。
  TestSwiftVC继承自UIViewController,基类NSObject,而Swift为了兼容Objective-C,凡是继承自NSObject的类都会保留其动态性,所以我们能通过runtime拿到他的方法。

  Swift 不能通过运行时获取对应类的属性和方法,但是如果是继承自 NSObject( imported from Objective-C ) 或被标记了 @objc 则可以被获取,能被获取到的属性也是有所限制的。

参考

  1. Swift language NSClassFromString
  2. Swift Runtime 分析:还像 OC Runtime 一样吗?
  3. I can’t get properties of a Class using swift by class_copyPropertyList
  4. List of class’s properties in swift