OC class principle exploration series articles
- OC class principle exploration: Class structure analysis
- OC class principle exploration: Class structure analysis supplement
- OC class principles exploration: the underlying principles of attributes
preface
OC class principle exploration: Class structure analysis of the overall analysis of the class structure, today to do some additions and extensions.
First, WWDC 2020 class structure optimization
WWDC 2020-Runtime optimization introduces the optimization of class data structure, method list optimization and tagged pointer format optimization three aspects, we will only analyze the optimization of class data structure today.
1. The difference between clean memory and dirty Memory
clean memory
clean memory
Memory that does not change after loading;class_ro_t
Belong toclean memory
Because it’s read-only;clean memory
You can remove it to save more memory space, because if you need toclean memory
, the system can reload from disk.
dirty memory
dirty memory
Memory that changes while the process is running;- The class structure becomes
dirty memory
Because the runtime writes new data to it, such as creating a new method cache and pointing to it from the class; dirty memory
thanclean memory
It’s much more expensive, it has to exist as long as the process is running;macOS
You can choose to swap outdirty memory
, but becauseiOS
Do not useswap
sodirty memory
iniOS
Medium costs a lot.
Dirty Memory is the reason this type of data is split into two parts. The more data that can be kept clean, the better. By separating out the data that never changes, most of the class data can be stored as clean memory.
2. Class_rw_t optimization
Structure of the class when it is first loaded into memory:
While this data is enough to get us started, the runtime needs to track more information about each class. So when a class is first used, the runtime allocates extra storage capacity for it. This runtime allocation is class_rw_t for reading and writing data.
In this data structure, we store new information that is generated only at run time. For example, all classes are concatenated into a tree structure through the use of First Subclass and Next Sibling Class Pointers, which allows the runtime to traverse all classes currently in use, which is useful for invalidating method caching.
But why do methods and properties exist in read-only data, and there are methods and properties here? Because they can be changed at run time, categry can add new methods to the class when it is loaded, or it can add them dynamically using the runtime API, because class_ro_t is read-only, so you need to track these things in class_rw_t.
The result of this is that it takes up quite a lot of memory, there are many classes in use on any given device, on the entire system on the iPhone, about 30 megabytes of these class_rw_T structures, so how do we shrink these structures? We need these things in the read-write section because they can be changed at run time, but by examining usage on actual devices, we find that only about 10% of the classes actually change their methods.
And only the Swift class will use the Demangled name field, and the Swift class doesn’t need it unless something asks them for their Objective-C name.
So we can remove the parts that we don’t normally use, which cuts the size of class_rw_t in half, and extract the parts that need to be dynamically updated and store them in class_rw_ext_t.
About 90% of the classes never need this extended data, which saves about 14MB of memory system-wide that can be used for more efficient purposes, such as storing your app’s data.
Second, API method parsing class method storage
OC class principle exploration: class structure analysis with LLDB to class method storage for parsing, today with THE API way to parse.
Define the SSLPerson class:
@interface SSLPerson : NSObject {
NSString *_hoby;
}
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
- (void)eat;
+ (void)run;
@end
Copy the code
1. Obtain the method list: class_copyMethodList
void logObjc_copyMethodList(Class pClass){ unsigned int count = 0; Method *methods = class_copyMethodList(pClass, &count); for (unsigned int i=0; i < count; i++) { Method const method = methods[i]; NSString *key = NSStringFromSelector(method_getName(method)); NSLog(@"Method, name: %@", key); } free(methods); } int main(int argc, const char * argv[]) { @autoreleasepool { SSLPerson *person = [SSLPerson alloc]; Class pClass = object_getClass(person); const char *className = class_getName(pClass); Class metaClass = objc_getMetaClass(className); logObjc_copyMethodList(pClass); SSLog(@"*************"); logObjc_copyMethodList(metaClass); } return 0; } Print result: Method, name: eat Method, name: name Method, name: name Method, name:. Cxx_destruct Method, name: setName: Method, name:. age Method, name: setAge: ************* Method, name: runCopy the code
- Printed separately
class
andThe metaclass
All methods in; metaClass
The printedrun
Belong toThe metaclass
In the method, others belong toclass
In the method.
2. Obtain the class method: class_getInstanceMethod
void logInstanceMethod_classToMetaclass(Class pClass){ const char *className = class_getName(pClass); Class metaClass = objc_getMetaClass(className); Method method1 = class_getInstanceMethod(pClass, @selector(eat)); Method method2 = class_getInstanceMethod(metaClass, @selector(eat)); Method method3 = class_getInstanceMethod(pClass, @selector(run)); Method method4 = class_getInstanceMethod(metaClass, @selector(run)); NSLog(@" %p \n %p \n %p \n %p",method1,method2,method3,method4); } int main(int argc, const char * argv[]) { @autoreleasepool { SSLPerson *person = [SSLPerson alloc]; Class pClass = object_getClass(person); logInstanceMethod_classToMetaclass(pClass); } return 0; } Print result: 0x1000083b8 0x0 0x0 0x100008350Copy the code
class_getInstanceMethod(pClass, @selector(eat))
I got the address0x1000083b8
.class_getInstanceMethod(metaClass, @selector(run))
I got the address0x100008350
.- Illustrates the
eat
Belong toclass
In the method,run
Belong toThe metaclass
In the method.
3. Obtain the metaclass method: class_getClassMethod
void logClassMethod_classToMetaclass(Class pClass){ const char *className = class_getName(pClass); Class metaClass = objc_getMetaClass(className); Method method1 = class_getClassMethod(pClass, @selector(eat)); Method method2 = class_getClassMethod(metaClass, @selector(eat)); Method method3 = class_getClassMethod(pClass, @selector(run)); Method method4 = class_getClassMethod(metaClass, @selector(run)); SSLog(@" %p \n %p \n %p \n %p",method1,method2,method3,method4); } int main(int argc, const char * argv[]) { @autoreleasepool { SSLPerson *person = [SSLPerson alloc]; Class pClass = object_getClass(person); logClassMethod_classToMetaclass(pClass); } return 0; } Print result: 0x0 0x0 0x100008350 0x100008350Copy the code
class_getClassMethod
Function parsing: If aOrdinary class
Just get its metaclass method, if it already isThe metaclass
The direct access method;- so
class_getClassMethod(pClass, @selector(run))
andclass_getClassMethod(metaClass, @selector(run))
You get the method address0x100008350
.
4. Obtain imp: class_getMethodImplementation
void logIMP_classToMetaclass(Class pClass){ const char *className = class_getName(pClass); Class metaClass = objc_getMetaClass(className); IMP imp1 = class_getMethodImplementation(pClass, @selector(eat)); IMP imp2 = class_getMethodImplementation(metaClass, @selector(eat)); Imp_farw IMP imp3 = class_getMethodImplementation(pClass, @selector(run)); // 0 // sel -> imp method lookup imp_farw IMP imp3 = class_getMethodImplementation(pClass, @selector(run)); // 0 IMP imp4 = class_getMethodImplementation(metaClass, @selector(run)); SSLog(@" %p \n %p \n %p \n %p",imp1,imp2,imp3,imp4); } int main(int argc, const char * argv[]) { @autoreleasepool { SSLPerson *person = [SSLPerson alloc]; Class pClass = object_getClass(person); logIMP_classToMetaclass(pClass); } return 0; } Print result: 0x100003ba0 0x7ffF203895c0 0x7FFF203895C0 0x100003bb0Copy the code
imp1
ispClass
Class methods of,imp4
ismetaClass
Metaclass method.- why
imp2
andimp3
There is also a value, look at the source analysis.
__attribute__((flatten)) IMP class_getMethodImplementation(Class cls, SEL sel) { IMP imp; if (! cls || ! sel) return nil; lockdebug_assert_no_locks_locked_except({ &loadMethodLock }); imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER); // Translate forwarding function to C-callable external version if (! imp) { return _objc_msgForward; } return imp; }Copy the code
You can see it! Imp returns _objc_msgForward, so imp2 and IMP3 have values.
IsKindOfClass interview questions
Look at the following code and print:
void logKindOfDemo(void){
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[SSLPerson class] isKindOfClass:[SSLPerson class]];
BOOL re4 = [(id)[SSLPerson class] isMemberOfClass:[SSLPerson 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)[SSLPerson alloc] isKindOfClass:[SSLPerson class]];
BOOL re8 = [(id)[SSLPerson alloc] isMemberOfClass:[SSLPerson class]];
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
logKindOfDemo();
}
return 0;
}
打印结果:
re1 :1
re2 :0
re3 :0
re4 :0
re5 :1
re6 :1
re7 :1
re8 :1
Copy the code
Why can have such a result, next we use the source code method analysis.
1. Incorrect source code
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
Copy the code
There are many articles on the Internet for this source code analysis, in fact, this is not true, this code is an old version, OC 2.0 has not used this code.
2. Source analysis
OC object principle exploration (a) in the introduction of the bottom of the exploration of three ways, we today with the compilation and source code of the way to resolve this interview questions.
Break point -> Start assembly debugging -> Run project -> find the underlying method objc_opt_isKindOfClass in assembly:
IsKindOfClass source analysis
Open objC4-818.2 source code and search for the objc_opt_isKindOfClass method:
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
objc_opt_isKindOfClass
Method analysis:object
andclass
Call theobjc_opt_isKindOfClass
Methods;obj
Get it firstclass
orThe metaclass
Assigned totcls
, andotherClass
Make a comparison, and return if they are equalYES
If they are not equaltcls
Loop to take its parent andotherClass
Compare.
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
Resolution:[NSObject class]
The entry method gets its metaclass assignment totcls
.TCLS = NSObject metaclass
with[NSObject class]
Obviously not equal to compare;- Into the loop
TCLS = NSObject metaclass
Let’s take its parent to beNSObject class
.TCLS = NSObject class
with[NSObject class]
To compare equals, sore1 = 1
;
BOOL re3 = [(id)[SSLPerson class] isKindOfClass:[SSLPerson class]];
Resolution:[SSLPerson class]
The entry method gets its metaclass assignment totcls
.TCLS = SSLPerson metaclass
with[SSLPerson class]
Obviously not equal to compare;- Enter the first cycle
TCLS = SSLPerson metaclass
Let’s take its parent to beA metaclass
.TCLS = root metaclass
with[SSLPerson class]
It’s not equal; - Second cycle
TCLS = root metaclass
Let’s take its parent to beNSObject class
.TCLS = NSObject class
with[SSLPerson class]
It’s not equal; - Third cycle
TCLS = NSObject class
Let’s take its parent to benil
.tcls = nil
with[SSLPerson class]
It’s still not equal, sore3 = 0
.
re5 = 1
.re7 = 1
.
IsMemberOfClass Source analysis
Open objC4-818.2 source code and search for isMemberOfClass method:
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
Copy the code
+ (BOOL)isMemberOfClass
Method analysis:- The metaclass sum of the current class
cls
Compare;
- The metaclass sum of the current class
- (BOOL)isMemberOfClass
Method analysis:- The class sum of the current object
cls
Compare;
- The class sum of the current object
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
Resolution:[NSObject class]
The metaclass and[NSObject class]
They’re not equal to each other, sore2 = 0
.
BOOL re4 = [(id)[SSLPerson class] isMemberOfClass:[SSLPerson class]];
Resolution:[SSLPerson class]
The metaclass and[SSLPerson class]
They’re not equal to each other, sore4 = 0
.
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
Resolution:[NSObject alloc]
The classes and[NSObject class]
To compare equals, sore6 = 1
.
BOOL re8 = [(id)[SSLPerson alloc] isMemberOfClass:[SSLPerson class]];
Resolution:-
The class of [SSLPerson alloc] is compared to [SSLPerson class], so re8 = 1.
-