preface

ClassMetadata in the underlying source code is a bit much, so will pick some attention, or to understand the difficulty of writing. I’m going to skip some of the basic ones because it’s a little bit too much. I wrote StructMetadata earlier, which is a little simpler, and some of the same stuff.

Similarly, ClassMetadata I also translated into Swift code to achieve a again, with GitHub link address, referring to the translation to see the source code will be a little simpler.

This article must be read in conjunction with the Swift source code; it makes no sense to read it without the source code. The purpose of this article is to speed up understanding of the source code.

ClassMetadata

Enter the subject, search the source code directly, we can find the following code:

using ClassMetadata = TargetClassMetadata<InProcess>;
Copy the code

And then when we click on TargetClassMetadata, all of the attributes are in TargetClassMetadata and its parent class TargetAnyClassMetadata, and in the root class Kind, so I’m going to merge them together

/ / TargetMetadata
StoredPointer Kind;

/ / TargetAnyClassMetadata
ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass;
TargetPointer<Runtime, void> CacheData[2];
StoredSize Data;

/ / TargetClassMetadata
ClassFlags Flags;
uint32_t InstanceAddressPoint;
uint32_t InstanceSize;
uint16_t InstanceAlignMask;
uint16_t Reserved;
uint32_t ClassSize;
uint32_t ClassAddressPoint;
TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
TargetSignedPointer<Runtime, ClassIVarDestroyer * __ptrauth_swift_heap_object_destructor> IVarDestroyer;
Copy the code

One of the notable ones is CacheData[2], which is an array of Pointers of 16 bytes.

I wonder if there is a familiar feeling? Very similar to the class structure in OC. So classes in Swift are compatible with classes in OC

Translated into Swift code:

Swift class metadata is compatible with OC classes
struct ClassMetadata {
    // if kind is greater than 0x7FF, it isa class
    var Kind: InProcess
    // Metadata of the parent class, if null, is the top-level class
    var Superclass: UnsafeMutablePointer<ClassMetadata>
    Cached data is used for some dynamic lookups, which are owned by the runtime and usually need to interoperate with the use of Objective-C. (It's OC stuff, after all)
    var CacheData1: UnsafeMutablePointer<UnsafeRawPointer>
    var CacheData2: UnsafeMutablePointer<UnsafeRawPointer>
    The pointer to this data, which is used for out-of-line metadata, is usually opaque, except that the compiler sets the low value to indicate that this is a Swift metatype.
    var Data: InProcess  
    // Swift-specific class flags.
    var Flags: ClassFlags
    // The address point of instances of this type.
    var InstanceAddressPoint: UInt32
    // The required size of instances of this type.
    var InstanceSize: UInt32
    // The alignment mask of The address point of instances of this type.
    var InstanceAlignMask: UInt16
    // Reserved for runtime use.
    var Reserved: UInt16
    // The total size of the class object, including prefix and suffix extents.
    var ClassSize: UInt32
    // The offset of the address point within the class object.
    var ClassAddressPoint: UInt32
    // A swift specific description of a superline of type, null if this is an artificial subclass. There is currently no mechanism to dynamically create non-artificial subclasses.
    var Description: UnsafeMutablePointer<TargetClassDescriptor>
    // A function that destroys instance variables to clean up after the constructor returns early. If null, no cleanup operation is performed, and all ivars must be simple.
    var IVarDestroyer: UnsafeMutablePointer<ClassIVarDestroyer>}Copy the code

TargetClassDescriptor

You don’t know what TargetClassDescriptor or ClassIVarDestroyer is in TargetClassMetadata.

ClassIVarDestroyer is an alias for HeapObject, there’s nothing left for me to explore…

So again, focus on TargetClassDescriptor, a lot of the Class information is going to be looked up through ClassIVarDestroyer.

Let’s take a look at ClassIVarDestroyer:

class TargetClassDescriptor final
    : public TargetTypeContextDescriptor<Runtime>,
      public TrailingGenericContextObjects<TargetClassDescriptor<Runtime>,
                              TargetTypeGenericContextDescriptorHeader,
                              /*additional trailing objects:*/
                              TargetResilientSuperclass<Runtime>,
                              TargetForeignMetadataInitialization<Runtime>,
                              TargetSingletonMetadataInitialization<Runtime>,
                              TargetVTableDescriptorHeader<Runtime>,
                              TargetMethodDescriptor<Runtime>,
                              TargetOverrideTableHeader<Runtime>,
                              TargetMethodOverrideDescriptor<Runtime>,
                              TargetObjCResilientClassStubInfo<Runtime>>
Copy the code

We can see TargetClassDescriptor inherited TargetTypeContextDescriptor, this and StructMetadata article TargetStructDescriptor parent class, this is no longer say more, Look at the attribute that’s unique to TargetClassDescriptor:

TargetRelativeDirectPointer<Runtime, const char> SuperclassType;
union {
    uint32_t MetadataNegativeSizeInWords;
    TargetRelativeDirectPointer<Runtime,
                                TargetStoredClassMetadataBounds<Runtime>>
      ResilientMetadataBounds;
  };
  
  union {
    uint32_t MetadataPositiveSizeInWords;
    ExtraClassDescriptorFlags ExtraClassFlags;
  };

  uint32_t NumImmediateMembers;
  uint32_t NumFields;
  uint32_t FieldOffsetVectorOffset;

Copy the code

TargetRelativeDirectPointer this in StructMetadata article also talked about.

What’s interesting here is union, union shares the same memory space, so when you translate to swift code, you can’t translate them all directly as storage properties, you can translate one of them as computational properties.

Take a look at swift’s translation:


struct TargetClassDescriptor {
    // The first common tag stored in any context descriptor
    var Flags: ContextDescriptorFlags

    // Reuse the RelativeDirectPointer type. It's not, but it looks the same
    // Parent context, or null if it is a top-level context.
    var Parent: RelativeDirectPointer<InProcess>

    // Get the class name
    var Name: RelativeDirectPointer<CChar>

    Call getAccessFunction() to get the real function pointer (there is no wrapper). You get a wrapper class for the MetadataAccessFunction pointer. This function provides an overloading of operator() to call it with the correct calling convention (variable length arguments), and accidentally named renormalization calls this method (I don't know much about this yet).
    var AccessFunctionPtr: RelativeDirectPointer<UnsafeRawPointer>

    // A pointer to the field descriptor of the type (if any). A description of a type field from which you can obtain the properties of a structure.
    var Fields: RelativeDirectPointer<FieldDescriptor>
    
    // The type of the superclass, expressed as a mangled type name that can refer to the generic arguments of the subclass type.
    var SuperclassType: RelativeDirectPointer<CChar>
    
    // The following two attributes are of the union type in the source code, so take the large type as the attribute (this looks like the same here), and check if you have a Resilient superclass
    
    Resilient Superclass (ResilientMetadataBounds) resilient Superclass (ResilientMetadataBounds
    var ResilientMetadataBounds: RelativeDirectPointer<TargetStoredClassMetadataBounds>
    / / not resilient use MetadataNegativeSizeInWords superclass, said the class metadata object of negative size (in bytes)
    var MetadataNegativeSizeInWords: UInt32 {
        get {
            return UInt32(ResilientMetadataBounds.offset)
        }
    }

    // Resilient Superclass exists. Using ExtraClassFlags indicates the existence of an Objective-C elastic class stub
    var ExtraClassFlags: ExtraClassDescriptorFlags
    / / not resilient use MetadataPositiveSizeInWords superclass, said the class metadata object is size (in bytes)
    var MetadataPositiveSizeInWords: UInt32 {
        get {
            return ExtraClassFlags.Bits}}/** The number of other members of this class added to the class metadata. By default, this data is opaque to the runtime and not made public by other members; It's really just the NumImmediateMembers * sizeof(void*) bytes of data. These bytes are added in the address point before or after, depending on the areImmediateMembersNegative () method. * /
    var NumImmediateMembers: UInt32
    
    
    // Number of attributes, not including the parent class
    var NumFields: Int32
    // The offset of the field offset vector that stores the structure. If the offset is 0, you have no attributes
    // If the class has an elastic parent, the metaData is offset from its elastic parent
    var FieldOffsetVectorOffset: Int32

}
Copy the code

And write like this don’t write ExtraClassDescriptorFlags, looking at the source code and translating, corresponds to is easy to understand.

FlagSet

When you read the source code, often see Flag class inheritance FlagSet, just like the above ExtraClassDescriptorFlags ignored by me.

Check out FlagSet’s source code:

template <typename IntType>
class FlagSet {
 
  IntType Bits;

protected:
  template <unsigned BitWidth>
  static constexpr IntType lowMaskFor(a) {
    return IntType((1 << BitWidth) - 1);
  }

  template <unsigned FirstBit, unsigned BitWidth = 1>
  static constexpr IntType maskFor() {
    return lowMaskFor<BitWidth>() << FirstBit;
  }

  /// Read a single-bit flag.
  template <unsigned Bit>
  bool getFlag(a) const {
    return Bits & maskFor<Bit>();
  }

  /// Read a multi-bit field.
  template <unsigned FirstBit, unsigned BitWidth, typename FieldType = IntType>
  FieldType getField(a)const {
    return FieldType((Bits >> FirstBit) & lowMaskFor<BitWidth>());
  }

  // A convenient macro for defining a getter and setter for a flag.
  // Intended to be used in the body of a subclass of FlagSet.
#define FLAGSET_DEFINE_FLAG_ACCESSORS(BIT, GETTER, SETTER) \
  bool GETTER() const {                                    \
    return this->template getFlag<BIT>(a); \ } \ void SETTER(bool value) { \ this->template setFlag<BIT>(value); The \}

  // A convenient macro for defining a getter and setter for a field.
  // Intended to be used in the body of a subclass of FlagSet.
#define FLAGSET_DEFINE_FIELD_ACCESSORS(BIT, WIDTH, TYPE, GETTER, SETTER) \
  TYPE GETTER() const {                                                  \
    return this->template getField<BIT, WIDTH, TYPE>(a); \ } \ void SETTER(TYPE value) { \ this->template setField<BIT, WIDTH, TYPE>(value); The \}

};
Copy the code

I just copied a few of the methods we needed

Let’s look at the attributes first. There’s only one IntType Bits. IntType is the equivalent of a generic and needs to be passed in externally, but it needs to be an integer.

There are four methods, lowMaskFor and maskFor for getFlag and getField services. If you are careful, you may also notice that several parameters are missing, such as BIT, WIDTH, TYPE, etc., which are also externally determined and passed in.

The last two are convenient method generated macros, we can see ExtraClassDescriptorFlags example:

class ExtraClassDescriptorFlags : public FlagSet<uint32_t> {
  enum {
    HasObjCResilientClassStub = 0};public:
  explicit ExtraClassDescriptorFlags(uint32_t bits) : FlagSet(bits) {}
  constexpr ExtraClassDescriptorFlags(a) {}

  FLAGSET_DEFINE_FLAG_ACCESSORS(HasObjCResilientClassStub,
                                hasObjCResilientClassStub,
                                setObjCResilientClassStub)
};
Copy the code

We can clearly see that FLAGSET_DEFINE_FLAG_ACCESSORS generates convenience methods for identifying and setting flags.

Since there are many Flag classes that inherit from the FlagSet class, WHEN TRANSLATING into swift code, I pull it out and turn it into a protocol:

protocol FlagSet {
    associatedtype IntType : FixedWidthInteger
    var Bits: IntType { get set }
    
    func lowMaskFor(_ BitWidth: Int) -> IntType
    
    func maskFor(_ FirstBit: Int) -> IntType
    
    func getFlag(_ Bit: Int) -> Bool
    
    func getField(_ FirstBit: Int._ BitWidth: Int) -> IntType
}

extension FlagSet {
    func lowMaskFor(_ BitWidth: Int) -> IntType {
        return IntType((1 << BitWidth) - 1)}func maskFor(_ FirstBit: Int) -> IntType {
        return lowMaskFor(1) << FirstBit
    }
    
    func getFlag(_ Bit: Int) -> Bool {
        return ((Bits & maskFor(Bit)) ! = 0)}func getField(_ FirstBit: Int._ BitWidth: Int) -> IntType {
        return IntType((Bits >> FirstBit) & lowMaskFor(BitWidth)); }}struct ExtraClassDescriptorFlags: FlagSet {
    
    enum kType: Int {
        case HasObjCResilientClassStub = 0
    }
    
    typealias IntType = UInt32
    var Bits: IntType
    
}
Copy the code

ExtraClassDescriptorFlags inherited the agreement, so you can quickly determine whether there is the Flag.

TrailingGenericContextObjects

This one is a little difficult for me, and I understand it by running breakpoints.

We went back to TargetClassDescriptor definition, except we analysis in front of the inherited TargetTypeContextDescriptor inherits TrailingGenericContextObjects, Introduced into 10 in TrailingGenericContextObjects class template classes.

We have a look at the definition of TrailingGenericContextObjects:

template<class Runtime.template <typename> class TargetSelf.template <typename> class TargetGenericContextHeaderType.typename.FollowingTrailingObjects>
class TrailingGenericContextObjects<TargetSelf<Runtime>, TargetGenericContextHeaderType, FollowingTrailingObjects... > :protectedswift::ABI::TrailingObjects<TargetSelf<Runtime>, TargetGenericContextHeaderType<Runtime>, GenericParamDescriptor, TargetGenericRequirementDescriptor<Runtime>, FollowingTrailingObjects... >Copy the code

We have seen, in addition to TargetSelf and TargetGenericContextHeaderType is fixed template, FollowingTrailingObjects is a variable-length template, We can do one to one correspondence with the templates that come in through the TargetClassDescriptor.

TrailingObjects TrailingGenericContextObjects inheritance, we put all incoming template in TrailingObjects, can get real TrailingObjects object.

Sort out all the TrailingObject sequences:

  • TargetClassDescriptor
  • TargetTypeGenericContextDescriptorHeader
  • GenericParamDescriptor
  • TargetGenericRequirementDescriptor
  • TargetResilientSuperclass
  • TargetForeignMetadataInitialization
  • TargetSingletonMetadataInitialization
  • TargetVTableDescriptorHeader
  • TargetMethodDescriptor
  • TargetOverrideTableHeader
  • TargetMethodOverrideDescriptor
  • TargetObjCResilientClassStubInfo

It is understood from breakpoint debugging that all of these class objects are next to each other (possibly with memory alignment). Of course, the number of these objects is not fixed, some are 0, which means none, some are 1, and some are several, and they need to be retrieved from memory somewhere.

So if you want to get the memory address of one of the class objects, you have to determine whether the class object exists, and you need to know the memory address of the previous class object.

Get TrailingObject method implementation:

static NextTy *
  getTrailingObjectsImpl(BaseTy *Obj, TrailingObjectsBase::OverloadToken
       
        )
        {
    auto *Ptr = TopTrailingObj::getTrailingObjectsImpl(
                    Obj, TrailingObjectsBase::OverloadToken<PrevTy>()) +
                TopTrailingObj::callNumTrailingObjects(
                    Obj, TrailingObjectsBase::OverloadToken<PrevTy>());

    if (requiresRealignment())
      return reinterpret_cast<NextTy *>(
          llvm::alignAddr(Ptr, llvm::Align(alignof(NextTy))));
    else
      return reinterpret_cast<NextTy *>(Ptr);
  }
Copy the code

This looks complicated, so there are just two core methods: getTrailingObjectsImpl and callNumTrailingObjects.

The recursive call getTrailingObjectsImpl gets the address of the last object and callNumTrailingObjects gets the number of that object. Take the address of an object and add the number of steps to it to get the starting position of the object you want.

Let’s look at the TrailingObjects core implementation:


// These two methods are the base of the recursion for this method.
  static const BaseTy *
  getTrailingObjectsImpl(const BaseTy *Obj,
                         TrailingObjectsBase::OverloadToken<BaseTy>) {
    return Obj;
  }

  static BaseTy *
  getTrailingObjectsImpl(BaseTy *Obj, TrailingObjectsBase::OverloadToken
       
        )
        {
    return Obj;
  }

  // callNumTrailingObjects simply calls numTrailingObjects on the
  // provided Obj -- except when the type being queried is BaseTy
  // itself. There is always only one of the base object, so that case
  // is handled here. (An additional benefit of indirecting through
  // this function is that consumers only say "friend
  // TrailingObjects", and thus, only this class itself can call the
  // numTrailingObjects function.)
  static size_t
  callNumTrailingObjects(const BaseTy *Obj,
                         TrailingObjectsBase::OverloadToken<BaseTy>) {
    return 1;
  }

  template <typename T>
  static size_t callNumTrailingObjects(const BaseTy *Obj,
                                       TrailingObjectsBase::OverloadToken<T>) {
    return Obj->numTrailingObjects(TrailingObjectsBase::OverloadToken<T>());
  }
Copy the code

We can obviously see that if we get the object itself, getTrailingObjectsImpl returns Obj directly, ending the recursive call, and the callNumTrailingObjects number also returns 1.

If the object is not itself, then getTrailingObjectsImpl is called recursively, CallNumTrailingObjects returns yes Obj – > numTrailingObjects (TrailingObjectsBase: : OverloadToken < T > ())

NumTrailingObjects ahead TargetClassDescriptor and TrailingGenericContextObjects class has achieved, I copied to:

size_t numTrailingObjects(OverloadToken<GenericContextHeaderType>) const {
    return asSelf() - >isGeneric()?1 : 0;
  }
  
  size_t numTrailingObjects(OverloadToken<GenericParamDescriptor>) const {
    return asSelf() - >isGeneric()?getGenericContextHeader().NumParams : 0;
  }

  size_t numTrailingObjects(OverloadToken<GenericRequirementDescriptor>) const {
    return asSelf() - >isGeneric()?getGenericContextHeader().NumRequirements : 0;
  }
  
  size_t numTrailingObjects(OverloadToken<ResilientSuperclass>) const {
    return this->hasResilientSuperclass()?1 : 0;
  }

  size_t numTrailingObjects(OverloadToken<ForeignMetadataInitialization>) const{
    return this->hasForeignMetadataInitialization()?1 : 0;
  }

  size_t numTrailingObjects(OverloadToken<SingletonMetadataInitialization>) const{
    return this->hasSingletonMetadataInitialization()?1 : 0;
  }

  size_t numTrailingObjects(OverloadToken<VTableDescriptorHeader>) const {
    return hasVTable()?1 : 0;
  }

  size_t numTrailingObjects(OverloadToken<MethodDescriptor>) const {
    if (!hasVTable())
      return 0;

    return getVTableDescriptor()->VTableSize;
  }

  size_t numTrailingObjects(OverloadToken<OverrideTableHeader>) const {
    return hasOverrideTable()?1 : 0;
  }

  size_t numTrailingObjects(OverloadToken<MethodOverrideDescriptor>) const {
    if (!hasOverrideTable())
      return 0;

    return getOverrideTable()->NumEntries;
  }

  size_t numTrailingObjects(OverloadToken<ObjCResilientClassStubInfo>) const {
    return hasObjCResilientClassStub()?1 : 0;
  }
  
Copy the code

So how to get the corresponding class object is clear, look at my translation of Swift code:

extension TargetClassDescriptor {
    
    mutating func getTargetTypeGenericContextDescriptorHeaderPointer(a) -> (resultPtr: UnsafeMutablePointer<TargetTypeGenericContextDescriptorHeader>, count: Int) {
        let pointer = withUnsafeMutablePointer(to: &self) {
            return UnsafeMutableRawPointer($0.advanced(by: 1)).assumingMemoryBound(to: TargetTypeGenericContextDescriptorHeader.self)}let count = Flags.isGeneric() ?  1 : 0
        return (pointer, count)
    }
    
    mutating func getGenericParamDescriptorPointer(a) -> (resultPtr: UnsafeMutablePointer<GenericParamDescriptor>, count: Int) {
        let (lastPointer, lastCount) = getTargetTypeGenericContextDescriptorHeaderPointer()
        let pointer = UnsafeMutableRawPointer(lastPointer.advanced(by: lastCount)).assumingMemoryBound(to: GenericParamDescriptor.self)
        let count = Flags.isGeneric() ?  Int(lastPointer.pointee.Base.NumParams) : 0
        return (pointer, count)
    }
    
    mutating func getTargetGenericRequirementDescriptorPointer(a) -> (resultPtr: UnsafeMutablePointer<TargetGenericRequirementDescriptor>, count: Int) {
        let (lastPointer, lastCount) = getGenericParamDescriptorPointer()
        let pointer = UnsafeMutableRawPointer(lastPointer.advanced(by: lastCount)).assumingMemoryBound(to: TargetGenericRequirementDescriptor.self)
        let GenericContextDescriptorHeaderPointer = getTargetTypeGenericContextDescriptorHeaderPointer().resultPtr
        let count = Flags.isGeneric() ?  Int(GenericContextDescriptorHeaderPointer.pointee.Base.NumRequirements) : 0
        return (pointer, count)
    }
    
    mutating func getTargetResilientSuperclassPointer(a) -> (resultPtr: UnsafeMutablePointer<TargetResilientSuperclass>, count: Int) {
        let (lastPointer, lastCount) = getTargetGenericRequirementDescriptorPointer()
        let pointer = UnsafeMutableRawPointer(lastPointer.advanced(by: lastCount)).assumingMemoryBound(to: TargetResilientSuperclass.self)
        let count = hasResilientSuperclass() ? 1 : 0
        return (pointer, count)
    }
    
    mutating func getTargetForeignMetadataInitializationPointer(a) -> (resultPtr: UnsafeMutablePointer<TargetForeignMetadataInitialization>, count: Int) {
        let (lastPointer, lastCount) = getTargetResilientSuperclassPointer()
        let pointer = UnsafeMutableRawPointer(lastPointer.advanced(by: lastCount)).assumingMemoryBound(to: TargetForeignMetadataInitialization.self)
        let count = hasForeignMetadataInitialization() ? 1 : 0
        return (pointer, count)
    }
    
    mutating func getTargetSingletonMetadataInitializationPointer(a) -> (resultPtr: UnsafeMutablePointer<TargetSingletonMetadataInitialization>, count: Int) {
        let (lastPointer, lastCount) = getTargetForeignMetadataInitializationPointer()
        let pointer = UnsafeMutableRawPointer(lastPointer.advanced(by: lastCount)).assumingMemoryBound(to: TargetSingletonMetadataInitialization.self)
        let count = hasSingletonMetadataInitialization() ? 1 : 0
        return (pointer, count)
    }
    
    mutating func getTargetVTableDescriptorHeaderPointer(a) -> (resultPtr: UnsafeMutablePointer<TargetVTableDescriptorHeader>, count: Int) {
        let (lastPointer, lastCount) = getTargetSingletonMetadataInitializationPointer()
        let pointer = UnsafeMutableRawPointer(lastPointer.advanced(by: lastCount)).assumingMemoryBound(to: TargetVTableDescriptorHeader.self)
        let count = hasVTable() ? 1 : 0
        return (pointer, count)
    }
    
    mutating func getTargetMethodDescriptorPointer(a) -> (resultPtr: UnsafeMutablePointer<TargetMethodDescriptor>, count: Int) {
        let (lastPointer, lastCount) = getTargetVTableDescriptorHeaderPointer()
        let pointer = UnsafeMutableRawPointer(lastPointer.advanced(by: lastCount)).assumingMemoryBound(to: TargetMethodDescriptor.self)
        let count = hasVTable() ? Int(lastPointer.pointee.VTableSize) : 0
        return (pointer, count)
    }
    
    mutating func getTargetOverrideTableHeaderPointer(a) -> (resultPtr: UnsafeMutablePointer<TargetOverrideTableHeader>, count: Int) {
        let (lastPointer, lastCount) = getTargetMethodDescriptorPointer()
        let pointer = UnsafeMutableRawPointer(lastPointer.advanced(by: lastCount)).assumingMemoryBound(to: TargetOverrideTableHeader.self)
        let count = hasOverrideTable() ? 1 : 0
        return (pointer, count)
    }
    
    mutating func getTargetMethodOverrideDescriptorPointer(a) -> (resultPtr: UnsafeMutablePointer<TargetMethodOverrideDescriptor>, count: Int) {
        let (lastPointer, lastCount) = getTargetOverrideTableHeaderPointer()
        let pointer = UnsafeMutableRawPointer(lastPointer.advanced(by: lastCount)).assumingMemoryBound(to: TargetMethodOverrideDescriptor.self)
        let count = hasOverrideTable() ? Int(lastPointer.pointee.NumEntries) : 0
        return (pointer, count)
    }
    
    mutating func getTargetObjCResilientClassStubInfoPointer(a) -> (resultPtr: UnsafeMutablePointer<TargetObjCResilientClassStubInfo>, count: Int) {
        let (lastPointer, lastCount) = getTargetMethodOverrideDescriptorPointer()
        let pointer = UnsafeMutableRawPointer(lastPointer.advanced(by: lastCount)).assumingMemoryBound(to: TargetObjCResilientClassStubInfo.self)
        let count = hasObjCResilientClassStub() ? 1 : 0
        return (pointer, count)
    }
}
Copy the code

In fact, the code is similar, maybe it can be extracted to have a better encapsulation, the source code has the memory alignment operation “alignAddr”, I am lazy, I did not do it when THERE was no error, haha.

Here’s the way to see if it’s wrong: LookTargetMethodDescriptorIn the classImplIf the property address is correct, you can use disassembly commandsdis -s 0x0000000100013ba0View, for example:

trySwiftClass method substitution

Using the above method, we can get the address of the function implementation. Have you thought about replacing them?

I tried it, and it didn’t work

A bad access to an address, I use the plugin to check the address:

Finding 0x10001fd58 in the __TEXT section indicates that this is read-only at runtime, and if it changes, it will report an error like I did.

conclusion

If you do not want to see the source code also does not matter, the structure of ClassMetadata in my translation of Swift code has been all reflected.

The failure of method substitution also gives us a sense of Swift’s security and a sense that Swift is a static language. But this does not mean that Swift compiled program can not be modified, we can directly by looking for the symbol table of Macho file, modify the Macho file function implementation address, re-signature, the modified file Macho can run on the phone again. Suffice it to say, for code compiled with Swift, reverse engineering is even harder.