The memory nature of NSObject

The definition of NSObject

To enter and view NSObject in Xcode, the definition is as follows

@interface NSObject  {
    Class isa;
}
Copy the code

The underlying implementation of NSObject

The c++ code of the corresponding file can be generated by using the following code in the corresponding folder directory of the terminal (take main.m as an example)

  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
  • If you need to link to other frameworks, use the -framework argument. For example – framework UIKit

The following structures can be found in the main.cpp file

struct NSObject_IMPL {
	Class isa;
};
Copy the code

This structure is the underlying implementation of NSObject; Thus, an NSObject object is essentially a single ISA pointer, and Pointers are eight bytes in arm64 architecture. So the instance size of the NSObject class is 8 bytes.

The OC Runtime function also provides a class_getInstanceSize() function (which returns the memory aligned size) to get the size of the corresponding instance, but this is not the size allocated to the instance. To get the size of the memory allocated to the instance, use the malloc_size() function.

The code verification is as follows:

NSObject *objc = [[NSObject alloc] init]; NSLog(@"-- --%zd-- ",class_getInstanceSize([objc class])); / / get the objc points to memory size (real allocates memory) NSLog (@ lh-zd - "" - %, malloc_size ((__bridge const void *) (objc)));Copy the code

The output is 8 and 16, respectively. (if you want to see these two functions of concrete implementation can go opensource.apple.com/tarballs/ob… Download the corresponding source code to view)

In OC, allocWithZone is actually called. Find the implementation of this function in nsobject. mm:

+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
Copy the code

The _objc_rootAllocWithZone function is implemented in objc-Runtime –new.mm:

id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}
Copy the code

The _class_createInstancesFromZone function is implemented as follows:

static ALWAYS_INLINE id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, int construct_flags = OBJECT_CONSTRUCT_NONE, bool cxxConstruct = true, size_t *outAllocatedSize = nil) { ASSERT(cls->isRealized()); // Read class's info bits all at once for performance bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); size_t size; Size = CLS ->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (zone) { obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else { obj = (id)calloc(1, size); } if (slowpath(! obj)) { if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { return _objc_callBadAllocHandler(cls); } return nil; } if (! zone && fast) { obj->initInstanceIsa(cls, hasCxxDtor); } else { // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } if (fastpath(! hasCxxCtor)) { return obj; } construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE; return object_cxxConstructFromClass(obj, cls, construct_flags); }Copy the code

Find the instanceSize method of CLS in objc-Runtime –new.h:

size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(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

The memory required by the instance object is at least 16 bytes

An NSObject object only has one pointer so it takes 16 bytes

Peek into NSObject memory with the LLDB directive

The LLDB commands are as follows:

  • Print, p: print
  • Po: Prints objects
Read from memory
  • Memory Read/Quantity Format Number of bytes Memory address
  • X/Quantity Format Number of bytes Memory address
  • X /3xw 0x10010 (prints three strings of data each in hexadecimal format of 4 bytes)
format

X is hexadecimal, F is floating point, and D is base 10

Byte size

B: Byte 1 byte, H: half word 2 bytes

W: word 4 bytes, G: giant word 8 bytes

Modifies the value in memory
  • Memory Write Memory address value

  • memory write 0x000010 10

You can see that an NSObject object is 16 bytes long

The nature of the Student

After converting the following code to c++ code

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

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *stu = [[Student alloc] init];
    }
    return 0;
}

Copy the code

Find the definition of Student as follows:

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

We already know that an NSObject_IMPL structure has only one isa pointer (arm64 for 8 bytes) and two ints for 8 bytes, which adds up to just 16 bytes.

Run the following code:

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

struct NSObject_IMPL {
    Class isa;
};

struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _no;
    int _age;
};


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

@end

@implementation Student


@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *stu = [[Student alloc] init];
        stu->_no = 4;
        stu->_age = 5;
        
        struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
        NSLog(@"no is %d,age is %d",stuImpl->_no,stuImpl->_age);
        
        NSLog(@"%zd",class_getInstanceSize([Student class]));
        NSLog(@"%zd",malloc_size((__bridge const void *)stu));
    }
    return 0;
}

Copy the code

Set a breakpoint at stuImpl, find the Memory address and use Xcode’s View Memory function to enter the address

graph LR
Debug --> B[Debug Workflow]--> C[View Memory]

You can find the values of _no and _age as shown above. Re-view memory after entering the LLDB command:

Release the breakpoint and print the following:

In fact, the Student object’s memory allocation looks like the following:

More complex inheritance structures

After the following code is run:

#import <Foundation/Foundation.h> #import <objc/runtime.h> #import <malloc/malloc.h> //struct NSObject_IMPL { // Class isa; / /}; // //struct Person_IMPL { // struct NSObject_IMPL NSObject_IVARS; // int _age; / /}; // // //struct Student_IMPL { // struct Person_IMPL Person_IVARS; // int _no; / /}; @interface Person : NSObject { int _age; } @end @implementation Person @end @interface Student : Person { int _no; } @end @implementation Student @end int main(int argc, const char * argv[]) { @autoreleasepool { Student *stu = [[Student alloc] init]; NSLog(@"stu - %zd",class_getInstanceSize([Student class])); NSLog(@"stu - %zd",malloc_size((__bridge const void *)stu)); Person *person = [[Person alloc] init]; NSLog(@"person - %zd",class_getInstanceSize([Person class])); NSLog(@"person - %zd",malloc_size((__bridge const void *)person)); } return 0; }Copy the code

The output is:

Note Person memory is used when the memory allocated to Person is large enough to hold Student member variables.

Add an NSInteger modifier _score to Student and print:

Run _score after _no and print the following result:

This is because of memory alignment: the size of the structure must be a multiple of the largest member variable (at this point the largest member variable in the Student structure is Person_IMPL, which is 16 bytes), so the memory size returned by stU alignment is 32 bytes.