Category: 通常我们叫它分类、类别和类目等
Category的主要作用是在不改变原有类的前提下,动态的给这个类添加一些方法。
使用场景:
- 给现有类添加方法;
- 分解体积较大的类文件
- 把framework私有方法公开
实际开发中,category应用的是比较多的,在搭建项目基础架构时可能会单独为Category建个文件夹专门放置需要的类的category文件,比如UIColor、NSdate等的category。
category源码(objc4-818.2 -> objc-runtime-new.h),category结构体如下:
struct category_t {
const char *name; //类名
classref_t cls;
WrappedPtr<method_list_t, PtrauthStrip> instanceMethods; //实例方法列表
WrappedPtr<method_list_t, PtrauthStrip> 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);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
通过源码可以看到, 分类中有实例方法列表、类方法列表、协议列表、属性列表,但没有成员变量列表。因此,分类中不能添加成员变量,分类中是不能添加成员变量的。分类中可以添加属性声明,但添加的属性并不会自动生成成员变量,只会生成get、set方法的声明,需要开发者自行实现访问器方法。
- 分类是用于给原有类添加方法的,原则上它只能添加方法,不能添加成员变量(instance variables),但我们知道可以通过runtime的关联对象方式给分类添加属性(property)。
- 分类中可以写@property,但不会生成setter/getter方法,也不会生成实现以及私有的成员变量,可以编译通过,但引用变量会报错;
- 如果分类中有和原有类同名的方法,会优先调用分类中的方法,调用顺序: 分类 > 本类 > 父类;实际不是覆盖原有类的方法,而是category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,因为运行时在查找方法时是顺着方法列表的顺序查找的,只要一找到对应名字的方法,就不再继续往后查找。
- 如果多个分类中都有和原有类中同名的方法,那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法,即: 分类中的方法优先级高于原类中的方法,后编译的分类优先级高于先编译的分类;
- category是在runtime时加载,不是在编译的时候。
给category添加属性
场景: 新建一个UIButton的分类,实现一个快速创建按钮的类方法,并且添加一个点击回调方法
.h:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIButton (Add)
/// 创建某种风格按钮类方法
+ (instancetype)button;
/// 点击回调方法
/// @param block 点击回到
- (void)addTargetWithBlock:(void(^)(UIButton *btn))block;
@end
NS_ASSUME_NONNULL_END
extension 通常叫它 扩展。
- 类扩展没有名字
- 只有声明没有实现,和原有的类共享一个实现
- 类扩展一般写在.m文件中,可以声明私有属性、声明私有方法(没什么意义)和声明私有成员变量
@interface ViewController ()
@property (nonatomic, strong) Test *test;
// 声明私有方法 不实现 编译时会报警告
// 警告: Method definition for 'privateMethod' not found
- (void)privateMethod;
@end
.m:
#import "UIButton+Add.h"
// step2
#import <objc/runtime.h>
typedef void(^TargetBlock)(UIButton *btn);
// step3
static void *targetKey = &targetKey;
/**
用来标记是哪一个属性的key常见有三种写法:
static NSNumber *numKey;
static void *numKey = &numKey;
static char numKey;
*/
@interface UIButton()
// step1
@property (nonatomic, copy) TargetBlock targetBlock;
@end
@implementation UIButton (Add)
// step4
- (void)setTargetBlock:(TargetBlock)targetBlock {
objc_setAssociatedObject(self, &targetKey, targetBlock, OBJC_ASSOCIATION_COPY);
}
// step5
- (TargetBlock)targetBlock {
return objc_getAssociatedObject(self, &targetKey);
}
+ (instancetype)button {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setupUI];
return btn;
}
- (void)setupUI {
self.layer.cornerRadius = 4;
self.layer.masksToBounds = YES;
self.titleLabel.font = [UIFont boldSystemFontOfSize:16];
[self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[self setTitleColor:[UIColor colorWithWhite:1 alpha:0.7] forState:UIControlStateHighlighted];
[self setBackgroundImage:[UIImage createImageWithColor:[UIColor colorWithRed:255/255.0 green:153/255.0 blue:18/255.0 alpha:1]] forState:UIControlStateNormal];
[self setBackgroundImage:[UIImage createImageWithColor:[UIColor colorWithRed:235/255.0 green:142/255.0 blue:85/255.0 alpha:1]] forState:UIControlStateHighlighted];
[self setBackgroundImage:[UIImage createImageWithColor:[UIColor lightGrayColor]] forState:UIControlStateDisabled];
}
- (void)addTargetWithBlock:(void (^)(UIButton * _Nonnull))block {
self.targetBlock = block;
[self addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnClick:(UIButton *)btn {
if (self.targetBlock) {
self.targetBlock(btn);
}
}
@end
给分类添加属性步骤:
- 声明需要添加的属性
- 引入头文件#import <objc/runtime.h>
- 声明一个标识static void *targetKey = &targetKey;
- 实现关联对象的objc_setAssociatedObject和objc_getAssociatedObject方法
扩展是编译时决议;
只以声明的形式存在,一般都在.m文件中
因为没有自己的实现文件,所以依赖于有源码的类,不能为系统类添加扩展
扩展与分类的区别:
- 分类有名字,扩展没有名字,是一个匿名的分类
- 分类是运行时决议,扩展是编译时决议;所以分类中的方法没有实现不会警告,而扩展声明的方法不实现会报警告;
- 分类可以添加实例方法、类方法,外部类可以访问;扩展能添加属性、方法、实例变量,默认是不对外公开的
- 分类有自己实现的部分,扩展没有,只能依赖对应的类本身来实现
- 可以为系统类添加分类,而不能为系统类添加扩展
参考:
网友评论