下面这套回答,基本就是 iOS 高级工程师面试 里关于 Category(分类) 的标准高分答案,既讲清楚「是什么」,也讲清楚「为什么」和「底层原理」。
一、什么情况下用分类(Category)?
常见使用场景:
-
给系统类或第三方类扩展功能
-
不继承、不改源码
-
例如:
UIView+Frame、NSString+Encrypt、NSArray+Safe
-
-
拆分臃肿的类
-
把一个巨型
.m按职责拆成多个 Category -
提高可维护性、可读性
-
-
声明私有方法 / 非正式协议
-
早期用于 delegate(现在多用 protocol)
-
仍常见于内部实现细节隐藏
-
-
解耦业务模块
-
不同业务线维护不同 Category
-
避免主类无限膨胀
-
✅ 总结一句话:
当你想“扩展已有类的功能,而不是新建一个子类”时,优先考虑 Category。
二、分类的优缺点
✅ 优点
|
优点
|
说明
|
|---|---|
|
无需继承
|
不影响原有继承体系
|
|
使用简单
|
引入头文件即可
|
|
运行时动态
|
运行时合并方法
|
|
解耦
|
可按功能拆分
|
❌ 缺点 & 风险
-
不能添加成员变量(ivars)
-
只能添加方法
-
实例变量在编译期确定,无法动态添加
-
-
同名方法覆盖风险
-
Category 方法会“覆盖”原类方法
-
多个 Category 同名方法 → 结果不确定(后编译的生效)
-
-
+load 顺序不可控
-
多个 Category 的
+load执行顺序不明确
-
-
可读性下降
-
方法散落在多个文件中
-
不利于新人理解调用链
-
✅ 面试加分点:
Category 不是用来替代子类的,而是用来“增强”,不能表达 is-a 关系。
三、分类中怎么使用属性(@property)?
1️⃣ 直接声明 @property会发生什么?
@interface NSObject (MyCategory)
@property (nonatomic, copy) NSString *name;
@end
❌ 只会生成声明,不会生成:
-
实例变量
-
getter / setter 实现
➡️ 直接访问会 编译报错 / crash
2️⃣ 正确做法:关联对象(Associated Object)
#import <objc/runtime.h>@implementation NSObject (MyCategory)- (void)setName:(NSString *)name {objc_setAssociatedObject(self,@selector(name),name,OBJC_ASSOCIATION_COPY_NONATOMIC);
}- (NSString *)name {return objc_getAssociatedObject(self, @selector(name));
}@end
✅ 本质:
属性只是语法糖,Category 中用关联对象“手动实现”存取器。
四、关联对象(Associated Object)原理
1️⃣ 核心 API
void objc_setAssociatedObject(id object,const void *key,id value,objc_AssociationPolicy policy);id objc_getAssociatedObject(id object,const void *key);void objc_removeAssociatedObjects(id object);
2️⃣ 底层存储结构(面试常问)
-
每个对象都有一个 Associations HashMap
-
key 是
void * -
value 是
ObjcAssociation(包含值和策略)
简化模型:
Object└── Associations├── key1 → value1├── key2 → value2
✅ 关键点:
-
关联对象 不在对象内存布局中
-
由 runtime 统一管理
-
生命周期 跟随对象销毁
五、关联策略(objc_AssociationPolicy)
|
策略
|
等价属性
|
|---|---|
OBJC_ASSOCIATION_ASSIGN |
assign
|
OBJC_ASSOCIATION_RETAIN_NONATOMIC |
strong, nonatomic
|
OBJC_ASSOCIATION_COPY_NONATOMIC |
copy, nonatomic
|
OBJC_ASSOCIATION_RETAIN |
strong, atomic
|
OBJC_ASSOCIATION_COPY |
copy, atomic
|
⚠️ 没有 weak 策略
六、分类怎么实现一个 weak 属性?
❌ 错误认知
runtime 不支持OBJC_ASSOCIATION_WEAK
✅ 正确实现方式:中间对象 + weak 引用
思路
-
用一个中间对象持有
weak指针 -
关联对象 strong 持有这个中间对象
-
中间对象内部用
__weak
✅ 示例代码
@interface WeakContainer : NSObject
@property (nonatomic, weak) id weakObject;
@end@implementation WeakContainer
@end
- (void)setWeakObj:(id)obj {WeakContainer *container = [[WeakContainer alloc] init];container.weakObject = obj;objc_setAssociatedObject(self,@selector(weakObj),container,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}- (id)weakObj {WeakContainer *container =objc_getAssociatedObject(self, @selector(weakObj));return container.weakObject;
}
✅ 这是面试标准答案
七、高级面试追问(加分项)
Q:Category 和 Extension 区别?
|
对比
|
Category
|
Extension
|
|---|---|---|
|
能否加属性
|
需关联对象
|
可直接
|
|
能否加 ivar
|
❌
|
✅
|
|
编译期
|
运行时
|
编译期
|
|
是否必须实现
|
❌
|
✅
|
Q:Category 为什么不能添加成员变量?
-
对象内存布局在 编译期确定
-
runtime 不支持动态增加 ivar
Q:Category 方法存在哪?
-
存放在 Category 自己的 method list
-
运行时合并到类的方法列表中
-
插入在方法列表前面(优先级高)
八、一句话总结(面试收尾)
Category 适合扩展行为,不适合扩展状态;属性需用关联对象实现;runtime 无 weak 关联策略,只能通过中间对象间接实现 weak。
如果你需要,我可以帮你把这一套整理成 面试逐字稿版 或 技术雷达图版,方便直接背诵。
