preface
In the previous iOS Underlying Principles: Class Loading Principles (Classification), classification loading was analyzed. Today, class extension and associated objects will be explored.
The preparatory work
- Objc4-818.2 – the source code
1. Class extension analysis
What is a class extension
- This is the
Class extensions
We usually use is very much, it is also called the classification without name.
Low-level C++ analysis class extension
Add the following code to main.m:
@interface SSLPerson : NSObject
@property (nonatomic, copy) NSString *name;
- (void)say1;
@end
@interface SSLPerson ()
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_say1;
@end
@implementation SSLPerson
+ (void)load {}
- (void)say1
{
NSLog(@"SSLPerson-----say1");
}
- (void)ext_say1
{
NSLog(@"SSLPerson-----ext_say1");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {}
return 0;
}
Copy the code
Class extensions
Should be placedThe statement
After that,implementation
Before, otherwise an error will be reported.
Open terminal, clang-rewrite-objc main.m -o main. CPP, and get main. CPP.
. struct SSLPerson_IMPL { struct NSObject_IMPL NSObject_IVARS; NSString *_name; NSString *_ext_name; }; static struct /*_method_list_t*/ { unsigned int entsize; // sizeof(struct _objc_method) unsigned int method_count; struct _objc_method method_list[10]; } _OBJC_$_INSTANCE_METHODS_SSLPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 10, {{(struct objc_selector *)"say1", "v16@0:8", (void *)_I_SSLPerson_say1}, {(struct objc_selector *)"ext_say1", "v16@0:8", (void *)_I_SSLPerson_ext_say1}, {(struct objc_selector *)"name", "@16@0:8", (void *)_I_SSLPerson_name}, {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_SSLPerson_setName_}, {(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_SSLPerson_ext_name}, {(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_SSLPerson_setExt_name_}, }; .Copy the code
- As you can see,
Class extensions
Did not likeclassification
Same thing, same thingcategory_t
That data structure. - A member variable for a class extension
_ext_name
And the main class_name
Is put together, class extensionmethods
And the main classmethods
Is also put in the same list, so when are these methods added to the next analysis.
Class extension load analysis
For debugging purposes, recreate the following classes:
The realizeClassWithoutSwift function is called when the class is not lazily loaded, and we add a breakpoint to test it. Run the program to the breakpoint:
LLDB print:
- As you can see,
ext_say1
,setName
,ext_name
If it already exists at this point, it means that the class extension was loaded with the content of the class, so to speak, as part of the class.
Class extension VS classification
1. What’s your name
- Used specifically to add classes
The new method
. - You cannot add a member attribute to a class. If you add a member variable, you cannot fetch it.
- Note: It can actually pass
runtime
Add attributes to the category. - Classification using
@property
If you define variables, only variables will be generatedgetter
,setter
Method declaration that cannot generate method implementations and underlined member variables.
Extension: Class extension
- It’s kind of a special category, it’s also called
Anonymous classification
. - You can add member variables to a class, but yes
Private variables
. - You can add methods to classes, again
Private methods
. - At compile time, and in the main class
Loaded together
.
2. Introduction of associated objects
We know that attributes in a category do not automatically generate member variables. This problem can be solved by associating objects.
Create SSLPerson+SSL1 category:
@interface SSLPerson (SSL1) @property (nonatomic, copy) NSString *cate_name; @end #import <objc/runtime.h> @implementation SSLPerson (SSL1) - (void)setCate_name (NSString *)cate_name{/** 1: object 2: Identifier 3: value 4: policy */ objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)cate_name{ return objc_getAssociatedObject(self, "cate_name"); } @endCopy the code
Click on objc_setAssociatedObject to go to the source code:
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
Copy the code
- below
_object_set_associative_reference
Make a detailed analysis.
Third, the underlying analysis of the associated object
_object_set_associative_reference
Enter the _object_set_associative_reference:
Void object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) { Key is an identity key, value is a value, and policy is a storage policy. DisguisedPtr< objC_Object > Premiere6.0 {(objc_Object *) Object}; ObjcAssociation Association {policy, value}; // retain the new value (if any) outside the lock. association.acquireValue(); // Key code {AssociationsManager manager; . } } template <typename T> class DisguisedPtr { uintptr_t value; static uintptr_t disguise(T* ptr) { return -(uintptr_t)ptr; } static T* undisguise(uintptr_t val) { return (T*)-val; } } class ObjcAssociation { uintptr_t _policy; id _value; . }Copy the code
DisguisedPtr<objc_object>
willobject
It’s packaged,ObjcAssociation
willpolicy
,value
It was packaged.- In the function of
AssociationsManager
There are many articles on the Internet saying it isThe singleton
Is it a singleton or not? Let’s analyze it.
AssociationsManager analysis
1. Constructor and destructor knowledge supplement
Create the SSLObjc structure and implement its constructor and destructor:
Struct SSLObjc {SSLObjc() {printf("SSL constructor call \n"); } ~SSLObjc() {printf("SSL destructor call \n"); }};Copy the code
Next breakpoint print debugging:
- At this time
SSLObjc objc;
Just got executed. We found outThe constructor
I was called.
A step down from the break point:
- At this point the scope ends,
objc
Destruction,The destructor
Is also automatically called.
2. Source analysis of AssociationsManager
View AssociationsManager source code:
class AssociationsManager { using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>; static Storage _mapStorage; public: AssociationsManager() { AssociationsManagerLock.lock(); } ~AssociationsManager() { AssociationsManagerLock.unlock(); } AssociationsHashMap &get() { return _mapStorage.get(); } static void init() { _mapStorage.init(); }};Copy the code
- You can see
AssociationsManager
Not a singleton, but through the constructorlock
, the destructorunlock
, to achieve thread-safe. AssociationsManager
It’s just for callingAssociationsHashMap
The just,AssociationsHashMap
Is a singleton as it passes_mapStorage.get()
To obtain,_mapStorage
Is a global static variable that is unique wherever it is placed.
Associated object data structure diagram
Data structures associated with associated objects:
Source breakpoint debugging
Add code to main:
int main(int argc, const char * argv[]) {
@autoreleasepool {
SSLPerson *person = [SSLPerson alloc];
person.cate_name = @"SSL";
}
return 0;
}
Copy the code
Break to enter _object_set_associative_reference:
- According to the print, you can see
disguised
,association
,associations
Of data structures, where_value = SSL
, which proves that the current debugging is correct.
Going down:
- print
refs_result
“, found that the data structure is very long, in fact, the above so long is only the type, do not pay too much attention. The real numbers are in the next few lines, andrefs_result.second
This piece of code is usedsecond
.
Fourth, the underlying analysis set value process of the associated object
Run again to continue debugging breakpoints:
- Next call
associations.try_emplace
The function,associations
isAssociationsHashMap
Type. - The incoming
disguised
It is aobject
And a new oneObjectAssociationMap
, make it twoKey/value pair
Make an association toassociations
In the.
try_emplace
Break point to try_emplace:
BucketT *TheBucket
To create an emptyBucketT
.- call
LookupBucketFor(Key, TheBucket))
And the incomingkey
isdisguised
, andTheBucket
.
LookupBucketFor
Enter the LookupBucketFor:
- There are two
LookupBucketFor
Function, which is passed noconst
theBucketT *&FoundBucket
Argument, so call the following first. - The above is called from inside the function
LookupBucketFor
.FoundBucket
Is pointer passing, there are values that can be taken back, so the key code is in the above function.
Go to the LookupBucketFor function above:
template<typename LookupKeyT> bool LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const { const BucketT *BucketsPtr = getBuckets(); const unsigned NumBuckets = getNumBuckets(); // FoundTombstone - Keep track of whether we find a tombstone while probing. const BucketT *FoundTombstone = nullptr; const KeyT EmptyKey = getEmptyKey(); const KeyT TombstoneKey = getTombstoneKey(); // This is a hash function that computes the index unsigned BucketNo = getHashValue(Val) & (NumBuckets-1); // This is a hash function that computes the index unsigned BucketNo = getHashValue(Val) & (NumBuckets-1); unsigned ProbeAmt = 1; // If FoundBucket is found, assign to FoundBucket, return true // FoundBucket is not found, While (true) {const BucketT *ThisBucket = BucketsPtr + BucketNo; FoundBucket = FoundBucket; FoundBucket = FoundBucket; FoundBucket = FoundBucket; FoundBucket = FoundBucket; FoundBucket = FoundBucket; If (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {FoundBucket = ThisBucket; return true; } FoundBucket = FoundBucket; FoundBucket = FoundBucket; FoundBucket = FoundBucket; If (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {FoundBucket = FoundTombstone? FoundTombstone : ThisBucket; return false; } BucketNo += ProbeAmt++; BucketNo &= (NumBuckets-1); }}Copy the code
- through
getHashValue(Val) & (NumBuckets-1)
Hash function, calculateBucketNo
The subscript. while
Loop, find the location according to the subscript, and then assign the address toFoundBucket
In this case, we have an unassigned address, namelyisEqual(ThisBucket->getFirst(), EmptyKey)
, so returnfalse
.
Return try_emplace
After the LookupBucketFor function is finished, return to try_emplace:
- The returned
false
So I went down to the bottom.TheBucket
It is now stored inAssociationsHashMap
I have a place in it, but I don’t have anything yet, so I’m going to insert something for it. - call
InsertIntoBucket
And the incomingTheBucket
And the incomingkey
That isdisguised
And the incomingArgs
That isObjectAssociationMap{}
.
InsertIntoBucket
Enter the InsertIntoBucket:
template <typename KeyArg, typename... ValueArgs> BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key, ValueArgs &&... Values) {TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket); // Assign the Key to TheBucket in the first TheBucket->getFirst() = STD ::forward<KeyArg>(Key); // Assign ObjectAssociationMap{} to second ::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...) ; return TheBucket; }Copy the code
Enter the InsertIntoBucketImpl:
template <typename LookupKeyT> BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup, BucketT *TheBucket) { unsigned NewNumEntries = getNumEntries() + 1; unsigned NumBuckets = getNumBuckets(); If (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {this->grow(NumBuckets * 2); LookupBucketFor(Lookup, TheBucket); NumBuckets = getNumBuckets(); } else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <= NumBuckets/8)) { this->grow(NumBuckets); LookupBucketFor(Lookup, TheBucket); } if (KeyInfoT::isEqual(TheBucket->getFirst(), GetEmptyKey ())) {// Replacing an empty bucket. // Increase the number of incrementNumEntries(); } else if (KeyInfoT::isEqual(TheBucket->getFirst(), GetTombstoneKey ())) {// Replacing a tombstone. // Increase the number of incrementNumEntries(); decrementNumTombstones(); } else { // we should be purging a zero. No accounting changes. TheBucket->getSecond().~ValueT(); } return TheBucket; }Copy the code
- When the quantity is greater than or equal to
Three quarters of
When,2
Doubled capacity, has been increasednum
The number of+ 1
In the operation.
Const void *, association
Back to try_emplace:
- You can see
TheBucket
It’s already worth it. makeIterator
In theiterator
isDenseMapIterator
Type.makeIterator(TheBucket, getBucketsEnd(), true)
The last parameter of is fixedtrue
.
Back to the _object_set_associative_reference function:
- You can see it now
const void *, objc::ObjcAssociation
Of, belonging toObjectAssociationMap
Data structure. - At this time
value
,policy
Not yet. It’s still thereassociation
In the water.
Continue debugging:
refs_result.first
What you get isbucket
.bucket.second
What you get is what you created aboveObjectAssociationMap
That is to sayrefs
It was created aboveObjectAssociationMap
.- And then call again
try_emplace
Function, pass inkey
(here,"cate_name"
), andassociation
. Store them as key-value pairs torefs
thebucket
Middle, to these two levelsHash stored
To complete.
Summary of associated object setting process
- To create a
AssociationsManager
Management class. - Gets a unique global static hash
Map
. - To determine whether the associated value for insertion exists:
- Go there first
4
Step. - Go without: The associated object inserts an empty process.
- Go there first
- Create an empty one
ObjectAssociationMap
To get the key-value pair of the query. - And if it doesn’t
key
Just insert an empty oneBucketT
Go in and return. - The tag object has an associated object.
- With the current decorating policy and value
ObjcAssociation
Replace the originalBucketT
In the air. - Mark the
ObjectAssociationMap
The first time of isfalse
.
Fifth, the underlying analysis of the associated object inserts the empty process
When value has no value, the code goes else:
Void object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) { Key is an identity key, value is a value, and policy is a storage policy. DisguisedPtr< objC_Object > Premiere6.0 {(objc_Object *) Object}; ObjcAssociation Association {policy, value}; // retain the new value (if any) outside the lock. association.acquireValue(); // Key code {AssociationsManager manager; AssociationsHashMap &associations(manager.get()); if (value) { ... } else { auto refs_it = associations.find(disguised); if (refs_it ! = associations.end()) { auto &refs = refs_it->second; auto it = refs.find(key); if (it ! = refs.end()) { association.swap(it->second); refs.erase(it); if (refs.size() == 0) { associations.erase(refs_it); } } } } } }Copy the code
- in
associations
In the table lookupdisguised
If not found proceederase
Clear the operation.
Vi. Process for the value of associated objects
- To create a
AssociationsManager
Management class. - Gets a unique global static hash
Map
. - According to the
DisguisedPtr
findAssociationsHashMap
In theiterator
Iterating the query. - If the iterated query is not the last to retrieve:
ObjectAssociationMap
(There are strategies andvalue
). - find
ObjectAssociationMap
The iterated query of thevalue
. - return
_value
.
Release the associated object
When is the associated object released? The life cycle of the object determines the life cycle of the associated object. We take the release of the object as the breakthrough point for analysis.
Look at the source below:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, key, value, policy); } void objc_removeAssociatedObjects(id object) { if (object && object->hasAssociatedObjects()) { _object_remove_assocations(object, /*deallocating*/false); }}Copy the code
- We see that there are
objc_removeAssociatedObjects
Function, you can do it the other way around, to see when the function was called. - It should be called when the object is released.
Search dealloc {, enter dealloc:
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
Copy the code
Enter the _objc_rootDealloc:
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
Copy the code
Enter the rootDealloc:
inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary? 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
Enter the object_dispose:
id object_dispose(id obj) { if (! obj) return nil; objc_destructInstance(obj); free(obj); return nil; }Copy the code
Enter the objc_destructInstance:
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, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
Copy the code
Enter the _object_remove_assocations:
void _object_remove_assocations(id object, bool deallocating) { ObjectAssociationMap refs{}; { AssociationsManager manager; AssociationsHashMap &associations(manager.get()); AssociationsHashMap::iterator i = associations.find((objc_object *)object); if (i ! = associations.end()) { refs.swap(i->second); // If we are not deallocating, then SYSTEM_OBJECT associations are preserved. bool didReInsert = false; if (! deallocating) { for (auto &ref: refs) { if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) { i->second.insert(ref); didReInsert = true; } } } if (! didReInsert) associations.erase(i); } } // Associations to be released after the normal ones. SmallVector<ObjcAssociation *, 4> laterRefs; // release everything (outside of the lock). for (auto &i: refs) { if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) { // If we are not deallocating, then RELEASE_LATER associations don't get released if (deallocating) laterRefs.append(&i.second); } else { i.second.releaseHeldValue(); } } for (auto *later: laterRefs) { later->releaseHeldValue(); }}Copy the code
- To create a
AssociationsManager
Management class to get a unique global static hashMap
, defined asassociations
. - In order to
object
forkey
In theassociations
Remove theiterator
Is defined asi
. - After some judgment, yes
associations.erase(i);
deletei
.