It started on my personal blog
Start by adding attributes to categories
In our article on categorizing Cateogry in iOS, we posed the question,
Can you add member variables to a Category? If so, how do YOU add member variables to a Category?
- You cannot add member variables to a Category directly, but you can achieve the effect of a Category having member variables indirectly, using the associative object technique
So, here are the details
What do you actually do when you add attributes
First of all, let’s recall that adding a property actually does three things
- Generating member variables
- Generate declarations for the set and GET methods
- Generate implementations of the set and GET methods
Eg: Define a YZPerson class and define the age attribute
#import <Foundation/Foundation.h>
@interface YZPerson : NSObject
@property (assign, nonatomic) int age;
@end
Copy the code
That’s like doing three things
- Generating member variables
_age
- generate
set
Methods andget
Method declaration - generate
set
Methods andget
The implementation of the method is as follows
#import <Foundation/Foundation.h>
@interface YZPerson : NSObject
{
int _age;
}
- (void)setAge:(int)age;
- (int)age;
@end
#import "YZPerson.h"
@implementation YZPerson
- (void)setAge:(int)age{
_age = age;
}
- (int)age{
return _age;
}
@end
Copy the code
So why not add attributes to categories?
Say first conclusion
- Generating member variables
_age
- Will not generate
set
Methods andget
Method declaration - Will not generate
set
Methods andget
Method implementation
Will not generateset
Methods andget
Method implementation
Define a category YZPerson+ ext.h and add the attribute weight
#import "YZPerson.h"
@interface YZPerson (Ext)
@property (nonatomic ,assign) int weight;
@end
Copy the code
use
YZPerson *person = [[YZPerson alloc] init];
person.weight = 10;
Copy the code
Will report an error directly,
IOS - Associated object [1009:10944] *** Terminating app due to uncaught exception'NSInvalidArgumentException',
reason: '-[YZPerson setWeight:]: unrecognized selector sent to instance 0x10182bd10'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff3550d063 __exceptionPreprocess + 250
1 libobjc.A.dylib 0x00007fff6ac8e06b objc_exception_throw + 48
2 CoreFoundation 0x00007fff355961bd -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff354b34b4 ___forwarding___ + 1427
4 CoreFoundation 0x00007fff354b2e98 _CF_forwarding_prep_0 + 120
6 libdyld.dylib 0x00007fff6c0183f9 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Program ended with exit code: 9
Copy the code
Reason: ‘-[YZPerson setWeight:]: unrecognized selector sent to instance 0x10182bd10
Will be generatedset
Methods andget
Method declaration
#import "YZPerson+Ext.h"
@implementation YZPerson (Ext)
- (void)setWeight:(int)weight{
}
- (int)weight{
return 100;
}
@end
Copy the code
And then call
YZPerson *person = [[YZPerson alloc] init];
person.age = 25;
person.weight = 10;
NSLog(@"person.age = %d",person.age);
NSLog(@"person.weight = %d",person.weight);
Copy the code
The output
2019-07-10 08:28:04.406972+0800 iOS- Associated object [1620:18520] person. Age = 25 2019-07-10 08:28:04.407291+0800 IOS - Associated object [1620:18520] person.weight = 100Copy the code
Further proof that implementations of the set and GET methods are not generated, but declarations of the set and GET methods are, because the method cannot be called without the declarations of the set and GET methods.
We can also declare the weight in the YZPerson+ ext. h file, and then write the implementation in YZPerson+ ext. m, we will be prompted
All the more proof that there is a claim.
Member variables cannot be defined directly in a classification
#import "YZPerson.h"@interface YZPerson (Ext) { int _weight; Instance variables may not be placedin categories
}
@property (nonatomic ,assign) int weight;
@end
Copy the code
Instance variables may not be placed in categories
Source proof
Objc-runtime-new. h classifiescateogry. Objc-runtimenew. h classifiescateogry
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
Copy the code
In this structure, there are no arrays for member variables, only properties, protocols, etc.
How to improve attributes
Is there any way to add attributes to a category that has the same effect as adding attributes to a class? The answer is yes
Plan one uses global variables
Class YZPerson+ ext.m defines the global variable _weight
#import "YZPerson+Ext.h"
@implementation YZPerson (Ext)
int _weight;
- (void)setWeight:(int)weight{
_weight = weight;
}
- (int)weight{
return _weight;
}
@end
Copy the code
When using
YZPerson *person = [[YZPerson alloc] init];
person.weight = 103;
NSLog(@"person.weight = %d",person.weight);
Copy the code
The output is
IOS - Associated object [1983:23793] person.weight = 103Copy the code
It looks like it does, but actually we can’t use it that way, because global variables are shared, so let’s say we have two persons, and the second Person modifies the weight property, and then prints the first person.weight
YZPerson *person = [[YZPerson alloc] init];
person.weight = 103;
NSLog(@"person.weight = %d",person.weight);
YZPerson *person2 = [[YZPerson alloc] init];
person2.weight = 10;
NSLog(@"person.weight = %d",person.weight);
Copy the code
The output is
Ios-associated object [1983:23793] person.weight = 103 ios-associated object [1983:23793] person.weight = 10Copy the code
Changing person. weight changes the value of person. weight because it is a global variable. So that’s not going to work
Plan two uses the dictionary
Since the previous scheme doesn’t work because global variables share a copy, shouldn’t we just guarantee one-to-one relationships?
Define the dictionary weights_ to store and use the address value of the object as the key and the weight value as the value
#import "YZPerson+Ext.h"@implementation YZPerson (Ext) NSMutableDictionary *weights_; + (void)load{ static dispatch_once_t onceToken; {dispatch_once(&onceToken, ^{dispatch_once(&onceToken, ^{); }); } - (void)setWeight:(int)weight{
NSString *key = [NSString stringWithFormat:@"%p",self]; Weights_ [key] = @(weight); } - (int)weight{NSString *key = [NSString stringWithFormat:@"%p",self];
return [weights_[key] intValue];
}
@end
Copy the code
In this way, when used, there is no interference between different objects and the result is as follows
Existing problems
- Because it is global, there are memory leaks
- Thread-safety issues, if multiple threads access at the same time, thread-safety issues
- Too much code. If you add a property every time, you’re writing too much code. Adverse to maintenance
Associated object scheme
Use of associated objects
The following is a brief explanation of the use of associated objects
Dynamically add
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
Copy the code
- A parameter:
id object
To add an attribute to an object, useself
. - Parameters of the two:
void * == id key
:key
Value, gets the value of the attribute of the associated object based on key, inobjc_getAssociatedObject
In time,key
Gets the value of the property and returns. - Three parameters:
id value
: The associated value, i.eset
The value passed by the method is saved to the property. - Four parameters:
objc_AssociationPolicy policy
: policy, in what form attributes are stored.
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, // Specify a weak reference to the associated object OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // specify a strong reference to the associated object, Non-atomic OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // specifies that the associated object is copied, non-atomic OBJC_ASSOCIATION_RETAIN = 01401, // Specifies a strong reference to the associated object, Atomicity OBJC_ASSOCIATION_COPY = 01403 // Specifies that the associated object is copied, atomicity};Copy the code
The table is as follows
objc_AssociationPolicy | Corresponding modifier |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | strong, nonatomic |
OBJC_ASSOCIATION_COPY_NONATOMIC | copy, nonatomic |
OBJC_ASSOCIATION_RETAIN | strong, atomic |
OBJC_ASSOCIATION_COPY | copy, atomic |
Eg: The use of OBJC_ASSOCIATION_RETAIN_NONATOMIC in our code is equivalent to the use of nonatomic and strong modifiers.
Notice that in the above list, there is no policy corresponding to the weak modification, because the object is transformed into the DISGUised_ptr_T type DISGUised_object through the DISGUISE function.
disguised_ptr_t disguised_object = DISGUISE(object);
Copy the code
The weak attribute will be destroyed when no object is owned and the pointer is set to nil. After the destruction of the object, the AssociationsHashMap corresponding to the value object still exists in the map, but the object address has been set to nil. It will cause bad address access and cannot be transformed into disguised_object according to the address of the object object. This paragraph can be seen again after the disguised_object.
The values
objc_getAssociatedObject(id object, const void *key);
Copy the code
- A parameter:
id object
: Gets the associated property of which object. - Parameters of the two:
void * == id key
: What attributes, withobjc_setAssociatedObject
In thekey
The corresponding is throughkey
Value to take out thevalue
.
Removing associated Objects
- (void)removeAssociatedObjects {// Remove the associated object objc_removeAssociatedObjects(self); }Copy the code
The specific application
#import "YZPerson.h"
@interface YZPerson (Ext)
@property (nonatomic,strong) NSString *name;
@end
#import "YZPerson+Ext.h"
#import <objc/runtime.h>
@implementation YZPerson (Ext)
const void *YZNameKey = &YZNameKey;
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, YZNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, YZNameKey);
}
- (void)dealloc
{
objc_removeAssociatedObjects(self);
}
@end
Copy the code
When you use it, just use it normally
YZPerson *person = [[YZPerson alloc] init];
person.name = @"jack";
YZPerson *person2 = [[YZPerson alloc] init];
person2.name = @"rose";
NSLog(@"person.name = %@",person.name);
NSLog(@"person2.name = %@",person2.name);
Copy the code
The output
Ios-associated object [4266:52285] person.name = Jack ios-associated object [4266:52285] person2.name = RoseCopy the code
It’s that simple to use
Principle of Associated Objects
Four core objects
The core objects that implement associative object technology are
- AssociationsManager
- AssociationsHashMap
- ObjectAssociationMap
- ObjcAssociation
The source code interpretation
The source of the associated object is in the Runtime source
objc_setAssociatedObject
Looking at the objC-Runtime. mm class, first find the objc_setAssociatedObject function and see its implementation
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
Copy the code
_object_set_associative_reference
To view
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if(i ! = associations.end()) { // secondary table exists ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key);if(j ! = refs->end()) { old_association = j->second; j->second = ObjcAssociation(policy, new_value); }else{ (*refs)[key] = ObjcAssociation(policy, new_value); }}else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects(); }}else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if(i ! = associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key);if(j ! = refs->end()) { old_association = j->second; refs->erase(j); } } } } // release the old value (outside of the lock).if (old_association.hasValue()) ReleaseValue()(old_association);
}
Copy the code
As is shown in
Inside the _object_set_associative_reference function we can find the four core objects that implement the associative object technique we described above. Let’s take a look at the internal implementations one by one and see how they relate to each other.
AssociationsManager
AssociationsManager static AssociationsHashMap *_map;
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return*_map; }};Copy the code
AssociationsHashMap
Now look at AssociationsHashMap
AssociationsHashMap (unordered_map) AssociationsHashMap (unordered_map) AssociationsHashMap (unordered_map
Parameter _Key and _Tp correspond to the Key and Value in the unordered_map. It can be seen that the pass in _Key is UNORDERED_MAP < DISGUised_ptr_t, and the pass in _Tp is ObjectAssociationMap *.
ObjectAssociationMap = ObjectAssociationMap = ObjectAssociationMap = ObjectAssociationMap We can know that object Association is also stored in ObjectAssociationMap in the form of key and Value.
ObjcAssociation
And then we go to objC Society, and you can see that
class ObjcAssociation { uintptr_t _policy; // policy id _value; Public: ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {} ObjcAssociation() : _policy(0), _value(nil) {} uintptr_t policy() const {return _policy; }
id value() const { return _value; }
bool hasValue() { return _value != nil; }
};
Copy the code
From the above code, we can see that objcasSociety stores _policy and _value, which we can see are the values passed in by calling objc_setAssociatedObject, In other words we pass in value and policy in calling objc_setAssociatedObject and those two values are ultimately stored in the ObjcasSociety.
Now that we have a preliminary understanding of the relationship between the four core objects AssociationsManager, AssociationsHashMap, ObjectAssociationMap, and ObjcasSociety, we will continue to read the source code carefully. In what object do the four parameters passed in the objc_setAssociatedObject function serve
read_object_set_associative_reference
_object_set_associative_reference
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (ifany) outside the lock. ObjcAssociation old_association(0, nil); // Obtain new_value id new_value = value? acquireValue(value, policy) : nil; { AssociationsManager manager; AssociationsHashMap & Associations (manager.associations()); // The object is transformed into disguised_object DISGUised_ptr_T type disguised_Ptr_T DISGUised_object = disguised_object (object);if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if(i ! = associations.end()) { // secondary table exists ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key);if(j ! = refs->end()) { old_association = j->second; J ->second = ObjcAssociation(policy, new_value); }else[key] = ObjcAssociation(policy, new_value); }}else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects(); }}else{// come here to say, The value to null / / setting the association to nil breaks the association. The AssociationsHashMap: : iterator I = associations.find(disguised_object);if(i ! = associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key);if(j ! = refs->end()) { old_association = j->second; Refs ->erase(j); } } } } // release the old value (outside of the lock).if (old_association.hasValue()) ReleaseValue()(old_association);
}
Copy the code
**acquireValue** The internal implementation returns different values by judging the policy
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return objc_retain(value);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
Copy the code
- First of all, based on what we passed in
value
afteracquireValue
Function processing returnsnew_value
.acquireValue
Internally, the function returns different values based on policy judgments
typedef uintptr_t disguised_ptr_t;
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }
Copy the code
- After creating
AssociationsManager manager
,manager
The inside of theAssociationsHashMap
namelyassociations
. Then we see the first argument we pass inobject
afterDISGUISE
The function is converted to thetadisguised_ptr_t
The type ofdisguised_object
.
typedef uintptr_t disguised_ptr_t;
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }
Copy the code
- And then it’s processed into
new_value
thevalue
, andpolicy
It was deposited togetherObjcAssociation
In the. whileObjcAssociation
Corresponding to what we passed inkey
Is placed into theObjectAssociationMap
In the.disguised_object
andObjectAssociationMap
withkey-value
Corresponds to the form stored inassociations
There is also inAssociationsHashMap
In the.
The value is null
If the value passed in is null, the associated object is deleted
// Come here to say, The value to null / / setting the association to nil breaks the association. The AssociationsHashMap: : iterator I = associations.find(disguised_object);if(i ! = associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key);if(j ! = refs->end()) { old_association = j->second; Refs ->erase(j); }}Copy the code
References for this article:
Table summarizes
The relationships between these core classes are summarized in a table below
summary
- The associated object is not stored in the associated object’s own memory, but has a global unified
AssociationsManager
In the - One instance object corresponds to one
ObjectAssociationMap
. - while
ObjectAssociationMap
Object that stores multiple associated objects of this instance objectkey
As well asObjcAssociation
. ObjcAssociation
That stores the associated objectvalue
andpolicy
strategy
objc_getAssociatedObject
Objc_getAssociatedObject internally calls _object_get_associative_reference
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
Copy the code
_object_get_associative_reference
function
id _object_get_associative_reference(id object, void *key) { id value = nil; uintptr_t policy = OBJC_ASSOCIATION_ASSIGN; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations()); disguised_ptr_t disguised_object = DISGUISE(object); / / find disguised_object AssociationsHashMap: : iterator I = associations. The find (disguised_object);if(i ! = associations.end()) { ObjectAssociationMap *refs = i->second; / / to check the key and the value ObjectAssociationMap: : iterator j = refs - > find (key);if(j ! = refs->end()) { ObjcAssociation &entry = j->second; value = entry.value(); policy = entry.policy(); // If there are keys and values, fetch the corresponding valuesif(policy & OBJC_ASSOCIATION_GETTER_RETAIN) { objc_retain(value); }}}}if(value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {objc_autoRelease (value); }return value;
}
Copy the code
The key code has been commented out above
objc_removeAssociatedObjects
function
The objc_removeAssociatedObjects function is used to remove all associated objects and internally calls _object_remove_assocations
void objc_removeAssociatedObjects(id object)
{
if(object && object->hasAssociatedObjects()) { _object_remove_assocations(object); }}Copy the code
_object_remove_assocations
Take a look at _object_remove_ASsocations
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if(i ! = AssociationsHashMap (); // Copy all of the associations that need to be removed. ObjectAssociationMap *refs = i->second;for(ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j ! = end; ++j) { elements.push_back(j->second); } // remove the secondary table. delete refs; / / delete associations. Erase (I); } } // the calls to releaseValue() happen outside of the lock. for_each(elements.begin(), elements.end(), ReleaseValue()); }Copy the code
As you can see in the code, take an object and iterate over it to delete all of its associated objects
conclusion
The relationships between these core classes are summarized in a table below
- The associated object is not stored in the associated object’s own memory, but has a global unified
AssociationsManager
In the - One instance object corresponds to one
ObjectAssociationMap
. - while
ObjectAssociationMap
Object that stores multiple associated objects of this instance objectkey
As well asObjcAssociation
. ObjcAssociation
That stores the associated objectvalue
andpolicy
strategy- Accept one when deleting
object
Object, and then iterate over and delete all associated objects for that object - Setting an Associated Object
_object_set_associative_reference
It’s time if the incomingvalue
If null, delete the associated object
References for this article:
Github address github
The Runtime source
Basic principles of iOS
More information, welcome to pay attention to the individual public number, not regularly share a variety of technical articles.