• iOS-分类Category详解和关联对象


     Category的实现原理

    • Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
    • 在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

    CategoryClass Extension的区别是什么?

    • Class Extension在编译的时候,它的数据就已经包含在类信息中
    • Category是在运行时,才会将数据合并到类信息中

     

    Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?

    • load方法
    • load方法在runtime加载类、分类的时候调用
    • load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用 (子类不会覆盖父类的load方法 因为load是根据函数地址直接调用 而不是是通过objc_msgSend调用具体的可以看这个链接 里面有详细的load 和 initialize的区别)

    Category能否添加成员变量?如果可以,如何给Category添加成员变量?

    • 不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果 实现方式
    1. 因为分类中的属性只是生成了 set 和 get 方法的声明,其实现和下划线变量都没有生成 所以我们需要将为做的事情完成
      #import "MJPerson.h"
      
      @interface MJPerson (Test)
      //{
      //    int _weight;
      //}
      
      //分类里面声明属性 只是生成 属性的set 和 get 方法 但是并没有去实现和生成_变量
      @property (assign, nonatomic) int weight;
      
      
      @property (copy, nonatomic) NSString* name;
      
      //- (void)setWeight:(int)weight;
      //- (int)weight;
      
      @end
      #define MJKey [NSString stringWithFormat:@"%p", self]
      
      @implementation MJPerson (Test)
      
      NSMutableDictionary *names_;
      NSMutableDictionary *weights_;
      + (void)load
      {
          weights_ = [NSMutableDictionary dictionary];
          names_ = [NSMutableDictionary dictionary];
      }
      
      - (void)setName:(NSString *)name
      {
      //    NSString *key = [NSString stringWithFormat:@"%p", self];
          names_[MJKey] = name;
      }
      
      - (NSString *)name
      {
      //    NSString *key = [NSString stringWithFormat:@"%p", self];
          return names_[MJKey];
      }
      
      - (void)setWeight:(int)weight
      {
      //    NSString *key = [NSString stringWithFormat:@"%p", self];
          weights_[MJKey] = @(weight);
      }
      
      - (int)weight
      {
      //    NSString *key = [NSString stringWithFormat:@"%p", self];
          return [weights_[MJKey] intValue];
      }
      
      
      @end
      //用字典来实现有已下问题
      /*1.存储的地方不一样
      //person2.age = 20; // 20是存储在peron2对象内部
      //person2.weight = 50; // 50是存放在全局的字典对象里面
      
       2.线程安全问题 (可以加锁解决)
      */
      
      #import <Foundation/Foundation.h>
      #import "MJPerson.h"
      #import "MJPerson+Test.h"
      
      int main(int argc, const char * argv[]) {
          @autoreleasepool {
              MJPerson *person = [[MJPerson alloc] init];
              person.age = 10;
              person.weight = 40;
              
              
              MJPerson *person2 = [[MJPerson alloc] init];
              person2.age = 20; // 20是存储在peron2对象内部
              person2.weight = 50; // 50是存放在全局的字典对象里面
              
              NSLog(@"person - age is %d, weight is %d", person.age, person.weight);
              NSLog(@"person2 - age is %d, weight is %d", person2.age, person2.weight);
          }
          return 0;
      }
    2. 使用关联对象来实现 
      #import "MJPerson+Test.h"
      #import <objc/runtime.h>
      
      @implementation MJPerson (Test)
      
      - (void)setName:(NSString *)name
      {
      //   关联对象 就是将传进来的name 和 person对象(self) 关联起来 ,达到 一个person对象对应一个name  就不用使用字典的方式来做了
          
      /*    参数讲解
          <#id  _Nonnull object#>  你要给哪一个对象添加关联对象 person对象(self)
       <#const void * _Nonnull key#> 关联的key 取的时候需要用相当于内部应该有个字典
       <#id  _Nullable value#>  关联对象是什么(关联的值) name
       <#objc_AssociationPolicy policy#> 关联策略
       objc_AssociationPolicy                   对应的修饰符
       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
       
      //    objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)
       */
          objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
      }
      
      - (NSString *)name
      {
          // 隐式参数
          // _cmd == @selector(name)
          return objc_getAssociatedObject(self, _cmd);
      }
      
      - (void)setWeight:(int)weight
      {
          objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
      }
      
      - (int)weight
      {
          // _cmd == @selector(weight)
          return [objc_getAssociatedObject(self, _cmd) intValue];
      }
      
      //- (NSString *)name
      //{
      //    return objc_getAssociatedObject(self, @selector(name));
      //}
      //- (int)weight
      //{
      //    return [objc_getAssociatedObject(self, @selector(weight)) intValue];
      //}
      
      //#define MJNameKey @"name"
      //#define MJWeightKey @"weight"
      //- (void)setName:(NSString *)name
      //{
      //    objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
      //}
      //
      //- (NSString *)name
      //{
      //    return objc_getAssociatedObject(self, MJNameKey);
      //}
      //
      //- (void)setWeight:(int)weight
      //{
      //    objc_setAssociatedObject(self, MJWeightKey, @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
      //}
      //
      //- (int)weight
      //{
      //    return [objc_getAssociatedObject(self, MJWeightKey) intValue];
      //}
      
      //static const void *MJNameKey = &MJNameKey;
      //static const void *MJWeightKey = &MJWeightKey;
      //- (void)setName:(NSString *)name
      //{
      //    objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
      //}
      //
      //- (NSString *)name
      //{
      //    return objc_getAssociatedObject(self, MJNameKey);
      //}
      //
      //- (void)setWeight:(int)weight
      //{
      //    objc_setAssociatedObject(self, MJWeightKey, @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
      //}
      //
      //- (int)weight
      //{
      //    return [objc_getAssociatedObject(self, MJWeightKey) intValue];
      //}
      
      //static const char MJNameKey;
      //static const char MJWeightKey;
      //- (void)setName:(NSString *)name
      //{
      //    objc_setAssociatedObject(self, &MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
      //}
      //
      //- (NSString *)name
      //{
      //    return objc_getAssociatedObject(self, &MJNameKey);
      //}
      //
      //- (void)setWeight:(int)weight
      //{
      //    objc_setAssociatedObject(self, &MJWeightKey, @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
      //}
      //
      //- (int)weight
      //{
      //    return [objc_getAssociatedObject(self, &MJWeightKey) intValue];
      //}
      
      @end
      #import <Foundation/Foundation.h>
      #import "MJPerson.h"
      #import "MJPerson+Test.h"
      #import <objc/runtime.h>
      
      int main(int argc, const char * argv[]) {
          @autoreleasepool {
              MJPerson *person = [[MJPerson alloc] init];
              person.age = 10;
              person.name = @"jack";
              person.weight = 30;
              
              
              MJPerson *person2 = [[MJPerson alloc] init];
              person2.age = 20;
              person2.name = @"rose";
              person2.name = nil;
              person2.weight = 50;
              
              NSLog(@"person - age is %d, name is %@, weight is %d", person.age, person.name, person.weight);
              NSLog(@"person2 - age is %d, name is %@, weight is %d", person2.age, person2.name, person2.weight);
          }
          return 0;
      }

       

    关联对象的原理

     

     

    Category的底层结构

    分类的本质(实现原理):

    • Category编译之后的底层结构是 struct category_t (结构体),里面存储着分类的对象方法、类方法、属性、协议信息
    • 在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

    分类底层结构定义的如下: 

    //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;
        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);
    };

    项目中每定义一个分类,底层都会增加一个category_t对象。

    Category源码阅读顺序:

    1. objc-os.mm (runtime入口)
      • _objc_init (runtime初始化)
      • map_images
      • map_images_nolock
    2. objc-runtime-new.mm
      • _read_images
      • remethodizeClass
      • attachCategories
      • attachLists
      • realloc、memmove、 memcpy

    category的加载过程:

    1. 通过runtime加载类的所有分类数据
    2. 将所有分类的方法,属性,协议数据分别合并到一个数组 (后面参与编译的Category数据,会在数组的前面)
    3. 将合并后的分类数据(方法,属性,协议)插入到类原来到数据之前

    由源码可见,对同名方法而言,会优先调用分类中的方法。如果多个分类中包含同名方法,则会调用最后参与编译的分类中的方法。

    摘录源码中核心的attachCategories实现如下(objc4-756.2):

    // Attach method lists and properties and protocols from categories to a class.
    // Assumes the categories in cats are all loaded and sorted by load order, 
    // oldest categories first.
    static void 
    attachCategories(Class cls, category_list *cats, bool flush_caches)
    {
        if (!cats) return;
        if (PrintReplacedMethods) printReplacements(cls, cats);
    
        bool isMeta = cls->isMetaClass();
    
        // fixme rearrange to remove these intermediate allocations
        //方法二维数组
        //[[method_t,method_t],[method_t,method_t,method_t]]
        //二维数组中的一个元素(数组)存放一个分类中的方法列表
        method_list_t **mlists = (method_list_t **)
            malloc(cats->count * sizeof(*mlists));
        //属性二维数组
        property_list_t **proplists = (property_list_t **)
            malloc(cats->count * sizeof(*proplists));
        //协议二维数组
        protocol_list_t **protolists = (protocol_list_t **)
            malloc(cats->count * sizeof(*protolists));
    
        // Count backwards through cats to get newest categories first
        int mcount = 0;
        int propcount = 0;
        int protocount = 0;
        int i = cats->count;
        bool fromBundle = NO;
        while (i--) {
            //取出分类
            auto& entry = cats->list[i];
    
            //取出分类中的方法列表
            method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
            if (mlist) {
                mlists[mcount++] = mlist;
                fromBundle |= entry.hi->isBundle();
            }
    
            property_list_t *proplist = 
                entry.cat->propertiesForMeta(isMeta, entry.hi);
            if (proplist) {
                proplists[propcount++] = proplist;
            }
    
            protocol_list_t *protolist = entry.cat->protocols;
            if (protolist) {
                protolists[protocount++] = protolist;
            }
        }
        //取出(元)类对象中的数据(class_rw_t)
        auto rw = cls->data();
        
        prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
        //将所有分类的方法添加到(元)类对象的方法列表中
        rw->methods.attachLists(mlists, mcount);
        free(mlists);
        if (flush_caches  &&  mcount > 0) flushCaches(cls);
        //将所有分类的属性添加到(元)类对象的属性列表中
        rw->properties.attachLists(proplists, propcount);
        free(proplists);
        //将所有分类的协议添加到(元)类对象的协议列表中
        rw->protocols.attachLists(protolists, protocount);
        free(protolists);
    }
    

    Category和Extension的区别

    区别一

    • Category
      • 专门用来给类添加新的方法
      • 不能给类添加成员属性(其实是可以通过runtime给分类添加属性)
      • 分类中用@property定义变量,只会生成变量的getter、setter方法的声明,不能生成方法实现和带下划线的成员变量。
    • Extension
      • 可以说是特殊的分类,也称作匿名分类
      • 可以给类添加成员属性,但是是私有变量
      • 可以给类添加方法,也是私有方法

    区别二

      虽然有人说Extension是一个特殊的Category,也有人将Extension成为匿名分类,但是两者的区别很大。

    • Category

      • 是运行期决定的
      • 类扩展可以添加实例变量,分类不能添加实例变量(原因:因为在运行期,对象的内存布局已经确定,如果添加实例变量会破坏类的内部布局,这对编译性语言是灾难性的。)
    • Extension

      • 在编译器决定,是类的一部分,在编译器和头文件的@interface和实现文件里的@implement一起形成了一个完整的类。
      • 伴随着类的产生而产生,也随着类的消失而消失。
      • Extension一般用来隐藏类的私有消息,必须有一个类的源码才能添加一个类的Extension,所以对于系统的一个类,比如NSString,就无法添加类扩展。
     

    1.Category 基本使用场合

      经常用的是给类添加方法,协议、属性,编写的分类里面的方法, 最终是在运行过程中的时候合并到类对象(对象方法)/元类对象(类方法)里面 (不是编译的时候合并的)

  • 相关阅读:
    扩欧(exgcd讲解)
    Django组件之forms
    Django组件之用户认证
    Django之中间件
    Django之cookie与session
    Django组件之分页器
    Django之Ajax
    Django之模型层2
    Django之模型层
    Django之模板层
  • 原文地址:https://www.cnblogs.com/junhuawang/p/14041163.html
Copyright © 2020-2023  润新知