OC class principle exploration series articles

  1. OC class principle exploration: Class structure analysis
  2. OC class principle exploration: Class structure analysis supplement
  3. 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 memoryMemory that does not change after loading;
  • class_ro_tBelong toclean memoryBecause it’s read-only;
  • clean memoryYou can remove it to save more memory space, because if you need toclean memory, the system can reload from disk.

dirty memory

  • dirty memoryMemory that changes while the process is running;
  • The class structure becomesdirty memoryBecause the runtime writes new data to it, such as creating a new method cache and pointing to it from the class;
  • dirty memorythanclean memoryIt’s much more expensive, it has to exist as long as the process is running;
  • macOSYou can choose to swap outdirty memory, but becauseiOSDo not useswapsodirty memoryiniOSMedium 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 separatelyclassandThe metaclassAll methods in;
  • metaClassThe printedrunBelong toThe metaclassIn the method, others belong toclassIn 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 theeatBelong toclassIn the method,runBelong toThe metaclassIn 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_getClassMethodFunction parsing: If aOrdinary classJust get its metaclass method, if it already isThe metaclassThe direct access method;
  • soclass_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
  • imp1ispClassClass methods of,imp4ismetaClassMetaclass method.
  • whyimp2andimp3There 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_isKindOfClassMethod analysis:
    • objectandclassCall theobjc_opt_isKindOfClassMethods;
    • objGet it firstclassorThe metaclassAssigned totcls, andotherClassMake a comparison, and return if they are equalYESIf they are not equaltclsLoop to take its parent andotherClassCompare.
  • BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];Resolution:
    • [NSObject class]The entry method gets its metaclass assignment totcls.TCLS = NSObject metaclasswith[NSObject class]Obviously not equal to compare;
    • Into the loopTCLS = NSObject metaclassLet’s take its parent to beNSObject class.TCLS = NSObject classwith[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 metaclasswith[SSLPerson class]Obviously not equal to compare;
    • Enter the first cycleTCLS = SSLPerson metaclassLet’s take its parent to beA metaclass.TCLS = root metaclasswith[SSLPerson class]It’s not equal;
    • Second cycleTCLS = root metaclassLet’s take its parent to beNSObject class.TCLS = NSObject classwith[SSLPerson class]It’s not equal;
    • Third cycleTCLS = NSObject classLet’s take its parent to benil.tcls = nilwith[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)isMemberOfClassMethod analysis:
    • The metaclass sum of the current classclsCompare;
  • - (BOOL)isMemberOfClassMethod analysis:
    • The class sum of the current objectclsCompare;
  • 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.