preface

In iOS, when we want to use an object, we usually call [[NSObject alloc] init] to initialize the object, so what do we do with alloc and init? With that in mind, let’s first look at some code

    DMPerson *p1 = [DMPerson alloc];
    DMPerson *p2 = [p1 init];
    DMPerson *p3 = [p1 init];
        
    NSLog(@"%@-%p-%p",p1,p1,&p1);
    NSLog(@"%@-%p-%p",p2,p2,&p2);
    NSLog(@"%@-%p-%p",p3,p3,&p3);
Copy the code

The result of this code is as follows:

   DMTest[12144:265363] <DMPerson: 0x10540ccc0>-0x10540ccc0-0x16fdff378
   DMTest[12144:265363] <DMPerson: 0x10540ccc0>-0x10540ccc0-0x16fdff370
   DMTest[12144:265363] <DMPerson: 0x10540ccc0>-0x10540ccc0-0x16fdff368
Copy the code

Conclusion: You can see that p1, P2, and P3 are all printed with the same address, but their pointer addresses are different. So we can think of alloc roughly as creating a memory space, and init doesn’t create a memory space, it instantiates a class object. Their relationship in memory can be represented by the following diagram

So is that the case? We set out to explore the alloc process

To prepare

In order to study the underlying iOS, I found the underlying iOS code that can be compiled on the Internet, which can be downloaded through GitHub – LGCooci/objc4_debug address. Since my system and Xcode have not been upgraded to the latest version, I am not sure whether the latest version can compile this code. The following is the configuration that I can run:

  • Mac os 11.0.1
  • Xcode 12.3
  • Obje4-818.2 –

Download the code, directly run the code, there may be some errors, you can refer to the iOS_objc4 source code compilation debugging error information to solve. After successful operation

  • On the Target screen, add a newTaget:DMTest

  • Bind binary dependencies

  • Run the code and you can debug it

Alloc source process

Now that we have the Apple open source code ready to compile, let’s begin the exploration of alloc by first preparing the defined object and the code as posted at the beginning of this article

1. Alloc and _objc_rootAlloc

The upper layer of the alloc code is

+ (id)alloc {
  return _objc_rootAlloc(self);
}

id
_objc_rootAlloc(Class cls)
{
  return callAlloc(cls, false/*checkNil*/.true/*allocWithZone*/);
}
Copy the code

There’s a little bit of a hole here, because we’re calling the alloc method of DMPerson, and he’s actually not going to go directly into the +(id)alloc method, but he’s going to go into the objc_alloc method, call the callAlloc method in that method, and then look up the method, Find the alloc method of the superclass NSObject, and then go to +(ID)alloc.

2.callAlloc

Find the first core method, callAlloc, as follows

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__ // Judging from the OC2.0 version, Apple switched the code to OC2.0 a long time ago, so it's almost certain to go there
  if (slowpath(checkNil && ! cls))return nil;
  if (fastpath(! cls->ISA() - >hasCustomAWZ())) {
    return _objc_rootAllocWithZone(cls, nil);
  }
#endif

  // No shortcuts available.
  if (allocWithZone) {
    return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
  }
  return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
Copy the code

3. _class_createInstanceFromZone

Because there’s a branch in the callAlloc method, so you have a breakpoint to find out where you’re going, and then the breakpoint finds out that it’s going to _objc_rootAllocWithZone, and then it calls _class_createInstanceFromZone, This method is the heart of the whole alloc method

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(a);bool hasCxxDtor = cls->hasCxxDtor(a);bool fast = cls->canAllocNonpointer(a);size_t size;
  
 // The instanceSize method calculates the memory size to be allocated
  size = cls->instanceSize(extraBytes);
  if (outAllocatedSize) *outAllocatedSize = size;

  id obj;
  if (zone) {
    obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
  } else {
  // The calloc method allocates memory space
    obj = (id)calloc(1, size);
  }
  if (slowpath(! obj)) {if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
      return _objc_callBadAllocHandler(cls);
    }
    return nil;
  }

  if(! zone && fast) {// Associate the requested memory space with our class
    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

4. Important methods in _class_createInstanceFromZone

In the _class_createInstanceFromZone method, the three most important methods are:

cls->instanceSize

This method is used to determine the size of memory that needs to be opened, as you can see from the code below

    inline size_t instanceSize(size_t extraBytes) const {
       // Calculate the memory size quickly
       if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
           return cache.fastInstanceSize(extraBytes);
       }
       // Calculate the size of memory to be created. 'extraBytes' means that extra memory is usually 0
       size_t size = alignedInstanceSize() + extraBytes;
       // CF requires all objects be at least 16 bytes.
       // Return 16 bytes minimum
       if (size < 16) size = 16;
       return size;
   }
Copy the code

When we create our own class, since we don’t implement the alloc method, we’ll call the method lookup process, where we’ll call unalignedInstanceSize to determine the internal size of the class, and here we’ll call alignedInstanceSize() to use 8-byte alignment

    // Class's ivar size rounded up to a pointer-size boundary.
   uint32_t alignedInstanceSize(a) const {
       // Return the size of memory to be opened
       return word_align(unalignedInstanceSize());
   }
   
   // May be unaligned depending on class's ivars.
   // The size depends only on the number of member variables
   uint32_t unalignedInstanceSize(a) const {
       ASSERT(isRealized());
       return data() - >ro()->instanceSize;
   }
Copy the code

We can see that the amount of memory that needs to be opened up is really only related to the number of member variables that the class has. A newly created class that does not have any attributes or member variables will actually contain a member variable to an ISA pointer, so it will return 8.

When CLS ->instanceSize is called, it will go through the fast process cache. FastInstanceSize, and we will study the byte alignment algorithm with align16(14) as an example

static inline size_t align16(size_t x) {
    return (x + size_t(15& ~))size_t(15);
}

//align16(14)14 + 15) & ~15It's essentially29 & ~15

29Binary:0001 1101
15Binary:0000 1111
~15Binary:1111 0000

29 & ~150001 0000 = 32
Copy the code

(id)calloc

This method is to use CLS ->instanceSize method to calculate the size of memory to open up, to the system to apply for a piece of the size of memory space. Analyze through breakpoint printing

Found in the callcallocBefore,objIt’s pointing to a dirty address, it’s printing something that’s not sure what it is, and when it’s called, it’s pointing directly to a piece of memory that we requested, right0x00000001050082f0

obj->initInstanceIsa

This method is to bind the memory address requested by Calloc to our class, which can be found through breakpoint analysis

In the callinitInstanceIsaMethod after ourobjwithDMPersonIt’s related to the way we use itpoThe command prints out a class object with the same result.

The flow chart

And then finally the wholeallocMethod execution flow chart is displayed

Summary and question

It’s clear from this process that the alloc method is actually a way for our class to request a piece of memory space from the system. Finally, there is a problem that when calculating memory size, the fast method uses 16-byte alignment, while the non-fast method uses 8-byte alignment, which is not less than 16 bytes.

This question, the author will continue to explore, if there are friends who know the answer, also welcome to leave a message below.

Subsequent discovery

The above left a question, in the follow-up exploration seems to have some results, the following to add in. Because we create DMPerson class, do not implement alloc method, so the system will go slow method to find the, in the process of slow method to find the, will the system will call realizeAndInitializeIfNeeded_locked method

And in this method, it executessetInstanceSizeMethod to set cache

Because I added two properties to DMPerson, size 24 is fine with 8-byte alignment. And then call[NSObject alloc]Is calledcls->instanceSizeMethod directly goes through the fast process of reading the cachecache.fastInstanceSize(extraBytes), will go straight to 16-byte alignment at the object level, so 32 will be returned.

Finally, 8-byte alignment is used at the internal level of the class, and 16-byte alignment is used for the entire object.