Yalda

哈库呐玛塔塔

嗨,我是张志方,一名 iOS 开发者。


Category内部实现真面目

在iOS开发中,我们经常使用Category来解决一些问题,但是在使用的过程中,category内部是如何进行处理的,我们来进行简单的了解,如有不足之处还望在评论区指导。

1、Category真面目

我们知道,所有的OC类和对象,在runtime层都是用struct表示的,category也不例外,在runtime层,category用结构体category_t(在objc-runtime-new.h中可以找到此定义),它包含了:

  • 1)、类的名字(name)
  • 2)、类(cls)
  • 3)、category中所有给类添加的实例方法的列表(instanceMethods)
  • 4)、category中所有添加的类方法的列表(classMethods)
  • 5)、category实现的所有协议的列表(protocols)
  • 6)、category中添加的所有属性(instanceProperties)
typedef 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;
} category_t;

从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。

2、Category加载

Objective-C的运行是依赖OC的runtime的,而OC的runtime和其他系统库一样,是OS X和iOS通过dyld动态加载的。

  • 把category的实例方法、协议以及属性添加到类上
  • 把category的类方法和协议添加到类的metaclass上

attachCategoryMethods做的工作相对比较简单,它只是把所有category的实例方法列表拼成了一个大的实例方法列表,然后转交给了attachMethodLists方法。

1、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA。

2、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休^_^,殊不知后面可能还有一样名字的方法。

3、Category中的+load()方法

如果我们创建了一个类的几个category,每个category都实现了+load方法,每个category中又都写了类中的方法,是如何调用的哪?

1、可以调用,因为附加category到类的工作会先于+load方法的执行

2、+load的执行顺序是先类,后category,而category的+load执行顺序是根据编译顺序决定的。 目前的编译顺序是这样的

按照当前的顺序,Category1会比Category2先编译,后编译的方法会被调用,如果调整category1和category2的顺序就会调用category1中的方法。

@implementation Person (Category)

-(void)showMyself {
  NSLog(@"第一个categoryshowMyself");
}

+(void)load {
   NSLog(@"第一个category中的load");
}

@end

@implementation Person (Category2)
-(void)showMyself {
   NSLog(@"第二个categoryshowMyself");
}

+(void)load {
  NSLog(@"第二个category中的load");
}

@end

虽然对于+load的执行顺序是这样,但是对于“覆盖”掉的方法,则会先找到最后一个编译的category里的对应方法。

4、Category方法覆盖

如何调用原来类中被categiry覆盖的方法?只要顺着方法列表找到最后一个对应方法名字就可以调用了。

Class currentClass = [MyClass class];

MyClass *my = [[MyClass alloc] init];

if (currentClass) {
   unsigned int methodCount;
   Method *methodList = class_copyMethodList(currentClass, &methodCount);
   IMP lastImp = NULL;
   SEL lastSel = NULL;
   for (NSInteger i = 0; i < methodCount; i++) {
       Method method = methodList[i];
       NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method)) 
       								encoding:NSUTF8StringEncoding];
       if ([@"printName" isEqualToString:methodName]) {
           lastImp = method_getImplementation(method);
           lastSel = method_getName(method);
       }
    }
   typedef void (*fn)(id,SEL);
   if (lastImp != NULL) {
   	  fn f = (fn)lastImp;
      f(my,lastSel);
   }
   free(methodList);
}   

5、Category关联对象

在objc-references.mm文件中有个方法_object_set_associative_reference,我们可以看到所有的关联对象都由AssociationsManager管理的。

class AssociationsManager {
  static OSSpinLock _lock;
  static AssociationsHashMap *_map;               *// associative references:  object pointer -> PtrPtrHashMap.*
public:
   AssociationsManager()   { OSSpinLockLock(&_lock); }
   ~AssociationsManager()  { OSSpinLockUnlock(&_lock); }
   AssociationsHashMap &**associations**() {
       if (_map == NULL)
           _map = new AssociationsHashMap();
       return *_map;
   }
};

AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。

runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作。

void objc_destructInstance(id obj) 
{
   if (obj) {
      Class isa_gen = _object_getClass(obj);
      class_t *isa = newcls(isa_gen);
       *// Read all of the flags at once for performance.*
      bool cxx = hasCxxStructors(isa);
      bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);
      *// This order is important.*
      if (cxx) object_cxxDestruct(obj);
      if (assoc) _object_remove_assocations(obj);
      if (!UseGC) objc_clear_deallocating(obj);
    }
    return obj;
}
最近的文章

Hashable 加强

概览 简介 提案缘由 现状 通用的哈希函数 解决方案 Hasher 结构体 hash(into:) 实现要求 ...…

继续阅读

更早的文章

手机触摸屏实现原理

对于屏幕,在生活中随处可见,电脑、电视、手机等等,我们今天要介绍一下平时用的手机触摸屏或者电脑触摸屏的基本原理。触摸屏的主要三大种类有电阻技术触摸屏、表面声波技术触摸屏、电容技术触摸屏,没一类触...…

继续阅读
-->