One, the question point
[[XXX alloc]init] [XXX alloc]init] [XXX new] [XXX new] [XXX alloc]init] [XXX new] What’s the connection between instance creation and instance creation? What’s the difference between them? With these doubts we go deep into the bottom to explore.
Person *p = [Person alloc];
Person *p1 = [[Person alloc]init];
Person *p2 = [Person new];
NSLog(@"%p,%p,%p",p,p1,p2);
Copy the code
Two, the bottom source code exploration
We need to know what the underlying code does, and fortunately Apple has open-source code for that. You can download it here. Download down the source code directly compiled is not through, you need to modify the configuration.
1. Alloc exploration
Going into the alloc source, we see that alloc calls the _objc_rootAlloc method, and _objc_rootAlloc calls the callAlloc method.
+ (id)alloc {
return _objc_rootAlloc(self);
}
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if(slowpath(checkNil && ! cls))return nil;
#if __OBJC2__
if(fastpath(! cls->ISA()->hasCustomAWZ())) {if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if(slowpath(! obj))return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if(slowpath(! obj))return callBadAllocHandler(cls);
returnobj; }}#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
Copy the code
What catches our attention in the callAlloc method is the if judgment condition. fastpath(! What does CLS ->ISA()->hasCustomAWZ() do? What is FastPath?
The fastPath definition looks like this
#define fastpath(x) (__builtin_expect(bool(x), 1))
Copy the code
To figure out what fastPath is, we need to know what __builtin_expect is. The almighty Google has given us the answer. This instruction was introduced by GCC to allow the programmer to tell the compiler which branch is most likely to be executed. This command is written as __builtin_expect(EXP, N). The probability that e to the N is equal to N is high
! What does CLS ->ISA()->hasCustomAWZ() do? A method like hasCustomAWZ is obviously called.
bool hasDefaultAWZ() {return data()->flags & RW_HAS_DEFAULT_AWZ;
}
#define RW_HAS_DEFAULT_AWZ (1<<16)
Copy the code
RW_HAS_DEFAULT_AWZ This is used to indicate whether the current class or superclass has a default alloc/allocWithZone:. Note that this value is stored in metaclass.
The hasDefaultAWZ() method is used to determine whether the current class has overridden allocWithZone. If CLS ->ISA()->hasCustomAWZ() returns YES, meaning that the current class has overridden the allocWithZone method, then allocWithZone the class directly.
if (allocWithZone) return [cls allocWithZone:nil];
+ (id)allocWithZone:(struct _NSZone *)zone {
return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
Copy the code
The _objc_rootAllocWithZone method is called inside the allocWithZone, so let’s look at the _objc_rootAllocWithZone method.
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); // Create an object#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
We see that calling the class_createInstance method directly to create an object seems to be getting closer to the truth of alloc.
id class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
tatic __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if(! cls)returnnil; assert(cls->isRealized()); 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
Whoa, whoa, whoa, whoa, whoa, whoa! But a bit of quiet analysis reveals something. Creating objects means creating memory for objects. Could this be the case? InstanceSize (); instanceSize (); instanceSize (); 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;
}
uint32_t alignedInstanceSize() {
returnword_align(unalignedInstanceSize()); // Uint32_t = uint32_tunalignedInstanceSize() {
assert(isRealized());
returndata()->ro->instanceSize; Uint32_t word_align(uint32_t x) {return (x + WORD_MASK) & ~WORD_MASK;
}
Copy the code
Wow, our guess is right. The instanceSize method calculates the size of the object, which must be greater than or equal to 16 bytes, and then calls calloc to allocate memory for the object. It is important to note that the alignment algorithm is very clever to use, and I explained memory alignment in the last article. To sum up, alloc creates an object and allocates no less than 16 bytes of memory to it.
But is it just a block of memory? We also notice the initInstanceIsa method, so what does that method do? In fact, this method is to initialize the ISA pointer, which is not described here, but will be covered in a future article.
What if hasDefaultAWZ() returns NO?
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if(slowpath(! obj))return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if(slowpath(! obj))return callBadAllocHandler(cls);
return obj;
}
Copy the code
HasDefaultAWZ () returns NO to determine whether the current class supports fast alloc. If possible, call calloc directly and request a block of bits.fastInstancesize (), then initialize the ISA pointer, otherwise call class_createInstance directly and we are done.
Summarizing the above analysis process, we draw a conclusion
Alloc creates an object for us and requests a chunk of memory of no less than 16 bytes.
If alloc created the object for us, why init it? So what does init do?
2. Init exploration
Let’s go to the source of the init method
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}
Copy the code
Oh my God, init does nothing but return the current object. Do we need to call init if we haven’t done anything? The answer is yes, init is a factory paradigm that allows developers to rewrite the definition themselves.
Let’s see what the new method does.
3. New inquiry
Go to the underlying code of New
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
Copy the code
New calls callAlloc and init, so new is alloc+init.
conclusion
- Alloc creates the object and claims a memory space of no less than 16 bytes.
- Init actually does nothing, returns the current object. Its purpose is to provide a paradigm for developers to customize.
- New is actually a combination of alloc+init.