category

作者: QYCD | 来源:发表于2021-04-02 17:43 被阅读0次

Category: 通常我们叫它分类、类别和类目等

Category的主要作用是在不改变原有类的前提下,动态的给这个类添加一些方法。

使用场景:

  1. 给现有类添加方法;
  2. 分解体积较大的类文件
  3. 把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方法的声明,需要开发者自行实现访问器方法。

  1. 分类是用于给原有类添加方法的,原则上它只能添加方法,不能添加成员变量(instance variables),但我们知道可以通过runtime的关联对象方式给分类添加属性(property)。
  2. 分类中可以写@property,但不会生成setter/getter方法,也不会生成实现以及私有的成员变量,可以编译通过,但引用变量会报错;
  3. 如果分类中有和原有类同名的方法,会优先调用分类中的方法,调用顺序: 分类 > 本类 > 父类;实际不是覆盖原有类的方法,而是category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,因为运行时在查找方法时是顺着方法列表的顺序查找的,只要一找到对应名字的方法,就不再继续往后查找。
  4. 如果多个分类中都有和原有类中同名的方法,那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法,即: 分类中的方法优先级高于原类中的方法,后编译的分类优先级高于先编译的分类;
  5. 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 通常叫它 扩展。

  1. 类扩展没有名字
  2. 只有声明没有实现,和原有的类共享一个实现
  3. 类扩展一般写在.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文件中
因为没有自己的实现文件,所以依赖于有源码的类,不能为系统类添加扩展

扩展与分类的区别:

  1. 分类有名字,扩展没有名字,是一个匿名的分类
  2. 分类是运行时决议,扩展是编译时决议;所以分类中的方法没有实现不会警告,而扩展声明的方法不实现会报警告;
  3. 分类可以添加实例方法、类方法,外部类可以访问;扩展能添加属性、方法、实例变量,默认是不对外公开的
  4. 分类有自己实现的部分,扩展没有,只能依赖对应的类本身来实现
  5. 可以为系统类添加分类,而不能为系统类添加扩展

参考:

iOS 扩展(Extension)的使用场景与作用
iOS-分类(Category)
iOS Category 详解

相关文章

网友评论

      本文标题:category

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