- 下面我们对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的交互头文件
- 查看后并没有发现我们定义的任何成员变量和方法、怎么办呢?
- 将我们的 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
- 因此、因此、因此、我们得出结论
- 对于纯 Swift 类来说,没有 动态特性。⽅法和属性不加任何修饰符的情况下。这个时候其实已经不具备我们所谓的 Runtime 特性了,这和我们在上篇的⽅法调度(V-Table调度)是不谋⽽合的。
- 对于纯 Swift 类,⽅法和属性添加 @objc 标识的情况下,当前我们可以通过 Runtime API 拿到,但是在我们的 OC 中是没法进⾏调度的。
- 对于继承⾃ 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() 打上断点。
(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相同的数据结构。
- 回到刚才的objc4-781源码工程中、我们在ASSERT打上断点、控制台输出获取到的methods
- 可以看到该二维数组中即为保存的函数调用信息、将methods展开即可看到我们熟悉的调用函数
- 综上:纯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 的