1. Review
In previous posts, we have focused on class loading, including the low-level exploration of classification loading. This time, we will look at class extension and associated objects.
Loading iOS Low-level Exploration and the like (3): attachCategories analysis
2. The extension
2.1 What are classification and extension
First let’s look at what is classification and extension
category
: Category/classification
- Used specifically to add new methods to a class
- You cannot add a member attribute to a class. If you add a member variable, you cannot fetch it
Pay attention to
: Yes, actuallyruntime
Add attributes to the category- Classification using
@property
If you define variables, only variables will be generatedgetter,setter
Method declaration, cannot generate method implementation and underlineMember variables
.
extension
: class extensions
- It’s kind of a special category, it’s also called
Anonymous classification
- You can add to a class
Member attribute
, but isPrivate variables
- You can add methods to classes, again
Private methods
We are already familiar with the categories, so we don’t need to talk about them too much
2.2 extensions
Class extension, we usually use is very much, as follows
what ? What, is this an extension? Use every day unexpectedly do not know!
Yes, this is the extension, usually used a lot, but a lot of people don’t know.
Note: The class extension should come after the declaration and before the implementation, otherwise an error will be reported.
Add attributes and methods to extensions, and let’s take a look at what low-level C++ looks like.
As you can see from the underlying C++ code, properties of class extensions are added to the list of member variables, and methods are added to methods.
Thought: Does an extension affect the loading of the main class as well as the classification?
- Create a separate extension file
LGPerson.m
The import#import "LGPerson+Ext.h"
Header file that implements the methods inside the extension
- (void)ext_sayHello {
NSLog(@"%s",__func__);
}
+ (void)ext_classMehod{
NSLog(@"%s",__func__);
}
Copy the code
- Breakpoints in
objc
The source code insiderealizeClassWithoutSwift
lldb
printro
Methods list
(lldb) p ro.baseMethods()
(method_list_t *) $0 = 0x0000000100004190
Fix-it applied, fixed expression was:
ro->baseMethods()
(lldb) p *$0
(method_list_t) $1 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 8)
}
(lldb) p $1.get(0).big()
(method_t::big) $2 = {
name = "saySomething"
types = 0x0000000100003e09 "v16@0:8"
imp = 0x00000001000039f0 (ObjcBuild`-[LGPerson(LGA) saySomething])
}
(lldb) p $1.get(1).big()
(method_t::big) $3 = {
name = "cateA_instanceMethod1"
types = 0x0000000100003e09 "v16@0:8"
imp = 0x0000000100003a20 (ObjcBuild`-[LGPerson(LGA) cateA_instanceMethod1])
}
(lldb) p $1.get(2).big()
(method_t::big) $4 = {
name = "cateA_instanceMethod2"
types = 0x0000000100003e09 "v16@0:8"
imp = 0x0000000100003a50 (ObjcBuild`-[LGPerson(LGA) cateA_instanceMethod2])
}
(lldb) p $1.get(3).big()
(method_t::big) $5 = {
name = "saySomething"
types = 0x0000000100003e09 "v16@0:8"
imp = 0x00000001000038a0 (ObjcBuild`-[LGPerson saySomething])
}
(lldb) p $1.get(4).big()
(method_t::big) $6 = {
name = "sayHello1"
types = 0x0000000100003e09 "v16@0:8"
imp = 0x00000001000038d0 (ObjcBuild`-[LGPerson sayHello1])
}
(lldb) p $1.get(5).big()
(method_t::big) $7 = {
name = "ext_sayHello"
types = 0x0000000100003e09 "v16@0:8"
imp = 0x0000000100003900 (ObjcBuild`-[LGPerson ext_sayHello])
}
(lldb) p $1.get(6).big()
(method_t::big) $8 = {
name = "name"
types = 0x0000000100003de3 "@ @ 0:8 16"
imp = 0x0000000100003930 (ObjcBuild`-[LGPerson name])
}
(lldb)
Copy the code
As you can see from the mode information, $1.get(5).big() prints the extended ext_sayHello method, which proves that the extended information of the class is also loaded as part of the class.
3. Associated objects
It is not possible to add member variables directly in a category, but we can add them indirectly, which involves the knowledge of the associated object.
// Get the associated object
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
// Set the associated object
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
// Remove the associated object
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object, /*deallocating*/false); }}Copy the code
- Objc_getAssociatedObject Gets the associated object
- Objc_setAssociatedObject Sets the associated object
- Objc_removeAssociatedObjects Removes associated objects
3.1 Setting process
Take a look at the objc_setAssociatedObject method, which calls the _object_set_associative_Reference method
**_object_set_associative_reference
阿鲁纳恰尔邦
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if(! object && ! value)return;
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));
DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
bool isFirstAssociation = false;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
auto &refs = refs_result.first->second;
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} 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);
}
}
}
}
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
Copy the code
The four main parameters are:
objc
: The object to be associated, that is, to whom the associated attribute is addedkey
: Identifier for easy search next timevalue
: The value to be storedpolicy
: Association policy
The DisguisedPtr method is a packaging strategy, just like express, packed into parcels
class DisguisedPtr {
uintptr_t value;
static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}
static T* undisguise(uintptr_t val) {
return (T*)-val;
}
Copy the code
We’re processing the PTR, we’re processing the value, we’re wrapping the object, we’re wrapping it into a unified data structure.
AssociationsManager
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 voidinit() { _mapStorage.init(); }}; AssociationsManager::Storage AssociationsManager::_mapStorage; }Copy the code
- Many people are seeing it for the first time
AssociationsManager
Must have thought it was.The singleton
“, but this is not a singleton, but passesThe constructor is locked
,The destructor unlocks
To achieve thread safety. AssociationsManager
It’s just for callingAssociationsHashMap
ButAssociationsHashMap
Is a singleton as it passes_mapStorage.get()
To obtain,_mapStorage
Is a global static variable that is unique wherever it is placed.
Create a new category and write several properties and methods in it
@interface LGPerson (LGA)
@property (nonatomic.copy) NSString *cate_name;
@property (nonatomic.copy) NSString *cate_age;
- (void)saySomething;
- (void)cateA_instanceMethod1;
- (void)cateA_instanceMethod2;
+ (void)cateA_classMethod1;
+ (void)cateA_classMethod2;
@end
- (void)setCate_name:(NSString *)cate_name{
objc_setAssociatedObject(self."cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
return objc_getAssociatedObject(self."cate_name");
}
- (void)setCate_age:(NSString *)cate_age{
objc_setAssociatedObject(self."cate_age", cate_age, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_age{
return objc_getAssociatedObject(self."cate_age");
}
Copy the code
Assign the property, let’s see the breakpoint
LGPerson * person = [LGPerson alloc];
person.cate_name = @"jp";
person.cate_age = @ "20";
[person saySomething];
Copy the code
You can see the associations structure and the refs_result structure, which may not be clear, so take a look at LLDB
What? What the hell! Refs_result what is that?
Pretty boy, don’t panic, calm down, please look at the following value
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
Copy the code
Although refs_result is very long, very creepy, the first so long is just the type, equivalent to NSObject, LGPerson, but the real content is only a few, and here only used second, the rest don’t care, this wave is not very comfortable, ha ha 😁
Refs_result is from associ.try_emplace (Overlap, ObjectAssociationMap{})
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
Copy the code
- *
try_emplace
template <typename. Ts> std::pair<iterator,bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// Otherwise, insert the new element.TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...) ;return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
Copy the code
So we’re going to create a new bucket, BucketT, where the key is the address of LGPerson. The first step is to go into LookupBucketFor and look for a bucket, and see two implementations, because in try_emplace BucketT has no const, so go to the following implementation. The following implementation calls the above implementation.
Here we have two methods with the same name, one of which takes a different argument, and here we call the above method, passing in an address, which is basically a pointer. I’ve also marked it in the picture.
- Get the hash index
- Start an endless loop
bucket
- The matching
- If you don’t find it, hash it again
When I first came in, the LookupBucketFor(Key, TheBucket) couldn’t be found, so I went below.
This is where you insert an empty bucket, and you double the capacity by three quarters.
To be continued… More to come
🌹 just like it 👍🌹
🌹 feel learned, can come a wave, collect + concern, comment + forward, lest you can’t find me next 😁🌹
🌹 welcome everyone to leave a message to exchange, criticize and correct, learn from each other 😁, improve self 🌹