• 下面我们对Swift的Runtime进行探究、先看纯Swift案例
class  HSTeacher {
    var age:Int = 20
    var name:String = "Holo"
    func teachMethod1() {
        print("teach Method1")
    }
    func teachMethod2() {
        print("teach Method2")
    }
}
let t = HSTeacher()
func runtimeMetood() {
    var methodCount:UInt32 = 0
    let methodList = class_copyMethodList(HSTeacher.self, &methodCount)
    for i in 0..<numericCast(methodCount) {
        if let method = methodList?[i] {
            let methodName  = method_getName(method)
            print("方法列表:\(String(describing: methodName))")
        }else{
            print("not found method")
        }
    }
    var count:UInt32 = 0
    let proList = class_copyPropertyList(HSTeacher.self, &count)
    for i in 0..<numericCast(count) {
        if let property = proList?[i]{
            let propertyName = property_getName(property)
            print("成员属性:\(String(utf8String: propertyName))")
        }else{
            print("not found property")
        }
    }
    print("test running")
}
runtimeMetood()
  • 此时查看我们的调用结果发现 只有 print("test running") 执行了,并没有获取到方法列表和成员属性。为什么呢?
  • 因为我们使用的是OC的Runtime机制获取的方法列表和成员属性、顾需要借助于 @objc 来修饰我们的Swift中属性和方法暴露给 OC来使用,对上述HSTeacher进行修改后
class  HSTeacher {
    @objc var age:Int = 20
    @objc var name:String = "Holo"
    @objc func teachMethod1() {
        print("teach Method1")
    }
    @objc func teachMethod2() {
        print("teach Method2")
    }
}
  • 此时调用我们的runtimeMethod ,我们看到返回结果如下、那么此刻即得到了我们想要的结果。
方法列表:teachMethod1
方法列表:teachMethod2
方法列表:name
方法列表:setName:
方法列表:age
方法列表:setAge:
成员属性:Optional("age")
成员属性:Optional("name")
test running
  • 然而、这样添加@objc 的方式修饰的成员变量和方法能传递给OC使用吗?我们通过如下方式查看暴露给OC的交互头文件

 

swift没有runtime机制 swift runtime_class_copyPrope

  • 查看后并没有发现我们定义的任何成员变量和方法、怎么办呢?
  • 将我们的 HSTeacher继承于NSObject、并将@objc修饰词都去除、我们再查看交互文件,在文件底部、我们可以看到已经暴露出了HSTeacher这个类了、
SWIFT_CLASS("_TtC15ReferenceCircle9HSTeacher")
@interface HSTeacher : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
  • 然鹅,其中的方法和属性木有了、输出日志也只有下列的结果
方法列表:init
方法列表:.cxx_destruct
test running
  • 所以、我们要通过OC来使用Swift中的属性和方法、还是离不开 @objc的修饰。添加上@objc之后我们貌似就看到了我们想要的结果、输出结果的日志也完整了
SWIFT_CLASS("_TtC15ReferenceCircle9HSTeacher")	//命名重整
@interface HSTeacher : NSObject
@property (nonatomic) NSInteger age;
@property (nonatomic, copy) NSString * _Nonnull name;
- (void)teachMethod1;
- (void)teachMethod2;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
  • 但是、但是、但是、我们此刻来查看下函数的调用方式。打上断点、打开Debug--Debug Workflow -- Always Show Disassembly汇编指令
let t = HSTeacher()
t.teachMethod1()	//断点
  • 当断点到来时、我们看到汇编代码、依然是将%rdx寄存器偏移 0x90然后取地址的值方式跳转函数(下方断点处 Ctrl+Step into即到调用函数ReferenceCircle`HSTeacher.teachMethod1(): ) 依然是函数表调用方式。
0x100004a74 <+68>: movq   %rax, %r13
    0x100004a77 <+71>: callq  *0x90(%rdx)		//断点
->  0x100004a7d <+77>: callq  0x1000056b0               ; ReferenceCircle.runtimeMetood() -> () at main.swift:22
  • 但是当我们使用 @objc dynamic来修饰了方法之后、被修饰的方法即得到了动态特性、此时此刻我们查看汇编调用、亲切的objc_msgSend出现在我们的视线中,Step into之后确实调用的objc_msgSend:
0x100004bbe <+62>: movq   %rax, %rdi
    0x100004bc1 <+65>: callq  0x10000793c               ; symbol stub for: objc_msgSend
->  0x100004bc6 <+70>: callq  0x100005740               ; ReferenceCircle.runtimeMetood() -> () at main.swift:22
  • 因此、因此、因此、我们得出结论
  1. 对于纯 Swift 类来说,没有 动态特性。⽅法和属性不加任何修饰符的情况下。这个时候其实已经不具备我们所谓的 Runtime 特性了,这和我们在上篇的⽅法调度(V-Table调度)是不谋⽽合的。
  2. 对于纯 Swift 类,⽅法和属性添加 @objc 标识的情况下,当前我们可以通过 Runtime API 拿到,但是在我们的 OC 中是没法进⾏调度的。
  3. 对于继承⾃ NSObject 类来说,如果我们想要动态的获取当前的属性和⽅法,必须在其声明前添加 @objc 关键字,如果想使用⽅法交换,需要添加dynamic的标识,否则也是没有办法通过 Runtime API 获取的。
  • 下面我们查看下纯Swift类调用OC底层过程、将案例修改为如下
class  HSTeacher{
    var age:Int = 20
    var name:String = "Holo"
    @objc func teachMethod1() {
        print("teach Method1")
    }
    @objc func teachMethod2() {
        print("teach Method2")
    }
} 
func runtimeMetood(){...}
runtimeMetood()
  • 并且将案例放入objc4-781可编译源码中直接调试查看底层、我们首先在 runtimeMethod()方法中、下方代码处打上断点。
let methodList = class_copyMethodList(HSTeacher.self, &methodCount)
  • 并且定位到 objc-runtime-new.mm中 class_copyMethodList方法中、
Method *
class_copyMethodList(Class cls, unsigned int *outCount)
  • 当运行至 runtimeMethod() 断点处、在源码class_copyMethodList方法中的如下位置打上断点 (5127行)
const auto methods = cls->data()->methods();
  • 此时此刻、我们输出 cls 、可以看到当前Class为 Swift的类
(lldb) po cls
objc[43384]: mutex incorrectly locked
objc[43384]: mutex incorrectly locked
Runtime.HSTeacher
  • 当前拿到methods时、首先获取当前的Swift类Runtime.HSTeacher:cls
  • 然后去获取cls的 data()、下面查看这个 data(),
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
     //在我们的OC里面存储类的信息
    class_rw_t *data() const {
        return bits.data();	//断点
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ......
}
  • 在OC中这个data是用来存储类的信息的,我们接着在 return bits.data() 打上断点。

swift没有runtime机制 swift runtime_objc dynamic_02

(lldb) p bits
(class_data_bits_t) $0 = (bits = 4312073810)
(lldb) p superclass
(Class) $1 = Swift._SwiftObject
  • 所以对于我们的Swift来说、它是由默认的基类的、就是我们的 _SwiftObject ,此时我们去编译的Swift源码工程中搜索 _SwiftObject
  • 在SwiftObject.h中我们查到该类
// Source code: "SwiftObject"
// Real class name: mangled "Swift._SwiftObject"
#define SwiftObject _TtCs12_SwiftObject
#if __has_attribute(objc_root_class)
__attribute__((__objc_root_class__))
#endif
SWIFT_RUNTIME_EXPORT @interface SwiftObject<NSObject> {
 @private
  Class isa;			//isa
  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;//refCounts
}
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
- (Class)superclass;
- (Class)class;
- (instancetype)self;
- (struct _NSZone *)zone;
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
- (BOOL)isProxy;
+ (BOOL)isSubclassOfClass:(Class)aClass;
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
- (instancetype)retain;
- (oneway void)release;
- (instancetype)autorelease;
- (NSUInteger)retainCount;
- (id /* NSString */)description;
- (id /* NSString */)debugDescription;
@end
  • 由上述可见:
  • SwiftObject本身实现了 NSObject协议、为了和我们的OC进行交互、保留了OC的数据结构、
  • 回想下之前的Swift类对象的探索过程、我们想到了TargetAnyClassMetadata(Metadata.h中)、在该结构体中 我们可以看到 isa、superclass、data等与OC相同的数据结构。

swift没有runtime机制 swift runtime_objc dynamic_03

  • 回到刚才的objc4-781源码工程中、我们在ASSERT打上断点、控制台输出获取到的methods

swift没有runtime机制 swift runtime_class_copyPrope_04

  • 可以看到该二维数组中即为保存的函数调用信息、将methods展开即可看到我们熟悉的调用函数

swift没有runtime机制 swift runtime_swift没有runtime机制_05

  • 综上:纯Swift类在底层调用中、与我们的OC底层调用在部分上保持了一致。
  • 下面我们查看在分析objc4-781 OC源码中、有个swift_class_t结构体继承自objc_class、保留了我们需要的TargetClassMetadata中多种数据结构 、其中内容如下
struct swift_class_t : objc_class {
    uint32_t flags;
    uint32_t instanceAddressOffset;
    uint32_t instanceSize;
    uint16_t instanceAlignMask;
    uint16_t reserved;
    uint32_t classSize;
    uint32_t classAddressOffset;
    void *description;
    // ...
    void *baseAddress() {
        return (void *)((uint8_t *)this - classAddressOffset);
    }
};
  • 并且我们在分析Swift编译源码的TargetClassMetadata时、上述结构体成员完全展现、多么惊人的雷同、One day day 的

swift没有runtime机制 swift runtime_objc dynamic_06