Category是指Objective-C中的类别也叫分类,是一种不需要继承即可给类添加方法的语法技术。
Category利用Objective-C的动态运行时分配机制(Runtime),主要作用是为现有的类(自己的或系统的或三方库的)添加新方法。
Category可以为任何已有的类添加新的方法,包括那些没有源代码的类。
Category的设计原则
Category 的实现可以依赖主类,但主类一定不依赖 Category,也就是说移除任何一个 Category 的代码不会对主类产生任何影响。
Category 可以直接使用主类已有的私有成员变量,但不应该为实现 Category 而往主类中添加成员变量。
所以 Category的设计一定是简单的插拔使用的,就像买个机械键盘来扩展在 MacBook 上的键盘,但拔了机械键盘之后,MacBook 的运行不会受到任何影响。
Category添加方法
Category添加方法是最常用也是最主要、最重要的使用方式。
第一步:我们要新建一个文件。
命名时应该加上大写前缀。
第二步:为这个Category添加方法。
注意:Category方法最好不要和类原有的方法重名,运行时会优先调用Category中的同名方法,也就是Category方法会覆盖掉类中原有的方法,这是不好的,因为Category本意为添加新的方法而不是重写。
所以Category方法在命名时,例如:小写前缀_方法名称 - zoc_viewLog。
如果一个类有多个Category,那么把小写前缀改为小写Category名避免错误,例如:小写Category名_方法名称 -zoconelog_viewLog。
#import <UIKit/UIKit.h>
@interface UIView (ZOCOneLog)
+(void) zoc_viewLog;
@end
#import "UIView+ZOCOneLog.h"
@implementation UIView (ZOCOneLog)
+(void) zoc_viewLog {
NSLog(@"我是ZOCOneLog中的zoc_viewLog方法!");
}
@end
第三步: 调用Category方法。
在需要使用的地方导入头文件。
#import "UIView+ZOCOneLog.h"
然后调用该方法即可。
[UIView zoc_viewLog];
调用结果:
2019-01-15 23:52:08.854 demo[1622:97931] 我是 ZOCOneLog 中的 zoc_viewLog 方法!
Category添加属性
网上很多人、很多博客都说Category不能添加属性呢?
那么Category到底能不能添加属性呢?
这个我们要从Category的结构体开始分析:
typedef struct category_t {
const char *name; //类的名字
classref_t cls; //类
struct method_list_t *instanceMethods; //category中所有给类添加的实例方法的列表
struct method_list_t *classMethods; //category中所有添加的类方法的列表
struct protocol_list_t *protocols; //category实现的所有协议的列表
struct property_list_t *instanceProperties; //category中添加的所有属性
} category_t;
Category的结构体中定义了类名、扩展的类,四个列表分别是实例方法、类方法、协议、属性。
从Category的结构体可以看出这个四个列表就是我们可以添加、扩展的东西。
而没有定义在Category的结构体中的成员变量等就无法添加。
所以答案是Category可以为类添加属性。
但是为什么那么多人、博客都说Category不能添加属性呢?
因为在Category中添加@property,是不会自动生成_成员变量(带下划线的成员变量)和该@property的setter和getter方法。
所以尽管添加了@property,但也因为没有该@property的setter、getter方法和_成员变量而无法使用该@property。
但其实我们可以利用Runtime手动为该@property实现setter、getter方法,从而达到Category可以为类添加属性的效果。
//添加属性
@property (nonatomic, strong) NSString *name;
//以下是添加属性之后的步骤
// 定义属性关联的key:属性名+Key = "属性名"
static const char *nameKey = "name";
// setter方法
-(void)setName:(NSString *)name {
/*
objc_setAssociatedObject:设置关联对象,看样式就很像setter方法
self:给哪个对象添加关联,一般为self
nameKey:属性关联的key,通过这个key来获取该属性
name:赋给属性的值,就是传入的参数
OBJC_ASSOCIATION_RETAIN_NONATOMIC:关联策略
*/
objc_setAssociatedObject(self, nameKey, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// getter方法
-(NSString *)name {
/*
objc_getAssociatedObject:获得关联对象,看样式就很像getter方法
self:从哪个对象获得关联,一般为self
nameKey:属性关联的key,通过这个key来获取该属性
*/
return objc_getAssociatedObject(self, nameKey);
}
//测试代码,导入头文件等步骤省略
UIView *view = [[UIView alloc]init];
view.name = @"SunSatan";
NSLog(@"UIView.name = %@",view.name);
//打印结果
2019-01-15 23:34:30.168 demo[1550:90531] UIView.name = SunSatan
通过以上几个步骤我们就可以实现Category为类添加属性。
调用优先级
如果你遵循Category中不重写类原有方法,同一类中的不同分类(Category) 也不实现重名方法,那么本节内容可以忽略。
调用优先级:类别(Category) > 本类 > 父类。
也就是说会优先调用cateory中的方法,然后调用本类方法,最后调用父类方法,这也是重写类原有方法会覆盖的原因。
一个类的两个分类(Category) 如果实现了同名方法,那么它们之间也有优先级。
我们需要来验证:
首先在已有ZOCOneLog的基础上,我们在再创建一个Category为ZOCTwoLog。
ZOCTwoLog中实现与ZOCOneLog中同名的方法:zoc_viewLog,但是打印信息以此来区分。
#import <UIKit/UIKit.h>
@interface UIView (ZOCTwoLog)
+ (void) zoc_viewLog;
@end
#import "UIView+ZOCTwoLog.h"
@implementation UIView (ZOCTwoLog)
+(void) zoc_viewLog {
NSLog(@"我是 ZOCTwoLog 中的 zoc_viewLog 方法!");
}
@end
第一次运行结果:此时调用的是ZOCOneLog中的zoc_viewLog方法。
2019-01-15 23:47:22.877 demo[1597:95854] 我是 ZOCOneLog 中的 zoc_viewLog 方法!
此时我们来看,文件的编译顺序,文件从上往下编译。
所以ZOCTwoLog比ZOCOneLog先编译。
然后我们将ZOCOneLog拖到ZOCTwoLog文件上面,再运行。
第二次运行结果:此时调用的是ZOCTwoLog中的zoc_viewLog方法。
2019-01-15 23:52:08.854 代理demo[1622:97931] 我是 ZOCTwoLog 中的 zoc_viewLog 方法!
结论:一个类的两个分类(Category)的调用优先级取决于哪个文件后编译,后编译先调用。
注意:Category是在运行时加载的,不是在编译时,因为Category使用的是Runtime。