一篇说透,OC对象和swift对象的关系

913 阅读4分钟

抱歉,标题党了😆😆,本文主要是笔者的一些探究,分享记录一下

思考

对于OC的实例对象、类对象、元类对象都很熟悉了。那么swift的对象呢?是否跟OC一样有这么多种对象?

温故

我们都知道OC中有3种对象,分别是实例对象、类对象、元类对象,数据结构如下:

typedef struct objc_class *Class; 
typedef struct objc_object *id;

struct objc_object {
    Class isa;
}

// 类对象、元类对象
struct objc_class : objc_object {
    Class superclass;
    cache_t cache;             
    class_data_bits_t bits;
}

实例化一个继承自NSObject的类对象,这个实例对象的第一个成员变量就是Class isa

@interface NSObject <NSObject> {
    Class isa;
}

struct 实例对象名 {
   Class isa;
   其它成员变量
}

实例对象中的isa指针,存的是指向该实例对象的类对象的Class地址,通过这个地址就可以找到这个类对象的类信息(实例方法,协议,属性信息、成员变量信息等)。而类对象中也有个isa,存的是指向该类对象的元类对象的地址,同样,通过这个地址就可以找到元类对象的类信息(类方法等)

以上老生常谈的OC对象的一些核心的信息。

那么swift中又是如何的呢?

知新

先来一个栗子:

class Animal: NSObject {
    @objc func getCat() -> AnyObject {
        let cat = Cat()
        cat.run()
        return cat
    }
}

class Cat {
    var age: Int = 10
    @objc var name: String = "Tom"
    
    @objc class func objcClassFun() {
        print(#function)
    }
    
    class func classFun() {
        print(#function)
    }
    
    func run() {
        print(#function)
    }
    
    @objc func eat() {
        print(#function)
    }
    
    @objc dynamic func look() {
        print(#function)
    }
    
    func hello() {
        print(#function)
    }
}
Animal *animal = [[Animal alloc] init];
id cat = [animal getCat];
[cat performSelector:@selector(eat)];

这样是否能够调用到eat方法? 答案是可以

是不是很意外,我们先来回顾下performSelector 是如何调用方法的,源码如下:

- (id)performSelector:(SEL)sel {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL))objc_msgSend)(self, sel);
}

objc_msgSend 第一个参数是self,也就是cat对象。我们都知道objc_msgSend 是通过isa来找到实例方法的,因为Cat是纯的swfit类,那么也就是说Cat类至少是有跟OC类对象相似的结构的。

于是带着这样的疑问,在objc的源码中找到了这样一个结构:

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_class_t 继承了objc_class,如果真的是这样,前面说的能够调到eat方法,就成立了。

但这仅仅只是猜测,需要进一步验证。

81dad6b8627d65889ca250bcf0de1032.png fb9b1a62845dd6935ba6b0c466b22b23.png be87300735aa6d8b5cbcf04e3e904646.png 8bc521b6caef32716001a30a1aebf7ab.png

从汇编代码可以看到,Cat最终调用了swift_allocObject方法。

static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  assert(isAlignmentMask(requiredAlignmentMask));
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask));

  // NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
  // check on the placement new allocator which we have observed on Windows,
  // Linux, and macOS.
  new (object) HeapObject(metadata);

  // If leak tracking is enabled, start tracking this object.
  SWIFT_LEAKS_START_TRACKING_OBJECT(object);

  SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);

  return object;
}

透过swift的源码可以看到,最终生成是一个HeapObject的对象,数据结构如下:

#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

/// The Swift heap-object header.
/// This must match RefCountedStructTy in IRGen.
struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *metadata;
  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}

HeapMetadata的数据结构如下:

struct TargetMetadata {
	StoredPointer Kind; // 指针
}

struct TargetHeapMetadata: TargetMetadata {
	// 没有成员
}

struct TargetAnyClassMetadata: TargetHeapMetadata {
	ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass;
	TargetPointer<Runtime, void> CacheData[2];
	StoredSize Data;
}

// 所以 TargetAnyClassMetadata 的结构如下
struct TargetAnyClassMetadata {
  StoredPointer Kind; // isa 
  ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass; 
	TargetPointer<Runtime, void> CacheData[2];
	StoredSize Data;
}

struct TargetClassMetadata : public TargetAnyClassMetadata {
  
  /// Swift-specific class flags.
  ClassFlags Flags;
  
  /// The address point of instances of this type.
  uint32_t InstanceAddressPoint;
  
  /// The required size of instances of this type.
  /// 'InstanceAddressPoint' bytes go before the address point;
  /// 'InstanceSize - InstanceAddressPoint' bytes go after it.
  uint32_t InstanceSize;
  
  /// The alignment mask of the address point of instances of this type.
  uint16_t InstanceAlignMask;
  
   /// Reserved for runtime use.
  uint16_t Reserved;
  
  /// The total size of the class object, including prefix and suffix
  /// extents.
  uint32_t ClassSize;
  
  /// The offset of the address point within the class object.
  uint32_t ClassAddressPoint;
  
  /// An out-of-line Swift-specific description of the type, or null
  /// if this is an artificial subclass.  We currently provide no
  /// supported mechanism for making a non-artificial subclass
  /// dynamically.
  TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
  
  /// A function for destroying instance variables, used to clean up after an
  /// early return from a constructor. If null, no clean up will be performed
  /// and all ivars must be trivial.
  TargetSignedPointer<Runtime, ClassIVarDestroyer * __ptrauth_swift_heap_object_destructor> IVarDestroyer;
}

这个结构有没有熟悉,跟上面swift_class_t 是一致的。

虽然最终生成的HeapObject对象,但最终暴露出去给OC层面的其实是另外一个类SwiftObject

#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts
  
SWIFT_RUNTIME_EXPORT @interface SwiftObject<NSObject> {
 @private
  Class isa;
  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}

可以看到,第一个成员变量的类型就是Class类型,也就是说NSObject和HeapObject是可以通过指定类型来进行类型转换的。

这一点可以通过lldb调试器来窥探到,如下图:

image-20210825230845903.png

明显可以看到,cat是继承了一个叫_TtCs12_SwiftObject的类,这也恰好印证我们上面的推测。并且通过object_getClass这个方法也可以证明到这一点。

既然类对象如此,那么是否存在元类对象呢?答案是肯定的。通过object_getClass来获取类对象的类对象的方法,同样的是可以获取到类方法,并且成功调用的。

image-20210825235246613.png

总结

至此,也就说明为什么一个纯Swift类可以通过OC的objc_msgSend的方式来调用了。因为Swift类保留了OC类的数据结构,只是在OC类的基础上,增加了一些新的信息。当然这样说并不是很严谨,但不妨碍我们去理解。

未完待续

如果调用的是run()方法,是否能调用成功?下篇会再继续探究,@objc到底做了什么?