This is the 7th day of my participation in Gwen Challenge

The structure of the class

In iOS development, almost every object is an instance of a class. We have analyzed the structure of some classes before, including the following items:

  1. Class
  2. MetaClass
  3. isa
  4. SuperClass
  5. property
  6. instance method
  7. ivar
  8. class method

Interested friends can look at the article iOS bottom exploration 04 — iOS class structure analysis (on), this article will be on the basis of the last article from a higher dimension to explore about the class related knowledge;

clean Memory & dirty memory

Refer to video resources

Advancements in the Objective-C runtime – WWDC 2020 – Videos – Apple Developer

IOS Memory Details

Clean Memory/ Dirty Memory in iOS

Memory

For more information about Memory, see iOSMemroy

The description of virtual memory is inaccurate

We all know that iOS system and most operating systems use virtual memory technology. Virtual memory and physical memory and even hard disks are mapped one by one through mapping relationship. For ease of mapping and management, both virtual and physical memory are divided into units of the same size. The smallest unit of physical memory is called a Frame, and the smallest unit of virtual memory is called a Page. When the physical memory is insufficient, it is released through Page Out. When the physical memory needs to be used, it is loaded from the hard disk to the physical memory. When the Page Out fails to be retrieved from the CPU Page table, the Page fault is triggered and the released data is reloaded from the hard disk to the memory.

Clean Memory is not accurate

Although page faults affect performance due to reloading into memory, they relieve at least some of the stress on physical memory. This part of the Memory data that can be reloaded from the hard disk is called Clean Memory and mainly includes:

  1. The system framework
  2. The binary execution file of the application.
  3. Memory data is mapped to files.

Operating systems prefer Clean Memory to Dirty Memory;

Dirty memory is not accurate

If data is generated at runtime, it is difficult to recover once released. This part of data that cannot be reloaded from the hard disk is called Dirty Memory. The operating system wants as little of this data as possible;

The Class is loaded into memory for the first time

When a class is first loaded from disk into memory, its structure is shown below

Class is used by the Runtime

When a class is first used by the Runtime, it may be changed by the Runtime because the system does the following to it; Add new methods to the class by category; Using the Runtime API to manually add attributes and methods to the Class class_ro_t is read-only, so we need to store this information in class_rw_t

The Class is dynamically updated

When a class needs dynamic update, the dynamically updated part is extracted and stored in class_rw_ext_t. Its structure is shown in the following figure

The overall structure of a class

The structural evolution of class is shown belowThe class structure diagram is shown below

firstSubclass

All classes are linked by firstSubclass and nextSiblingClass Pointers, so the runtime iterates through all classes currently in use. Note that classes are lazily loaded, and only the corresponding firstSubclass class is used. Only in him will there be value;

The structure of the class

Properties & member variables & methods

  1. The best way to see the difference between member variables, attributes, and instance variables is to rewrite them in c++ using clang; The following code

Clang-rewrite-objc-fobjc-arc-fobjc-runtime = ios-14.2.0-isysroot / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator14.2. The SDK GCPerson.m -o GCPerson.cpp

The rewritten code is shown below

2. After keyword search, observe the difference between member variables and attributes, and find that the underlying are all member variables, but the attributes of the member variables are underlined _, attribute added set method and get method; 2 as shown in the figure 3.

  • Member variables: Member variables of the base data type;
  • Instance variable: a member variable of an object type (of the object type)
  • Properties: underlined member variables + setter methods + getter methods
  • methods
  1. whilemethodThe structure in OBJC is as follows
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
Copy the code
  1. While looking at the method, we found some strange characters, which are the type encoding of the member variable, throughcommand + shift + 0Jump to documents, searchivar_getTypeEncoding(), jump to the official website

See objective-C Type encodings as shown below

Code Meaning
c A char
i An int
s A short
l A longl is treated as a 32-bit quantity on 64-bit programs.
q A long long
C An unsigned char
I An unsigned int
S An unsigned short
L An unsigned long
Q An unsigned long long
f A float
d A double
B A C++ bool or a C99 _Bool
v A void
* A character string (char *)
@ An object (whether statically typed or typed id)
# A class object (Class)
: A method selector (SEL)
[array type] An array
{name=type… } A structure
(name=type…) A union
bnum A bit field of num bits
^type A pointer to type
? An unknown type (among other things, this code is used for function pointers)
  1. There is also numeric information in the method, such as @16@0:8

    • @: id return value type;
    • 16: Sum of memory occupied by all incoming parameters;
    • @: The type of the first argument;
    • 0: The starting position of the first argument;
    • :: The type of the second argument;
    • 8: The starting position of the second argument;
  2. The encoding format is as follows:

[Return value type][total size of the input parameter][first parameter type][start position of the first parameter][second parameter type][start position of the second parameter][third parameter type][start position of the third parameter]…. [NTH parameter type][NTH parameter start position] outputs all parameters backward.

  1. You can also use the following methodsobjc_copyIvar_copyProperiesTo determine whether or notClassThe inside of theivarpropertyThe difference between;
void objc_copyIvar_copyProperies(Class pClass){
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Ivar const ivar = ivars[i];
        // Get the instance variable name
        const char*cName = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:cName];
        NSLog(@"class_copyIvarList:%@",ivarName);
    }
    free(ivars);
    unsigned int pCount = 0;
    objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
    for (unsigned int i=0; i < pCount; i++) {
        objc_property_t const property = properties[i];
        // Get the attribute name
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        // Get the attribute value
        NSLog(@"class_copyProperiesList:%@",propertyName);
    }
    free(properties);
}
Copy the code

objc_setProperty

  1. When we look at the overwritten property set method, we see that some properties haveobjc_setPropertySome are passedMemory offset assignmentWhy are there these two differences?
  2. OCThe inside of thesetThere are too many methods to write for every oneOCthesetMethods add an underlying implementation,setThe essence of the method is to assign a value to a certain memory region. In order to facilitate the implementation of attribute assignment,objcinOCandThe underlyingSome intermediate methods have been added betweenobjc_setPropertyMethods; The bottom layer can beobjc_setPropertyMethods for different implementation;
  3. In order for all of the topivarthesetMethod can be called directly toobjc_setPropertyLLVM implements the upper-layer set method at compile timesel->IMPIMP redirected toobjc_setProperty;
  4. inLLVMSource searchobjc_setPropertyKeywords find the corresponding method implementation, find the GetPropertySetFunction() method, after reading the source code of LLVM, finally find that the property modified by copy will be added to the bottom layerobjc_setPropertyMethods;
  5. whyobjc_setPropertyExists only incopyGrooming? When copy decorates,objc_setPropertyWill performreallySetProperty()Methods tozoneMemory copy to achieve the effect of memory copy. Don’t usecopyModified, the underlying is a simple memory offset after assignment, nocopyThe effect of;

Class method

  1. The FunctionStarts with MachOView. The FunctionStarts with MachOView.

2. The object method exists in the class. If a class method and an object method exist in the class, it is impossible to distinguish between the class method and the object method based on the method name. Do class methods exist in MetaClass? 3. Successfully obtain the class method from MetaClass after imitating the search process of object method; The LLDB mimics the steps of obtaining object methodsMetaClassIn themethod, finally successfully found the class method;

(lldb) x/4gx metaClass     // Step 1 Obtain the address of the metaClass
0x100004638: 0x00000001003540f0 0x00000001003540f0
0x100004648: 0x000000010034b360 0x0000e03100000000
(lldb) p/x 0x100004638 + 0x20    // Step 2 Structure pointer offset by 32 bytes
(long) $1 = 0x0000000100004658
(lldb) p (class_data_bits_t *)$1    / / step 3
(class_data_bits_t *) $2 = 0x0000000100004658
(lldb) p $2->data()    // Step 4 Obtain data
(class_rw_t *) $3 = 0x0000000100719a60
(lldb) p *$3    // Important step 5
(class_rw_t) $4 = {
  flags = 2684878849
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4294983992
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff884e2cd8
}
(lldb) p $4.methods()  // Step 6 Obtain methods
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100004180
      }
      arrayAndFlag = 4294984064
    }
  }
}
(lldb) p $5.list    // Step 7 Obtain the list
(const method_list_t_authed_ptr<method_list_t>) $6 = {
  ptr = 0x0000000100004180
}
(lldb) p $6.ptr    // Step 8 Obtain the PTR
(method_list_t *const) $7 = 0x0000000100004180
(lldb) p *$7      // Step 9 Restore data
(method_list_t) $8 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $8.get(0).big()  // Step 10 Print the method information through big
(method_t::big) $9 = {
  name = "sayHi"
  types = 0x0000000100003ed2 "v16@0:8"
  imp = 0x0000000100003a10 (KCObjcBuild`+[GCPerson sayHi] at main.m:30)
}
(lldb) 
Copy the code

Method API summary

  1. class_getClassMethod(pClass, @selector(sayHello));// Get the instance method of the metaclass passed to the class; The instanceMethod of the MetaClass passed in to the class is found, so only the class method is found.
  2. class_getInstanceMethod(pClass, @selector(sayHello));// Gets the instance method of the currently passed class. The passed metaclass gets the class method. The passed class gets the instance method
  3. class_copyMethodList(pClass, &count);// Copy the method of the current class.
  4. class_getMethodImplementation(pClass, @selector(sayHello))// Gets a method implementation of the current class, which is triggered if the current class cannot be found_objc_msgForward;

isKindOfClass && isMemberOfClass

isKindOfClass:

  1. returns YES if the receiver is an instance of the specified class or an instance of any class that inherits from the specified class.
  2. Returns YES if the method caller is an instance object of the passed class, or if the caller is an instance object of a class in the successor chain of the passed class.
  3. The underlying call isobjc_opt_isKindOfClass(id _Nullable obj, Class _Nullable cls), but the effective version isOBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); When usingobjc_opt_isKindOfClass(id obj, Class otherClass)Class method or object method, the first parameterid objIs the class to call, the second argumentotherClassIs a comparison class; fromobjClass starts fetching from its parent classisawithotherClassCompare;
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if(slowpath(! obj))return NO;
    Class cls = obj->getIsa();
    if(fastpath(! cls->hasCustomCore())) {for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*) (id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
Copy the code
  1. Close inspection revealedobjc_opt_isKindOfClass(id obj, Class otherClass)and-iskindofClassAs well as+iskindofClassThe realization of is basically the same;

+ isKindOfClass attention point

+isKindOfClass is supposed to determine if a class object is equal to a metaclass or a parent of a metaclass, but since the superClass of the NSObject metaclass refers to NSObject, So objcBOOL re1 = [(id)[NSObject Class] isKindOfClass:[NSObject Class]];

Normally the parent of a metaclass is a metaclass, you can never be equal to a class, but it happens that nsobject’s metaclass is also Nsobject’s class, so it’s equal;

isMemberOfClass:

returns YES if the receiver is an instance of the specified class. The method caller must be an instance object of the passed class to return YES.

void gcisKindofDemo(void) {Iskindofclass: Returns YES if the method caller is an instance object of the passed class, or if the caller is an instance object of a class in the successor chain of the passed class. Explain -
    // iskindofClass: the method caller's isa or isa's superclass = the class passed in. Explain two
    // Class objects are compared with metaclass or super metaclass,
    //isMemberOfClass: The method caller must be an instance object of the incoming class to return YES.
    BOOL re1 = [(id) [NSObject class] isKindOfClass:[NSObject class]].The parent of a metaclass is still a metaclass. It can never be equal to the class, but nsobject's metaclass is also nsobject's class, so it is equal;
    BOOL re2 = [(id) [NSObject class] isMemberOfClass:[NSObject class]].// The left side of the metaclass is member, the right side is class, so I don't want to wait
    BOOL re3 = [(id)[GCPerson class] isKindOfClass:[GCPerson class]].// The left side is an instance of the GCPerson metaclass or a parent of the GCPerson metaclass, and the right side is a class object of the GCPerson metaclass.
    BOOL re4 = [(id)[GCPerson class] isMemberOfClass:[GCPerson class]].// The GCPerson metaclass is member; the GCPerson metaclass is member; the GCPerson metaclass is member;
    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]].// the left isa isa class, the right isa isa class, equal;
    BOOL re6 = [(id) [NSObject alloc] isMemberOfClass:[NSObject class]].// the left isa isa class, and the right isa isa class, equal
    BOOL re7 = [(id)[GCPerson alloc] isKindOfClass:[NSObject class]].// The isa on the left is the gcPerson class, and the isa on the right is the gcPerson class.
    BOOL re8 = [(id)[GCPerson alloc] isMemberOfClass:[GCPerson class]].// The isa on the left is the gcPerson class, and the isa on the right is the gcPerson class.
    NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}

Copy the code