When I wrote “iOS (Objective-C) Memory Management & Blocks,” I didn’t find that the NSObject code was already open source, so I analyzed the GNUStep source code and guessed at the Apple part.
Essentially, the implementation of NSObject is open source in ObjC4-706. So I started learning about objC4.
Here’s a look at some of Apple’s NSObject memory management.
SideTable
Go to nsobject.mm and start with some very important information for later understanding.
objc4-706 NSObject.mm SideTable:
struct SideTable {
// Ensure the atomic operation of the spin lock
spinlock_t slock;
// Reference count hash table
RefcountMap refcnts;
// weak references the global hash table
weak_table_t weak_table;
};
Copy the code
The SideTable structure sets several very important variables for weight.
// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))
#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)
Copy the code
Several important offsets are defined above. The reference count retainCount is stored in an unsigned integer that is 8 bytes long. Its structure can be shown as follows:
SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
(represents the first bit of the memory where the object resides), indicating whether the object has a weak object.SIDE_TABLE_DEALLOCATING (1UL<<1)
(represents the second bit of memory in which the object resides), indicating whether the object is dealloc (destructor).SIDE_TABLE_RC_ONE (1UL<<2)
(represents the third bit of memory in which the object resides), which stores the reference count value (in fact, the third bit is used to store the reference count value).
retainCount
Find the implementation of retainCount and look down layer by layer.
objc4 NSObject.mm retainCount:
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
Copy the code
objc4 objc-object.h rootRetainCount:
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
return sidetable_retainCount();
}
Copy the code
objc4 NSObject.mm sidetable_retainCount:
uintptr_t
objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if(it ! = table.refcnts.end()) {// this is valid for SIDE_TABLE_RC_PINNED too
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
Copy the code
It ->second refers to the 8-bit unsigned integer that holds the reference count.
Several important offsets in Sidetable were described above, and the true reference count can be obtained by shifting SIDE_TABLE_RC_SHIFT.
So, the main thing in sidetable_retainCount() is to traverse the reference count table looking for objects to get the reference count +1 and return the result.
retain
Find the implementation of retain.
objc4 NSObject.mm retain:
- (id)retain {
return ((id)self)->rootRetain();
}
Copy the code
objc4 objc-objc.h rootRetain:
// Base retain implementation, ignoring overrides.
// This does not check isa.fast_rr; if there is an RR override then
// it was already called and it chose to call [super retain].
inline id
objc_object::rootRetain()
{
if (isTaggedPointer()) return (id)this;
return sidetable_retain();
}
Copy the code
objc4 NSObject.mm sidetable_retain:
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISAassert(! isa.nonpointer);#endif
SideTable& table = SideTables()[this];
table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
Copy the code
RefcntStorage += SIDE_TABLE_RC_ONE We illustrate by distance:
If obj had a reference count value of 1 (binary 00000100, because the first digit and the second digit are used to identify other things) and now if you want to retain and increment the reference count value by 1, then 00000100 => 00001000 is required. So it’s actually retainCount + 4 from an integer point of view, not +1 as we understand it.
RefcntStorage += SIDE_TABLE_RC_ONE; SIDE_TABLE_RC_ONE = SIDE_TABLE_RC_ONE; .
release
objc4-706 NSObject.mm release:
- (oneway void)release {
((id)self)->rootRelease();
}
Copy the code
objc4-706 objc-object.h rootRelease:
inline bool
objc_object::rootRelease()
{
if (isTaggedPointer()) return false;
return sidetable_release(true);
}
Copy the code
objc4-706 NSObject.mm sidetable_release:
// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISAassert(! isa.nonpointer);#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) {
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
it->second -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
Copy the code
Look at the next few judgments.
- If the object is recorded last in the reference count table:
do_dealloc
Set it to true and the reference count value to SIDE_TABLE_DEALLOCATING (binary 00000010). - If the 8-bit reference count is smaller than SIDE_TABLE_DEALLOCATING (binary 00000010), it would be 00000001 or 00000000:
do_dealloc
Set it to true and add the DealLocating identifier. (but as for what use do not quite understand, I hope which god to point out). - If you have already
8-bit reference count & SIDE_TABLE_RC_PINNED
, the object was not deallocating, wasn’t weakly referenced, and the 8-bit mark didn’t overflow: the 8-bit reference count was reduced by 4, which is the true reference count value -1. - And finally, if
do_dealloc
和performDealloc
If both are true, execute SEL_dealloc to release the object. - Method returns do_dealloc.
If you just want to know ARC reference count correlation, just look at the code above. Alloc and dealloc are mostly memory allocations for objects.
alloc
View alloc code.
objc4-706 NSObject.mm alloc:
+ (id)alloc {
return _objc_rootAlloc(self);
}
Copy the code
objc4-706 NSObject.mm _objc_rootAlloc:
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/.true/*allocWithZone*/);
}
Copy the code
objc4-706 NSObject.mm callAlloc:
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
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())) {// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
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
Slowpath (checkNil &&! cls)) return nil; Judgment.
objc4-706 objc-os.h slowpath:
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
Copy the code
The __builtin_expect(exp, n) method means that exp is most likely zero and returns exp. You can think of fastPath (x) as true and slowpath(x) as false.
So, checkNil is false, checkNil &&! CLS is also false. So this is not going to return nil. Read on.
This is followed by an Objective-C 2.0 conditional compilation instruction. Of course, what we’re using now is objtive -C 2.0 and will execute the code. First make a judgment if (fastPath (! cls->ISA()->hasCustomAWZ()))… else … , which determines whether a class has a custom +allocWithZone implementation.
If there is no custom +allocWithZone implementation. If (fastPath (CLS ->canAllocFast()))… else … Is true only if the object does not exist, there is no ISA, etc. So look at the else in between.
Id obj = class_createInstance(CLS, 0); . Note that the Coptyright at the top of objc-Runtime-new. h is Copyright (c). 2005-2007 Apple Inc. All Rights Reserved.)
CanAllocFast () in objc-Runtime-new.mm is defined as follows:
objc4-706 objc-runtime-new.h canAllocFast:
#if FAST_ALLOC
bool canAllocFast() {
return bits & FAST_ALLOC;
}
#else
bool canAllocFast() {
return false;
}
#endif
Copy the code
Looking at the FAST_ALLOC definition again, look at the following image:
#elif 1 intercepts define directly, so #if FAST_ALLOC doesn’t work. So, canAllocFast() returns false and fastPath (CLS ->canAllocFast()) is judged to be false.
perform
// 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
objc4 objc-runtime-new.mm class_createInstance:
id
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
Copy the code
objc4 objc-runtime-new.mm _class_createInstanceFromZone:
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone)
{
void *bytes;
size_t size;
// Can't create something for nothing
if(! cls)return nil;
// Allocate and initialize
size = cls->alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
if (zone) {
bytes = malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
bytes = calloc(1, size);
}
return objc_constructInstance(cls, bytes);
}
Copy the code
Bytes = calloc(1, size); And return objc_constructInstance (CLS, bytes); . Bytes is the memory required by the object.
FYI: calloc(size_t __count, size_t __size) is a C method used to allocate n consecutive sizes of memory in the dynamic storage area. The function returns a pointer to the starting address of the allocation. If the assignment is unsuccessful, NULL is returned.
objc_constructInstance(Class cls, void *bytes)
{
if(! cls || ! bytes)return nil;
id obj = (id)bytes;
obj->initIsa(cls);
if (cls->hasCxxCtor()) {
return object_cxxConstructFromClass(obj, cls);
} else {
returnobj; }}Copy the code
The objc_constructInstance method defines bytes (Pointers to sub-objects) as obj and assigns the isa of OBj to the CLS passed in. Finally, we return to OBJ.
FYI: hasCxxCtor() is used to determine whether the current class or superclass has an implementation of the.cxx_construct constructor. HasCxxDtor () is an implementation of the.cxx_destruct method that determines whether the current class or superclass has one. Reference: Objc objects in this life
dealloc
Find the implementation of dealloc.
objc4 NSObject.mm dealloc:
- (void)dealloc {
_objc_rootDealloc(self);
}
Copy the code
objc4 NSObject.mm _objc_rootDealloc:
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
Copy the code
objc4 NSObject.mm _objc_rootDealloc:
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return;
object_dispose((id)this);
}
Copy the code
FYI: Understand Tagged Pointer in depth
objc4 objc-runtime-new.mm object_dispose:
id
object_dispose(id obj)
{
if(! obj)return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
Copy the code
objc4 objc-runtime-new.mm objc_destructInstance:
/*********************************************************************** * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. * Calls ARC ivar cleanup. * Removes associative references. * Returns `obj`. Does nothing if `obj` is nil. **********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
Copy the code
objc4 objc-object.h clearDeallocating:
inline void
objc_object::clearDeallocating()
{
sidetable_clearDeallocating();
}
Copy the code
objc4 NSObject.mm sidetable_clearDeallocating:
void
objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if(it ! = table.refcnts.end()) {if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
table.refcnts.erase(it);
}
table.unlock();
}
Copy the code
So, objc_destructInstance (obj); Destroying the instance without freeing memory, calling the C++ destructor (if the object has one), processing the first closed object (if it has one), and finally calling obj->clearDeallocating(). Clear weak references and excess retain count. objc_destructInstance(obj); After is free (obj); Frees the memory occupied by OBj.
#Other
(when writing this article, my biggest feeling is that I don’t understand C++.)
All information for this post:
Tracy Wang- ARC(Part 1)
Turns out I’m not unhappy. – What’s going on with our date
Desgard-weak Specifies how weak references are implemented
Objc Objects of this life
Objective-c reference counting principle
Objective-c Runtime first day of admission — ISA and Class
Sindrilin – Chatter memory management