The introduction

This series follows a series of articles from Effective Objective-C 2.0.

  • Objective-c (1) Familiar with Objective-C

  • Objective-c (2) Objects, messages, and runtime

  • Objective-c language (3) Interface and API design

  • Objective-c (4) Protocols and classification

  • Objective-c language (5) System framework

  • Objective-c (6) Block and GCD

    The following series of articles will explore objective-C 2.0 features in practice, with reference to some blog posts, official documentation, and a series of MJ lessons — highly recommended:

  • Objective C (7) Object memory analysis

  • Objective C (8) The nature and classification of objects

  • Objective-c (9) KVC and KVO

  • Objective-c (10) Category

  • Objective-c load and Initialize

  • Objective-c (12) Associative objects

This article mainly aims at several classes to peep into the memory storage of instance objects, we start with member variables and attributes, the relevant code in this article is here.

Most of the objective-C code we write is actually C++ code

So objective-C object orientation is implemented based on C\C++ data structures

Objective-c objects and classes are mainly implemented based on C\C++ data structures — structures.

How much memory does NSObject take up

NSObject *obj = [[NSObject alloc] init];

// Get the size of the member variables of the NSObject instance object >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));

// Get the size of the memory pointed to by the obj pointer >> 16
NSLog(@"%zu", malloc_size((__bridge const void *)obj));
Copy the code

There are two functions above:

How much memory is required to create an instance object?

# import <objc/runtime.h>

class_getInstanceSize([NSObject class]);

How much memory is actually allocated for creating an instance object?

#import <malloc/malloc.h>

malloc_size((__bridge const void *)obj);

Convert Objective-C code to C\C++ code

// If you need to link to other frameworks, use the -framework argument. For example, -framework UIKit // xcrun -sdk iphoneos clang-arch arm64 -rewrite-objc OC source file -o CPP file $xcrun -sdk iphoneOS clang -arch arm64 -rewrite-objc main.mCopy the code

Where, we can see that the underlying structure of NSObject converted to C++ is:

//main.cpp
struct NSObject_IMPL {
	Class isa;		//typedef struct objc_class *Class;
};
Copy the code

As we can see directly from breakpoint debugging, obj does have only one ISA member variable.

1.1 Viewing Real-time Memory

Let’s observe by looking at the memory corresponding to OBj:

As you can see from the figure above, the first eight bytes of address 0x1029000A0 have data, and the next eight bytes are all zeros.

According to the initial print:

// Get the size of the member variables of the NSObject instance object >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));

// Get the size of the memory pointed to by the obj pointer >> 16
NSLog(@"%zu", malloc_size((__bridge const void *)obj));
Copy the code

We guess that the first 8 bytes are the memory space occupied by isa in obj, and the last 8 bytes are padding bytes allocated for memory alignment.

1.2 Structure conversion

To verify this guess, we convert the obj object to the corresponding structure:

struct NSObject_IMPL {
    Class isa;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        
        NSLog(@"%zd", class_getInstanceSize([NSObject class]));
        NSLog(@"%zu", malloc_size((__bridge const void *)(obj)));
        
        struct NSObject_IMPL *objImpl = (__bridge struct NSObject_IMPL *)obj;
        NSLog(@"obj address: %p", obj);
        NSLog(@"objImpl address: %p, objImpl isa: %p", objImpl, objImpl->isa);
        NSLog(@ "-- -- -- -- --");
    }
    return 0;
}
Copy the code

Run it again, and the output is as follows:

From the picture above, we can see that:

  • Obj can convert toNSObject_IMPLStructure, address consistent;
  • isaThe value of0x1dffffa4575141, the same as the last run, and only 8 bytes.
  • Obj ends up occupying 16 bytes, of which 8 bytes are allocated toisa, 8 bytes are memory aligned padding bytes.

Here, why 16 bytes? IOS allocates objects at least 16 x N bytes.

More complex object: BFPerson

@interface BFPerson : NSObject
{
    @public
    int _age;
    int _male;
}
@property (nonatomic.assign) double height;
@end

@implementation BFPerson

@end
    
int main(int argc, const char * argv[]) {
    @autoreleasepool {
       
        BFPerson *jack = [[BFPerson alloc] init];
        jack->_age = 24;
        jack->_male = 1;
        jack.height = 185;
        NSLog(@"jack age is %d, male: %d, height: %f", jack->_age, jack->_male, jack.height);
        
        BFPerson *rose = [[BFPerson alloc] init];
        rose->_age = 21;
        rose->_male = 0;
        rose.height = 165;
        NSLog(@"rose age is %d, male: %d, height: %f", rose->_age, rose->_male, rose.height);

        NSLog(@"%zd", class_getInstanceSize([BFPerson class]));
        NSLog(@"%zd", malloc_size((__bridge const void *)jack));
        
    }
    return 0;
}
Copy the code

This time, the object is more complex and inherits NSObject, so how many bytes are allocated in the member variables of the instance object and how much of itself are actually occupied?

Final output:

By converting the above code to C++ code, we can get the structure of BFPerson:

BFPerson_IMPL contains the NSObject_IMPL structure, so it can be formalized as:

As you can see, the first variable is again an ISA pointer, followed by the two member variables we defined, and a property we defined.

We know that the property eventually converts a corresponding member variable, so there are three member variables.

Where isa, we know is an 8-byte one, so we get the following bytes for each member variable:

This is consistent with the 24 bytes we used to print the instance object, but it was allocated 32 bytes anyway.

There are two ways to check that the in-memory instance objects are storing the member variables as expected.

2.1 Viewing Memory in Real time

The following;

  • View the BFPerson class, jack object address 0x100683240
  • Jack instance object member objects occupy 24 bytes, and the system allocates 32 bytes.
    • The first eight bytes of the instance object are: 71 12 00 00 01 80 1D 00, isisaValue;
    • Then four bytes: 18 00 00 00, is the _age value, that is, _age = 24;
    • Then four bytes: 01 00 00 00, which is the _male value, that is, _male=1;
    • 00 00 00 00 20 67 40, IEEE representation of floating point 185, height=185;
    • The last eight bytes are padding bytes
      • For system alignment, both 0:00 00 00 00 00 00 00 00;
      • Non-structure internal memory alignment, structure internal memory alignment, refers to the structure’s memory footprint is an integer multiple of the variable of the maximum memory footprint, whereisaIt takes 8 bytes, but the structure ends up taking 24 bytes, which is already an integer multiple.
  • Mac CPUS are in small-endian mode.
  • You can also observe whether the Rose object conforms to expectations without further elaboration.

2.2 Structure conversion

We converted jack into our own custom structure based on the previous analysis of converting C++ code constructs.


struct NSObject_IMPL {
    Class isa;
};

struct BFPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    int _male;
    double _height;
};

struct BFPerson_IMPL *jackImpl = (__bridge struct BFPerson_IMPL *)jack;
NSLog(@"jack age is %d, male: %d, height: %f", jackImpl->_age, jackImpl->_male, jackImpl->_height);
Copy the code

The output is as follows, and you can see that the results are exactly the same, so ours fits our guess.

BFProgrammer

The inheritance relationship is as follows:

@interface BFPerson : NSObject
{
@public
    int _age;
    int _male;
}
@property (nonatomic.assign) double height;
@end

@implementation BFPerson
@end

@interface BFProgrammer : BFPerson
{
    @public
    char *company;
}
@end

@implementation BFProgrammer
@end
Copy the code

The test code is as follows;

        BFPerson *jack = [[BFPerson alloc] init];
        jack->_age = 24;
        jack->_male = 1;
        jack.height = 185;
        NSLog(@"jack age is %d, male: %d, height: %f", jack->_age, jack->_male, jack.height);
        NSLog(@"jack instance size: %zd", class_getInstanceSize([BFPerson class]));
        NSLog(@"jack malloc size: %zd", malloc_size((__bridge const void *)jack));
        
        BFProgrammer *tony = [[BFProgrammer alloc] init];
        tony->_age = 28;
        tony->_male = 1;
        tony.height = 178;
        tony->company = "Google";
        NSLog(@"tony age is %d, male: %d, height: %f, company: %s", tony->_age, tony->_male, tony.height, tony->company);
        
        NSLog(@"tony instance size:%zd", class_getInstanceSize([BFProgrammer class]));
        NSLog(@"tony malloc size: %zd", malloc_size((__bridge const void *)tony));
Copy the code

Corresponding results:

3.1 structure

Let’s analyze the results below:

For Tony, the programmer:

  • Its member variable size is 32 bytes, compared with jack, the BFPerson, 8 bytes more memory variable. So these 8 bytes are used to hold a pointer to char star company.

    • Finally, the instance memory variable occupies 32 bytes, and the actual system allocation is 32 bytes.

3.2 Memory analysis of Company

Let’s now go one step further and read Tony’s company name directly from memory: Google.

3.2.1 Google ASCII Characters

Google is a C language string constant, which is stored in memory and encoded with ASCII characters. Its final structure is:

From the above figure, we find that the address of Tony is 0x103300700. According to its structure, we can know that the address of Company is:

Compyan address = 0x103300700 + ISA + _age + _male + _height

​ = 0x103300700 + 8 + 4 + 4 + 8

	        = 0x103300718
Copy the code

3.2.2 LLDB Viewing memory

The corresponding instructions are as follows:

(lldb) x/2wx 0x103300718
0x103300718: 0x00000f52 0x00000001
(lldb) x/4wx 0x0000000100000f52
0x100000f52: 0x676f6f47 0x7400656c 0x20796e6f 0x20656761
(lldb) x/4wx 0x103300700
0x103300700: 0x00001391 0x001d8001 0x0000001c 0x00000001
Copy the code

3.2.3 analysis

  • 32 byte member variables, all conform to the analysis;
  • The company character pointer address is0x0000000100000f52To point toGoogleString, Google is 7 bytes, note 7 bytes, the last character is ‘\0’;
  • Pay attention to the size of the writing, do not write backwards, or read wrong.
    • LLDB displays in normal readable big-endian mode, and displays high bytes in high order and low bytes in low order.
    • The memory displays the unendian mode, 52 0F 0000010000 00, changed to our normal read and write big-endian mode: 0x0000000100000F52. That is, read each byte from right to left.

4. Source code analysis

Let’s look at the print statements above:

        NSLog(@"%zd", class_getInstanceSize([NSObject class]));
        NSLog(@"%zu", malloc_size((__bridge const void *)(obj)));
Copy the code

Alloc and class_getInstanceSize source z are in the Apple Souce ObjC4 library.

4.1 alloc

**[NSObject alloc]

  • Calloc returns the number of bytes allocated for the last memory
    • The size in _class_createInstanceFromZone is fetched from instanceSize;
    • The size in _class_createInstanceFromZone is the space occupied by the instance variable, not the last allocated memory;
    • Size in _class_createInstanceFromZone is the structure’s byte-aligned space (instanceSize is the byte-aligned space);
  • instanceSizeAccess isalignedInstanceSize.
    • whenalignedInstanceSizeIf the value is smaller than 16 bytes, it will be completed to 16 bytes.
      • _class_createInstanceFromZone中size = 16
    • whenalignedInstanceSizeGreater than 16 bytes, return directlyalignedInstanceSize
      • Size = alignedInstanceSize in _class_createInstanceFromZone

As you can see, when the alignedInstanceSize in the last step above is still less than 16 bytes after struct byte alignment, the alignedInstanceSize is completed to 16 bytes.

Why do I need to complete 16 bytes?

The code document has a comment:

CF requires all objects be at least 16 bytes.

Or we can think of it as a stipulation made by the OC object to improve the efficiency of the system to allocate and find addresses.

This is why, in the first instance NSObject analysis above, the instance object actually takes 8 bytes and allocates 16 bytes. AlignedInstanceSize actually returns 8 bytes, but calloc when size is 16 bytes.

4.2 class_getInstanceSize

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

As you can see, class_getInstanceSize finally returns the size described above, byte-aligned, but before calloc in alloc.

So we see that what it actually returns is how much space the member variable actually needs.

4.3 calloc

The calloc code is in another library — libmalloc.

The source code is more difficult to understand, so direct conclusion.

  • Calloc returns the actual memory allocated by the system, which must be a multiple of 16;

  • So in the BFPerson instance, the member variable takes up 24 bytes, but calloc returns 32 bytes (16*2);

  • Malloc_size returns the size pointed to by the object pointer, which is the actual memory allocated by calloc;

    extern size_t malloc_size(const void *ptr);

    ​ /* Returns size of given ptr */

Five, view the memory

5.1 LLDB

For details about how to use LLDB, see: To be repaired -iOS Debugging (2) LLDB

5.2 View the Memory

Debug -> Workfllow -> View Memory (Shift + Command + M)

reference

link

  • Apple souce objc4

The sample source code

  • Object memory analysis