美文网首页
iOS-总结Category

iOS-总结Category

作者: IBigLiang | 来源:发表于2019-01-08 17:53 被阅读0次

Category这个知识点,相信有了解过的同学们都知道,用到的地方虽然不多,但是相对比较集中,一般一个类的内容信息过于庞大,导致里面代码信息过于混乱,我们就可以试着用category去管理这个类。其实举个简单的例子,就好像“人”这个物种,现在不就是有各类人吗,这也是一种分类,而每一类人体现出来的特点或者说是优点等等都是不一样的。
言归正传,我们还是通过面试题来入手,从而一步一步了解Category,这里总共有四个面试题,如下:

问题1:Category的实现原理
问题2:Category和Class Extension的区别是什么
问题3:Category中有load方法吗?什么时候调用?load方法能继承吗?
问题4:load、initialize方法的区别是什么?它们在Category中调用的顺序?以及在出现继承的时候,它们之间的调用过程

这里,同样我们还是通过用代码入手,这里笔者定义了一个BLPerson类和它的BLPerson+Test的分类,以及通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc BLPerson+Test.m -o BLPerson+Test.cpp这个命令行(这个命令行笔者就不解释了)得到的BLPerson+Test.m的底层c++实现文件BLPerson+Test.cpp(代码链接在最底部)。这时我们可以在BLPerson+Test.cpp文件中看到对应分类的m文件的实现,当然我们主要关心的是这个Category对应的实现具体是什么样子的,其实就是如下代码:

struct _class_t {
    struct _class_t *isa;
    struct _class_t *superclass;
    void *cache;
    void *vtable;
    struct _class_ro_t *ro;
};
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;
};

Category在底层就是一个_category_t结构体,其实对于这个结果,相信看了笔者之前iOS-Objective-C的本质这篇文章的都知道,一个类的本质其实就是一个结构体。在_category_t这个结构体中,我们可以很清楚的看到,它里面包含了类的名称、对象方法列表、类方法列表、协议列表和属性列表,而_class_t这个结构体里面包含的就是这个类的isa、父类等信息
这里在分类中加入了两个方法:

@interface BLPerson (Test)
- (void)instanceTest;
+ (void)classTest;
@end

#import "BLPerson+Test.h"

@implementation BLPerson (Test)

- (void)instanceTest {
    
    NSLog(@"BLPerson (Test) instanceTest");
}

+ (void)classTest {
    
    NSLog(@"BLPerson (Test) classTest");
}

@end

在cpp文件中,我们可以看到以下代码:

static struct _category_t _OBJC_$_CATEGORY_BLPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "BLPerson",
    0, // &OBJC_CLASS_$_BLPerson,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_BLPerson_$_Test,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_BLPerson_$_Test,
    0,
    0,
};

从这个代码中我们可以看到_category_t结构体中对应的instance_methods和class_methods就是上面所展示:

(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_BLPerson_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_BLPerson_$_Test,

接下来,笔者在BLPerson的本类中添加同样的两个方法:

@interface BLPerson : NSObject
- (void)instanceTest;
+ (void)classTest;
@end

#import "BLPerson.h"

@implementation BLPerson

- (void)instanceTest {
    
    NSLog(@"BLPerson instanceTest");
}

+ (void)classTest {
    
    NSLog(@"BLPerson classTest");
}

@end

然后笔者在main函数中调用BLPerson的这个两个方法:

#import <Foundation/Foundation.h>
#import "BLPerson.h"
#import "BLPerson+Test.h"

int main(int argc, const char * argv[]) {
    
    BLPerson *person = [[BLPerson alloc] init];
    [person instanceTest];
    [BLPerson classTest];
    
    return 0;
}

控制台打印如下:


image.png

接下来,我们把分类去掉:

#import <Foundation/Foundation.h>
#import "BLPerson.h"

int main(int argc, const char * argv[]) {
    
    BLPerson *person = [[BLPerson alloc] init];
    [person instanceTest];
    [BLPerson classTest];
    
    return 0;
}

控制台打印如下:


image.png

所以我们得到,不管分类是否导入,只要分类中有一样的方法,则就会优先调用分类中的方法。
这是为什么呢?大家都知道,方法的调用,其实就是runtime中的消息发送,先发送消息子,然后在方法列表中查找对应的方法;而且当方法名称一样时,肯定是优先调用先找到的那个。所以从这一点上面,我们可以看出,分类中的同名方法应该是排在本类的前面,只有这样,在调用这个方法的时候,才会先调用分类中的方法。事实就是如此,其实我们还是需要从源代码入手,这里笔者只摘录最直接的部分代码,如下:

//将本类中原有的方法列表地址向后移动一定位置,移动的位置个数就是分类中的方法列表的个数
memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
// 将所有分类的方法列表,拷贝到经过移动之后的本类的方法列表中
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));

这样,大家就应该明白,为什么会先调用分类的方法,因为在系统底层的现实上就是将分类的方法放在本类之前。
至此,问题1的答案已经很明白了:

Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息;在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)。

至于第二个问题,笔者在这里就直接给出答案,也不做具体分析了:

Class Extension是在编译的时候就已经把数据包含在本类中了,而Category是在运行时,才会将数据合并到本类的信息中。

接下来,我们再来分析第三个和第四个问题,首先,在本类和分类中都添加load和initialize这两个方法,代码如下:

// BLPerson的实现
@implementation BLPerson

+ (void)load {
    
    NSLog(@"BLPerson load");
}

+ (void)initialize {
    
    NSLog(@"BLPerson initialize");
}
.........

// 分类的实现
#import "BLPerson+Test.h"

@implementation BLPerson (Test)

+ (void)load {
    
    NSLog(@"BLPerson+Test load");
}

+ (void)initialize {
    
    NSLog(@"BLPerson+Test initialize");
}
........

然后我们启动程序,这里我们先后两次启动,第一次启动的时候,我们不调用BLPerson类,代码如下:

#import <Foundation/Foundation.h>
//#import "BLPerson.h"
//#import "BLPerson+Test.h"

int main(int argc, const char * argv[]) {
    
//    BLPerson *person = [[BLPerson alloc] init];
    
    return 0;
}

这时候控制台打印出来的结果是:


image.png

第二次启动,我们调用调用BLPerson类,代码如下:

#import <Foundation/Foundation.h>
#import "BLPerson.h"
#import "BLPerson+Test.h"

int main(int argc, const char * argv[]) {
    
    BLPerson *person = [[BLPerson alloc] init];
    
    return 0;
}

这时候控制台打印出来的结果是:


image.png

我们可以看到,不管代码中有没有调用BLPerson类,load方法都会调用,而且它不是通过消息发送的方式来调用的,是通过方法的地址直接调用。所以第三个问题的答案就是:

Category中又load方法,在程序启动时,系统就会通过runtime加载load方法。load方法也可以继承

而initialize这个方法,在程序调用BLPerson类时,会先去调用test分类中的initialize方法。其实大家有兴趣可以试试,如果多次调用BLPerson这个类的时候,initialize方法的调用情况,其实这个方法只会每个类对应的只会调用一次,而且是在第一次调用这个类的时候才会触发。所以第四个问题的答案是:

1、load是程序一运行就根据函数地址直接调用,initialize是类第一次接收到消息的时候调用。2、先调用本类的load再调用分类,而且不同的分类,先编译的先调用;分类中的initialize会被优先调用,后编译的分类先调用,这个是按照消息发送机制实现(上述中,笔者就说了分类的方法会在运行时,合并到本类方法列表中,而且放在本类方法列表之前)

这里有个很特别的情况,大家有兴趣可以去试一试,如果一个子类BLStudent继承于BLPerson,他们都没有分类,这时候BLPerson中实现initialize方法,而BLStudent不实现,这个时候看看控制台打印情况,你会有不一样的发现。
代码链接:https://github.com/IBIgLiang/iOS-CategoryStudy

相关文章

  • iOS-总结Category

    Category这个知识点,相信有了解过的同学们都知道,用到的地方虽然不多,但是相对比较集中,一般一个类的内容信息...

  • AVPlayer音乐锁屏功能

    [iOS]iOS AudioSession详解 Category选择 听筒扬声器切换iOS- 关于AVAudioS...

  • iOS-Category原理

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

  • 2-1 分类 类后面加括号

    1.什么是分类 = 类别。通过runtime将方法添加进 类里面 iOS-分类(Category)[https:...

  • iOS底层原理总结 - Category的本质

    iOS底层原理总结 - Category的本质 iOS底层原理总结 - Category的本质

  • iOS-虚线Category

    实际项目中已用到 绝对可用。记录下来方便以后查阅 .h #import typedef NS_ENUM(NSIn...

  • iOS-分类(Category)

    Category是Objective-C 2.0之后添加的语言特性,分类、类别其实都是指的Category。Cat...

  • iOS-浅谈Category

    Category简介和基本使用 一个类有不同的功能,可以创建不同的分类进行区分。 比如有一个Person类,自己有...

  • iOS-分类(Category)

    在iOS开发中我们常使用Category来给类添加方法或者属性,原理是什么呢. 分类概况 我们先尝试写个分类.NS...

  • iOS-分类 Category

    Category(objc_category) Category是表示一个指向分类的结构体的指针,其定义如下: 分...

网友评论

      本文标题:iOS-总结Category

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