OC Object nature (Middle) : The type of OC object

The nature of the OC object (part 2) : the ISA&superclass pointer

How much memory does an NSObject take up?

The nature of Objective-C when we write OC code, the underlying implementation is C/C++ code

Objective-c –> C/C++ –> assembly language –> machine code

So objective-C object orientation is implemented based on C/C++ data structures, so we can convert Objective-C code into C/C++ code to investigate the nature of OC objects.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
    }
    return 0;
}
Copy the code

We define a simple object inside the main function and passclang -rewrite-objc main.m -o main.cppCommand,main.mFile rewrite, can be converted into the corresponding C/C++ code. One problem you can see, however, is that the converted file is too long, nearly 10W lines.Because different platforms support different code (Windows/Mac/iOS), then the same OC code, after compilation, into C/C++ code, and the final assembly code, is not the same, assembly instructions are heavily dependent on the platform environment. We’re currently focused on iOS development, so we just need to generate C/C++ code that iOS supports. Therefore, you can use the following command

Xcrun - SDK iphoneOS clang -arch -rewrite-objc <OC source file > -o < output CPP file >

-sdk: specify the SDK-arch: Specifies the machine CPU architecture (emulator -i386, 32bit, 64bit-arm64)-framework UIKitArm64 is widely used in most mobile phones, so the architecture parameter here uses ARM64, and the CPP code generated is as follows

Next, let’s take a look at the main_arm64.cpp source file. If you’re familiar with this file, you’ll find a structure like this

struct NSObject_IMPL {
	Class isa;
};
Copy the code

Let’s compare the definition of the NSObject header again

@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

So if we simplify this, it’s 1, 2, 3

@interface NSObject  {
    Class isa ;
}
@end
Copy the code

Did you guess something? Yeah, struct NSObject_IMPL is actually the underlying structure or implementation of NSObject. To put it another way, it can be said that the C/C++ structure type supports the OC aspect object.

Typedeft struct objc_class *Class;

Class isa; Is equivalent tostruct objc_class *isa;
Copy the code

So inside the NSObject object isa pointer called isa that points to a struct objc_class.

Summary 1: How is an OC object laid out in memory?

Guess: The underlying NSObject object is a structure that contains a pointer, so is it 8 bytes (64-bit pointer types are 8 bytes)?To verify the conjecture, we need to import the Runtime header file using some of the tools provided by Runtime,class_getInstanceSize ()The method calculates how much space a class instance object actually needs

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        size_t size = class_getInstanceSize([NSObject class]);
        NSLog(@"NSObject object size: %zd",size);
    }
    return 0;
}
Copy the code

As a result,Perfect validation, it’s over, let’s go home!








#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        size_t size = class_getInstanceSize([NSObject class]);
        NSLog(@"NSObject instance object size: %zd",size);
        size_t size2 = malloc_size((__bridge const void *)(obj));
        NSLog(@" Size of memory to which object obj points: %zd",size2);
    }
    return 0;
}
Copy the code

It’s 16. How do you explain that?

To really understand why, you need to take a look inside Apple’s official open source code. Apple’s open source generation is here. Take a look at the implementation of class_getInstanceSize. We need to go to objc4/ file and download the latest source code. My current latest version is objC4-750.1.tar.gz. After the download decompression, open the project, you can view the runtime implementation source code. Search class_getInstanceSize to find the implementation code

size_t class_getInstanceSize(Class cls)
{
    if(! cls)return 0;
    return cls->alignedInstanceSize();
}
Copy the code

Click on the implementation of the alignedInstanceSize method

// Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }
Copy the code

Class’s ivar size rounded up to a pointer-size boundary. It is the size of the underlying structure corresponding to the Class. Rounded up to a pointer-size boundary refers to the memory alignment when the system allocates memory for the structure of the class. The length of a pointer should be used as the alignment coefficient. The length of the 64-bit system pointer is 8 bytes. The result must be the smallest integer multiple of 8. Why use the length of the pointer as an alignment factor? The pointer must be the largest basic data type in the structure because the header of the class must be an ISA pointer. This is done according to the structure’s memory alignment rules. If you have any doubts about this, take a refresher on memory alignment. So the class_getInstanceSize method can help us get the actual size of the structure corresponding to the instance object of a class.

The alloc method is actually an AllocWithZone method. If you look in the objC source project, you can find an _objc_rootAllocWithZone method in the object. mm file.

id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;

#if __OBJC2__
    // allocWithZone under __OBJC2__ ignores the zone parameter
    (void)zone;
    obj = class_createInstance(cls, 0);
#else
    if(! zone) { obj = class_createInstance(cls,0);
    }
    else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }
#endif

    if(slowpath(! obj)) obj = callBadAllocHandler(cls);return obj;
}
Copy the code

Click on the implementation of the key class_createInstance method

id  class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}
Copy the code

Continue with the _class_createInstanceFromZone method

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if(! cls)return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if(! zone && fast) { obj = (id)calloc(1, size);
        if(! obj)return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if(! obj)return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}
Copy the code

So this method is a little bit long, so sometimes if you look at a method, don’t get too into the details, try to find the key points of the problem that we’re looking for, like this long method, we know that its main function is to create an instance, open up memory for it, Obj = (id)calloc(1, size); Size_t size = CLS ->instanceSize(extrabize); size_t = CLS ->instanceSize(extraBytes); size_t = CLS ->instanceSize(extraBytes); So let’s go ahead and click instanceSize

size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }
Copy the code

CF requires all objects to be at least 16 bytes. CF has a hard and fast rule that when creating an instance object, the space allocated to it should not be less than 16 bytes. My current understanding is that this is probably equivalent to a development specification, or the specification provided for some implementations within the CF framework. Size_t instanceSize(size_t extraBytes) is the number of bytes returned by size_t instanceSize(size_t extraBytes), which is the amount of memory allocated to create instance objects for a class. Here our NSObject class creates an instance object and allocates 16 bytes. We click on the alignedInstanceSize method in the above code

// Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }
Copy the code

That’s what we did aboveclass_getInstanceSize The one you see in the methodalignedInstanceSize .

Conclusion two:class_getInstanceSize&malloc_sizeThe difference between

  • class_getInstanceSize: Gets the actual size of an instance of an ObjC class, which can be interpreted as at least the space needed to create the instance object (the system may actually allocate more space for this object due to system memory alignment).
  • malloc_size: Gets the size of the memory to which a pointer points. Our OC object is a pointer. Using this function, we can get the memory used by the object, which is the actual amount of memory allocated by the system for the object to which the object (pointer) points.

Sizeof () : Gets the storage space occupied by a type or variable, which is an operator.

  • [NSObject alloc]After that, the system allocates 16 bytes of memory, and finallyobjObject (i.estruct NSObject_IMPLStructure), which actually uses 8 bytes of memory (the internal one)isaThe 8 bytes used by the pointer, which we are talking about on a 64-bit system.)

Some understanding of the contrast between operators and functions

  • After the function is compiled, it can be called in the running stage of the program, with the occurrence of calling behavior
  • The operator is directly replaced by the result constant after the operation at the moment of compilation. Similar to the macro definition, there is no call behavior, so it is very efficient

More complex custom classes

We’re going to be customizing all sorts of classes as we develop, which are basically subclasses of NSObject. What about the memory layout of more complex subclass objects? Let’s create a new subclass of NSObject, Student, and add some member variables to it

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

@end

@implementation Student

@end
Copy the code

Take a look at the underlying implementation code for this class, using the method we’ve described before

struct NSObject_IMPL {
	Class isa;
};

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

Student’s underlying structure contains its member variables, as well as the NSObject_IMPL structure of its parent class. NSObject_IMPL requires 8 bytes of memory, but the NSObject object is allocated 16 bytes of memory. How much memory should the Student member NSObject_IMPL get allocated? Let’s verify that.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        // Get the size of the member variables of the instance object of the NSObject class
        size_t size = class_getInstanceSize([NSObject class]);
        NSLog(@"NSObject instance object size: %zd",size);
        // Get the size of the memory that obj points to
        size_t size2 = malloc_size((__bridge const void *)(obj));
        NSLog(@" Size of memory to which object obj points: %zd",size2);
        
        Student * std = [[Student alloc]init];
        size_t size3 = class_getInstanceSize([Student class]);
        NSLog(@"Student instance object size: %zd",size3);
        size_t size4 = malloc_size((__bridge const void *)(std));
        NSLog(@" Size of memory pointed to by object STD: %zd",size4);
    }
    return 0;
}
Copy the code

As can be seen from the result, the underlying structure of the Student class is equivalent to

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

To summarize, the underlying structure of a subclass is equal to all the member variables in the parent class structure plus the member variables defined by the subclass itself. For the sake of rigor, I added a few more member variables to the Student class to verify my guess.

@interface Student : NSObject
{
   @public
    int _age;
    int _no;
    int _grade;
}
Copy the code

This seems to be true, but why is STD allocated 32 memory using malloc_size? Let’s try it again

@interface Student : NSObject
{

   @public
    // The parent isa takes up another 8 bytes
    int _age;/ / 4 bytes
    int _no;/ / 4 bytes
    int _grade;/ / 4 bytes
    int *p1;/ / 8 bytes
    int *p2;/ / 8 bytes
}
Copy the code

StudentThe total space required for all the member variables of the structure is 36 bytes. According to the memory alignment principle, the final space required by the structure should be a multiple of 8, which should be 40. Let’s look at the result

This is true, but it is also observed that as STD member variables increase, the amount of memory allocated for Student STD increases by a factor of 16 (16 ~ 32 ~ 48……). We did not see this setting before analyzing the source code

The above method can only be used to calculate the actual memory size of a structure object. What actually allocates memory to it is the following methodThis method is located in the Apple source codelibmallocFolder. However, if I go further into the code, it is still quite difficult because of my current knowledge reserve and professional background (mathematics major 😓). Fortunately, the conclusion was drawn from some great gods.At the beginning of this article, we talked about memory alignment of structures, which applies to data structures. At the system level, for the sake of optimizing memory management and access efficiency, the Apple system plans many blocks in memory. These blocks are large and small, but they are multiples of 16. For example, some are 32, some are 48, and some are 48libmallocSource,nano_zone.hThere’s another piece of code in there

#define NANO_MAX_SIZE	 256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ... } * /
Copy the code

NANO is one of the memory allocation methods in the source code, and so onfrozen,legacy,magazine,purgeable.These are memory management-related libraries that Apple has set up based on various scenarios optimization requirements, so don’t read too much into them for now. The aboveNANO_MAX_SIZEThere’s a word in the explanationBuckets sizedIs apple’s pre-planned memory block size requirements fornano, memory blocks are set to multiples of 16, and the maximum value is 256. For example, if an object structure needs 46 bytes, it will find a 48-byte memory block and allocate it to another structure that needs 58 bytes, it will find a 64-byte memory block and allocate it to another structure. This should basically explain why the student structure allocated 48 bytes of memory when 40 bytes were required. At this point, the memory footprint of an NSObject and its extended memory layout, and its subclasses, should be solved.





Interview Answers

  • How much memory does an NSObject take up?

1) The system allocates 16 bytes to the NSObject object (available via the malloc_size function)

2) NSObject uses only 8 bytes of internal space for isa pointer variables (on 64-bit systems, this can be obtained by class_getInstanceSize)

OC Object nature (Middle) : The type of OC object

The nature of the OC object (part 2) : the ISA&superclass pointer

Special note

This series of articles are summarized from the basic principles of OC taught by MJ in Tencent class. The relevant pictures are taken from the courseware in the course.