preface
As a programmer working on iOS, the most commonly used method is alloc, so what does allo actually do at the bottom? First, we write a test code as follows:
SPPerson *p1 = [SPPerson alloc];
SPPerson *p2 = [p1 init];
SPPerson *p3 = [p1 init];
SPPerson *p4 = [SPPerson alloc];
NSLog(@"%@-%p-%p",p1,p1,&p1);
NSLog(@"%@-%p-%p",p2,p2,&p2);
NSLog(@"%@-%p-%p",p3,p3,&p3);
NSLog(@"%@-%p-%p",p4,p4,&p4);
Copy the code
The console output is as follows:
<SPPerson: 0x600000050410>-0x600000050410-0x7ffee943e098
<SPPerson: 0x600000050410>-0x600000050410-0x7ffee943e090
<SPPerson: 0x600000050410>-0x600000050410-0x7ffee943e088
<SPPerson: 0x600000050420>-0x600000050420-0x7ffee943e080
Copy the code
By analyzing the output results, we found that:
-
- The contents of p1, p2, and p3 point to the same address, but the addresses of Pointers are different
-
- The contents of p4 and P1, P2, p3, the addresses of the objects they point to, the addresses of the Pointers are different
We conclude that:
Alloc can open up a chunk of memory, whereas init does not. The stack area opens up memory from high address to low address, and the heap area is low address to high address.
So it looks like alloc is still allocating memory, so how does alloc actually allocate memory? We click on alloc and we get to:
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
Copy the code
And then I couldn’t get it in, so I had to find another way
Three basic approaches to low-level exploration
First: symbolic breakpoints:
Console hold down Control + Step into and see objc_alloc and add a sign breakpoint to objC_Alloc
Second: assembly debugging:
Xcode -> Debug -> Debug Workflow -> Always Show Disassembly
Second: sign breakpoint, judge position:
Alloc is a symbolic breakpoint, which is a breakpoint that is broken when the code is executed
Through the analysis of the above three methods, we find that alloc is actually called
libobjc.A.dylib`objc_alloc
So we downloaded the source code, solved the compilation problems and started analyzing the source code
Source code analysis
We search alloc {and come up
+ (id)alloc {
return _objc_rootAlloc(self);
}
Copy the code
Point into _objc_rootAlloc:
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/.true/*allocWithZone*/);
}
Copy the code
Point into callAlloc:
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
CallAlloc is first a macro __OBJC2__ which is defined as:
// Define __OBJC2__ for the benefit of our asm files.
#ifndef __OBJC2__
# if TARGET_OS_OSX && !TARGET_OS_IOSMAC && __i386__
// old ABI
# else
# define __OBJC2__ 1
# endif
#endif
Copy the code
This definition is in objC4-787.1, but not in objC4-818.2, presumably because the compiler automatically adds this macro and then fastPath and slowPath, click to see
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
Copy the code
Fastpath (x):x is most likely true, slowPath (x):x is most likely false and then through the breakpoint we go to _objc_rootAllocWithZone and click in:
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0.nil.OBJECT_CONSTRUCT_CALL_BADALLOC);
}
Copy the code
Click on _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)
{
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();
size_t size;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
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
Oh, my God. How do you analyze a chunk of code like that? Let’s focus on the return value, return obj, and then see what the obj assignment does, analyzing the core three steps:
- 1. InstanceSize the space needed to calculate and allocate objects
- 2. Calloc, allocate space
- 3. InitIsa, associate the object to isa pointer
Step 1 instanceSize, click:
inline 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
See less than 16 bytes returned 16, then alignedInstanceSize and extraBytes, via annotation extraBytes actually is depending on class’s ivars. AlignedInstanceSize is byte aligned, click in:
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
Copy the code
- (x + 7) &~ 7 is equivalent to (x + 7) >> 3 << 3
- (x + 15) &~ 15 is equivalent to (x + 7) >> 4 << 4