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 new
Taget: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 & ~15 : 0001 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 callcalloc
Before,obj
It’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 callinitInstanceIsa
Method after ourobj
withDMPerson
It’s related to the way we use itpo
The command prints out a class object with the same result.
The flow chart
And then finally the wholealloc
Method 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 executessetInstanceSize
Method 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->instanceSize
Method 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.