Class extension vs. classification.

  1. categoryCategory n
  • Used specifically to add new methods to a class
  • You cannot add a member attribute to a class, add a member variable, or fetch itruntimeAdd attributes to categories.
  • Classification usingpropertyDefine variables, only generate variablesgetter.setterMethod declaration, cannot generate method implementations and underlined member variables.
  1. extension: class extensions
  • It’s called a special category, also known as an anonymous category
  • You can add member attributes to a class, but as private variables
  • You can add methods to a class, which are also private

Class extension underlying code implementation

First we define a LGPerson class in the main.m file and add attributes and methods to the class extension. We then generate CPP files with clang-rewrite-objc main.m to see the implementation of the underlying c++ code.

@interface LGStudent : NSObject
@property (nonatomic.copy) NSString *name;
@property (nonatomic.assign) int age;

- (void)instanceMethod;
+ (void)classMethod;

@end

@interface LGStudent(a)

@property (nonatomic.copy) NSString *ext_name;
@property (nonatomic.assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end

@implementation LGStudent
- (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 {

        LGPerson * person = [LGPerson alloc];
        [person saySomething];
    }
    return 0;
}
Copy the code

After generating the CPP file we can see that the ext_instanceMethod method added in the class extension is loaded into method_list.

Whether class extension affects class loading and compilation

We know that classification affects the loading and compiling of classes, but does class extension also affect the loading and compiling of classes? Here we add a class extension to the LGPerson class and add a method, and then execute the source code and print ro in the realizeClassWithoutSwift function to take a look.

@interface LGPerson(a)

- (void)ext_instanceMethod;

+ (void)ext_classMethod;
@end

@implementation LGPerson

+ (void)load{}

- (void)saySomething{
    NSLog(@"%s",__func__);
}

+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}



- (void)sayHello1{
    NSLog(@"sayHello1 %s", __func__);
}

+ (void)say6661{
    NSLog(@"say6661 %s", __func__);
}


- (void)ext_instanceMethod{
    NSLog(@"%s",__func__);
}

+ (void)ext_classMethod{
    NSLog(@"%s",__func__);
}

@end
Copy the code

Get (n).big() : p $1.get(n).big() : p $1.get(2).big() : p $1.get(2).big()

associations

- (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");

}
Copy the code

We normally add associatedobject objects to a class via the Runtime objc_setAssociatedObject method, so let’s look at the underlying implementation of the objc_setAssociatedObject method (here in version 779).

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}
Copy the code

The underlying source code calls the _object_set_associative_reference method. The implementation varies from version to version, but the top layer calls the objc_setAssociatedObject method. So this is the idea of apple’s layering, and it’s something we can learn from, and this is what makes the top level API stable.

We then move on to the _object_set_associative_reference method.

First let’s take a look at the simple annotation of the _object_set_associative_reference method and this structure diagram. From the structure diagram, we can see that objcasSociety and ObjCasSociety will be packaged as objcasSociety, and then stored in ObjectAssociationMap in the form of key-value matching. Each object corresponds to an ObjectAssociationMap table. Finally, each object and its corresponding ObjectAssociationMap are stored in AssociationsHashMap in the form of key values, which is a double-layer hashMap structure. AssociationsHashMap is a globally unique table. For specific reasons, we can look at AssociationsManager.

class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    // _mapStorage is a static variable. Only AssociationsManager can call _mapStorage
    // So different associationsManagers call the same _mapStorage
    static Storage _mapStorage;

public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }

    // The resulting AssociationsHashMap is unique
    AssociationsHashMap &get() {
        return _mapStorage.get();
    }

    static voidinit() { _mapStorage.init(); }};Copy the code

We can also see the AssociationsHashMap and RefS_Result data structures by printing. Now that we have an overview of the data structure of the associated objects, how are keys and values stored? So let’s move on.

if (value) {
            // Try_emplace creates an empty TheBucket the first time it is called
            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;
            // The second time, the value of TheBucket will be assigned to association
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else {
            // If value is empty, this will be empty
            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
  template <typename. Ts> std::pair<iterator,bool> try_emplace(const KeyT &Key, Ts &&... Args) {
    // An empty BucketT is created
    BucketT *TheBucket;
    // Where key is the wrapped object, the associated object will determine whether the corresponding TheBucket can be found
    if (LookupBucketFor(Key, TheBucket))
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map.

    // Insert an empty TheBucket if you can't find itTheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...) ;return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }
Copy the code

Finally, summarize the value setting process of the associated object:

Procedure 1: Create an AssociationsManager management class. 2: Obtain a unique global static hash Map. 3: Determine whether the inserted association value exists. Step 4: Create an empty ObjectAssociationMap to fetch the key-value pair of the query. Step 5: Insert an empty BucketT if the key is not found. Step 6: Mark the object with an associationobject 7: Replace empty 8 in BucketT with an ObjectAssociation consisting of the current decoration policy and value: mark ObjectAssociationMap as false\ for the first time

The associated object is inserted empty in process 1: according to the DisguisedPtr, the iterator in AssociationsHashMap is found. The iterator is cleared. The iterator is cleared