preface
The loading principle of OC Class (below) of iOS underlying principle has analyzed the process of classification loading, as well as the situation of main class and classification with loading. This paper will analyze the special classification – class extension and access of classification attributes – associated objects.
The preparatory work
- Objc4-818.2 – the source code.
A:list_array_tt
Data structure analysis
Before we get started on today’s main topic, just a little bit.
Based on the previous analysis, we know that the underlying data structure of the method list is method_array_t. Let’s explore this data structure and the origin of the functions used in LLDB debugging.
Method_array_t inherits from the template class list_array_tt.
// Element = method_t, List = method_list_t, Ptr = method_list_t_authed_ptr
template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
struct array_t {
uint32_t count;
Ptr<List> lists[0];
static size_t byteSize(uint32_t count) {
return sizeof(array_t) + count*sizeof(lists[0]);
}
size_t byteSize(a) {
return byteSize(count); }}; .public:
union { / / the mutex
Ptr<List> list;
uintptr_t arrayAndFlag;
};
bool hasArray(a) const {
return arrayAndFlag & 1;
}
array_t *array(a) const {
return (array_t *)(arrayAndFlag & ~1);
}
void setArray(array_t *array) {
arrayAndFlag = (uintptr_t)array | 1; }...uint32_t count(a) const {
uint32_t result = 0;
for (auto lists = beginLists(), end = endLists(a); lists ! = end; ++lists) { result += (*lists)->count; }returnresult; }...const Ptr<List>* beginLists(a) const {
if (hasArray()) {
return array()->lists;
} else {
return&list; }}const Ptr<List>* endLists(a) const {
if (hasArray()) {
return array()->lists + array()->count;
} else if (list) {
return &list + 1;
} else {
return&list; }}// attachLists function is also here, omitted here, interested friends can look at the article
void attachLists(List* const * addedLists, uint32_t addedCount) {... }Copy the code
list_array_tt
Classes can be generated by passing in templatesmethod_array_t
.property_array_t
.protocol_array_t
And so on.
The template Element = method_t, List = method_list_t, and Ptr = method_list_t_authed_ptr.
Method_list_t inherits from entsize_list_tt and can generate method_list_t, property_list_t, protocol_list_t, etc., depending on the template passed in.
- According to the
get(i)
Function to obtaini
The location of themethod_t *
.
Method_t ::pointer_modifier implementation.
struct method_t {.struct pointer_modifier {
template <typename ListType>
static method_t *modify(const ListType &list, method_t *ptr) {
if (list.flags() & smallMethodListFlag)
return (method_t((*)uintptr_t)ptr | 1);
returnptr; }}; . }Copy the code
PointerModifierNop implementation.
struct PointerModifierNop {
template <typename ListType, typename T>
static T *modify(__unused const ListType &list, T *ptr) { returnptr; }};Copy the code
Let’s look at the data structure for method_T.
struct method_t {
static const uint32_t smallMethodListFlag = 0x80000000;
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
private:
// Arm64 architecture (including M1 iMac), small end mode
bool isSmall(a) const {
return ((uintptr_t)this & 1) = =1;
}
// small stores three relative offsets
struct small {
RelativePointer<const void *> name;
RelativePointer<const char *> types;
RelativePointer<IMP> imp;
bool inSharedCache(a) const {
return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
objc::inSharedCache((uintptr_t)this)); }};// Get small information
small &small(a) const {
ASSERT(isSmall());
return *(struct small *)((uintptr_t)this& ~ (uintptr_t)1); }...// Get big information
big &big(a) const {
ASSERT(!isSmall());
return *(struct big *)this;
}
SEL name(a) const {
if (isSmall()) {
return (small().inSharedCache()? (SEL)small().name.get()
: *(SEL *)small().name.get());
} else {
return big().name; }}... }Copy the code
method_t
It has the ones we use all the timebig()
.small()
.name()
Etc. Function.
The list_array_tt data structure and the functions we use to debug the LLDB are described here.
Two: class extension
1.1: category
VS extension
category
(Category/Category)
- Used specifically to add new methods to a class.
- You cannot add a member variable to a class, and even if you do, you cannot fetch it.
- Can be achieved by
runtime
Add attributes to categories. - In the classification
@property
Defined properties, only properties will be generatedgetter, setter
Method declaration that does not generate method implementations and underlined member variables.
extension
(Class extension)
- It’s called a special category, also known as an anonymous category.
- You can add member variables and attributes to a class, but they are private.
- You can add methods to a class, which are also private.
1.2: extension
format
1.2.1: .m
In the file
For us iOS developers, the most common extension is defined in a.m file:
⚠️ Extension must be written after the class declaration and before the implementation. The declaration part of the.h file is also expanded into the.m file at compile time.
1.2.2: Create files separately
You can also create a separate extension file:
This will only generate a.h file, because the implementation of the extension is also required in the.m file of the class, and the.h file must be imported into the.m file of the class, otherwise the class will not be able to access the properties and member variables defined in extension (and will not generate getters and setters for the properties). The extension will not be incorporated into the class’s RO when compiled (the method is implemented in the class, so it does not matter if it is not imported).
This is just a separate header for extension from the.m file.
1.3: extension
Underlying principle analysis
Create the XJBoy class in main.m and add Extension.
@interface XJBoy : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
- (void)instanceMethod;
+ (void)classMethod;
@end
@interface XJBoy (a)
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation XJBoy
- (void)instanceMethod{
NSLog(@"%s",__func__);
}
+ (void)classMethod{
NSLog(@"%s",__func__);
}
- (void)ext_instanceMethod{
NSLog(@"%s",__func__);
}
+ (void)ext_classMethod{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
XJPerson * person = [XJPerson alloc];
[person saySomething];
}
return 0;
}
Copy the code
Explore using clang to generate main. CPP files with main.m.
XJBoy class implementation:
XJBoy member variables, attributes, methods list:
You can see that the extension data is stored in the class at compile time.
Add +load to XJBoy and add breakpoint debugging to realizeClassWithoutSwift:
From the debugging results, you can see that the extension data is also in the RO, which is determined at compile time.
-
Category affects class loading because it has its own.m file and can implement its own +load method.
-
Extension does not affect the loading of the class because it does not have its own.m file, and no matter how many extensions there are, all implementations are in the.m file of the class.
Three: Associated objects
After adding attributes to a category, the corresponding warning will be reported:
- Prompts developers to implement attributes themselves
getter & setter
Methods.
This is because classes that declare properties with @property only generate declarations of getter & setter methods, not the implementation of the method and underlined (_) member variables. Getter & setter methods store and value a member variable by its offset value. There is no member variable, so there is no implementation of getter & setter methods.
In this case, you need to add getter & setter methods to the class properties via the associated object.
- (void)setXja_name:(NSString *)xja_name
{
objc_setAssociatedObject(self, "xja_name", xja_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)xja_name
{
return objc_getAssociatedObject(self, "xja_name");
}
Copy the code
3.1: Associated objectssetter
3.1.1: objc_setAssociatedObject
Objc_setAssociatedObject objc_setAssociatedObject
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
Copy the code
- Directly called
_object_set_associative_reference
Function.
In objC4-781 and earlier versions, the _base_objc_setAssociatedObject function is obtained by setassocook.get () and then the _object_set_associative_reference function is called.
3.1.2: _object_set_associative_reference
// The associated object store self xja_name value OBJC_ASSOCIATION_COPY_NONATOMIC
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
if(! object && ! value)return;
// Whether to disable associated objects
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
// Package object into the unified type DisguisedPtr
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// Wrap {policy, value} to ObjcAssociation
ObjcAssociation association{policy, value};
// Operate on value according to policy
// retain or copy value
association.acquireValue(a);bool isFirstAssociation = false;
{
// The manager is not a singleton. The constructor is called to create the manager, which is locked internally
AssociationsManager manager;
// AssociationsHashMap is a singleton. The AssociationsHashMap is obtained from the AssociationsManager
// It is initialized at 'map_images'.
AssociationsHashMap &associations(manager.get());
if (value) {/ / a value
// Find or create an iterator in the global associated object hash table for inserting object
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {// Second is true the first time the bucket is inserted
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
// Get the hash table associated with 'object'
auto &refs = refs_result.first->second;
// Insert association into the object table
// Insert without value, insert without value
auto result = refs.try_emplace(key, std::move(association));
if(! result.second) {// If second is false, LookupBucketFor is found and has a value
// result.first->second
// Replace the table with new. Association changes to the old value
association.swap(result.first->second); }}else {// There is no value.
// Find the iterator of the hash table associated with object
auto refs_it = associations.find(disguised);
// Not the end tag
if(refs_it ! = associations.end()) {
// Get the hash table associated with object
auto &refs = refs_it->second;
// Find the association iterator corresponding to the key
auto it = refs.find(key);
// Not the end tag
if(it ! = refs.end()) {
// it->second = association
// Change table value to nil, association to previous value
association.swap(it->second);
// Erases the association iterator corresponding to the key
refs.erase(it);
if (refs.size() = =0) {
// If the hash table corresponding to 'object' is empty, erase this table
associations.erase(refs_it); }}}}// Out of scope, the destructor of AssociationsManager is called
// Unlock
}
// Only the first time the object's ISA is marked to see if it has an associated object
if (isFirstAssociation)
object->setHasAssociatedObjects(a);// Release the old values swapped in 'association'
association.releaseHeldValue(a); }Copy the code
-
Return the object and value if both are null.
-
Object’s class disallows associated objects.
-
The object is uniformly wrapped as the DisguisedPtr type.
-
Wrap policy and value into object Society, and retain or copy values based on policy.
-
AssociationsManager Creates the AssociationsManager by calling the constructor of the AssociationsManager class. The constructor and destructor do nothing but lock and unlock internally. Manager is not a singleton (more on that later).
-
AssociationsHashMap & Associations (manager.get()) Obtain the total hash table of AssociationsHashMap from the Manager. This table is a singleton that is initialized in the MAP_images process (more on that later).
-
Value:
- call
associations.try_emplace
Function to find or create inserts in the global associated object total hash tableobject
Iterator corresponding to the hash table of the associated object, returns the iterator and whether the associated object was added for the first timepair
, i.e.,refs_result
. - If the current
object
Insert into the hash table for the first time,refs_result.second
fortrue
That will beisFirstAssociation
Marked astrue
. refs_result.first->second
To obtainobject
The associated object hash table ofrefs
.- call
refs.try_emplace
willpolicy
andvalue
Packaged into theassociation
Insert the currentobject
Returns the current hash table of the associated objectobject
The iterator of the associated object hash table and whether it already existspair
, i.e.,result
. - If it was there before,
result.second
forfalse
Through theresult.first->second
Get beforeObjcAssociation
Class object, calledassociation.swap
That will bepolicy
andvalue
The new values are stored and the old values are swapped out in preparation for subsequent release.
- call
-
If value is null:
- call
associations.find
Function looks in the hash table of global associated objectsobject
Iterator to the corresponding associated object hash tablerefs_it
. - The returned iterator is not
end
Flag, then passrefs_it->second
To obtainobject
Hash table of the corresponding associated objectrefs
. - call
refs.find
inobject
Lookup in the hash table of the corresponding associated objectkey
The correspondingObjcAssociation
Class iteratorit
. - The returned iterator is not
end
Mark, passit->second
Get beforeObjcAssociation
Class object, calledassociation.swap
, store {policy, nil}, and swap old values toassociation
To prepare for subsequent release. - call
refs.erase
eraseobject
The corresponding associated object in the hash tablekey
The correspondingObjcAssociation
Class iterator. - if
object
If the hash table of the corresponding associated object is empty, erase the table.
- call
-
Set whether the ISA of object has associated objects according to isFirstAssociation.
-
Release old values swapped in association.
3.1.3: lldb
To viewdisguised
andassociation
3.1.4: acquireValue
inline void acquireValue(a) {
if (_value) {
// 0xFF = 0B 1111 1111
switch (_policy & 0xFF) {
// 0B 0000 0001 & 0B 1111 1111 = 0B 0000 0001
case OBJC_ASSOCIATION_SETTER_RETAIN:
_value = objc_retain(_value);
break;
// 0B 0000 0011 & 0B 1111 1111 = 0B 0000 0011
case OBJC_ASSOCIATION_SETTER_COPY:
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
break; }}}Copy the code
- According to the
policy
The value ofretain
orcopy
Operation.
3.1.5: AssociationsManager
withAssociationsHashMap
// The hash table type associated with the object
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
// The total hash table type of the associated object. The two-layer hash table structure contains object and ObjectAssociationMap
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
// Total hash table of associated objects
// Static variable, declared in AssociationsManager, can only be called by an instance of AssociationsManager class
// is equivalent to a singleton
static Storage _mapStorage;
public:
// constructor, lock
AssociationsManager() { AssociationsManagerLock.lock(a); }// destructor to unlock
~AssociationsManager() { AssociationsManagerLock.unlock(a); }// Get the total hash table of the associated object, global singleton
// _mapStorage is a static variable, equivalent to a singleton
AssociationsHashMap &get(a) {
return _mapStorage.get(a); }// static represents the class method, called in the map_images process
static void init(a) {
_mapStorage.init();
}
};
AssociationsManager::Storage AssociationsManager::_mapStorage;
Copy the code
_mapStorage
Is a static variable declared inAssociationsManager
Class, can onlyAssociationsManager
Class, equivalent to a singleton.AssociationsManager
Constructors and destructors perform add unlock operations.AssociationsHashMap
Class associated object total hash table, throughAssociationsManager
Of the classget
Method.- The associated object total hash table is in
AssociationsManager
Class method of classinit
Is initialized in,init
inmap_images
Process invocation.
3.1.5.1: AssociationsManager
Verification of structure and destruction imitation
- Out of scope, the destructor is called.
To validate c++ constructors and destructors, change the.m file to.mm or change the Type of the file to Objc++:
3.1.5.2: AssociationsHashMap
Initialization Process
Add a breakpoint to the AssociationsManager init function to view the function call stack:
Initialization process of the total hash table of associated objects:
map_images -> map_images_nolock -> arr_init -> _objc_associations_init -> AssociationsManager::init() -> _mapStorage.init()
.
Map_images:
Map_images_nolock:
Arr_init:
_objc_associations_init:
AssociationsManager: : init () :
3.1.5.3: AssociationsHashMap
Singleton validation
Instantiate multiple instances, LLDB output address verification.
AssociationsManager
Not a singleton,AssociationsHashMap
Is a singleton (global associated object total hash table, all objects associated object hash table exists in).
Validation requires temporary annotation of AssociationsManager class construction and destructor for add unlock operations, otherwise deadlock will occur:
3.1.6: try_emplace
// Backup (backup) Key = backup (object) Args = ObjectAssociationMap{}
Memory hash table key = key Args = association
template <typename. Ts>std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
// Find the corresponding Bucket according to the key
// TheBucket pointer is passed, which changes as the value inside changes
if (LookupBucketFor(Key, TheBucket)) // The system already exists
// Make the pair with make_pair
/ / second to false
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// If there is no query to insert data into bucket, return bucket
// The first of TheBucket is the key, and the second is Args
// The Args can be obtained by XXX. First ->second
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...) ;// Make the pair with make_pair
/ / second to true
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
Copy the code
try_emplace
invalue
If there is a value, it will be called twice (the total hash table of the associated object is a two-layer hash table structure) :- The first is the global associated object total hash table call,
key
isobject
Packaged into theDisguisedPtr
Class object,Args
isDisguisedPtr
Hash table of associated objects corresponding to class objects. - The second,
DisguisedPtr
Class object corresponding to the associated object hash table call,key
Is associated with the objectkey
.Args
Is wrapped with {policy, value}ObjcAssociation
Class objects.
- The first is the global associated object total hash table call,
- call
LookupBucketFor
To find thekey
The correspondingTheBucket
, the first time is the hash table of the associated object corresponding to the object, and the second time is the associated objectkey
The correspondingObjcAssociation
Class object (all wrapped accordingly). - If found, the iterator and
false
A team (make_pair
Return). - Call if not found
InsertIntoBucket
willkey & Args
insertTheBucket
And generate iterators withtrue
Group returns (if there is an old value, it is returned without substitution, so that the old value can be released after a later swap).
Try_emplace LLDB debug try_emplace LLDB debug try_emplace LLDB debug try_emplace
Object wrapped into the DisguisedPtr class object corresponding to the associated object hash table calls try_emplace’s LLDB debugging (the second time) :
3.1.6.1: LookupBucketFor
LookupBucketFor has two different parameters in the source code, one with const modifier and one without. The buckett-type TheBucket passed by try_emplace has no const modifier, so let’s look at it first:
It is clear that a BucketT function without const calls a function with const. The BucketT parameter is also passed as a pointer.
- According to the
key
To obtainbucket
The address toConstFoundBucket
The pointer. - will
ConstFoundBucket
The address of the pointer assigned to&FoundBucket
The pointer looks like thisFoundBucket
The stored data is updated in real time. - Query to
Result
returntrue
.
Next look at LookupBucketFor with const BucketT type parameter:
template<typename LookupKeyT>
// BucketT is decorated with const
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
// Get buckets first address
const BucketT *BucketsPtr = getBuckets(a);// Get buckets
const unsigned NumBuckets = getNumBuckets(a);// If buckets is 0, return false and FoundBucket = nullptr
if (NumBuckets == 0) {
FoundBucket = nullptr;
return false;
}
// FoundTombstone - Keep track of whether we find a tombstone while probing.
const BucketT *FoundTombstone = nullptr;
// Get the key of the empty bucket
const KeyT EmptyKey = getEmptyKey(a);// Get the tombstone key
const KeyT TombstoneKey = getTombstoneKey(a);assert(! KeyInfoT::isEqual(Val, EmptyKey) && ! KeyInfoT::isEqual(Val, TombstoneKey) &&
"Empty/Tombstone value shouldn't be inserted into map!");
// Compute the hash subscript
// The hash value of key & (capacity -1) is similar to the mask of cache (find index)
unsigned BucketNo = getHashValue(Val) & (NumBuckets- 1);
unsigned ProbeAmt = 1;
// Similar to cache insertion
while (true) {
ThisBucket = first address + number of bytes
// Find the corresponding bucket according to the subscript
const BucketT *ThisBucket = BucketsPtr + BucketNo;
// Found Val's bucket? If so, return it.
// LLVM_LIKELY 就是 fastpath
// If a bucket is found, the bucket already exists.
if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
FoundBucket = ThisBucket;
return true;
}
// If we found an empty bucket, the key doesn't exist in the set.
// Insert it and return the default value.
// An empty bucket can be inserted into the empty bucket
if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
// If we've already seen a tombstone while probing, fill it in instead
// of the empty bucket we eventually probed to.
// If we already see the tombstone while exploring, please fill it instead of the empty bucket we eventually detected
FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
return false;
}
// If this is a tombstone, remember it. If Val ends up not in the map, we
// prefer to return it than something that would require more probing.
// Ditto for zero values.
if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) && ! FoundTombstone) FoundTombstone = ThisBucket;// Remember the first tombstone found.
if (ValueInfoT::isPurgeable(ThisBucket->getSecond() &&! FoundTombstone) FoundTombstone = ThisBucket;// Otherwise, it's a hash collision or a tombstone, continue quadratic
// probing.
if (ProbeAmt > NumBuckets) {
FatalCorruptHashTables(BucketsPtr, NumBuckets);
}
BucketNo += ProbeAmt++;
// compute the subscript
BucketNo &= (NumBuckets- 1); }}Copy the code
- The logic and method are cached here
cache
Very similar, first of allHash of key & (capacity -1)
Compute the hash subscript. - And then look it up by hash subscript
bucket
If there is a value, return itbucket
Pointer and returntrue
If not, return emptybucket
orGrave markers
And returnfalse
. - Hash conflict, hash again, recalculate the hash index.
LLDB debugging verification:
Set FoundBucket = nullptr, return false, and then go through the InsertIntoBucket process.
The second time, after the initial space expansion or expansion, an empty bucket returns ready to insert data:
3.1.6.2: InsertIntoBucket
If no corresponding bucket is found, the insert process is entered.
Btemplate <typename KeyArg, typename. ValueArgs>BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key, ValueArgs &&... Values) {
// Get an empty bucket
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
// the first value of TheBucket is Key and the second value is Values
XXX. First ->second
// values is the hash table associated with the object for the first time,
// The second time is the 'objCassociety' class object wrapped with {policy, value}
TheBucket->getFirst() = std::forward<KeyArg>(Key); : :new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...) ;return TheBucket;
}
Copy the code
- Get empty
bucket
If the capacity is not enough, expand it. - Set up the
bucket
The corresponding value,first
forKey
.second
forValues
.- For the first time,
Key
isobject
Packaged into theDisguisedPtr
Class object,Values
isDisguisedPtr
Hash table of associated objects corresponding to class objects. - The second time
Key
Is associated with the objectkey
.Values
Is wrapped with {policy, value}ObjcAssociation
Class objects.
- For the first time,
LLDB debugging verification:
The first assignment:
The second assignment:
3.1.6.2.1: InsertIntoBucketImpl
template <typename LookupKeyT>
BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
BucketT *TheBucket) {
// NewNumEntries indicate that a bucket will be inserted
unsigned NewNumEntries = getNumEntries() + 1;
// Get the total number of buckets
unsigned NumBuckets = getNumBuckets(a);// If the number to be inserted is greater than or equal to 3/4 of the total number, double the capacity
if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
this->grow(NumBuckets * 2);// Bucket = 0; // Bucket = 0; // Bucket = 0
// Retrieve an empty bucket
LookupBucketFor(Lookup, TheBucket);
NumBuckets = getNumBuckets(a); }else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
NumBuckets/8)) {
// When the bucket marked with a tombstone reaches 7/8, the new bucket is re-opened and the non-tombstone marks are replaced with the new bucket
this->grow(NumBuckets);
LookupBucketFor(Lookup, TheBucket);
}
ASSERT(TheBucket);
if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
// Replacing an empty bucket.
// Find an Empty bucket that is occupied by +1 and return that bucket
// The number of buckets currently occupied + 1
incrementNumEntries(a); }else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
// Replacing a tombstone.
// Find a bucket with a tombstone marker, the number of occupied +1, tombstone marker -1, and return the bucket
incrementNumEntries(a);decrementNumTombstones(a); }else {
// we should be purging a zero. No accounting changes.
ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond()));
TheBucket->getSecond(). ~ValueT(a); }return TheBucket;
}
Copy the code
- Call in case of insufficient capacity
grow
Function to open or expand, the first open capacity is4
, capacity expansion can be divided into two situations:- Load factor up to
Three quarters of
When,two
Times the capacity. - Occupied plus the number of tombstone markers
7/8
, re-open the space and replace the non-tombstone markers with new onesbucket
In the.
- Load factor up to
- call
LookupBucketFor
To find thebucket
To obtain theempty bucket
The number of cases occupied+ 1
, return thisbucket
.A bucket marked with a tombstone
The number of cases occupied+ 1
, the number of tombstone markers- 1
, return thisbucket
.
LLDB debugging verification:
3.1.6.2.2: grow
When running out of space, space creation and expansion enter the grow function.
Start with the grow function of the DenseMapBase class.
void grow(unsigned AtLeast) {
static_cast<DerivedT *>(this) - >grow(AtLeast);
}
Copy the code
Debugging continues with the DenseMap class’s grow function.
void grow(unsigned AtLeast) {
unsigned OldNumBuckets = NumBuckets;
BucketT *OldBuckets = Buckets;
// #define MIN_BUCKETS 4
allocateBuckets(std::max<unsigned>(MIN_BUCKETS, static_cast<unsigned> (NextPowerOf2(AtLeast- 1))));
ASSERT(Buckets);
if(! OldBuckets) {this->BaseT::initEmpty(a);return;
}
this->moveFromOldBuckets(OldBuckets, OldBuckets+OldNumBuckets);
// Free the old table.
operator delete(OldBuckets);
}
Copy the code
- through
max
Function to takeMIN_BUCKETS
andNextPowerOf2(AtLeast-1)
As the maximum value ofbucket
The number of.MIN_BUCKETS
for4
The first development is4
. - call
allocateBuckets
Function opens the number calculated in the previous stepbucket
Type of memory space. OldBuckets
If it is empty, the traversal will bebucket
All initialized to nullbucket
(empty bucket
thefirst
for1
) and return.- call
moveFromOldBuckets
The function will be oldbuckets
Move to newbuckets
Phi, this sumcache
Expansion is different. - The release of the old
buckets
.
3.1.6.2.3: NextPowerOf2
/ / 32 bits
NextPowerOf2 - returns the NextPowerOf2 (32 bits)
/// strictly greater than A. Returns zero on overflow.
inline uint32_t NextPowerOf2(uint32_t A) {
A |= (A >> 1);
A |= (A >> 2);
A |= (A >> 4);
A |= (A >> 8);
A |= (A >> 16);
return A + 1;
}
/ / 64
NextPowerOf2 - returns the NextPowerOf2 (64 bits)
/// strictly greater than A. Returns zero on overflow.
inline uint64_t NextPowerOf2(uint64_t A) {
A |= (A >> 1);
A |= (A >> 2);
A |= (A >> 4);
A |= (A >> 8);
A |= (A >> 16);
A |= (A >> 32);
return A + 1;
}
Copy the code
A
Is an unsigned integer, first passed in- 1
.- 1
Converted to1
The binary rules are:1
The original code is0b0000000000000001
Covariates,0b1111111111111110
, add1
is0b1111111111111111
In order not to be so long, only to16
Bit representation), then proceedA |= (A >> n)
The operation, it turns out, is still equal to0b1111111111111111
And thenA+1
The overflow is equal to the0
.- The second one that came in was
7
Because the first open for4
), binary is0b0000000000000111
,A |= (A >> n)
Computation, better still0b0000000000000111
And thenA+1
Is equal to the8
.
LLDB debugging verification:
3.1.6.2.4: initEmpty
Debugging continues into initEmpty:
void initEmpty(a) {
setNumEntries(0);
setNumTombstones(0);
ASSERT((getNumBuckets() & (getNumBuckets(a)- 1)) = =0 &&
"# initial buckets must be a power of two!");
// Set an empty key
const KeyT EmptyKey = getEmptyKey(a);for (BucketT *B = getBuckets(), *E = getBucketsEnd(a); B ! = E; ++B) ::new (&B->getFirst()) KeyT(EmptyKey);
}
Copy the code
To continue debugging, enter getEmptyKey:
Start with llvm-densemap. h getEmptyKey:
static const KeyT getEmptyKey(a) {
static_assert(std::is_base_of<DenseMapBase, DerivedT>::value,
"Must pass the derived type to this template!");
return KeyInfoT::getEmptyKey(a); }Copy the code
Struct DenseMapInfo getEmptyKey = llvm-densemapinfo. h
static inline DisguisedPtr<T> getEmptyKey(a) {
return DisguisedPtr<T>((T*)(uintptr_t)- 1);
}
Copy the code
DisguisedPtr
You should be familiar with this,object
It’s also encapsulatedDisguisedPtr
Type.
template* * (typename T>
class DisguisedPtr {
uintptr_t value;
static uintptr_t disguise(T* ptr) {
return- (uintptr_t)ptr; }... }Copy the code
ptr
=(uintptr_t)-1
Pass by again-(uintptr_t)ptr
Calculation. Is equal to-(uintptr_t)((uintptr_t)-1)
=1
, which means emptybucket
thefirst
is1
.
3.1.6.2.5: moveFromOldBuckets
void moveFromOldBuckets(BucketT *OldBucketsBegin, BucketT *OldBucketsEnd) {
initEmpty(a);// Insert all the old elements.
const KeyT EmptyKey = getEmptyKey(a);const KeyT TombstoneKey = getTombstoneKey(a);for(BucketT *B = OldBucketsBegin, *E = OldBucketsEnd; B ! = E; ++B) {if (ValueInfoT::isPurgeable(B->getSecond())) {
// Free the value.
B->getSecond(). ~ValueT(a); }else if(! KeyInfoT::isEqual(B->getFirst(), EmptyKey) && ! KeyInfoT::isEqual(B->getFirst(), TombstoneKey)) {
// Insert the key/value into the new table.
BucketT *DestBucket;
bool FoundVal = LookupBucketFor(B->getFirst(), DestBucket);
(void)FoundVal; // silence warning.
ASSERT(! FoundVal &&"Key already in new map?");
// If a bucket's first is not EmptyKey && a new bucket is not TombstoneKey
DestBucket->getFirst() = std::move(B->getFirst()); : :new (&DestBucket->getSecond()) ValueT(std::move(B->getSecond()));
// Occupied number +1
incrementNumEntries(a);// Free the value.
B->getSecond(). ~ValueT(a); } B->getFirst(). ~KeyT();
}
}
Copy the code
-
Initialize the first of the new buckets as EmptyKey.
-
To walk through the OldBucket in OldBuckets:
OldBucket
thesecond
Can befree
:- directly
free
.
- directly
- Do not
free
:OldBucket
thefirst
Don’t forEmptyKey
And not forTombstoneKey
, then insert the new onebuckets
In the.- Occupied quantity
+ 1
. free OldBucket
thesecond
.
-
Free OldBucket’s first.
3.1.7 setHasAssociatedObjects
associations.try_emplace(disguised, ObjectAssociationMap{}) global associated object total hash table associations Try_emplace function is called to add the corresponding associated object hash table to the DisguisedPtr type object packaged by Object for the first time. The team (make_pair) returns the hash table of the corresponding associated object added and true. IsFirstAssociation is set to true. This is followed by whether the object has an associated object.
.if (isFirstAssociation)
object->setHasAssociatedObjects()
...
Copy the code
inline void
objc_object::setHasAssociatedObjects(a)
{
//Tagged Pointer returns directly
if (isTaggedPointer()) return;
// Pure pointer && has default release, retain, etc. && non-future class && non-metaclass
if (slowpath(!hasNonpointerIsa() && ISA() - >hasCustomRR() &&!ISA() - >isFuture() &&!ISA() - >isMetaClass()) {
// Get the _noteAssociatedObjects method
void(*setAssoc)(id, SEL) = (void(*)(id, SEL)) object_getMethodImplementation((id)thisThe @selector(_noteAssociatedObjects));
// Do not forward the message, that is, find the method.
if((IMP)setAssoc ! = _objc_msgForward) {/ / call _noteAssociatedObjects
(*setAssoc)((id)thisThe @selector(_noteAssociatedObjects)); }}// Set up a new ISA
isa_t newisa, oldisa = LoadExclusive(&isa.bits);
do {
newisa = oldisa;
// A pure pointer/already has an associated object tag
if(! newisa.nonpointer || newisa.has_assoc) {ClearExclusive(&isa.bits);
return;
}
// isa associated object flag
newisa.has_assoc = true;
} while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
}
Copy the code
isa
Pure pointer call_noteAssociatedObjects
Function, which the system determines whether it has been implemented.isa
Impure Pointers willhas_assoc
Set totrue
.
The _noteAssociatedObjects function system is not implemented and should be provided for implementation. But methods that begin with an _ should be private. We don’t normally use it.
3.1.8: Associated object setting process
-
Wrap Object into the DisguisedPtr class object.
-
Generate objcasSociety class objects with policies and values, and process (retain or copy) the values in the objcasSociety based on the policy.
-
Get AssociationsHashMap from AssociationsManager class.
-
Check that the associated object value to be set exists:
- 4.1. If yes, go to the insert process
- To obtain
DisguisedPtr
Hash table of the corresponding associated objectObjectAssociationMap
If not, insert emptyObjectAssociationMap
And marks the first time an associated object is added to the object (isFirstAssociation
). - The query
ObjectAssociationMap
If therekey
The correspondingObjcAssociation
, if no, insert, if yes, return, and2
In theObjcAssociation
Interactive data, number one6
Step to release.
- To obtain
- 4.2. Not present, insert null value (insert null value, equivalent to clear)
- To obtain
DisguisedPtr
Hash table of the corresponding associated objectObjectAssociationMap
. - To obtain
key
The correspondingObjcAssociation
. - Empty values are swapped in (that is, erased) and then erased
ObjcAssociation
. - if
DisguisedPtr
The correspondingObjectAssociationMap
If it’s empty, erase it.
- To obtain
- 4.1. If yes, go to the insert process
-
Set whether an object has an associated object based on isFirstAssociation.
-
Release the old values swapped out in 4.1.
3.2: Associated objectsgetter
The associated object values call objc_getAssociatedObject.
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
Copy the code
- Directly called
_object_get_associative_reference
Function.
3.2.1: _object_get_associative_reference
id
_object_get_associative_reference(id object, const void *key)
{
// Create an empty ObjcAssociation object
ObjcAssociation association{};
{
// AssociationsManager manager calls the constructor to lock
AssociationsManager manager;
// Get the hash table of the global associated object
AssociationsHashMap &associations(manager.get());
// Get the iterator for the hash table of the associated object
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
// Not the end tag
if(i ! = associations.end()) {
// Get the hash table of the associated object
ObjectAssociationMap &refs = i->second;
// Get the iterator of the 'objcassociety' class corresponding to the associated object 'key'
ObjectAssociationMap::iterator j = refs.find(key);
// Not the end tag
if(j ! = refs.end()) {
// Get an ObjcAssociation object
association = j->second;
// Retain value is required according to the policy
association.retainReturnedValue();
}
}
// Out of scope, the destructor of AssociationsManager is called
// Unlock
}
// Set value to autorelease as required by the policy
return association.autoreleaseReturnedValue(a); }Copy the code
- Gets the total hash table of global associated objects
associations
. - packaging
object
forDisguisedPtr
Type objects fromassociations
Gets an iterator for the hash table of the associated object corresponding to the object. - through
key
Getting associated Objectskey
The correspondingObjcAssociation
Class object iterator. - from
ObjcAssociation
Type objectvalue
To return.
3.2.2: find
iterator find(const_arg_type_t<KeyT> Val) {
BucketT *TheBucket;
if (LookupBucketFor(Val, TheBucket))
return makeIterator(TheBucket, getBucketsEnd(), true);
return end(a); }Copy the code
- call
LookupBucketFor
Function to find theTheBucket
, returns the correlation iterator if it exists, and returns the correlation iterator if it does notend
The tag.
LLDB debugging check:
3.2.3: Associated object value process
- According to the
AssociationsManager
Class to get the global associated object total hash tableAssociationsHashMap
. - Based on the current
object
encapsulatedDisguisedPtr
inAssociationsHashMap
To get the corresponding iterator. - This iterator is not
end
Tag to retrieve the hash table of an associated objectObjectAssociationMap
. - According to the
key
inObjectAssociationMap
To get the corresponding iterator. - This iterator is not
end
Tag, extract the correspondingObjcAssociation
({policy, value}
). - Process according to policy requirements
ObjcAssociation
In thevalue
And then return.
3.3: Associated objectsremove
Apple provides the objc_removeAssociatedObjects function to remove associated objects.
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object, /*deallocating*/false); }}Copy the code
- judge
object
Is there an associated object? If so, call_object_remove_assocations
The function removes the associated object.
3.3.1: hasAssociatedObjects
Determine whether an object has an associated object:
inline bool
objc_object::hasAssociatedObjects(a)
{
if (isTaggedPointer()) return true;
if (isa.nonpointer) return isa.has_assoc;
return true;
}
Copy the code
- The default return
true
.
3.3.2 rainfall distribution on 10-12:_object_remove_assocations
void
_object_remove_assocations(id object, bool deallocating)
{
ObjectAssociationMap refs{};
{
// AssociationsManager manager calls the AssociationsManager constructor and locks it
AssociationsManager manager;
// Get the hash table of the global associated object
AssociationsHashMap &associations(manager.get());
// Get the iterator for the hash table of the associated object
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
// Not the end tag
if(i ! = associations.end()) {
// Interact the object's associated object hash table data with refs
refs.swap(i->second);
// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
bool didReInsert = false;
if(! deallocating) {// The object is not freed
// Iterate over all iterators of the 'Object Society' class in the hash table of the corresponding associated object
for (auto &ref: refs) {
// ref.second是ObjcAssociation类型对象{policy, value}
if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
// Re-insert the associated object of OBJC_ASSOCIATION_SYSTEM_OBJECT
i->second.insert(ref);
didReInsert = true; }}}if(! didReInsert)// If there is nothing to reinsert, erase the iterator of the corresponding associated object hash table
associations.erase(i); }}// Associations to be released after the normal ones.
SmallVector<ObjcAssociation *, 4> laterRefs;
// release everything (outside of the lock).
// Iterate over all iterators of the 'Object Society' class in the hash table of the corresponding associated object
for (auto &i: refs) {
// i.second是ObjcAssociation类型对象{policy, value}
if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
// If we are not deallocating, then RELEASE_LATER associations don't get released.
if (deallocating)
// When dealloc, the associated object of OBJC_ASSOCIATION_SYSTEM_OBJECT will be put into laterRefs and released later. Otherwise, it will not be processed.
laterRefs.append(&i.second);
} else {
// Release non-objc_association_system_object associated objects
i.second.releaseHeldValue(a); }// Out of scope, call AssociationsManager destructor to unlock
}
for (auto *later: laterRefs) {
// dealloc releases the associated object of OBJC_ASSOCIATION_SYSTEM_OBJECT
later->releaseHeldValue();
}
}
Copy the code
- According to the second parameter
deallocating
Judge whether or notdealloc
Is called when. - Create a temporary
ObjectAssociationMap
objectrefs
. - Gets the total hash table of global associated objects
associations
. - Get the hash table of the associated object (
ObjectAssociationMap
)i
. - Correspond objects to
ObjectAssociationMap
Data interaction torefs
In, the originali
Empty. - non
deallocating
In the case (that is, call yourself), the system’s associated object (throughpolicy & OBJC_ASSOCIATION_SYSTEM_OBJECT
Judge) re-inserti
And markdidReInsert
fortrue
. - If there is no need to reinsert, the corresponding object is erased
ObjectAssociationMap
The iteratori
. - create
laterRefs
To recorddealloc
The case requires whether the systemObjcAssociation
. - cycle
refs
The non-system associated objects are directly released, and the associated objects of the system determine whether to releasedeallocating
.deallocating
In the case of joininglaterRefs
. - Release cycle
laterRefs
In theObjcAssociation
, which is the associated object of the system (deallocating
In the case of).
3.3.3: Associated object removal process
- Create a temporary
ObjectAssociationMap
objectrefs
. -
- According to the
AssociationsManager
Class to get the global associated object total hash tableAssociationsHashMap
.
- According to the
- Based on the current
object
encapsulatedDisguisedPtr
inAssociationsHashMap
To get the corresponding iterator. - This iterator is not
end
Tags that correspond to objectsObjectAssociationMap
Data interaction torefs
In, the original iterator is empty for subsequent storage of system associated objects that need to be reinserted. - non
deallocating
In the case of loop acquisitionObjcAssociation
, re-inserts the associated objects of the system, and erases the empty iterators if there is nothing to re-insert. - Loop gain
ObjcAssociation
, if it is a system associated object,deallocating
In the case of temporary joinVector
In, the non-system is released directly. - If the system is associated with objects
Vector
There is data, traversal release (system associated objects indeallocating
In case of release).
Four:dealloc
In the actual development process, we generally do not actively call the API to remove the associated object, so the object to release, must remove the associated object, the following check dealloc process, see if there is a corresponding operation.
- (void)dealloc {
_objc_rootDealloc(self);
}
Copy the code
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc(a); }Copy the code
Call the process dealloc -> _objc_rootDealloc -> obj->rootDealloc, rootDealloc function has the associated object related process.
4.1: rootDealloc
inline void
objc_object::rootDealloc(a)
{
// Tagged Pointer
if (isTaggedPointer()) return; // fixme necessary?
// isa is an impure pointer
// No weak references
// There is no associated object
// no c++ destructor
// No reference count table
// If all 5 conditions are met, please call object_dispose
if (fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc &&#if ISA_HAS_CXX_DTOR_BIT! isa.has_cxx_dtor &&#else! isa.getClass(false) - >hasCxxDtor() &&
#endif! isa.has_sidetable_rc)) {assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this); }}Copy the code
isa
The pure pointer&&
No weak references&&
No associated object&&
There is noc++
The destructor&&
There is no reference count table that simultaneously satisfies this5
One condition directfree
, otherwise callobject_dispose
Function.
4.2: object_dispose
id
object_dispose(id obj)
{
if(! obj)return nil;
// Destroy objects
objc_destructInstance(obj);
free(obj);
return nil;
}
Copy the code
- call
objc_destructInstance
The function destroys the object and thenfree
.
Obviously the core logic for removing associated objects is in the objc_destructInstance function.
4.3: objc_destructInstance
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor(a);bool assoc = obj->hasAssociatedObjects(a);// This order is important.
// call the C++ destructor
if (cxx) object_cxxDestruct(obj);
// Remove the associated object
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
// Clear the weak reference table and reference count table corresponding to 'object'
obj->clearDeallocating(a); }return obj;
}
Copy the code
- If you have
c++
Destructor, call. - If there are associated objects, remove them.
- remove
object
Corresponding weak reference table and reference count table.
4.4: clearDeallocating
inline void
objc_object::clearDeallocating(a)
{
// Two methods do not call the same reason is
SIDE_TABLE_WEAKLY_REFERENCED
// Nonpointer can be determined by the ISA field
if (slowpath(! isa.nonpointer)) {/ / pointer to pure
// Slow path for raw pointer isa.
// Clear the hash table
sidetable_clearDeallocating(a); }else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {// Impure Pointers have weak reference tables or reference count tables
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow(a); }assert(!sidetable_present());
}
Copy the code
- According to the
isa
Whether or not a pure pointer is divided into two logic, pure pointer callsidetable_clearDeallocating
Function, impure pointer callclearDeallocating_slow
Function.
4.4.1: sidetable_clearDeallocating
andclearDeallocating_slow
Sidetable_clearDeallocating:
void
objc_object::sidetable_clearDeallocating(a)
{
SideTable& table = SideTables(to)this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock(a);// Find your reference count table iterator in the hash table
RefcountMap::iterator it = table.refcnts.find(this);
// No end tag
if(it ! = table.refcnts.end()) {
// Determine if there are weak references
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
// Clear your own weak reference table
weak_clear_no_lock(&table.weak_table, (id)this);
}
// Erase your own reference count table
table.refcnts.erase(it);
}
table.unlock(a); }Copy the code
- Get the hash table.
- Find your own reference count table iterator in the hash table.
- According to the
SIDE_TABLE_WEAKLY_REFERENCED
Check if there are weak references and clear them. - Erase your own reference counter table.
ClearDeallocating_slow:
NEVER_INLINE void
objc_object::clearDeallocating_slow(a)
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables(to)this];
table.lock(a);if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock(a); }Copy the code
- Get the hash table.
- through
isa.weakly_referenced
Check if there are weak references and clear them. - through
isa.has_sidetable_rc
Check if there is a reference count table and erase it if there is.
4.4.2: weak_clear_no_lock
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
// The object to be cleared
objc_object *referent = (objc_object *)referent_id;
// Find the corresponding weak_entry_t
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); ...// Zero out References weak object reference table
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
// weak reference set to nil
*referrer = nil;
}
else if(* referrer) {...objc_weak_error(a); }}}// Remove entry from the weak reference table
weak_entry_remove(weak_table, entry);
}
Copy the code
- Find your own in the weak reference count table
weak_entry_t
. - According to the
out_of_line
Gets a weak reference pointer to an objectreferrers
withinline_referrers
. - The loop weak reference pointer array sets the weak reference pointer to
nil
. - Will a weak reference
weak_entry_t
Delete from the weak reference table.
4.5: dealloc
Calling process
- Conditions for direct release (
isa
The pure pointer&&
No weak references&&
No associated object&&
There is noc++
The destructor&&
No reference count table) :- directly
free
.
- directly
- Conditions that cannot be directly released:
- If you have
c++
Destructor, call. - If there are associated objects, remove them.
- Clears the weak reference table (traverses the array of weak reference Pointers, all set to
nil
). - Erases its own data from the reference count table.
free
.
- If you have
It is known that in ARC mode dealloc method no longer need to explicitly call [super Dealloc], analysis of dealloc process in objC source code also did not see [super Dealloc] call (objC part is already run time), then the relevant processing must be compiled.
Use Hopper disassembly to view pseudocode:
- It is added at compile time
[super dealloc]
Related processing.Code compilation related content, later have the opportunity to send a separate document analysis.
Five: associated object implementationweak
attribute
The objc_AssociationPolicy for adding associated objects to a category is as follows:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0./**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1./**< Specifies a strong reference to the associated object. * The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3./**< Specifies that the associated object is copied. * The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401./**< Specifies a strong reference to the associated object. * The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. * The association is made atomically. */
};
Copy the code
This enumeration uses OBJC_ASSOCIATION_ASSIGN to create an association_weak object, but weak and assign are different:
Weak: Modifies OBJC objects. It does not hold the pointer modifiers, and the reference count for the same object is not increased. When the pointer is released, the weak object is set to nil.
Because heap memory is dynamic, when an object at an address is released, all Pointers to it should be null. Weak is used to avoid circular references and can be left empty when the object is released.
Assign: Modifies the basic data type in stack memory. When an OBJC object is assigned, it will not be set to nil when the object to which it is assigned is released. This may result in wild Pointers.
In order to avoid the occurrence of wild Pointers, we can only use another way: to forcibly reference an intermediate, and let the intermediate hold the weak attribute. The main implementation is to use blocks: strongly reference blocks that hold weak attributes.
@property (nonatomic, weak) id weak_obj;
- (void)setWeak_obj:(id)weak_obj {
id __weak weakObject = weak_obj;
// Declare the implementation of block, executing block returns weakObject
id (^block)(void) = ^ {return weakObject; };
// Store blocks, copy policy
return objc_setAssociatedObject(self, "weak_obj", block, OBJC_ASSOCIATION_COPY);
}
- (id)weak_obj {
/ / get block
id (^block)(void) = objc_getAssociatedObject(self, "weak_obj");
// Execute block to get weakObject returned by block
id weakObject = (block ? block() : nil);
return weakObject;
}
Copy the code
The object is still a strong reference, but the block holds a weak reference to the property inside. After weak_OBj is released, block cannot obtain Weak_OBj.
When can associated objects cause memory leaks?
An associative object can be understood as holding an object, and if it is strongly held and the object strongly holds the class, it will result in circular references.
conclusion
The storage structure of the associated object is a two-layer hash map, which requires two layers of processing for saving, fetching and removing.