Category In Objective-C

Category In Objective-C

在这里插入图片描述

来源

Objective-C 2.0中新增的语言特性

可以用来做什么 ?

  1. 扩展已有的类 (仅限于为已有类增加方法) ;

  2. 分割类实现 ;

    官方原文: Distribute the implementation of your own classes into separate source files—for example, you could group the methods of a large class into several categories and put each category in a different file.

    几个显而易见的好处 :
    a. 可以减少单个文件的体积 ;
    b. 可以把不同的功能组织到不同的category里 ;
    c. 可以由多个开发者共同完成一个类 ;
    d. 可以按需加载想要的category ;

  3. 声明私有方法 ; (虽然是apple推荐, 但我并没有理解这么做有什么卵用) ;

    /*
     * 官方示例代码, MyClass.m file
     */ 
    #import "MyClass.h"
     
    @interface MyClass (PrivateMethods)
    // private method declarations
    @end
     
    @implementation MyClass
    // private method definitions
    @end
    
  4. 模拟多继承 ;

  5. 把framework的私有方法公开 ;

Category的本质是什么 ?

所有的Objective-C的类和对象,在 runtime 层都是用 struct 表示的,category 也不例外, 在 objc-class.h 中, 找到如下定义 :

/* 
 *	Category Template
 */
typedef struct objc_category *Category;

struct objc_category {
	char *category_name;
	char *class_name;
	struct objc_method_list *instance_methods;
	struct objc_method_list *class_methods;
 	struct objc_protocol_list *protocols;
};

Category 的定义中, 不难窥测, 其可为与不可也 ~ (可以增加 instance_methods, class_methods, protocols, 不可增加 instance, 故若在 Category 中新增 Property, 本质只会保留方法声明)

Category的实现分析

1. 生成测试代码

/*
 * SQIClass.h 文件
 */
 #import <Foundation/Foundation.h>

@interface SQIClass : NSObject

- (void)sqiFunc;

@end

@interface SQIClass(SQIAddition)

@property(nonatomic, copy) NSString *sqiProperty;

- (void)sqiFunc;

@end
/*
 * SQIClass.m 文件
 */
 #import "SQIClass.h"

@implementation SQIClass

- (void)sqiFunc
{
    NSLog(@"%@",@"I'm SQIClass func");
}

@end

@implementation SQIClass(SQIAddition)

- (void)sqiFunc
{
    NSLog(@"%@",@"I'm SQIAddition func");
}

@end

说明:

确定分析工具

$ clang -rewrite-objc SQIClass.m

clang -rewrite-objc 的作用是把oc代码转写成c/c++代码,这里选用它来窥探 OC Category 的底层实现.

具体分析 (SQIClass.cpp文件的源码分析)

以上命令会在当前目录生成大小为 3.8MB 的文件 (SQIClass.cpp), 代码共 65475 行 🤣🤮

生成文件及相关源码下载地址

Category 的 C++ 定义
// 起始于 65354 行
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;
};

与上文的 struct objc_category 基本一一对应, 多出来的 properties 暂时忽略.

Category增加的实例方法列表
// 起始于 65431 行
static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_SQIClass_$_SQIAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"sqiFunc", "v16@0:8", (void *)_I_SQIClass_SQIAddition_sqiFunc}}
};

实例方法列表为: _OBJC_$_CATEGORY_INSTANCE_METHODS_SQIClass_$_SQIAddition, method_count 值为 1, 说明共有 1 个方法.

Category增加的属性列表
// 起始于 65441 行
static struct /*_prop_list_t*/ {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count_of_properties;
	struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_SQIClass_$_SQIAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	1,
	{{"sqiProperty","T@\"NSString\",C,N"}}
};

属性列表为: _OBJC_$_PROP_LIST_SQIClass_$_SQIAddition, count_of_properties 的值为 1, 说明共有 1 个属性.

创建分类实体, 并用上面的列表对象初始化
static struct _category_t _OBJC_$_CATEGORY_SQIClass_$_SQIAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"SQIClass",
	0, // &OBJC_CLASS_$_SQIClass,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_SQIClass_$_SQIAddition,
	0,
	0,
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_SQIClass_$_SQIAddition,
};
在DATA段下的objc_catlist section里保存了一个大小为1的_category_t的数组
// start line: 65472
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
	&_OBJC_$_CATEGORY_SQIClass_$_SQIAddition,
};

Category是如何加载的 ?

觉得这个问题本身有问题, 没有具体说明白是加载什么, 也没有说明白加载到哪 !

Category是 Objective-C 运行时的一部分, 其所有相关数据结构都定义于 runtime 中, 而OC的runtime和其他系统库一样,是OS X和iOS通过dyld动态加载的.

Category与类的结合

OC运行时在初始化时, 会把 Category 的方法列表与类的方法列表合并 (实例方法列表与合并到类上, 类方法列表合并到类的 metaclass 上);

方法覆盖

当 Category 中的方法与原类中的方法名相同, 原类中的方法会被覆盖, 原因是新的方法列表中, Category新增的方法在前, 运行时在查找方法时, 找到一个对应名称的方法后, 不会向下继续;

注意: 方法覆盖与是否 import 分类的头文件无关, 即无需 import 分类的头文件, 就会产生覆盖现象 !

// SQIClass.h 文件


#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface SQIClass : NSObject

- (void)sqiFunc;

@end

NS_ASSUME_NONNULL_END

// SQIClass.m 文件

#import "SQIClass.h"

@implementation SQIClass

- (void)sqiFunc {
    NSLog(@"SQIClass's sqiFunc");
}

@end

// SQIClass + SQIAddition.h 文件

#import "SQIClass.h"

NS_ASSUME_NONNULL_BEGIN

@interface SQIClass (SQIAddition)

- (void)sqiFunc;

@end

NS_ASSUME_NONNULL_END

// SQIClass + SQIAddition.m 文件

#import "SQIClass+SQIAddition.h"

@implementation SQIClass (SQIAddition)

// warnning: Category is implementing a method which will also be implemented by its primary class
- (void)sqiFunc {
    NSLog(@"SQIAddition's sqiFunc");
}

@end

// SQITest.m 文件

#import "SQIClass.h"

@interface SQITest ()

@end

@implementation SQITest

- (void)test {
    // print: SQIAddition's sqiFunc
    [[SQIClass new] sqiFunc];
}


@end

+load方法的调用

  1. 在 +load 方法中可以调用 Category 方法么 ?

可以, Category 方法的附加要早于 +load 方法的调用.

  1. 当原类, 多个原类的 Category 中都实现了 +load 方法, 调用顺序是怎样的 ?

原类早于Category, Category的 +load 调用顺序取决于编译顺序, 若在 Xcode 中编译, 可以从 Compile Sources中查看或调整编译顺序.


备注

  1. 博客时间: 2023.03.18 ;
  2. 环境: macOS Monterey 12.6, Xcode Version 14.2, clang version 14.0.0
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

依旧风轻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值