About Category

作者: YY_Lee | 来源:发表于2018-09-01 21:57 被阅读17次

category在编译阶段会被编译成category_t这种结构体,在运行时分类的方法、协议、属性会被合并到类和元类中。

首先我们先看下category_t的结构:

struct _category_t {
const char *name;//类名
struct _class_t *cls;//类
const struct _method_list_t *instance_methods;//实例方法列表
const struct _method_list_t *class_methods;//类方法列表
const struct _protocol_list_t *protocols;//协议列表
const struct _prop_list_t *properties;//属性列表
};

下面是我新建的一个分类的.m文件,该分类只有一个load方法。

//
//  YYPerson+Test.m
//  CategoryDemo
//
//  Created by apple on 2018/8/25.
//  Copyright © 2018年 liyayun. All rights reserved.
//

#import "YYPerson+Test.h"

@implementation YYPerson (Test)

+ (void)load
{
    NSLog(@"YYPerson (test) load");
}

@end

然后我们查看此文件编译后的代码:

static struct _category_t _OBJC_$_CATEGORY_YYPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
"YYPerson",
0, // &OBJC_CLASS_$_YYPerson,
0,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_YYPerson_$_Test,
0,
0,
};

static struct /*_method_list_t*/ {
unsigned int entsize;  // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_YYPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"load", "v16@0:8", (void *)_C_YYPerson_Test_load}}
};

main函数是iOS app程序的入口,但其实从程序开始运行到main函数被调用之间,程序做了很多事情。系统先读取app的可执行文件,从里面获得dyld的路径去加载dyld,然后dyld初始化运行环境,开启缓存策略;加载程序相关依赖库并对这些库进行链接,最后调用每个库的初始化方法,在这一步,runtime被初始化;当所有的依赖库初始化后,轮到最后一个可执行文件初始化时,runtime会对项目中的所有类进行类结构初始化,然后调用所有的load方法;最后dyld返回main函数地址,main函数被调用。

load方法调用的时机:在程序启动,类初始化时runtime系统自动调用,且只调用一次;

load方法调用的方式:直接找到方法的地址直接调用,不是通过消息发送;

load方法调用的顺序:先调用父类的load方法,再调用子类的load方法(这里是递归调用),最后调用分类的load(分类的load方法调用的顺序跟编译文件的顺序有关,先编译先调用);

从schedule_class_load的实现中我们可以看出父类会先加入到class_loadable_list中

static void schedule_class_load(Class cls)
{
  if (!cls) return;
  assert(cls->isRealized());  // _read_images should realize

  if (cls->data()->flags & RW_LOADED) return;

  // Ensure superclass-first ordering
  schedule_class_load(cls->superclass);

  add_class_to_loadable_list(cls);
  cls->setInfo(RW_LOADED); 
}

下面是runtime源码中call_load_methods方法的实现:

void call_load_methods(void)
{
  static bool loading = NO;
  bool more_categories;

  loadMethodLock.assertLocked();

  // Re-entrant calls do nothing; the outermost call will finish the job.
  if (loading) return;
  loading = YES;

  void *pool = objc_autoreleasePoolPush();

do {
      // 1. Repeatedly call class +loads until there aren't any more
       while (loadable_classes_used > 0) {
        call_class_loads();
    }

      // 2. Call category +loads ONCE
      more_categories = call_category_loads();

    // 3. Run more +loads if there are classes OR more untried categories
  } while (loadable_classes_used > 0  ||  more_categories);

  objc_autoreleasePoolPop(pool);

  loading = NO;
}

从该方法我们可以看出runtime会先调用所有类的load方法,然后再调用所有分类的load方法;

下面是call_class_loads的实现:

static void call_class_loads(void)
{
  int i;

  // Detach current loadable list.
  struct loadable_class *classes = loadable_classes;
  int used = loadable_classes_used;
  loadable_classes = nil;
  loadable_classes_allocated = 0;
  loadable_classes_used = 0;

  // Call all +loads for the detached list.
  for (i = 0; i < used; i++) {
      Class cls = classes[i].cls;
      load_method_t load_method = (load_method_t)classes[i].method;
      if (!cls) continue; 

      if (PrintLoading) {
          _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
      }
      (*load_method)(cls, SEL_load);
  }  

  // Destroy the detached list.
  if (classes) free(classes);
}

从该方法的实现可以看出,runtime取出所有需要调用load方法的类,然后按顺序(跟类的编译顺序有关,先编译先调用)找到类对应的load方法地址去调用load方法;

同样看下call_category_loads,runtime取出所有需要调用load方法的分类,然后按顺序(跟分类的编译顺序有关,先编译先调用)找到分类对应的load方法地址去调用load方法;

static bool call_category_loads(void)
{
  int i, shift;
  bool new_categories_added = NO;

  // Detach current loadable list.
  struct loadable_category *cats = loadable_categories;
  int used = loadable_categories_used;
  int allocated = loadable_categories_allocated;
  loadable_categories = nil;
  loadable_categories_allocated = 0;
  loadable_categories_used = 0;

  // Call all +loads for the detached list.
  for (i = 0; i < used; i++) {
      Category cat = cats[i].cat;
      load_method_t load_method = (load_method_t)cats[i].method;
      Class cls;
      if (!cat) continue;

      cls = _category_getClass(cat);
      if (cls  &&  cls->isLoadable()) {
          if (PrintLoading) {
              _objc_inform("LOAD: +[%s(%s) load]\n", 
                         cls->nameForLogging(), 
                         _category_getName(cat));
          }
          (*load_method)(cls, SEL_load);
          cats[i].cat = nil;
      }
  }
  ......
}

initialize调用的方式:通过objc_msgSend进行调用;

void callInitialize(Class cls)
{
  ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
  asm("");
}

initialize调用的顺序:先调用父类的initialize方法,再调用子类的initialize方法(这里是递归调用),最后调用分类的initialize(分类的initialize方法调用的顺序跟编译文件的顺序有关,先编译先调用);

void _class_initialize(Class cls)
{
  assert(!cls->isMetaClass());

  Class supercls;
  bool reallyInitialize = NO;

  // Make sure super is done initializing BEFORE beginning to initialize cls.
  // See note about deadlock above.
  supercls = cls->superclass;
  if (supercls  &&  !supercls->isInitialized()) {
      _class_initialize(supercls);
  }
  ......
}

initialize方法调用的时机:第一次给类发送消息的时候,只调用一次,由于initialize是通过消息发送机制实现的,所以当子类没有实现initialize方法时,父类的initialize会调用多次;分类如果实现了initialize方法,则会调用分类的initialize方法;

下面我们通过runtime源码查看category的信息是怎么合并到类中的。

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

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

从源码的注解我们也能看出attachCategories方法会将分类的方法列表、属性列表、协议列表添加到类中并且是根据加载的顺序倒序添加,这也当分类重写方法时,类中的方法被“覆盖”的原因,因为分类的方法再列表的前面,所以优先被找到;

下面是具体添加的实现方法attachLists:

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

    if (hasArray()) {
        // many lists -> many lists
        uint32_t oldCount = array()->count;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
    } 
    else {
        // 1 list -> many lists
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
}

从分类的结构中,我们看到分类中是没有成员变量的,在分类中声明成员变量会报错。分类虽然可以声明属性,但声明的属性只会自动生成setter、getter方法的声明,并不会生成对应的实现。但我们可以通过runtime中关联对象的API实现访问分类中属性的效果;

关联对象有下面三个API:

/**********************************************************************
* Associative Reference Support
**********************************************************************/
//根据key取出关联对象的值
id objc_getAssociatedObject(id object, const void *key) {
  
  return _object_get_associative_reference(object, (void *)key);
}

//设置关联对象的值
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
  _object_set_associative_reference(object, (void *)key, value, policy);
}

//删除关联对象
void objc_removeAssociatedObjects(id object) 
{
  if (object && object->hasAssociatedObjects()) {
      _object_remove_assocations(object);
  }
}

关联对象的核心对象是下面这四个:

AssociationsManager  //管理关联对象全局的manager对象
AssociationsHashMap  //存放对象的关联对象信息
ObjectAssociationMap //存放关联的对象信息
ObjcAssociation      //存放关联对象的值和内存策略

首先来看设置关联对象的实现文件,关键步骤我都加了注释:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
    AssociationsManager manager;
    //从manager中取出所有关联类的信息 
    AssociationsHashMap &associations(manager.associations());
    disguised_ptr_t disguised_object = DISGUISE(object);
    if (new_value) {//新值存在
        // break any existing association.
        //根据被关联的对象,取出对象所有的关联信息
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {//该对象关联过对象
            // secondary table exists
            ObjectAssociationMap *refs = i->second;
            //根据key,取出关联信息
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) {//若存在,设置新值
                old_association = j->second;
                j->second = ObjcAssociation(policy, new_value);
            } else {//不存在,新建一个ObjcAssociation对象,存入ObjectAssociationMap中
                (*refs)[key] = ObjcAssociation(policy, new_value);
            }
        } else {//该对象未关联过对象,新建一个ObjectAssociationMap对象,存入AssociationsHashMap对象中
            // create the new association (first time).
            ObjectAssociationMap *refs = new ObjectAssociationMap;
            associations[disguised_object] = refs;
            (*refs)[key] = ObjcAssociation(policy, new_value);
            object->setHasAssociatedObjects();
        }
    } else {//新值不存在
        // setting the association to nil breaks the association.
        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()) {
                old_association = j->second;
                refs->erase(j);//新值为空,删除对应的ObjectAssociationMap对象
            }
        }
    }
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}

接着看关联对象取值的实现文件,也是一步步取值最终取到ObjcAssociation对象,将值赋值给value并返回value:

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

最后看下删除关联对象的实现文件:

void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
    AssociationsManager manager;
    AssociationsHashMap &associations(manager.associations());
    //如果associations.size为0代表没有关联过对象
    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());
}

相关文章

网友评论

    本文标题:About Category

    本文链接:https://www.haomeiwen.com/subject/tcbucxtx.html