博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Category知识点
阅读量:6606 次
发布时间:2019-06-24

本文共 7258 字,大约阅读时间需要 24 分钟。

1 、Category的简单应用

Category的使用非常频繁,他可以动态的为已经存在的类添加新的行为。当你不想对原类文件进行修改的时候,你就可以通过添加Category去定制自己需要的方法。这样,不需要访问其源代码、也不需要创建子类,Category使用简单的方式,实现了类的相关方法的模块化,把不同的类方法分配到不同的分类文件中,这样可以保证类的原原来的基础上,较小的改动就可以增加需要的功能。比如扩展系统提供的方法,扩展CocoaPod管理的方法时,我们会经常用到Category。

上example(我习惯将原理写在代码注释中,所以认真看哟,当然最后我也会做总结):

创建MJPerson类和两个分类MJPerson+Test、MJPerson+Eat:

//MJPerson.h和MJPerson.m文件#import 
@interface MJPerson : NSObject@property(nonatomic,strong)NSString *personName;@property(nonatomic,assign)int personAge;-(void)run;@end===============================================#import "MJPerson.h"@implementation MJPerson-(void)run{ NSLog(@"I can run");}@end复制代码
//MJPerson+Test.h和MJPerson+Test.m文件#import "MJPerson.h"@interface MJPerson (Test)-(void)test;@end===============================================#import "MJPerson+Test.h"@implementation MJPerson (Test)-(void)test{    NSLog(@"I can test");}@end复制代码
//MJPerson+Eat.h和MJPerson+Eat.m文件#import "MJPerson.h"@interface MJPerson (Eat)
@property(nonatomic,strong)NSString *name;@property(nonatomic,assign)int age;-(void)eat;-(void)eat1;+(void)eat2;+(void)eat3;@end===============================================#import "MJPerson+Eat.h"@implementation MJPerson (Eat)- (void)setName:(NSString *)name{ self.personName = name;}- (void)setAge:(int)age{ self.personAge = age;}-(NSString *)name{ return self.personName;}-(int)age{ return self.personAge;}-(void)eat{ NSLog(@"I can eat");}-(void)eat1{ NSLog(@"实例方法eat1");}+(void)eat2{ NSLog(@"类方法eat2");}+(void)eat3{ NSLog(@"类方法eat3");}@end复制代码
//ViewController.m文件#import "ViewController.h"#import "MJPerson.h"#import "MJPerson+Test.h"#import "MJPerson+Eat.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];   }-(void)touchesBegan:(NSSet
*)touches withEvent:(UIEvent *)event{ MJPerson *person = [[MJPerson alloc] init];// 这种方法调用的方式都是通过 objc_msgSend(对象名,@selector(方法名)) 来实现的 [person run]; [person test]; [person eat]; person.age = 10; person.name = @"ychen3022"; NSLog(@"刚刚赋值的age: %d",person.age); NSLog(@"刚刚赋值的name: %@",person.name);// 分类的实现原理// 给person发送一个消息:通过objc_msgSend(person,@selector(eat))// 发消息的这种机制又是怎么实现的呢?去哪里找到对应的方法呢?// -(void)eat是个实例方法,存储在class(类对象)里面的,所以是给实例对象发送消息,通过isa指针找到这个class(类对象),在class(类对象)的方法里面找到实例方法的实现(IMP),然后进行调用 }- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated.}@end=========================================================打印结果:CategoryTest[2737:706571] I can runCategoryTest[2737:706571] I can testCategoryTest[2737:706571] I can eatCategoryTest[2737:706571] 刚刚赋值的age: 10CategoryTest[2737:706571] 刚刚赋值的name: ychen3022复制代码

2 、探究Category本质

使用clang编译命令行,把MJPerson+Eat.m文件转成.cpp文件

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc MJPerson+Eat.m复制代码

从文件中,我们可以找到有个_category_t的结构体,如下:

struct _category_t {	const char *name; //类名,并不是category小括号里写的名字,而是类的名字	struct _class_t *cls; //cls要扩展的类对象,编译期间这个值是不会有的,在app被runtime加载时才会根据name对应到类对象	const struct _method_list_t *instance_methods; //存放这个category所有的对象方法列表	const struct _method_list_t *class_methods; //存放这个category所有的类方法列表	const struct _protocol_list_t *protocols; //存放这个category所有的协议列表	const struct _prop_list_t *properties; //存放这个category所有的属性列表};复制代码

同时,我们也可以找到MJPerson+Eat这个分类的数据内容 (可以继续找到对应的详细内容,我就不贴代码了)

static struct _category_t _OBJC_$_CATEGORY_MJPerson_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {	"MJPerson",	0, // &OBJC_CLASS_$_MJPerson,	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MJPerson_$_Eat,	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MJPerson_$_Eat,	(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_MJPerson_$_Eat,	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MJPerson_$_Eat,};复制代码

通过上面的分析,我们可以感觉到Category被存储在这个结构体里的,那一直是存储在这里吗?调用的时候也是从这个结构体调用?带着疑问往下看:

从下载下来runtime的objc4-723源码,分析得出: 在运行时初始化的时候,会加载项目中所有的类,并将Category中的内容附加到类对象中。 所以当含有同样的方法名时,会优先调用分类中的方法(因为后添加进来先调用)。同是分类中的同名方法,谁后编译就调用谁的。

obje-runtime-new.mm文件中的部分代码如下:

// 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.// 将方法列表、属性、协议从分类文件中附加到class(类对象)中static voidattachCategories(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;    // 通过循环,将某个类的所有分类文件中的方法列表、属性列表、协议列表都放到了mlists、proplists、protolists    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);    //将所有分类的对象方法附加到class(类对象)的方法列表中    rw->methods.attachLists(mlists, mcount);    free(mlists);    if (flush_caches  &&  mcount > 0) flushCaches(cls);    //将所有分类的属性附加到class(类对象)的属性列表中    rw->properties.attachLists(proplists, propcount);    free(proplists);    //将所有分类的协议附加到class(类对象)的协议列表中    rw->protocols.attachLists(protolists, protocount);    free(protolists);}复制代码

通过上面的分析,我们可以知道: 在编译阶段,Category的底层结构是struct category_t,里面存储了该分类文件中的数据(方法、属性、协议等)。 在程序运行时候,通过runtime加载某个类的所有Category数据,把该类对应的所有Category数据合并到一个大数组中。 最后,将合并后的Category数据插入到该类原来数据的前面。

3、总结

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

<2>Category和Class Extension的区别是什么?

  • 扩展是在编译的时候,它的数据就已经包含在类信息中, 分类是在运行时,才会将数据合并到类信息中

  • 分类中原则上只能增加方法(能添加属性的的原因只是通过runtime解决无setter/getter的问题而已)

  • 扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型(使用范围只能在自身类,而不是子类或其他地方)

  • 扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中

  • 扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,扩展所声明的方法必须依托对应类的实现部分来实现

  • 定义在 .m 文件中的扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。扩展是在 .m 文件中声明私有方法的非常好的方式

<3>Category中能定义属性吗? 是可以的,但是分类可以用@property来添加属性,此种方式会自动生成对应属性的set和get方法的声明,但是没有set和get方法的实现,也不会自动生成带有“_”的属性。 如上example所示,我们必须在MJPerson+Eat.m中对属性age、name的get、set方法进行重写,而且是对其原来的类MJPerson的属性personAge和personName进行赋值的,否则会报错。

转载于:https://juejin.im/post/5bc3535ce51d450e3d2d2eb7

你可能感兴趣的文章
Best Part
查看>>
ClassPathXMLApplicationContext上下文加载过程
查看>>
JS模拟select下拉菜单
查看>>
线性方程组迭代求解——Jacobi迭代算法(Python实现)
查看>>
vmware workstation14永久激活密钥分享
查看>>
PHP面向对象的进阶学习(抽像类、接口、final、类常量)
查看>>
iOS 多线程 之 GCD(大中枢派发)(一)
查看>>
mysql用户与权限管理笔记
查看>>
Myeclipse中打开接口实现类的快捷键
查看>>
<20190516> 一次比较糟糕的售后维修体验(某硕主板)
查看>>
iOS网络篇2-http协议通信规则
查看>>
删除sql dump中的AUTO_INCREMENT
查看>>
使用JdbcTemplate和JdbcDaoSupport
查看>>
C博客作业--指针
查看>>
版本12.2.0.1.0数据库,复制种子数据库快速创建租户数据库PDB
查看>>
吴忠军中华演出网
查看>>
Page翻页分页css代码,分页div+css代码
查看>>
编程之美 第1章 游戏之乐——游戏中碰到的题目(十一)
查看>>
mysql for Mac 下创建数据表中文显示为?的解决方法
查看>>
2016阿里巴巴73款开源产品全向图
查看>>