Category、Extension和属性

Category详解及和Extension的区别
Category为什么不能添加实例变量,但是可以添加方法?
关联对象

  1. Category: 类别,分类

    1. 类别是一种为现有的类添加新方法的方式
    2. 可以添加属性@property,但不会生成成员变量,也不会生成setter和getter方法实现,但是可以通过runtime关联对象生成setter和getter方法实现
    # 3. 是在 运行时决议 的
    

    . 优点

    将类的实现代码分散到不同文件或框架
    可以减少单个文件的体积
    可以把不同的功能组织到不同的Category中
    可以按需加载需要相关功能的Category
    ~创建对私有方法的前向引用~
    模拟多继承
    

    . 分类实现原理

  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);
  
      protocol_list_t *protocolsForMeta(bool isMeta) {
          if (isMeta) return nullptr;
          else return protocols;
      }
};
  1. Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议等信息
  2. 在程序运行的时候,runtime会将Category的数据合并到类信息中(类对象、元类对象中)

. Category加载处理过程

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

. Q

  1. 如果多个category中存在同名的方法,运行时调用那个方法
      由编译器决定,最后一个参与编译的方法会被调用
  1. category中的方法会覆盖原有方法?

    1.不会覆盖,
    2.category加载完成后,类的方法列表中会有两个该方法,只是category的方法被放到了列表前面,运行时查找方法的时候按顺序查找,只要找到对应名字的方法,就会停止
    3.这样看起来是原来方法被覆盖了
    
  2. 为什么Category不能添加成员变量, 而可以添加方法
    Category为什么不能添加实例变量,但是可以添加方法?

    1. 成员变量时属于类实例的,而方法不属于类实例
    2. 分类不能改变类实例的内存结构
    

    从runtime源码出发,Class提供的方法列表存在于class_rw_t中,提供了读写方法;成员变量列表存在于class_ro_t中,只提供了读方法

    Objective-C中类的定义

    typedef struct objc_class *Class;
    

    objc_class结构体

        struct objc_class {
            Class _Nonnull isa OBJC_ISA_AVAILABILITY;
        #if !__OBJC2__
            Class _Nullable super_class                              OBJC2_UNAVAILABLE;
            const char * _Nonnull name                               OBJC2_UNAVAILABLE;
            long version                                             OBJC2_UNAVAILABLE;
            long info                                                OBJC2_UNAVAILABLE;
            long instance_size                                       OBJC2_UNAVAILABLE;
            struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
            struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
            struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
            struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    } OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

OBJC2中objc_class的实际定义

      struct objc_class : objc_object {
          // Class ISA;
          Class superclass;
          cache_t cache;             // formerly cache pointer and vtable
          class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
          ...
      }
      struct class_data_bits_t {
          friend objc_class;
          // Values are the FAST_ flags above.
          uintptr_t bits;
      private:
          ...
      public:
          /// 方法列表,提供了读写方法
          class_rw_t* data() const {
              return (class_rw_t *)(bits & FAST_DATA_MASK);
          }
          void setData(class_rw_t *newData) {
              ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
              // Set during realization or construction only. No locking needed.
              // Use a store-release fence because there may be concurrent
              // readers of data and data's contents.
              uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
              atomic_thread_fence(memory_order_release);
              bits = newBits;
          }
          /// 成员变量列表,只提供了读方法
          const class_ro_t *safe_ro() {
              class_rw_t *maybe_rw = data();
              if (maybe_rw->flags & RW_REALIZED) {
                  // maybe_rw is rw
                  return maybe_rw->ro();
              } else {
                  // maybe_rw is actually ro
                  return (class_ro_t *)maybe_rw;
              }
          }
          ...
      }
  1. 分类可以为类添加属性吗?属性和成员变量的区别是什么

    可以添加属性
    成员变量是属于类实例,存在于类的内存结构中,
    属性是属于语法糖,会自动为该类生成成员变量及setter和getter方法;如果类声明了属性,以及自己实现了setter和getter方法,系统(编译器)就不会自动生成成员变量,如果要关联成员变量,需使用:
        @synthesize name = _name;
    
  2. 属性的实质

    1. 属性就是OC提供的语法糖,自动生成成员变量,并根据不同语义为成员变量自动生成读写方法
    2. 可以自己实现读写方法,如果读写方法全部由自己实现,编译器不会自动生成成员变量(可利用@synthesize name = _name,强制编译器生成成员变量)
    3. category可以声明属性,不过其针对的是属性的读取方法,不会生成成员变量
    4. protocol可以声明属性,不过遵循该协议的类,不一定要拥有该属性,只要拥有setter和getter方法即可
    
  3. Extension

    1. 匿名分类
    2. 可以给类添加属性和方法
    3. ~编译时决议~, 是类的一部分
    4. 伴随类产生,伴随类消失
    5. 可以放在.h和.m文件中,但是方法必须在@impletion中实现
    6. 一般 用来隐藏类的私有信息,必须拥有类的源码才能添加,对于系统类就无法添加
    
  4. objc_setAssociatedObject关联对象原理探究

  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));
      // 找到object对象指针
      DisguisedPtr disguised{(objc_object *)object};
      // 存储着关联策略policy和关联对象的值value
      ObjcAssociation association{policy, value};

      // retain the new value (if any) outside the lock.
      // value引用(引用计数加一)
      association.acquireValue();

      {
          // 关联对象并不是存储在关联对象本身内存中,而是存储在全局统一的一个容器中
          // 由 AssociationsManager 管理并在它维护的一个单例 Hash 表 AssociationsHashMap 中存储
          // 线程安全
          AssociationsManager manager;
          // 获取到容器中存储的哈希表
          AssociationsHashMap &associations(manager.get());
          // value存在
          if (value) {
              // 从associations中object关联对象的结果迭代器
              // try_emplace的用法为,如果key值存在,直接返回value;若不存在,添加一个key&value对
              auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
              if (refs_result.second) {
                  /* it's the first association we make */
                  // 如果map中有值,标识为存在关联对象
                  object->setHasAssociatedObjects();
              }

              /* establish or replace the association */
              // 获取关联对象的关联值map
              auto &refs = refs_result.first->second;
              // 从refs中获取object的关联对象键值为key的结果迭代器
              auto result = refs.try_emplace(key, std::move(association));
              if (!result.second) {
                  // 若存在旧值,替换为新值
                  association.swap(result.first->second);
              }
          } else {    // value不存在(nil),删除关联关系
              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);
                      }
                  }
              }
          }
      }
      // release the old value (outside of the lock).
      // value释放(引用计数-1)
      association.releaseHeldValue();
}
  1. objc_getAssociatedObject原理探究
  id
_object_get_associative_reference(id object, const void *key)
{
      // 关联对象
      ObjcAssociation association{};

      {
          // 关联对象全局容器
          AssociationsManager manager;
          AssociationsHashMap &associations(manager.get());
          // object关联对象的迭代器
          AssociationsHashMap::iterator i = associations.find((objc_object *)object);
          if (i != associations.end()) {
              // object关联对象map
              ObjectAssociationMap &refs = i->second;
              // object关联对象为key的迭代器
              ObjectAssociationMap::iterator j = refs.find(key);
              if (j != refs.end()) {
                  association = j->second;
                  // 判断value 语义,如果是retain,引用计数加
                  association.retainReturnedValue();
              }
          }
      }
      // 返回值
      return association.autoreleaseReturnedValue();
}
  1. objc_removeAssociatedObjects原理探究
  void _object_remove_assocations(id object)
{
      ObjectAssociationMap refs{};
      {
          AssociationsManager manager;
          AssociationsHashMap &associations(manager.get());
          AssociationsHashMap::iterator i = associations.find((objc_object *)object);
          if (i != associations.end()) {
              refs.swap(i->second);
              associations.erase(i);
          }
      }
      // release everything (outside of the lock).
      for (auto &i: refs) {
          i.second.releaseHeldValue();
      }
}

该方法一般用不到,如果要删除某个关联属性,直接将其置位nil即可

你可能感兴趣的:(Category、Extension和属性)