preface
Memory management is a very important part of ios development, and the right time to free and reclaim memory can ensure efficient application. Stack memory, heap memory, we really need to care about the heap memory, stack memory is managed by the system itself. Functions and variables are stored in the stack, and their memory management is done by the system. Objects created by alloc, block copy and other memory are stored in the heap, and their memory management needs to be implemented by ourselves. Objects are released when the reference count is 0, so we need to pay attention to how the reference count of objects is increased. We often use the weak modifier object to solve circular references. Why does the weak modifier object solve circular references? So let’s explore with these questions.
TaggedPointer
Before exploring reference counting, let’s take a look at TaggedPointer, because the related source code exploration below first determines whether an object is of type TaggedPointer.
TaggedPointer origin?
Since apple introduced the iphone5s in 2013, the addressing space on iOS has expanded to 64 bits. We can use 63 bits to represent a number (one sign bit). So this number has a range of 2 to the 63, and obviously we don’t use numbers that big, so when we define a number NSNumber *num = @100, we’re actually wasting a lot of memory. Apple recognized the problem and introduced tagged Pointer, a special “pointer” that stores real data and additional information, not an address. This is very similar to our previous exploration of ISA. When ISA is not a pure pointer, it stores not only the associated object but also the reference count of the object. Apple has optimized ISA to store more information.
TaggedPointer characteristics
- Tagged Pointer is used for storage
Small objects
, e.g.NSNumber
andNSDate
- Tagged Pointer Specifies the value of a Pointer
It's not an address anymore
, butThe real value
. So, it’s not really an object anymore, it’s just a normal variable in an object’s skin. So, its memory is notNot stored in the heap
In, there is no need for malloc and free - Three times more efficient at memory reads and 106 times faster at creation.
- The length of the string is
10
When within, useinitwithformat
Initialized string of typeNSTaggedPointerString
Type; whenMore than ten
The type of the string is__NSCFString
TaggedPointer judgment
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
#if OBJC_SPLIT_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#elif OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
Copy the code
Determining whether an object is of type NSTaggedPointerString is essentially a bit-by-bit operation of the object’s address with _OBJC_TAG_MASK, which is then compared with _OBJC_TAG_MASK. An object with a 64-bit binary address (_OBJC_TAG_MASK) is of type tagged pointer if the highest bit in the 64-bit data is 1. Here is an example of string initialization
NSString *str = [NSString stringWithFormat:@"hello"];
NSLog(@"str:%p-%ld-%@",str,CFGetRetainCount((CFTypeRef)str),str.class);
NSString *str2 = @"Helloword, quick to learn, not impatient";
NSLog(@"str2:%p-%ld-%@",str2,CFGetRetainCount((CFTypeRef)str2),str2.class);
NSString *str3 = [[NSString alloc]initWithString:str2];
NSLog(@"str3:%p-%ld-%@",str3,CFGetRetainCount((CFTypeRef)str3),str3.class);
NSString *str4 = [NSString stringWithString:str2];
NSLog(@"str4:%p-%ld-%@",str4,CFGetRetainCount((CFTypeRef)str4),str4.class);
NSString *str5 = [[NSString alloc]initWithFormat:@"he%@"The @"llo"];
NSLog(@"str5:%p-%ld-%@",str5,CFGetRetainCount((CFTypeRef)str5),str5.class);
NSString *str6 = [[NSString alloc]initWithFormat:@"he%@"The @"Llo, quick to learn, slow to learn."];
NSLog(@"str6:%p-%ld-%@",str6,CFGetRetainCount((CFTypeRef)str6),str6.class);
Copy the code
Output:
str:0xa00006f6c6c65685-9223372036854775807-NSTaggedPointerString
str2:0x1085cf0a0-1152921504606846975-__NSCFConstantString
str3:0x1085cf0a0-1152921504606846975-__NSCFConstantString
str4:0x1085cf0a0-1152921504606846975-__NSCFConstantString
str5:0xa00006f6c6c65685-9223372036854775807-NSTaggedPointerString
str6:0x600001759c40-1-__NSCFString
Copy the code
Initialize a string using stringWithFormat, string direct assignment, initWithString, stringWithString, initWithFormat, and print a pointer, reference count, and string type
NSCFConstantString
The reference count of the string type is 1152921504606846975, and the pointer is 0x1085CF0A0, indicating that NSCFConstantString is oneString constant
After creation, the object cannot be released, the same content__NSCFConstantString
Objects have the same address, which means that a constant string object is a singletonNSCFString
and__NSCFConstantString
Different,_NSCFString
Object is inThe runtime
A kind of creationNSString
Subclass, it is not a string constant, so and otherobject
The same is obtained when it is created1
Reference count ofNSTaggedPointerString
Similar to NSCFConstantString, its reference count is 9223372036854775807, which is also very largeA singleton constant that cannot be freed
And if the value is the same, the pointer is the same, except that NSTaggedPointerString is a special pointer whose valueStored in the
It’sPointer to the address
In the.- Only use
stringWithFormat
andinitWithFormat
Initializes the string object and the string length is in10 the following
Is theTaggedPointer
Type, or NSCFString otherwise
Conclusion:
taggedpointer
OBJC_TAG_NSString, OBJC_TAG_NSNumber, OBJC_TAG_NSIndexPath, etc. TaggedPointer can be identifiedSign a
I’m not going to go into the details here.- Tagged Pointer follows the previous explorationNature of objectJust like in the article, there’s a property in the object
isa
Apple has optimized ISA to store not only associated objects but also other values, such as object reference counts and so onThe first flag bit
To determine whether ISA isnonpointer
Type, 0 means pure pointer, 1 means isa contains class information, objects, reference counts, etc.,For one
[0].extra_rc
: indicates the objectReferential count
For example, if the object’s reference count is 10, then extra_rc is 9, and extra_rc takeseight
[57] 64
Object reference count
So I’m going to write a demo, where multiple threads are reading and writing NSString.
self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
// Multithreading example 1
for (int i = 0; i<1000000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"hello"];
NSLog(@"% @",self.nameStr);
});
}
// Multithreading example 2
for (int i = 0; i<10000000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"Hello_ Learn in harmony without being impatient"];
NSLog(@"% @",self.nameStr);
});
}
Copy the code
Analysis: In example 1, nameStr is a TaggedPointer type, while in Example 2, nameStr is a NSCFString type. The NSCFString type is very similar to the ordinary object type. Why is the memory read and write safe in example 1 and not in example 2? First of all, memory reads and writes are the retain and release of the new value, so let’s focus on these two functions.
objc_retain
objc_retain(id obj)
{
if (obj->isTaggedPointerOrNil()) return obj;
return obj->retain();
}
Copy the code
Check whether the object is of type TaggedPointer. If it is returned directly, it does not retain the new value or release the old value, so its memory read and write is safe and efficient. If not TaggedPointer then follow the source code and find the rootRetain function.
rootRetain
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
oldisa = LoadExclusive(&isa.bits);
/ /... Omit some interfering code
do {
transcribeToSideTable = false;
newisa = oldisa;
// If it is pure ISA, the reference count table in sideTable +1,
if(slowpath(! newisa.nonpointer)) { ClearExclusive(&isa.bits);if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain(sideTableLocked);
}
// If ISA is destructing
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil;
} else {
return (id)this;
}
}
uintptr_t carry;
Extra_rc ++ in isa
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//extra_rc takes [57-64] 8 bits, or 2 to the eighth for 256 bits
// If extra_rc is full (i.e., greater than 256), take half of it and store it in the reference count table in sideTable.
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if(variant ! = RRVariant::Full) { ClearExclusive(&isa.bits);return rootRetain_overflow(tryRetain);
}
// Keep half the reference count and be ready to copy the other half to the hash table.
if(! tryRetain && ! sideTableLocked) sidetable_lock(); sideTableLocked =true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true; }}while(slowpath(! StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));/ /.. omit
return (id)this;
}
Copy the code
- if
Isa isa pure pointer
, then the reference count is storedsidetable
In theReference counter table
In the - Nature of objectWe explored isa if
Not pure pointer
Flag bits 57th to 64th store the reference count of the objectextra_rc
That is, extra_RC stores at most 2 to the eighth total256 reference counts
- if
extra_rc
When it’s full, it’s greater than 256, so it’sOut of the half
depositsidetable
In theIn the reference count table
SideTable
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
/ /...
}
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}
class StripedMap {#ifTARGET_OS_IPHONE && ! TARGET_OS_SIMULATOR enum { StripeCount =8 };
#else
enum { StripeCount = 64 };
#endif
/ /...
}
Copy the code
The SideTable hash table contains a lock slock, a reference count table (Refcnts), a weak_table (Weak_table), and a StripedMap hash table (StripedMap). 64 in other environments. In addition to being stored in the EXTRA_RC of ISA, object reference counts can also be stored in the reference count table of SideTable.
lldb
Break point look at thatextra_rc
Verify that reference counts are storedobjectobj
Initialization reference count is 1, retain reference count +1, and the total reference count should be 2.obj
isaMoves to the right of 57
Bits are extra_rc, extra_rc has a value of 1, and the reference count equalsextra_rc
Plus 1.
A weak reference table
There are two tables in the hash table, one reference counter table and we already know what’s going on, so let’s look at the weak reference table that’s left. Write a demo, create a new NSObject object, weakly reference this object, as follows.
NSObject *obj = [[NSObject alloc] init];
__weak typeof(obj) weakobj=obj;
NSLog(@"% @",weakobj);
Copy the code
The breakpoint is NSLog to look at the assemblyWeak reference to an object will call objc_initWeak to initialize the object.
id objc_initWeak(id *location, id newObj)
{
if(! newObj) { *location = nil;return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
Copy the code
LLDB breakpoint
obj
The pointer andnewObj
Pointer to the same objectnewObj
Is the object that needs a weak reference, andlocation
It should beA pointer to weak references
.storeWeak
Mark is the key function that stores weak references, passing three template variables: there is no old object DontHaveOld= False, there is a new object DoHaveNew= True, in Dealloc you can crash DoCrashIfDeallocating= True, and two actual parametersA pointer to weak references
andThe object that needs a weak reference
.
storeWeak
static id
storeWeak(id *location, objc_object *newObj)
{
HaveNew =true haveOld=false
HaveNew =false,haveOld=true
ASSERT(haveOld || haveNew);
if(! haveNew) ASSERT(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; retry:// If the object is freed, haveOld=true, the weak-reference pointer points to the value that needs to be freed
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
/* The weak reference table */ is used in the SideTable of the weak reference table. The weak reference table */ is used in the SideTable of the weak reference table
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
if(haveOld && *location ! = oldObj) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
if (haveNew && newObj) {
// Get the class of the object
Class cls = newObj->getIsa();
// If the class is not initialized
if(cls ! = previouslyInitializedClass && ! ((objc_class *)cls)->isInitialized()) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); class_initialize(cls, (id)newObj); previouslyInitializedClass = cls; goto retry; }}// When the object is released, the corresponding object and pointer in the weak reference table are deleted
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Weak pointer is added to the weak reference table
if (haveNew) {
/* Weak_register_no_lock parameter: &newTable-> Weak_table: the weak_table address is removed from the SideTable. The pointer to weak reference CrashIfDeallocating: Dealloc releases the Crash flag bit */
newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location,crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
//newObj is valued and not of type TaggedPointer. Set the weak reference flag bit in the refCount table
if(! newObj->isTaggedPointerOrNil()) { newObj->setWeaklyReferenced_nolock(); }// Do not set *location anywhere else
*location = (id)newObj;
}
else {
// No new value, no storage changes
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
}
Copy the code
Location is the weak reference pointer, newObj is the weak reference object, and the return value of this function is the weak reference object. When a weak reference object is used, newObj is used to find the corresponding hash table SideTable(hash table is a hash table, the real machine has 8 hash tables, other environment has 64 hash tables). Weak_register_no_lock = weak_register_no_lock = weak_register_no_lock = weak_register_no_lock = weak_register_no_lock = weak_register_no_lock = weak_register_no_lock = weak_register_no_lock
weak_register_no_lock
/* Weak_table: weak reference table of the object Referent_id: weak reference object referrer_id: weak reference pointer of the object */
id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
if (referent->isTaggedPointerOrNil()) return referent_id;
/ /... omit
weak_entry_t *entry;
// Find the weak reference array entry for the corresponding object in the weak reference table
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// If there is an array of weak references, add the weak reference pointer to the array
append_referrer(entry, referrer);
}
else {
// If the weak-reference array does not exist, create the array, add the weak-reference object and the weak-reference pointer to the array, and add the array to the weak-reference table
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
Copy the code
Analysis: First, find the weak reference array entry corresponding to the object in the weak reference table. If the entry is empty, create the array entry, add the weak reference object and weak reference pointer to the array, and add the array to the weak reference table. If entry is not empty, a weak reference pointer is added to the array.
Add weak variable to weak reference table
- Weak, the underlying call
objc_initWeak
To passWeak reference pointer
And the need toWeakly referenced objects
As an argument, and then callstoreWeak
storeWeak
Function to find the corresponding object in the hash tableSideTable
(There are two tables, reference counting table and weak reference table), from SideTableA weak reference table
Address, callweak_register_no_lock
Function to add a weak reference variable.weak_register_no_lock
Function, first find the corresponding object in the weak reference tableWeakly referenced array
Entry, if there is a weak reference array, the weak reference pointerAdd to this array
If the weak reference array does not exist, thenCreate this array
, add the weak-reference object and weak-reference pointer to the array, and add the array to the weak-reference table
Once an object is freed, how can a weak reference object be freed?
NSObject *obj = [[NSObject alloc] init];
__weak typeof(obj) weakobj=obj;
obj=nil;
NSLog(@"% @",weakobj);
Copy the code
Let’s make a breakpoint at NSLog and look at assemblyAfter the weak-referenced object is released, the objc_destroyWeak function is called
void
objc_destroyWeak(id *location){(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
Copy the code
The storeWeak function is also called after the weak-referenced object is freed, which passes three template variables: There is an old object DontHaveOld=true, no new object DoHaveNew=false, no crash DoCrashIfDeallocating=false in Dealloc, and a pointer and nil that are weak references to two actual parameters, StoreWeak function has been analyzed above, and the annotation also explains in detail that if the weak reference object is released, it will first obtain the object pointing to according to the pointer of the weak reference, and then obtain the corresponding weak reference table of the object, and call Weak_unregister_no_lock to subtract the weak reference object.
weak_unregister_no_lock
voidweak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if(! referent)return; / Find the weak reference array entry for the corresponding object in the weak reference tableif ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);
bool empty = true;
if(entry->out_of_line() && entry->num_refs ! =0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break; }}}if (empty) {
// Remove the weak reference array from the weak reference tableweak_entry_remove(weak_table, entry); }}}Copy the code
Weak_unregister_no_lock is actually the weak_register_NO_lock function added to weak_register_no_lock object in the weak reference table for subtraction, until the weak reference array in the weak reference table has no value to remove the array from the weak reference table.
conclusion
Weak references resolve circular references because the object’s reference count is not increased, and its memory management is managed through weak reference tables.