In the last article, we explored the structure of classes, leaving us with the question of the difference between class_ro_t and class_rw_t. Let’s start with that distinction.

class_ro_tandclass_rw_tThe difference between

class_ro_t

Class_ro_t stores the properties, methods, and protocols of the current class that were determined at compile time. There are no category methods in class_ro_t. Methods added at run time are stored in class_rw_t generated at run time.

Ro indicates read only and cannot be modified. Let’s take a look at its definition:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif
    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };
    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;// Obtain the method of the method list
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;// The list of member variables
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;// Attribute list

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    _objc_swiftMetadataInitializer swiftMetadataInitializer(a) const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            return _swiftMetadataInitializer_NEVER_USE[0];
        } else {
            returnnil; }}const char *getName(a) const {
        return name.load(std::memory_order_acquire);
    }
    static const uint16_t methodListPointerDiscriminator = 0xC310;
#if 0 // FIXME: enable this when we get a non-empty definition of __ptrauth_objc_method_list_pointer from ptrauth.h.
        static_assert(std::is_same<
                      void * __ptrauth_objc_method_list_pointer *,
                      void * __ptrauth(ptrauth_key_method_list_pointer, 1, methodListPointerDiscriminator) *>::value,
                      "Method list pointer signing discriminator must match ptrauth.h");
#endif
    method_list_t *baseMethods(a) const {
#if __has_feature(ptrauth_calls)
        method_list_t *ptr = ptrauth_strip((method_list_t *)baseMethodList, ptrauth_key_method_list_pointer);
        if (ptr == nullptr)
            return nullptr;
        // Don't auth if the class_ro and the method list are both in the shared cache.
        // This is secure since they'll be read-only, and this allows the shared cache
        // to cut down on the number of signed pointers it has.
        bool roInSharedCache = objc::inSharedCache((uintptr_t)this);
        bool listInSharedCache = objc::inSharedCache((uintptr_t)ptr);
        if (roInSharedCache && listInSharedCache)
            return ptr;

        // Auth all other small lists.
        if (ptr->isSmallList())
            ptr = ptrauth_auth_data((method_list_t *)baseMethodList,
                                    ptrauth_key_method_list_pointer,
                                    ptrauth_blend_discriminator(&baseMethodList,
                                                                methodListPointerDiscriminator));
        return ptr;
#else
        return (method_list_t *)baseMethodList;
#endif
    }
	// Omit the code
};
Copy the code

class_rw_t

The attributes, methods, and protocols of the class are stored in class_rw_t, which is both readable and writable:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif
    explicit_atomic<uintptr_t> ro_or_rw_ext;
    Class firstSubclass;
    Class nextSiblingClass;
		// Omit the code
    const method_array_t methods(a) const {
        auto v = get_ro_or_rwe(a);if (v.is<class_rw_ext_t* > ()) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
        }
    }
    const property_array_t properties(a) const {
        auto v = get_ro_or_rwe(a);if (v.is<class_rw_ext_t* > ()) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t*>(&ro_or_rw_ext)->baseProperties}; }}const protocol_array_t protocols(a) const {
        auto v = get_ro_or_rwe(a);if (v.is<class_rw_ext_t* > ()) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t*>(&ro_or_rw_ext)->baseProtocols}; }}};Copy the code

Class_rw_t generation At runtime, during compilation, the class_ro_t structure is identified, and the data part of the bits in objc_class holds the address of the structure. After Runtime is run, specifically when runtime’s realizeClass method is run, the class_rw_t structure is generated, which contains class_ro_t, and the data section is updated with the address of the class_rw_t structure.

Class structure optimization in WWDC2020

Binary classes behave on disk as follows:

The class object itself contains the most frequently accessed information: Pointers to the metaclass, superclass, and method cache, and within the class structure, Pointers to the more data-containing structure class_ro_t, which contains the class name, methods, protocols, instance variables, and other compile-time information. Ro indicates read only.

After a class is loaded by Runtime, there are some changes to the structure of the class. To understand these changes, we need to understand two concepts:

**Clean Memory: a block of Memory that does not change after loading. Class_ro_t belongs to Clean Memory because it is read-only. **Dirty Memory: ** The block of Memory that changes at Runtime. Once a class is loaded, it becomes Dirty Memory. For example, we can dynamically add methods to a class at Runtime.

Dirty Memory is much more expensive than Clean Memory because it requires more Memory information and must be kept as long as the process is running. For us, more Clean Memory is obviously better because it saves more Memory. We can keep most class data in Clean Memory by separating out the parts of the data that never change. What should we do?

So let’s take a look at the structure of the loaded class

The class_rw_t structure is assigned to read/write data after the class is loaded into Runtime.

It turns out that class_rw_t takes up more memory than class_ro_t, and on the iPhone we measured about 30MB of these class_rw_T structures in the system. How do you optimize this memory? By measuring usage on actual devices, we find that about 10% of the classes actually have dynamic change behavior, such as dynamically adding methods, using Category methods, etc. So, what we can do is we can extract this dynamic part, which we call class_rw_ext_t, so the structure will look like this.

After splitting, 90% of the classes can be optimized as Clean Memory, which results in about 14MB of Memory savings at the system level, making the Memory available for more efficient uses.

Check out the Apple method video for more

summary

Class_ro_t is stored at compile time; Class_rw_t is determined at Runtime, which copies the contents of class_ro_t and then copies the properties, methods, and so on of the current class. So we can say that class_rw_t is a superset of class_ro_t, and of course the methods, properties, and so on that are actually accessing the contents of class_rw_t.

Type Encodings

When we ran clang earlier to convert the main.m file to the main.cpp file, we found some encodings. for example

{(struct objc_selector *)”setNickName:”, “v24@0:8@16”, (void *)_I_JSPerson_setNickName_} : v24@0:8@16

Apple official link address

v24@0:8@16

  • v:void
  • 24: Memory occupied
  • @: Object type parameterself
  • 0: The upper parameter is from0Position to start
  • :: SEL
  • 8:SELfrom8Position to start
  • @: Object type, the first argument actually passed
  • 16From:16Position to start

setterMethods the underlying

Let’s first define a JSPerson class:

@interface JSPerson : NSObject
{
    NSString *hobby; // 
    int a;
    NSObject *objc;  // 
}

@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;

@property (nonatomic, strong) NSString *name;
@property (atomic, strong) NSString *aname;

@end

@implementation JSPerson


@end
Copy the code

Convert it to c++ code using the clang command:

clang -rewrite-objc main.m -o main.cpp

We search for JSPerson globally in main. CPP and locate the code for the method of the class:

// @implementation JSPerson
static NSString * _I_JSPerson_nickName(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool.bool);

static void _I_JSPerson_setNickName_(JSPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _nickName), (id)nickName, 0.1); }

extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long.bool);

static NSString * _I_JSPerson_acnickName(JSPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _acnickName), 1); }
static void _I_JSPerson_setAcnickName_(JSPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _acnickName), (id)acnickName, 1.1); }

static NSString * _I_JSPerson_nnickName(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_nnickName)); }
static void _I_JSPerson_setNnickName_(JSPerson * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_nnickName)) = nnickName; }

static NSString * _I_JSPerson_anickName(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_anickName)); }
static void _I_JSPerson_setAnickName_(JSPerson * self, SEL _cmd, NSString *anickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_anickName)) = anickName; }

static NSString * _I_JSPerson_name(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_name)); }
static void _I_JSPerson_setName_(JSPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_name)) = name; }

static NSString * _I_JSPerson_aname(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_aname)); }
static void _I_JSPerson_setAname_(JSPerson * self, SEL _cmd, NSString *aname) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_aname)) = aname; }
// @end
Copy the code

As you can see, the get and set methods for each property are shown above. You can see that the set method for different properties does not always perform the same method. For example:

  • namethesetMethods:static void _I_JSPerson_setName_(JSPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_name)) = name; }
  • nickNamethesetMethods:static void _I_JSPerson_setNickName_(JSPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _nickName), (id)nickName, 0, 1); }

For example, the set method for name is shifted and the set method for nickName is called objc_setProperty. For example, the set method for nickName is called objc_setProperty. To explore this problem we need to use LLVM, we download LLVM source code from Github, download address.

In the LLVM source code, we’re going to search globally for objc_setProperty, find where this method is called, Found getSetPropertyFn method, it is the return value of CGM. CreateRuntimeFunction (FTy effects, “objc_setProperty”); :

  llvm::FunctionCallee getSetPropertyFn(a) {
    CodeGen::CodeGenTypes &Types = CGM.getTypes(a); ASTContext &Ctx = CGM.getContext(a);// void objc_setProperty (id, SEL, ptrdiff_t, id, bool, bool)
    CanQualType IdType = Ctx.getCanonicalParamType(Ctx.getObjCIdType());
    CanQualType SelType = Ctx.getCanonicalParamType(Ctx.getObjCSelType());
    CanQualType Params[] = {
        IdType,
        SelType,
        Ctx.getPointerDiffType() - >getCanonicalTypeUnqualified(),
        IdType,
        Ctx.BoolTy,
        Ctx.BoolTy};
    llvm::FunctionType *FTy =
        Types.GetFunctionType(
          Types.arrangeBuiltinFunctionDeclaration(Ctx.VoidTy, Params));
    return CGM.CreateRuntimeFunction(FTy, "objc_setProperty");
  }
Copy the code

Next, search for the keyword getSetPropertyFn(), which is also the place to find the call:

  llvm::FunctionCallee GetPropertySetFunction(a) override {
    return ObjCTypes.getSetPropertyFn(a); }Copy the code

Here’s just an intermediate call to the method, let’s continue to search for the keyword GetPropertySetFunction() to find where to call it:

void
CodeGenFunction::generateObjCSetterBody(const ObjCImplementationDecl *classImpl,
                                        const ObjCPropertyImplDecl *propImpl,
                                        llvm::Constant *AtomicHelperFn) {
  // Omit the code
  switch (strategy.getKind()) {
  case PropertyImplStrategy::Native: {
    // We don't need to do anything for a zero-size struct.
    if (strategy.getIvarSize().isZero())
      return;

    Address argAddr = GetAddrOfLocalVar(*setterMethod->param_begin());

    LValue ivarLValue =
      EmitLValueForIvar(TypeOfSelfObject(), LoadObjCSelf(), ivar, /*quals*/ 0);
    Address ivarAddr = ivarLValue.getAddress(*this);

    // Currently, all atomic accesses have to be through integer
    // types, so there's no point in trying to pick a prettier type.
    llvm::Type *bitcastType =
      llvm::Type::getIntNTy(getLLVMContext(),
                            getContext().toBits(strategy.getIvarSize()));

    // Cast both arguments to the chosen operation type.
    argAddr = Builder.CreateElementBitCast(argAddr, bitcastType);
    ivarAddr = Builder.CreateElementBitCast(ivarAddr, bitcastType);

    // This bitcast load is likely to cause some nasty IR.
    llvm::Value *load = Builder.CreateLoad(argAddr);

    // Perform an atomic store. There are no memory ordering requirements.
    llvm::StoreInst *store = Builder.CreateStore(load, ivarAddr);
    store->setAtomic(llvm::AtomicOrdering::Unordered);
    return;
  }

  case PropertyImplStrategy::GetSetProperty:
  case PropertyImplStrategy::SetPropertyAndExpressionGet: {

    llvm::FunctionCallee setOptimizedPropertyFn = nullptr;
    llvm::FunctionCallee setPropertyFn = nullptr;
    if (UseOptimizedSetter(CGM)) {
      // 10.8 and iOS 6.0 code and GC is off
      setOptimizedPropertyFn =
          CGM.getObjCRuntime().GetOptimizedPropertySetFunction(
              strategy.isAtomic(), strategy.isCopy());
      if(! setOptimizedPropertyFn) { CGM.ErrorUnsupported(propImpl, "Obj-C optimized setter - NYI");
        return; }}else {
      setPropertyFn = CGM.getObjCRuntime().GetPropertySetFunction(a);if(! setPropertyFn) { CGM.ErrorUnsupported(propImpl, "Obj-C setter requiring atomic copy");
        return; }}}Copy the code

Here is a switch statement, and the condition of the call depends on Strategy.getkind (). We then search PropertyImplStrategy to find out when the type was set. In the definition of PropertyImplStrategy we find the answer: The property decorated by copy is going to have Kind = GetSetProperty, which means the set method is going to call objc_setProperty

  // If we have a copy property, we always have to use getProperty/setProperty.
  // TODO: we could actually use setProperty and  an expression for non-atomics.
  if (IsCopy) {
    Kind = GetSetProperty;
    return;
  }
Copy the code

Let’s write code to verify:

@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;
Copy the code

We define four attributes and convert them to c++ code using clang:

static NSString * _I_JSPerson_nickName(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool.bool);

static void _I_JSPerson_setNickName_(JSPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _nickName), (id)nickName, 0.1); }//copy

extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long.bool);

static NSString * _I_JSPerson_acnickName(JSPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _acnickName), 1); }
static void _I_JSPerson_setAcnickName_(JSPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct JSPerson, _acnickName), (id)acnickName, 1.1); }//copy

static NSString * _I_JSPerson_nnickName(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_nnickName)); }
static void _I_JSPerson_setNnickName_(JSPerson * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_nnickName)) = nnickName; }//strong

static NSString * _I_JSPerson_anickName(JSPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_anickName)); }
static void _I_JSPerson_setAnickName_(JSPerson * self, SEL _cmd, NSString *anickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_JSPerson$_anickName)) = anickName; }//strong
Copy the code

summary

  • setMethods are not defined very much at the bottomsetMethod calls, instead of usingTranslation memoryOr callobjc_setPropertyMethods.
  • usecopyOf the properties of the modifiedsetMethod call isobjc_setPropertyMethods.
  • There is nocopyOf the properties of the modifiedsetMethod isTranslation memory.

isKindOfClassvsisMemberOfClass

We often use isKindOfClass and isMemberOfClass methods to determine the type of an object. Let’s start with an example. First define two inherited classes:

@interface JSPerson : NSObject{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
@end
@implementation JSPerson
@end
@interface JSTeacher : JSPerson
@property (nonatomic, copy) NSString *hobby;
- (void)teacherSay;
@end
@implementation JSTeacher
- (void)teacherSay{
    NSLog(@"%s",__func__);
}
@end
Copy the code

We define a method to print the result of the method:

void jsKindofDemo(void){
    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
    BOOL re3 = [(id)[JSPerson class] isKindOfClass:[JSPerson class]];       //
    BOOL re4 = [(id)[JSPerson class] isMemberOfClass:[JSPerson class]];     //
    NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
    BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
    BOOL re7 = [(id)[JSPerson alloc] isKindOfClass:[JSPerson class]];       //
    BOOL re8 = [(id)[JSPerson alloc] isMemberOfClass:[JSPerson class]];     //
    NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
Copy the code

The print result is as follows:

 re1 :1
 re2 :0
 re3 :0
 re4 :0
 re5 :1
 re6 :1
 re7 :1
 re8 :1
Copy the code

IsKindOfClass: re5-re8 = re1-re4; isKindOfClass: re5-re8 = re1-re4;

// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__// Let's just look at objc2 here
    if (slowpath(! obj))return NO;
    Class cls = obj->getIsa(a);// Get the class (metaclass) of the object (class object)
    if (fastpath(! cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {// Iterate over the parent class
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
Copy the code

As you can see by looking at the source code and the path and inheritance chain of ISA in the previous article

  • Instance object calls: 1. Get the class of the object. If the class is equal to the class passed in, return YES. If the class is not found, return NO

    That is, the search order: class of object -> superclass -> root class (NSObject)->nil

  • Class object: 1. Obtain the metaclass of the class. If the metaclass is equal to the class passed in, return YES. If NO metaclass is found at the end of the traversal, return NO

    Metaclass -> metaclass parent -> root metaclass -> root class (NSObject) ->nil

With the conclusion above, let’s look at the following example:

  • re1: is the incomingNSObjectThe root metaclass, whose parent class is the root metaclassNSObject= the second parameter passed in, sore1=1
  • re3: is the incomingJSPersonClass object, first look for its metaclass, then look for the metaclass’s parent class to the root metaclass, and finally to the root class, no class =[JSPerson class], sore3=0
  • re5: is the incomingNSObjectInstance object of, the class to find it isNSObject=[NSObject class], sore5=1
  • re7: is the incomingJSPersonInstance object of, the class to find it isJSPerson=[JSPerson class], sore7=1

Let’s move on to the source code for isMemberOfClass

+ (BOOL)isMemberOfClass:(Class) CLS {return self->ISA() == CLS; } // Instance method - (BOOL)isMemberOfClass (Class) CLS {return [self Class] == CLS; }Copy the code

It can be seen that:

  • Instance object: Determines whether the class of the instance object and the class passed in are equal, i.eclassAre equal
  • Class object: Determines whether the metaclass of a class is equal to the class passed inThe metaclass

Let’s move on to the following example:

  • re2: is the incomingNSObjectThe root metaclass is not the root metaclassNSObjectClass, sore2=0
  • re4: is the incomingJSPersonClass object, first look for its metaclass, metaclass! =[JSPerson class], sore4=0
  • re6: is the incomingNSObjectInstance object of, the class to find it isNSObject=[NSObject class], sore6=1
  • re8: is the incomingJSPersonInstance object of, the class to find it isJSPerson=[JSPerson class], sore8=1

In this paper, we mainly studied the difference between ro and rw, class attribute set method, as well as isKindOfClassvsisMemberOfClass source, class to explore here, there are omissions behind will be added.