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, yes
machoView
To 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 + b
The black,Show In Finder
The package contents are displayed and the executable file is dragged inmachoView
.
machoView
As 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_getClass
Output 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
seemethods
There 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 stored
class_ro_t
In theivar_list_t
- Properties in
class_rw_t
In theproperty_array_t
和class_ro_t
In theproperty_list_t
It keeps a copy of it, and it generates instance variables and corresponding methods - Methods in the
class_rw_t
In themethod_array_t
和class_ro_t
In themethod_list_t
They all keep a copy - Object methods are stored
class
inside - Class methods are stored
The metaclass
inside
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.