Summary of iOS underlying principles – Explore the nature of OC objects

Summary and record of video learning in the bottom class of small code brother. Part of the interview questions, through the analysis of the opposite questions to explore the essence of the content of the question.

Interview question: How much memory does an NSObject take up?

Exploring the nature of OC objects, we usually write objective-C code, the underlying implementation is actually C\C++ code.

OC object structures are implemented through the base C\C++ structure. We explore the nature of OC objects by creating OC files and objects and converting OC files into C++ files

OC the following code

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *objc = [[NSObject alloc] init];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
Copy the code

We converted OC’s mian. M file to a c++ file through the command line.

Clang-rewrite-objc main.m -o main.cpp // This approach does not specify a schema such as arm64 where CPP represents (c plus plus) main.cppCopy the code

We can specify schema patterns on the command line using the Xcode tool XCRun

Xcrun-sdk iphoneOS clang-arch arm64-rewrite-objc main.m -o main-arm64.cpp generates main-arm64.cppCopy the code

In main-arm64.cpp, search for NSObjcet and find NSObjcet_IMPL (IMPL stands for implementation)

Let’s look inside the NSObject_IMPL

struct NSObject_IMPL { Class isa; }; Typedef struct objc_class *Class; It turns out that a Class is really just a pointer, and that's what the underlying implementation of an object looks like.Copy the code

Consider: how an OC object is laid out in memory. The underlying implementation of NSObjcet, click NSObjcet to discover the internal implementation of NSObject

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
@end
Copy the code

Converting to C is actually a structure

struct NSObject_IMPL {
    Class isa;
};
Copy the code

So how much memory does this structure take up? We find that this structure has only one member, the ISA pointer, and Pointers take up 8 bytes in a 64-bit architecture. That is, an NSObjec object takes up 8 bytes of memory. So this is basically the answer to the first question. But there are a lot of methods in NSObject, don’t they take up memory? Methods of a class take up memory, but the storage that these methods take up is not in NSObject objects.

To see how OC objects are represented in memory, let’s look at the following code

NSObject *objc = [[NSObject alloc] init];
Copy the code

How does the above code look in memory? In the preceding code, the NSObject object is allocated 8 bytes of memory to hold a member ISA pointer. The address of the isa pointer variable is the address of the structure, that is, the address of the NSObjcet object. Assuming the address of ISA is 0x100400110, the above code allocates storage to the NSObject object and then assigns the address of storage to the objC pointer. Objc stores the address of isa. Objc points to the NSObject object address in memory, that is, to the structure in memory, the isa location.

Internal implementation of a custom class

@interface Student : NSObject{
    
    @public
    int _no;
    int _age;
}
@end
@implementation Student

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        Student *stu = [[Student alloc] init];
        stu -> _no = 4;
        stu -> _age = 5;
        
        NSLog(@"% @",stu);
    }
    return 0;
}
@end
Copy the code

Follow the above steps to generate the c++ file as well. Student, we find Student_IMPL

struct Student_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	int _no;
	int _age;
};
Copy the code

The first one found is an implementation of NSObject_IMPL. Struct NSObject_IMPL NSObject_IVARS; struct NSObject_IMPL NSObject_IVARS; Equivalent to Class ISA;

You can convert the above code to

struct Student_IMPL {
	Class *isa;
	int _no;
	int _age;
};
Copy the code

So the object takes up as much storage space as the structure takes up. Isa pointer 8 bytes space +int _NO4 bytes space +int _age4 bytes space total 16 bytes space

Student *stu = [[Student alloc] init];
stu -> _no = 4;
stu -> _age = 5;
Copy the code

Creating Student allocates 16 bytes, stores 3 things, isa pointer 8 bytes, _no 4 bytes, and _age 4 bytes

The three variables of the sutdent object have their own addresses. Stu points to the address of the ISA pointer. So the address of stu is 0x100400110, and the STU object takes up 16 bytes of memory. And we assign 4 to _no and 5 to _age

Verify Student’s appearance in memory

struct Student_IMPL { Class isa; int _no; int _age; }; @interface Student : NSObject { @public int _no; int _age; } @end @implementation Student int main(int argc, Const char * argv[]) {@autoreleasepool {// Force conversion struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu; NSLog(@"_no = %d, _age = %d", stuImpl->_no, stuImpl->_age); _no = 4, _age = 5}return 0;
}
Copy the code

The above code forces the OC object into a structure for Student_IMPL. So that means I’m going to point a pointer to an OC object to this structure. Since we assumed that the layout of objects in memory is the same as that of structures in memory, if the transformation can be successful, it means that our guess is correct. This shows that the memory that stu points to is indeed a structure.

To actually get the memory footprint of an object, you can use a more convenient run-time method.

class_getInstanceSize([Student class])
NSLog(@"%zd,%zd", class_getInstanceSize([NSObject class]) ,class_getInstanceSize([Student class])); // Prints information 8 and 16Copy the code

Snooping memory structure

View memory data in real time

Method 1: Through the interruption point. Debug Workflow -> viewMemory Address Enter the STU address

Method 2: Use the debugger of the LLDB directive Xcode

memory read0x10074C450 // short x 0x10074C450 // Add read condition // Memoryread/ Quantity format Number of bytes Memory address // abbreviation X/Quantity format Number of bytes Memory address // format X is hexadecimal, F is floating point, d is base 10 // Size of bytes B: byte 1 byte, H: half word 2 bytes, W: word 4 bytes, G: Giant word 8 bytes example: x/4xw // / how to read data w means 4 bytes read 4 bytes, X means read data in hexadecimal format, and 4 means read data 4 timesCopy the code

LLDB can also be used to change the value in memory

Memory Write 0x100400C68 6 Change the _no value to 6Copy the code

So how much memory does an NSObject take up? NSObjcet is actually a structure with only one pointer named ISA, and therefore takes up the amount of memory used by a pointer variable, 8 bytes if 64-bit or 4 bytes if 32bit.

More complicated inheritance

Interview question: What is the output of the following code in 64bit?

/* Person */
@interface Person : NSObject
{
    int _age;
}
@end

@implementation Person
@end

/* Student */
@interface Student : Person
{
    int _no;
}
@end

@implementation Student
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"%zd %zd",
              class_getInstanceSize([Person class]),
              class_getInstanceSize([Student class])
              );
    }
    return 0;
}
Copy the code

How much memory is occupied by a Person object and how much memory is occupied by a Student object?

According to the above analysis and discovery, the class object is essentially stored in memory in the form of structure, drawing a real memory legend

We found that whenever an object inherits from NSObject, there must be an ISA pointer in the underlying structure. So how much memory do they take up? Simply add up the memory occupied by Pointers and member variables? The code above actually prints 16, 16, which means that both the Person and student objects take up 16 bytes of memory. In fact, the Person object really only uses 12 bytes. But because of memory alignment. Make the Person object take 16 bytes as well.

When making space for a structure, the compiler first looks for the widest base datatype in the structure, and then looks for the location where the memory address can be an integer multiple of that base datatype as the first address of the structure. Use the size of the widest base data type as the alignment modulus. Open space of a member of the structure before the compiler first check first address relative to the structure in the process of the open space first address offset is the member of integer times, if so, deposit, our members, on the other hand, is a member on the members and between fill certain bytes, to meet the requirements of integer times, and in the process of the open space is the first address backwards a few bytes.

We can summarize the two principles of memory alignment: Principle 1. The preceding address must be a positive multiple of the following address. Principle 2. The address of the entire Struct must be an integer multiple of the largest byte.

The first address of the person object to hold the ISA pointer requires 8 bytes, and the second address to hold the _age member variable requires 4 bytes. According to principle 1, 8 is an integer multiple of 4, which meets principle 1 and does not need to be completed. Then check principle 2, the person object currently occupies 12 bytes of memory, not an integer multiple of the maximum byte size of 8 bytes, so 4 bytes need to be filled out, so the Person object occupies 16 bytes.

For student, we know that the sutdent object contains the struct implementation of the Person object and a _no member variable of type int. Again, the isa pointer is 8 bytes, the _age member variable is 4 bytes, and the _no member variable is 4 bytes, which satisfies principles 1 and 2. So the student object takes up 16 bytes of memory.

OC Object classification

Interview question: Where is the OC class information stored? Interview question: Where does the object’s ISA pointer point to?

The sample code

#import <Foundation/Foundation.h>
#import <objc/runtime.h>/* Person */ @interface Person : NSObject <NSCopying> { @public int _age; } @property (nonatomic, assign) int height; - (void)personMethod; + (void)personClassMethod; @end @implementation Person - (void)personMethod {} + (void)personClassMethod {} @end /* Student */ @interface Student :  Person <NSCoding> { @public int _no; } @property (nonatomic, assign) int score; - (void)studentMethod; + (void)studentClassMethod; @end @implementation Student - (void)studentMethod {} + (void)studentClassMethod {} @end int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *object1 = [[NSObject alloc] init]; NSObject *object2 = [[NSObject alloc] init]; Student *stu = [[Student alloc] init]; [Student load]; Person *p1 = [[Person alloc] init]; p1->_age = 10; [p1 personMethod]; [Person personClassMethod]; Person *p2 = [[Person alloc] init]; p2->_age = 20; }return 0;
}

Copy the code

Where is the OC class information stored OC objects can be divided into three main types

  1. Instance object (instance object)
  2. Class object (Class object)
  3. Meta-class objects (metaclass objects)

An instance object is an object that comes out of the alloc class, and every time you call that alloc, you create a new instance object

NSObjcet *object1 = [[NSObjcet alloc] init];
NSObjcet *object2 = [[NSObjcet alloc] init];
Copy the code

Object1 and Object2 are both Instace objects (instance objects) of NSObject, but they are two different objects and occupy two different pieces of memory. The instance object stores information in memory including

  1. Isa pointer
  2. Other member variables

Derivative question: Where is the code for the method of the instance object? So where does the method information of the class, the protocol information, the property information go?

We get a class object either through the class or runtime methods. A class object is a class object

Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = [NSObject class];

// runtime
Class objectClass4 = object_getClass(object1);
Class objectClass5 = object_getClass(object2);
NSLog(@"%p %p %p %p %p", objectClass1, objectClass2, objectClass3, objectClass4, objectClass5);
Copy the code

Each class has one and only one class object in memory. This can be proved by printing memory addresses

Class objects store information in memory mainly including

  1. Isa pointer
  2. Superclass pointer
  3. Class attribute information (@Property), class member variable information (IVAR)
  4. Instance method of a class, protocol of a class

The value of a member variable is stored in the instance object because the value of the member variable is assigned only when we create the instance object. But you only need to have a copy of the name and type of the member variable. So it’s stored in a class object.

Where do class methods go? Metaclass object meta-class

// objectMetaClass = object_getClass([NSObject Class]); CLS = [[NSObject class] class]; CLS = [[NSObject class] class]; Class objectClass3 = [NSObject class]; Class_isMetaClass (objectMetaClass) // Determine whether this object is a metaclass object NSLog(@"%p %p %p", objectMetaClass, objectClass3, cls); // If you call class multiple times, you get the same class objectCopy the code

Each class has one and only one meta-class object in memory. Meta-class objects have the same memory structure as class objects, but their purpose is different. The information stored in the memory mainly includes

  1. Isa pointer
  2. Superclass pointer
  3. Information about the class method of a class

Meta -class objects have the same memory structure as class objects, so meta-class objects also have member variables such as class attributes and object methods, but the values may be empty.

Where does the isa pointer to the object point

  1. When an object calls an instance method, as we mentioned above, the instance method information is stored in a class object, so in order to find the instance method, you must find the class object. This is where isa comes in.
[stu studentMethod];
Copy the code

Instance isa refers to class. When an object method is called, the class is found through the instance ISA and the implementation of the object method is found.

  1. When a class object calls a class method, as above, the class method is stored in a meta-class metaclass object. To find the class method, you need to find the meta-class metaclass, and the ISA pointer to the class object points to the metaclass
[Student studentClassMethod];
Copy the code

When a class method is called, the class isa is used to find the meta-class, and the implementation of the class method is found to call

  1. How does an object find its parent object method when it calls its parent object method? , then you need to use the superclass pointer to the class object.
[stu personMethod];
[stu init];
Copy the code

When a Student instance calls a Method on a Person object, it finds the Student class through isa, the Person class through superclass, and the implementation of the object method. Similarly, if Person finds that it has no object method to respond to, it will find NSObject’s class object through Person’s superclass pointer to find the method to respond to

  1. When a class object calls a class method of its parent class, it needs to find the meta-class through the ISA pointer, and then find the method in response through the superclass
[Student personClassMethod];
[Student load];
Copy the code

When a Student class calls a Person class method, Student’s meta-class is first identified by ISA, then Person’s meta-class is identified by superclass, and finally the implementation of the class method is identified

Finally, there is this stationary ISA pointing diagram. After the above analysis, when we look at this diagram, it becomes much clearer.

Summary of ISA and Superclass

  1. Instance isa points to class
  2. Class’s ISA points to meta-class
  3. The META-class’s ISA points to the meta-class of the base class, and the base class’s ISA points to itself
  4. The superclass of class points to the superclass of the superclass. If there is no superclass, the superclass pointer is nil
  5. The superclass of a meta-class points to the meta-class of the parent class, and the superclass of a base class points to the class of the base class
  6. Instance calls the trace of an object’s method, isa finds the class, and if the method does not exist, it finds the superclass
  7. Class calls the trace of a class method, isa looks for meta-class, and if the method does not exist, superclass looks for its parent

How to prove that the ISA pointer really points as stated above?

We prove this by the following code:

NSObject *object = [[NSObject alloc] init];
Class objectClass = [NSObject class];
Class objectMetaClass = object_getClass([NSObject class]);
        
NSLog(@"%p %p %p", object, objectClass, objectMetaClass);
Copy the code

Interrupt point and print the ISA pointer to the corresponding object through the console

We find that object-> ISA is different from objectClass. This is because from 64bit, ISA needs to perform a bit operation to calculate the real address. The value of the bit operation can be found by downloading objC source code.

We verify this with a bit operation.

We find that object- ISA pointer address 0x001DffFF96537141 through the same 0x00007FFffffffffFFFFff8-bit operation, objectClass address 0x00007FFF96537140

We then verify that the ISA pointer to the class object also requires a bit operation to compute the address of the meta-class object. When we print the objectClass-> ISA pointer in the same way, we find that it cannot be printed

Also notice that there is no ISA pointer in the left objectClass object. Let’s take a look inside Class

typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if ! __OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
Copy the code

The first object is an ISA pointer. In order to get the address of the ISA pointer, we create an identical structure and cast it to get the ISA pointer.

struct xx_cc_objc_class{
    Class isa;
};


Class objectClass = [NSObject class];
struct xx_cc_objc_class *objectClass2 = (__bridge struct xx_cc_objc_class *)(objectClass);
Copy the code

Now let’s reverify that

Indeed, the bitwise address of objectClass2’s isa pointer isa meta-class address.

Summary of the interview questions:

  1. How much memory does an NSObject take up? A: The size of a pointer variable (64bit 8 bytes, 32bit 4 bytes)

  2. Where does the isa pointer to the object point? A: The ISA pointer of an instance object points to a class object, the ISA pointer of a class object points to a meta-class object, and the ISA pointer of a meta-class object points to a meta-class object, and the base class’s OWN ISA pointer points to itself.

  3. Where is the OC class information stored? A: The specific value of the member variable is stored in the Instance object. Object methods, protocols, properties, and member variables are stored in the class object. Class method information is stored in a meta-class object.


Welcome to point out any mistakes in the article. I am XX_CC, a long grown but not enough of a guy.