Welcome to the iOS Low-level series (suggested in order) iOS Low-level – Alloc and Init exploration

IOS Bottom – Isa for everything

IOS Underlying – Nature of Classes iOS Underlying – Cache_t Process Analysis iOS Underlying – Nature of Methods and Lookup Process Analysis iOS Underlying – How dyld is added to Apps

1. Overview of this paper

This article aims to analyze the actual existence of classes in memory through the creation time of class&metaclass, the structure and related attributes of classes, and the added class information, and share some classic interview questions about classes

2. Class & metaclass creation timing

Objects are associated with classes through isa. Objects of the same type can be created more than once. How about classes, based on development experience, it is easy to conclude that there is only one class in memory, then how to implement the hammer. Provide authentication mode:

  • Command + b, yesmachoViewTo view

Create a new project and create a CJPerson class in the Products directory. The file at the end of app has not been compiled yet.

command + bThe black,Show In FinderThe package contents are displayed and the executable file is dragged inmachoView.

machoViewAs shown below

As you can see, the CJPerson class has been loaded in _objC_classrefs of the DATA segment with the memory address specified, indicating that the class was created at compile time and there is only one copy.

Metaclasses are also created by the system at compile time. In my understanding, the purpose of metaclasses is: 1. 2. Cache class methods always feel that Apple took great trouble to introduce the concept of metaclass, and other functions, as for what, hope to guide meCopy the code

2. The nature of classes

Initialize a CJPerson object under main.m

int main(int argc, const char * argv[]) {
    @autoreleasepool {        
        CJPerson * person = [[CJPerson alloc]init];        
        NSLog(@"%@",person.class);    
    }    
    return 0;
}
Copy the code

Use clang to compile main.m

clang -rewrite-objc main.m -o main.cpp
Copy the code

Open the compiled main. CPP file and go straight to the end, where objc_getClass is called for initialization

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
         CJPerson * person = ((CJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((CJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CJPerson"), sel_registerName("alloc")), sel_registerName("init"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_3995175d2_v6g81lknk8jdwh0000gn_T_main_e6fa2f_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("class")));
    }    
    return 0;
}
Copy the code

Search for objc_getClass in the file and see

__OBJC_RW_DLLIMPORT struct objc_class *objc_getClass(const char *);
Copy the code

Search objc_class again to find the redefinition

typedef struct objc_class *Class;
Copy the code

Objc_class redefines the class as objc_class. Objc_class redefines the class as objC_class

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 class_rw_t *data() { return bits.data(); }... }Copy the code

Post the structure of objc_Object incidentally

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
Copy the code
  • ISA is commented out in objc_class. That doesn’t mean ISA doesn’t exist in objc_class, because objc_class inherits from Objc_Object, so ISA comes from the parent class

  • You can also use clang to compile NSObject to get objc_Object

  • The structure of ISA is ISA_t, but it is ok to use the class receiver here, and internally get the class via shift_class

Conclusion: The true type of the class is objc_class, which inherits from objc_Object (the underlying structure of NSObject), indicating that everything is an object, the class is an object, and CJPerson is technically a class object

3. Data structure of class

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
}
Copy the code

The objc_class structure has four attributes: ISA, superclass, Cache, and bits

1.Class isa

Isa has already been analyzed in (iOS low-level – all-inclusive ISA) and takes up 8 bytes, except that isa here refers to the metaclass

2.Class superclass

The superclass is the parent class to point to

typedef struct objc_class *Class;
Copy the code

The superclass structure is also objc_class because it is a structure pointer and takes up 8 bytes

3.cache_t cache

A cache is a method cache. Method caching involves method lookup processes, caching policies, dynamic capacity expansion, and more, as described in the next chapter. Let’s look at the cache_t and bucket_t structures

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
}

struct bucket_t {
private:
#if __arm64__
    MethodCacheIMP _imp;
    cache_key_t _key;
#else
    cache_key_t _key;
    MethodCacheIMP _imp;
#endif
...
}
Copy the code
  • Buckets is the bucket that holds the method, in which is the key generated by method implementation IMP and method numbering SEL

  • Mask is the expansion factor and determines the expansion timing

  • Occupied is the current occupied capacity

  • Mask_t is an int. Mask_t is an int. Mask_t is an int. Mask_t is an int

  • Cache_t is a structure whose size is the sum of all the internal sizes, so cache_t occupies 16 bytes

4.class_data_bits_t bits

Bits is where data is stored. In bits there is data, which is class_rw_t read from macho

class_rw_t *data() { return bits.data(); } struct class_rw_t { ... const class_ro_t *ro; method_array_t methods; // Method list property_array_t properties; // Protocol_array_t protocols; // Protocol list... }Copy the code

Class_rw_t has a list of methods, a list of properties, a list of protocols, and so on

Class_rw_t has class_ro_t in it

struct class_ro_t { .. method_list_t * baseMethodList; // List of methods protocol_list_t * baseProtocols; // protocol list const ivar_list_t * ivars; // List of instance variables const uint8_t * weakIvarLayout; property_list_t *baseProperties; Method_list_t *baseMethods() const {return baseMethodList; }};Copy the code

Class_ro_t also has a method list, attribute list, protocol list, etc., but instead has ivar_list_t instance variable list. Let’s explore the differences between the two

4. Class added attribute methods

Class add instance variable sex, property age, object method work, class method play

@interface CJPerson : NSObject{
    NSString * sex;
}
@property (nonatomic , copy , readonly) NSString * age;

- (void)work;
+ (void)play;
Copy the code

object_getClassOutput the next class object

CJPerson * person = [CJPerson alloc];
Class cls = object_getClass(person);         
NSLog(@"%@",cls);
Copy the code

X /4gx Prints the memory address of the CLS as follows:

By analyzing the data structure of the class, bits is located after ISA, superclass, and cache, which are 8, 8, and 16 bytes respectively. According to the memory offset, bits is 32 bytes after ISA.

0x100001218 + 32 Bytes = 0x100001238Copy the code

Po and P cannot be printed directly, so you need to use class_datA_bits_t to print forcefully

Fetch class_rw_t in bits

The methods of class_rw_t

seemethodsThere are three methods in. Output them respectively

  • .cxx_destruct system adds c++ destructor methods

  • Work object method

  • The age method generated by the age property

  • The types in method_t are specified in the Apple developer documentation

Conclusion: There is an object method work, but there is no class method play

The properties of class_rw_t

Similarly, output the property list properties

Conclusion: As expected, there is only one added age attribute

So let’s keep going class_ro_t

The class_ro_t ivar_list_t

Conclusion: ivar_list_t has the added instance variable sex, as well as a _age, which is also consistent with the general perception that the attribute generates the underlined instance variable underneath

The class_ro_t baseMethodList

Conclusion: It is consistent with methods in class_rw_T

The class_ro_t baseProperties

Conclusion: Same as class_rw_t properties

Search suspects

Currently added instance variables, attributes, object methods are found in memory, but the lack of class methods, but according to experience can know, class methods are cached in the metaclass, that try to metaclass search

Find the metaclass through isa_mask, then step by step to find the baseMethodList, and sure enough, find the class method play

conclusion

  • Member variables are storedclass_ro_tIn theivar_list_t
  • Properties inclass_rw_tIn theproperty_array_tclass_ro_tIn theproperty_list_tIt keeps a copy of it, and it generates instance variables and corresponding methods
  • Methods in theclass_rw_tIn themethod_array_tclass_ro_tIn themethod_list_tThey all keep a copy
  • Object methods are storedclassinside
  • Class methods are storedThe metaclassinside

The class_ro_t and class_rw_t contents are mostly the same for the following reasons:

Class_ro_t stores the properties, methods, and protocols that are determined at compile time of the current class. Class_rw_t is determined at run time. It copies the contents of class_ro_T first, and then copies the properties, methods, and other attributes of the current class into it. Class_rw_t is a superset of class_ro_t.

5. Kind of interview questions

This is a classic interview question,CJPerson inherits NSObject

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //        
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //        
BOOL re3 = [(id)[CJPerson class] isKindOfClass:[CJPerson class]];       //        
BOOL re4 = [(id)[CJPerson class] isMemberOfClass:[CJPerson class]];     //        
NSLog(@"%hhd%hhd%hhd%hhd",re1,re2,re3,re4);      

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

The output is **1000, **1111

Re1 is NSObject calling class method isKindOfClass

+ (BOOL)isKindOfClass:(Class)cls { for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } recursively find if NSObject's metaclass exists, and keep looking for the parent of the metaclass until the root metaclass points to NSObject, and return YESCopy the code

Re2 is NSObject calling class method isMemberOfClass

+ (BOOL)isMemberOfClass:(Class)cls { return object_getClass((id)self) == cls; } just once, check if NSObject's metaclass is equal to NSObject, return NOCopy the code

Re3 is CJPerson calling the class method isKindOfClass

The first step is to go to the CJPerson metaclass and recurse all the way back to the CJPerson class, returning NOCopy the code

Re4 is CJPerson calling the class method isMemberOfClass

Compare the CJPerson metaclass and the CJPerson class only once, returning NOCopy the code

Re5 is an NSObject object that calls the object method isKindOfClass

- (BOOL)isKindOfClass:(Class)cls { for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } recursively finds if the object's superclass equals NSObject, always equals NSObject once, returning YESCopy the code

Re6 is an NSObject object that calls the object method isMemberOfClass

- (BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; } only once, to see if the class of NSObject is equal to NSObject, just when the inheritance relationship is satisfiedCopy the code

Re7 is the CJPerson object calling the object method isKindOfClass

Recursively find if the class of the CJPerson object is equal to CJPerson, satisfying the inheritance relationship the first timeCopy the code

Re8 is the CJPerson object calling the object method isMemberOfClass

Just once, check whether the class of the CJPerson object is equal to CJPerson, just when the inheritance relationship is satisfiedCopy the code

Write in the last

That’s it for class exploration. The next chapter in cache_T examines the flow of sending messages. We’ll continue to explore the underlying structure of classes, blocks, locks, and multithreading, as well as application loading, startup optimization, and memory optimization.