美文网首页
七、分类(Category)的结构与加载

七、分类(Category)的结构与加载

作者: LNG61 | 来源:发表于2018-06-22 09:59 被阅读0次

一、Category的声明

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

Category主要包含实例方法、类方法、遵循的协议和属性,主要是为了扩充类而设计的。

二、Category的生成

首先来定义一个分类

@interface TestCategoryClass(Category)
- (void)categoryMethod;
-@property(nonatomic) NSString *categoryProperty;
@end

@implementation TestCategoryClass(Category)
- (void)categroyMethod{}
- (void)setCategoryProperty:(NSString *)categoryProperty{}
- (NSString *)categoryProperty{return nil;}
@end

接下来用clang重写该文件,来分析下重写后的cpp文件:

1. 生成方法列表

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_TestCategoryClass_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    3,
    {{(struct objc_selector *)"categoryMethod", "v16@0:8", (void *)_I_TestCategoryClass_Category_categoryMethod},
    {(struct objc_selector *)"setCategoryProperty:", "v24@0:8@16", (void *)_I_TestCategoryClass_Category_setCategoryProperty_},
    {(struct objc_selector *)"categoryProperty", "@16@0:8", (void *)_I_TestCategoryClass_Category_categoryProperty}}
}; 

分类TestCategoryClass(Category)共三个方法,分别是categoryMethod、setCategoryProperty:和categoryProperty。

2.生成属性

static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_TestCategoryClass_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"categoryProperty","T@\"NSString\",N"}}
}; 

分类中的property只会声明setter和getter,不会生成setter和getter。

3.分类初始化

_OBJC_$_CATEGORY_TestCategoryClass_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "TestCategoryClass",
    0, // &OBJC_CLASS_$_TestCategoryClass,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_TestCategoryClass_$_Category,
    0,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_TestCategoryClass_$_Category,
};

这里进行的工作是初始化category_t结构体,将属性、方法和协议等填充进去,并将分类的cls指向对应的类。

三、Category的加载

1._read_images

Category的加载主要在_read_images中,接下来看下_read_images中的实现:

map_images()
  ->map_images_no_lock()
    ->_read_images()

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses{
  // other
  
  for(EACH_HEADER){
      category_t **catlist = _getObjc2CategoryList(hi, &count);
      bool hasClassProperties = hi->info()->hasCategoryClassproperties();
      
      for(int i = 0; i < count; i++){
        category_t *cat = catlist[i];
        Class cls = remapClass(cat->cls);
        bool classExists = NO;
        if(cat->instanceMethods || cat->protocols || cat->instanceProperties){
          addUnattachedCategoryForClass(cat, cls, hi);
          if(cls->isRealized()){
            remethodizeClass(cls);
            classExists  = YES;
          }
        }

        if(cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties)){
          addUnattachedCategoryForClass(cat, cls->ISA(), hi);
          if(cls->ISA()->isRealized()){
            remethodizeClass(cls->ISA());
          }
        }
      }
  }
}

_read_images主要的工作就是将category的方法和属性通过remethodizeClass方法添加在class上,如果是类方法的话则添加到元类上。
addUnattachedCategoryForClass的作用是将category添加到NXMap中,以方便之后取出来使用。

2.attachCategories

接下来看一下remethodizeClass的实现,remethodizeClass主要是调用了attachCategories将cls和category连接起来:

void remethodizeClass(Class cls){
    ...
    attachCategories(cls, cats, true);
}

void attachCategories(Class cls, category_list *cats, bool flush_caches){
    method_list_t **mlists;
    property_list_t **proplists;
    protocols_list_t ** protolists;
    
  int i = cats->count;
  while(i--){
      auto& entry = cats->list[i];
      mlists[mcount++] = entry.cat->methodsForMeta(isMeta);
      proplists[propcount++] = entry.cat->methods->propertiesForMeta(isMeta, entry.hi);
      protolists[protocount++] = entry.cat->protocols;
  }

  auto rw = cls->data();
  rw->methods.attachLists(mlists, mcount);
  rw->properties.attachLists(proplists, propcount);
  rw->protocols.attachLists(protolists, protocount);
}

可以看出,attachCategories就是将方法、属性和协议添加到类cls中。这里要注意一下,attachLists会将新添加的list添加到前面去,这样Category如果有同名方法的话就会优先被调用到,也是为什么会覆盖类的同名方法的原因。

四、Category与Extension的区别

Extension(类扩展)是Category的一个特例,其名字为匿名(为空)。比如:

@interface TestCategoryClass()
@end

Extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。Extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。

小结

  1. Category的主要作用是为已经存在的类添加方法。
  2. Category在编译期决议,然后在加载时将Category附加到对应类上。
  3. Category的方法会覆盖原有类的同名方法。
  4. Extension是类的一部分,能添加变量,必须有一个类的源码才能添加extension。

相关文章

网友评论

      本文标题:七、分类(Category)的结构与加载

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