Alloc source code process analysis
First throw up a flow chart,oc
objectalloc
The general flow is shown in the figure below
XXXObject *object1 = [XXXObject alloc];
XXXObject *object2 = [XXXObject alloc]init];
XXXObject *object3 = [XXXObject new];
Copy the code
These are some of the most familiar lines of code. Creates and returns an instance of a class. So what is its internal process? What do alloc and init and new do? We’ll explore this next.
Environment: Xcode 11.5
Source: objc4-781
[XXXObject alloc]
As you can see from the source code, the alloc method should call NSObject’s alloc method
+ (id)alloc {
return _objc_rootAlloc(self);
}
Copy the code
But the interruption point is found in the executionalloc
Method will be entered firstobjc_alloc
Method is then executed[NSObject alloc]
.Internal implementation is as follows:
// call [CLS alloc]. Objc_alloc (Class CLS) {&1 return callAlloc(CLS, true/*checkNil*/, false/*allocWithZone*/); }Copy the code
If we write the code as follows:
XXXObject *object2 = [XXXObject alloc]init];
Copy the code
So it’s going to enter firstobjc_alloc_init
Method, the stack looks like this:Internal implementation is as follows:
// Calls [[cls alloc] init].
id
objc_alloc_init(Class cls)
{
return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init];
}
Copy the code
CallAlloc is called in the same way, and the arguments are identical, except that init is added.
The LLVM compiler is responsible for compiling the LLVM file.
/// Instead of '[[MyClass alloc] init]', try to generate
/// 'objc_alloc_init(MyClass)'. This provides a code size improvement on the
/// caller side, as well as the optimized objc_alloc.
Copy the code
LLVM optimizes code like [[MyClass alloc] init] or [MyClass alloc] during compilation.
Alloc, _objc_rootAlloc
Either objc_alloc or objc_alloc_init will eventually call the [NSObject alloc] method.
+ (id)alloc {return _objc_rootAlloc(self); } + (id)_objc_rootAlloc(Class CLS) {&2 return callAlloc(CLS, false/*checkNil*/, true/*allocWithZone*/); }Copy the code
callAlloc
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { #if __OBJC2__ 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
As mentioned above, the objc_alloc and objC_alloc_init methods also call callAlloc, eventually performing the objc_msgSend message on the last line, and finally recalling the [NSObject alloc] method, Then the alloc method also calls the callAlloc method again, this time calling the _objc_rootAllocWithZone(CLS, nil) method.
A few points to note are:
- The slowPath (x) and FastPath (x) macros are used for compilation optimization, which allows the compiler to adjust the order of instructions and increase execution efficiency.
#define fastPath (x) (__builtin_expect(bool(x), 1)) slowPath (__builtin_expect(bool(x)), 0)) means x is likely to be 0Copy the code
- HasCustomAWZ () whether there is a custom
allocWithZone
Methods.
bool hasCustomAWZ() const { return ! cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ); }Copy the code
You can see that the return value of this function is related to the cache.
_objc_rootAllocWithZone
NEVER_INLINE id _objc_rootAllocWithZone(Class cls, // allocWithZone under __OBJC2__ ignores the zone parameter return _class_createInstanceFromZone(cls, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC); }Copy the code
_class_createInstanceFromZone
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) {// Avoid multithreading problems 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(); // isa type distinction for isa. If a class and instances of its parent cannot use isa of type isa_t, the return value is false, but in objective-c 2.0, most classes are supported. Size_t size = CLS ->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (zone) { obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); Obj = (id)calloc(1, size); } if (slowpath(! obj)) { if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { return _objc_callBadAllocHandler(cls); } return nil; } if (! Zone && fast) {// 3. Initialize the instance object's ISA 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; }}Copy the code
The core process is as follows:
- Gets the size of memory that the instance needs to open up
size
- According to the
size
Clear the corresponding memory space and returnid
Pointer to type - Initialize the corresponding
isa
Pointer to the
InstanceSize calculates memory space
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 fastInstanceSize function in cache_t is called
size_t fastInstanceSize(size_t extra) const { ASSERT(hasFastInstanceSize(extra)); if (__builtin_constant_p(extra) && extra == 0) { return _flags & FAST_CACHE_ALLOC_MASK16; } else { size_t size = _flags & FAST_CACHE_ALLOC_MASK; // remove the FAST_CACHE_ALLOC_DELTA16 that was added // by setFastInstanceSize return align16(size + extra - FAST_CACHE_ALLOC_DELTA16); }}Copy the code
Finally, the align16 method is called to return the true memory size that needs to be turned on, which must be a multiple of 16.
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
Copy the code
obj->initInstanceIsa
The initInstanceIsa method eventually calls initIsa.
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { ASSERT(! cls->instancesRequireRawIsa()); ASSERT(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); } inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { ASSERT(! isTaggedPointer()); if (! nonpointer) { isa = isa_t((uintptr_t)cls); } else { ASSERT(! DisableNonpointerIsa); ASSERT(! cls->instancesRequireRawIsa()); isa_t newisa(0); #if SUPPORT_INDEXED_ISA ASSERT(cls->classArrayIndex() > 0); newisa.bits = ISA_INDEX_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.indexcls = (uintptr_t)cls->classArrayIndex(); #else newisa.bits = ISA_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.shiftcls = (uintptr_t)cls >> 3; #endif // This write must be performed in a single store in some cases // (for example when realizing a class because other threads // may simultaneously try to use the class). // fixme use atomics here to guarantee single-store and to // guarantee memory order w.r.t. the class index table // ... but not too atomic because we don't want to hurt instantiation isa = newisa; }}Copy the code
Put the source code, the details will be analyzed next time.
init
Init is simpler and basically provides an interface for developers to override.
- (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;
}
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
Copy the code
New is alloc init, init init, alloc init, alloc init, init, etc.