This article, the 12th in the Objective-C series, covers the underlying structure and use of associative objects.

  • Objective C (7) Object memory analysis
  • Objective C (8) The nature and classification of objects
  • Objective-c (9) KVC and KVO
  • Objective-c (10) Category
  • Objective-c load and Initialize
  • Objective-c (12) Associative objects

Why do we need associated objects?

Can I add member variables to a Category?

We saw in Objective-C (10) categories that you can add protocols, methods, properties, etc. See the structure below.

//from objc-runtime-new.h
struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    structproperty_list_t *instanceProperties; . };Copy the code

If you can add attributes, can you add member variables?

The answer is: no.

In the following example, after calling Crash

Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[BFPerson setAge:]: unrecognized selector sent to instance 0x60000202c4b0’

// Category declaration
@interface BFPerson (BFBoy)
@property (nonatomic.assign) NSInteger age;
@end
    
/ / call
BFPerson *person = [[BFPerson alloc] init];
person.age = 28;
Copy the code

So, we know:

Categories can add attributes, but they don’t add corresponding member variables, and they don’t implement corresponding setter and getter methods.

2. How to add member variables to a Category?

For example, see 01-Category member variables.

Since it is not possible to add attributes to the Category directly, we can use global variables to store the values of member variables and implement corresponding setters and getters to simulate the effect of adding attributes (member variables) to the Category completely.

@interface BFPerson (BFBoy)
@property (nonatomic.assign) NSInteger age;
@end

@implementation BFPerson (BFBoy)
NSMutableDictionary *ages_;
+ (void)load
{
    ages_ = [[NSMutableDictionary alloc] init];
}
- (void)setAge:(NSInteger)age
{
    NSString *key = [NSString stringWithFormat:@"%p".self];
    ages_[key] = @(age);
}
- (NSInteger)age
{
    NSString *key = [NSString stringWithFormat:@"%p".self];
    return [ages_[key] integerValue];
}
@end
Copy the code

The above scheme has some disadvantages:

  • Each time you add an attribute, you need to create a dictionary to hold the corresponding member variable
    • Or you can share a dictionary, but you have to make sure that different attributes have different keys;
  • The implementation is cumbersome.

3. What are associated objects?

Association refers to the association of two objects so that one of them is part of the other. The correlation feature is only available on Mac OS X V10.6, iOSV3.1, and later.

With association, we can add storage to a class’s objects without changing its definition. This is useful when we do not have access to the source code of the class or for binary compatibility purposes.

Associations are keyword-based, so we can add as many associations to any object as we want, using different keywords for each. Association ensures that the associated object is available throughout the lifetime of the associated object (and does not make resources unrecyclable in an automatic garbage collection environment).

Second, the use of associated objects

Example code 02- Associated object

1. Associated object API

2. key

  • The keyword is a pointer to void and must be unique

  • Static variables are often used as keys. Static char is generally recommended for keys — Pointers may be better, and only inside getters and setters.

  • The simpler solution is to use a selector directly, since SEL is generated as a unique constant.

3. policy

  • Policy indicates the value memory semantics, whether they are associated by assignment, reference retention, or copy.

  • Policy also indicates whether atomic or non-atomic.

The association policy is similar to that used when declaring attributes. This association strategy is expressed by using predefined constants.

4. The application

At the beginning of this article, the association object is an efficient and feasible solution to add member variables to a Category

However, we still need to remind you:

  • Correlation objects should be used as a last resort (only when necessary), not to find a solution.
  • categoryIt should not be a problem solving tool of choice by itself

Here, we often see a in the development, or experienced a phenomenon – a developer, in a blog or other ways that a certain technology, this technology has some specific points that is clever tricks and hacks or alternative solutions, the developer at that time, in order to use and use, When the technical points or principles are not fully understood, they are used rashly.

My advice is to ** understand the principles, analyze the scenario, and ** apply these tricks to normal development.

Here are some scenarios used in other open source libraries:

4.1 Class add status

Add private variables to help with the details. When extending a built-in class, it may be necessary to keep track of some additional state, which is the most common use case for associated objects.

For example, AFNetworking uses an association object in the UIImageView category to store an operation object that asynchronously retrieves images from a remote location.

Decoupling 4.2

Use the associated object instead of X, where X represents the following items:

  • Subclassing, when inheritance is more appropriate than composition.
  • Target-action adds an interaction event to the responder.
    • button
    • Gesture recognition, when target-action mode is not enough.
  • Proxy, when events can be delegated to other objects.
  • Message & Message center uses a low-coupling approach to broadcast messages.

Third, the principle of

1. Core objects

The corresponding API is:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) 
Copy the code

1.1 AssociationsManager

Global associated object management class, which has a map dictionary used to store all associated objects of the associated object object.

 class AssociationsManager {
    static AssociationsHashMap *_map;

    AssociationsHashMap &associations(a) {
        if (_map == NULL)
        _map = new AssociationsHashMap();
        return*_map; }};Copy the code

1.2 AssociationsHashMap

AssociationsHashMap is a dictionary of all managed objects of an associated object, as defined by the following class: AssociationsHashMap

class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
    };
Copy the code
  • Inheritance inunordered_map, and the last three parameters are respectivelyhash,equalandallocatorThe implementation of a function.

Where the dictionary corresponds to:

  • Key:disguised_ptr_ttype
  • Value:ObjectAssociationMaptype

1.3 ObjectAssociationMap

ObjectAssociationMap is a real carrier for storing associated objects in an Object.

class ObjectAssociationMap : public std: :map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
};
Copy the code

Where the dictionary:

  • Key:void *Pointer type, which is the key passed in.
  • Value:ObjcAssociationType that stores the incomingvalueandpolicy.

1.4 ObjcAssociation

ObjcAssociation Stores the value and policy of the associated object.

class ObjcAssociation {
    uintptr_t _policy;
    id _value;
};
Copy the code

2. Structure of associated objects

3. Core processes

3.1 Setting Associated Objects

  • objectCannot be nil
  • valueCan be nil, where nil means that the associated object is cleared
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
  if (new_value) {
        // break any existing association.
        / / 1. Value has a value
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if(i ! = associations.end()) {1.1.1 Find the ObjectAssociationMap corresponding to this Object
            ObjectAssociationMap *refs = i->second;
            // 1.1.2 Finding the ObjectAssociation corresponding to the Key in ObjectAssociationMap
            ObjectAssociationMap::iterator j = refs->find(key);
            if(j ! = refs->end()) {// 1.1.2.1 Find ObjcAssociation and save the old value in old_association
                // 1.1.2.2 Resets the new value
                old_association = j->second;
                j->second = ObjcAssociation(policy, new_value);
            } else {
                // 1.1.2.3 No corresponding objcasSociety is found, set the new value directly(*refs)[key] = ObjcAssociation(policy, new_value); }}else {
            1.2.1 ObjectAssociationMap Was Not found, that is, no associated object has been set for Object
            // ObjectAssociationMap is added, ObjcAssociation is added
            ObjectAssociationMap *refs = newObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects(); }}else {
        // 2. Value is empty
        2.1 Finding an ObjectAssociationMap corresponding to an Object
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if(i ! = associations.end()) { ObjectAssociationMap *refs = i->second;// 2.1.1 Finding the ObjcasSociety corresponding to the key
            ObjectAssociationMap::iterator j = refs->find(key);
            if(j ! = refs->end()) {// 2.2.1.1 Find ObjcAssociation and save the old value in old_association
                // 2.2.1.2 Reset the new valueold_association = j->second; refs->erase(j); }}}}Copy the code

3.2 Obtaining Associated Objects

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); 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()) { ObjcAssociation &entry = j->second; value = entry.value(); policy = entry.policy(); if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) { objc_retain(value); } } } } if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) { objc_autorelease(value); } return value; }Copy the code

3.3 Removing An Associated Object

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 ! = associations.end()) { // 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; associations.erase(i); } } // the calls to releaseValue() happen outside of the lock. for_each(elements.begin(), elements.end(), ReleaseValue()); }Copy the code

reference

link

  1. Objc source
  2. Example code 01-Category member variable
  3. Example code 02- Associated object