美文网首页
iOS-category

iOS-category

作者: 李永开 | 来源:发表于2019-01-11 18:08 被阅读0次

一.简介

category可以不通过继承的方式给类添加额外的方法.

@interface NSString (catas)
- (NSString *)retInstanceMethod;
+ (NSString *)retClassMethod;
- (BOOL)isEqualToString:(NSString *)aString;
@end

@implementation NSString (catas)
- (NSString *)retInstanceMethod
{
    return @"实例方法哦";
}
+ (NSString *)retClassMethod
{
    return @"类方法哦";
}
- (BOOL)isEqualToString:(NSString *)aString
{
    return YES;
}

# 给NSString添加了一个 retInstanceMethod()实例方法,所有继承NSString的对象都可以使用retInstanceMethod().
NSString *str = @"hello wordl";
[str retInstanceMethod];

# 给NSString添加了一个 retClassMethod()类方法,所有继承NSString的对象都可以使用retClassMethod().
[NSString retClassMethod];

二.使用runtime分析category

1.分类的实例方法

#打印NSString方法列表
unsigned int count;
Method *methodArr = class_copyMethodList(NSString.class, &count);
for (int i =0; i < count; i ++)
{
    Method method = methodArr[i];
    SEL sel = method_getName(method);
    NSLog(@"NSString的实例方法[%d]:%s",i,sel_getName(sel));
  }
 //释放
 free(methodArr);

打印:共589个方法,筛选retInstanceMethod得到如下列表
2019-01-11 17:18 iOSWorld[884:21371] NSString的实例方法 [3]:retInstanceMethod

我们发现NSString (catas)这个分类的方法被添加到了NSString这个类的方法列表中.

2.分类的类方法

  //打印NSString元类(meta_Class)的类方法列表
  unsigned int count;
  Method *methodArr = class_copyMethodList(object_getClass(NSString.class), &count);
   for (int i =0; i < count; i ++)
   {
       Method method = methodArr[i];
       SEL sel = method_getName(method);
       NSLog(@"NSString的类方法[%d]:%s",i,sel_getName(sel));
   }
 //释放
 free(methodArr);

打印:共94个类方法,筛选retClassMethod
2019-01-11  iOSWorld[910:23001] NSString的元类的类方法方法 [5]:retClassMethod

发现NSString (catas)的类方法被添加到了NSString元类的类方法列表中.

  • 注意NSString.classobject_getClass(NSString.class)的区别

3.分类中重写已有的方法

unsigned int count;
Method *methodArr = class_copyMethodList(NSString.class, &count);
for (int i =0; i < count; i ++)
{
    Method method = methodArr[i];
    SEL sel = method_getName(method);
    NSLog(@"NSString的实例方法[%d]:%s",i,sel_getName(sel));
 }
 //释放
 free(methodArr);

打印:发现有两个一模一样的isEqualToString:方法
2019-01-11 17:46:53 iOSWorld[935:25910] NSString的实例方法[47]:isEqualToString:
2019-01-11 17:46:53 iOSWorld[935:25910] NSString的实例方法[513]:isEqualToString:

所以说:分类中重写原来类的方法并不是replace原来的方法,而是添加了一个方法,而且新添加的方法调用顺序在原方法之上看起来好像被替换了 0.0

  • 如果两个分类都重写了原来的方法,那么方法调用优先级要看Build phases - Compile Sources的编译顺序,谁后被添加谁的优先级就更高

三.总结

category本质:我们编写的category在编译时会生成一个struct catagory_t,里面包含了category的名称、方法列表等,然后使用runtime将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);
};

四.category加载过程

  1. objc-os.mm文件加载_objc_init()
  2. _objc_init内部实现
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
  1. 加载镜像
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
  1. 先加载&map_images--->load_images就是加载load方法
    return map_images_nolock(count, paths, mhdrs);
  1. 读取镜像
    _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
  1. 初始化类表,将方法添加到方法列表中,添加分类的方法
    也就是说:编译的时候只编译变量,无关方法列表,方法类表是在运行时添加的
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses{
        //1.如果是第一次,则初始化类名表
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

      //2.把类添加到类表中去,并将标记懒加载的类,标记了懒加载类方便后续的初始化它们
     gdb_objc_realized_classes

      //3.重映射类,重映射的类都是非懒加载的类

     // 4.把方法添加到方法列表中去
            //---sel_cname函数内部就是将SEL强转为常量字符串
            const char *name = sel_cname(sels[i]);
            //---把方法名添加到namedSelectors表中去
            sels[i] = sel_registerNameNoLock(name, isBundle);
     //6.把协议添加到protocol_map表中去
     //7.重新映射协议表,因为优化后的images可能是正确的,但是并不确定
         过程:查看表中的value:protocol_t能否对的着
    //8.初始化所有非懒加载类的一些信息(rw、ro)
    //9.初始化所有懒加载类的一些信息(rw、ro)

    //10.发现分类
          //如果是实例方法
         if (cat->instanceMethods ||  cat->protocols  ||  cat->instanceProperties)
         //---将未添加到类的分类添加分类表中去
         addUnattachedCategoryForClass(cat, cls, hi);
         //---将分类的method、protocol、property添加到class中去
         remethodizeClass(cls);

          //如果是类方法
          if (cat->classMethods  ||  cat->protocols  ||  (hasClassProperties && cat->_classProperties)) 
         //将分类添加到元类的分类数组中去
         addUnattachedCategoryForClass(cat, cls->ISA(), hi);
}

接下来是remethodizeClass添加过程的实现

    //---获取类对应的分类数组,并从分类的哈希表中删除掉分类数组
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/)))
    {
        if (PrintConnecting)
        {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        //---添加分类的方法、协议、属性到class中去
        attachCategories(cls, cats, true /*flush caches*/);
    }


    //开始添加---将类和类别的方法传进去
    category_list *cats;
    attachCategories(cls, cats, true /*flush caches*/);

    //创建临时数组用于存储分类的方法、协议、属性
    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));

    //遍历所有分类,把每个分类里的方法添加到临时数组中去
    //这里是倒序添加分类方法的,后编译的分类方法在前面
    while (i--) 
    {
        //---返回类的方法列表,并拼接在临时方法数组中
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        //---返回类的属性列表,并拼接在临时属性数组中
        //---返回类的协议列表,并拼接在临时协议数组中

      auto rw = cls->data();
      //1.把分类中的方法添加class中的方法列表里去
      rw->methods.attachLists(mlists, mcount);
      //2.把分类中的属性添加class中的属性列表里去
      rw->properties.attachLists(proplists, propcount);
      //3.把分类中的协议添加class中的协议列表里去
      rw->protocols.attachLists(protolists, protocount);

      //attachLists的实现
      //旧方法列表往后移动addedCount个位置
      memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
      //新方法列表拷贝到array()->lists表头到addedCount-1处
      memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));

    }

五.骚操作

  1. 我使用分类重写了原来类的方法,但我还想使用原来那个方法怎么办?
    答案:你可以使用runtime的class_copyMethodList去遍历方法,并调用.
  2. 怎样给category增加属性?
    答案: 可以使用runtime的AssociatedObject(关联对象)来实现.

拓展 : associatedObject实现原理

  • 系统会生成一个associationsManager.
  • associationsManager管理着一个associationsHashMap
  • associationsHashMap的key为实例对象,value为另一个associationMap
  • associationMap的key为我们传进去的key(&indexKey),value为我们传进去的value和OBJC_ASSOCIATION_ASSIGN
objc_setAssociatedObject(self, &indexKey, value, OBJC_ASSOCIATION_ASSIGN);

//manager里面有个AssociationsHashMap
class AssociationsManager {
    static AssociationsHashMap *_map;
}

//

注意事项:

1.objc_setAssociatedObject()需要传一个const void * _Nonnull key类型的key,也就是一个指针
2.定义一个static const void *key = &key,将自己的地址赋给key,可以直接将key传进去.加static是为了不让其他类拿到key这个变量.
3.或者: static const char key,将&key传进去.
4.或者:直接写字符串. key = @"name";以为字符串都是放在常量区的,永不变.
5.或者:写@selector(name),因为@selector(name)在一个类中也是独有的.

相关文章

  • Objective-C的本质(5)——Category原理

    参考:iOS-Category原理iOS底层原理总结 - Category的本质 1、load能继承吗 可以继承,...

  • iOS-Category管理对象的原理

    上次我们说到 iOS-Category添加成员变量[https://www.jianshu.com/p/9c466...

  • iOS-Category

    转载:http://book.51cto.com/art/201105/262265.htm category是O...

  • iOS-category

    一.简介 category可以不通过继承的方式给类添加额外的方法. 二.使用runtime分析category 1...

  • iOS-Category

    面试题 Category的使用场合是什么? Category的实现原理 Category编译之后的底层结构是str...

  • iOS-Category详解

    OC中提供的category特性可以让我们动态的为现有类添加新的行为,以比继承更为简洁的方法来对Class进行扩展...

  • iOS-Category原理

    参考篇:iOS-分类(Category) 前言:本文简述Category原理,如有错误请留言指正。 第一部分:有关...

  • ios-category解析

    category 精简于Dive into Category 一、作用 a) 将类的实现分开在几个不同的文件中 b...

  • ios-Category相关

    Category 这是一种比继承更简洁的方法来对类进行扩展,不需创建子类就能为现有的类提供方法,它不但可以添加新方...

  • iOS-Category、Extension

    一、分类的使用注意事项: 1、分类只能增加方法,不能增加成员变量。 原因: Category是运行时决议,因为在...

网友评论

      本文标题:iOS-category

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