OC objects and Pointers
GPerson *p1 = [GPerson alloc];
GPerson *p2 = [p1 init];
GPerson *p3 = [p1 init];
LGPrint(@"%@ - %p - %p", p1, p1, &p1); //<GPerson: 0x6000022f4160> - 0x6000022f4160 - 0x7ffee7a11078
LGPrint(@"%@ - %p - %p", p2, p2, &p2); //<GPerson: 0x6000022f4160> - 0x6000022f4160 - 0x7ffee7a11070
LGPrint(@"%@ - %p - %p", p3, p3, &p3); //<GPerson: 0x6000022f4160> - 0x6000022f4160 - 0x7ffee7a11068
Copy the code
Three pointer variables point to the same memory space, p1, P2, p3 are placed in the stack space, so each pointer variable address is different;
Because it’s allocated in the stack space, the stack space goes from high to low;
Because of 64-bit devices, the pointer size is 8 bytes, so subtract 0x8 from 0x7FFEE7a11078.
%p -- p1 is the address of the printed object
%p -- &p1 prints the address of P1 in the address space
0x00 Prepare the source code project
-
Download the objC4-781 source project from the official website
-
Let’s start with the flow chart of alloc
0x01 — Alloc explore
- in
main
In the functionalloc
The method goes to the implementation section, and from there, work your way down to π
+ (id)alloc {
return _objc_rootAlloc(self);
}
Copy the code
- Jump to the
_objc_rootAlloc
The implementation part of.
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/.true/*allocWithZone*/);
}
Copy the code
- Jump to the
callAlloc
The implementation part of the process, this time there is a branch, after the breakpoint debugging, here go_objc_rootAllocWithZone
In the objc-config.h file
// Define __OBJC2__ for the benefit of our asm files.
#ifndef __OBJC2__
# ifTARGET_OS_OSX && ! TARGET_OS_IOSMAC && __i386__
// old ABI
# else
# define __OBJC2__ 1
# endif
#endif
Copy the code
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__ // macro conditions determine whether OBJC2 version is available
if (slowpath(checkNil && ! cls))return nil;
// CLS ->ISA()->hasCustomAWZ()
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
Compiler optimization
Fastpath and slowPath reference
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
__builtin_expect is used by GCC for programmers to provide “branch” information to the compiler so that the compiler can optimize the code to reduce the performance degradation caused by instruction jumps
In fact, the deletion of slowPath and Fastpath in the code does not affect the function of this code. The addition of slowPath and FastPath tells the compiler whether there are high or low probability events in the if conditional statement, so that the compiler can optimize the code.
if (x)
return 1;
else
return 30;
Copy the code
Since the computer reads multiple instructions rather than one instruction at a time, return 1 will also be read when the if statement is read. If x value is 0, return 30 will be read again. Reread instructions are relatively time-consuming. If x has a high probability of being 0, the return 1 command will inevitably be read, but there is almost no chance to execute it, resulting in unnecessary command reread. Therefore, after defining two macros, FastPath (x) still returns x, only telling the compiler that x is not generally 0, so that compilation can be optimized. Similarly, the slowpath(x) flag that x is likely to have a value of 0 can be tuned at compile time.
- In daily development, through the
Xcode
Set up theOptimization Level
theDebug
theNone
Set tofastest, Smallest[-Os]
; To achieve the purpose of performance optimization.
- Came to
_objc_rootAllocWithZone
NEVER_INLINE
id
_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
- Go straight to the core method
_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()); // error tolerance
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(a);// check whether there is a c++ constructor
bool hasCxxDtor = cls->hasCxxDtor(a);// check whether there is a c++ destructor
bool fast = cls->canAllocNonpointer(a);size_t size;
ExtraBytes = 0
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// Apply to the system to open memory, return memory address pointer
obj = (id)calloc(1, size);
}
// error tolerance
if (slowpath(! obj)) {if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if(! zone && fast) {// The ISA binding is associated with the corresponding 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
Three core approaches:
β οΈNOTE: The following series of articles will focus on the internal implementation of these functions:
size = cls->instanceSize(extraBytes);
obj = (id)calloc(1, size);
obj->initInstanceIsa(cls, hasCxxDtor);
Conclusion under_class_createInstanceFromZone
process
1 Open up the memory. Calculate the required memory space
2 Apply for memory. Apply for memory and return the address pointer
3 ISA binding is associated with the corresponding class
Analyze the first key functionsize = cls->instanceSize(extraBytes);
Go to the instanceSize method
size_t instanceSize(size_t extraBytes) const {
// The compiler optimizes fast computation
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
// Execute here
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
FastInstanceSize is called here
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
//Gcc's built-in function __builtin_constant_p is used to determine if a value is compile-time,
The function returns 1 if EXP is a constant, 0 otherwise
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
// _flags
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
Then call align16
// 0010 0111 39 (15+24)
/ / 0000 ~ 1111
/ / 0010 0000
/ / 0000 1111 15
// 16-byte alignment algorithm
static inline size_t align16(size_t x) {
return (x + size_t(15& ~))size_t(15);
}
Copy the code
The align16 method passes in 24 and then executes the alignment algorithm, 24+15 = 39. Binary 0010 0111 and 15 take the inverse 1111 0000 and press the bitfield
You get 0010, 0000 which is 32 in decimal;
You could move it right and then left, or you could set the end to 0
Why are bytes aligned?
- Convenient CPU read, improve the read efficiency, because cross-byte access will affect the IO read efficiency; Typically space for time;
- An object that defaults to the first property
isa
If it’s 8 bytes, if it has no attributes, it’s going to leave 8 bytes with the other object’s, rightisa
There is a certain width, to ensure security, will not address out of bounds, illegal access to the content of other objects; - For example π°: now the implementation of garbage classification, is to facilitate recycling, convenient disposal of waste kitchen garbage, with byte alignment is also the same, are for convenience, high efficiency; This is convenient and efficient compared to humans, while byte alignment is safe and efficient compared to CPU machines;
In the byte alignment algorithm, objects are mainly aligned, and the underlying nature is struct objc_Object.
Member variables in a structure are stored consecutively in memory, so you can force the type of the structure.
Earlier versions were 8-byte aligned, now 16-byte aligned;
Memory alignment rules can be seen in my previous struct article alignment rules, because oc objects compiled into the underlying representation is also a struct objc_Object structure; Access the link
0x02 — callAlloc exploration
obj = (**id**)calloc(1, size);
With this line of code, obj will have a hexadecimal address.
In oc, print the address of an object
; Object name + pointer address. Why is it different here?
- It’s mostly opened up
Memory address
There is no binding associated with the class passed in.
0x03 — initInstanceIsa exploration
obj->initInstanceIsa(cls, hasCxxDtor);
Then start binding the address to the class association. Initialize the initIsa pointer.
So if I print two obj’s here, that’s an object pointer.
Summarize the ALLOC process
- Mainly open up memory, 16 – byte alignment, the size of 16 integer times.
- Memory Trilogy:
Calculate the size of the object --> Apply for a memory address ----> Class and address association
0x04init
Method underlying implementation
+ (id)init {
return (id)self;
}
- (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
Factory design methods, which provide an entry point for users to customize their own initialization methods; The reason for using id strong, because memory alignment, we can use type strong to cast to the type we want;
0x05 new
The underlying implementation of the
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
Copy the code
Apple doesn’t recommend using new to initialize an object. If you override init in a custom class, using new will go to this method.
Init - (instanceType)init {self = [super init]; if (self) { NSLog(@"come here"); } return self; } Demo *obj = [Demo new]; // Print come hereCopy the code
If you don’t override the init method, using new will override the init method of the parent class
In general, our custom initWithXXX method will use the super init method instead of the parent init method, and the initialization process will be more complete.
Source code download website
Apple open source collection
Apple open Source project download
Takes you through the OC object creation process