iOS 深挖 Category 与 Extension 本质区别

18 阅读5分钟

一、前言:90%开发者都会混淆的两个概念

在 iOS 开发日常工作中,Category(分类)Extension(扩展) 是两个出镜率极高的语法特性。

但绝大多数开发者都存在认知误区:

  • 认为「分类和扩展是同一个东西,只是OC和Swift叫法不同」
  • 分不清什么时候用Category,什么时候用Extension
  • 乱用Extension私有扩展、导致代码冲突、属性覆盖、编译报错
  • 不懂底层原理,线上出现 方法覆盖、执行顺序错乱 疑难bug

其实二者底层机制、使用权限、编译逻辑、适用场景完全不同

一句话前置总结:

Category 是 OC 动态编程特性,主打「动态新增方法、解耦业务、批量增强类能力」

Extension 是 Swift 静态语法特性,主打「补全协议、拆分代码、模块化规整」

本文从零拆解底层原理、核心区别、代码实战、落地规范、高频坑点,彻底终结混淆认知。

二、基础定义:彻底分清两者归属

1. Category(分类)

专属 Objective-C 特性,是 Runtime 动态机制的经典实现。

可以在不修改原类源码、不继承、不创建子类的前提下,动态为已有类(系统类/自定义类)新增方法。

文件后缀:.m / .h

2. Extension(扩展)

主要用于 Swift,是静态编译语法,无Runtime动态特性。

作用是对已有类、结构体、枚举进行静态代码补充、协议补全、代码拆分

Swift 中无 Category 概念,所有类增强统一用 Extension。

重要纠正:OC 也有 Extension(类扩展/匿名分类),但几乎不用!日常口语中 OC 说的「扩展」全部指 Category,Swift 全部指 Extension。

三、核心本质差异(底层原理级)

这是全文最核心内容,看懂底层,就不会用错场景。

对比维度Category(OC分类)Extension(Swift扩展)
语言归属Objective-C 专属动态特性Swift 专属静态语法特性
执行机制Runtime 动态加载,运行时合并方法编译期静态合并,无运行时行为
能否新增属性只能通过Runtime关联对象间接新增,无ivar可直接新增计算属性,不能存存储属性
能否重写方法可以重写原类方法(高危!会覆盖)绝对不能重写原有方法,编译报错
多分类优先级多个分类重写同一方法,最后编译的分类覆盖前者不支持重写,无覆盖逻辑
协议支持可实现协议,但不推荐主打协议扩展,Swift模块化核心
代码拆分能力弱,主要用于方法增强极强,按业务、协议、功能拆分代码

四、OC Category 完整实战(代码+场景)

1. 基础用法:系统类能力增强

业务场景:项目高频场景,给 NSString、UIButton、UIImage 新增通用业务方法,不侵入系统源码。

NSString+Valid.h


#import <Foundation/Foundation.h>

@interface NSString (Valid)
// 校验手机号合法性
- (BOOL)isValidPhoneNumber;
// 校验邮箱合法性
- (BOOL)isValidEmail;
@end

NSString+Valid.m


#import "NSString+Valid.h"

@implementation NSString (Valid)

- (BOOL)isValidPhoneNumber {
    if (self.length != 11) return NO;
    NSString *regex = @"^1[3-9]\d{9}$";
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
    return [pred evaluateWithObject:self];
}

- (BOOL)isValidEmail {
    NSString *regex = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}";
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
    return [pred evaluateWithObject:self];
}

@end

2. Category 新增属性(关联对象实战)

Category 无法直接生成成员变量,只能通过 Runtime 关联对象实现属性存储。


#import <UIKit/UIKit.h>

@interface UIButton (Ext)
@property (nonatomic, copy) NSString *btnTag;
@end

#import "UIButton+Ext.h"
#import <objc/runtime.h>

@implementation UIButton (Ext)

- (void)setBtnTag:(NSString *)btnTag {
    objc_setAssociatedObject(self, @selector(btnTag), btnTag, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)btnTag {
    return objc_getAssociatedObject(self, _cmd);
}

@end

3. Category 核心落地场景(OC项目规范)

  • 系统类工具增强:字符串校验、日期格式化、图片处理、按钮点击防重复
  • 业务解耦:将独立业务逻辑抽离分类,瘦身主类
  • 全局统一UI样式:统一按钮、Label、View样式构建方法
  • 埋点、统计通用方法:统一页面点击、曝光埋点

4. Category 高危坑点(必看)

  • 禁止重写系统原有方法:如重写 [UIButton init],会全局污染,引发诡异bug
  • 多个分类新增同名方法,后编译覆盖先编译,方法执行不可控
  • 关联对象不支持 KVO、不参与归档,不能当正式属性重度使用
  • 分类中不要写复杂初始化逻辑,容易引发加载时序问题

五、Swift Extension 完整实战(代码+场景)

Swift Extension 是工程化核心工具,比 OC Category 更规范、更安全、能力更强。

1. 基础能力扩展


import UIKit

extension String {
    // 手机号校验
    func isValidPhone() -> Bool {
        guard count == 11 else { return false }
        let regex = "^1[3-9]\d{9}$"
        return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: self)
    }
}

extension UIButton {
    // 快速创建按钮
    static func create(title: String, color: UIColor) -> UIButton {
        let btn = UIButton(type: .custom)
        btn.setTitle(title, for: .normal)
        btn.setTitleColor(color, for: .normal)
        return btn
    }
}

2. Extension 计算属性实战

Swift Extension 支持计算属性,极大简化代码,无存储属性、无内存占用。


extension Date {
    // 时间戳字符串
    var timeStampStr: String {
        return String(format: "%.0f", self.timeIntervalSince1970)
    }
    
    // 格式化日期字符串
    var ymdStr: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter.string(from: self)
    }
}

3. 高阶用法:协议扩展(Swift 核心精髓)

Category 做不到,Extension 专属高阶能力:协议默认实现,统一全局能力


// 定义协议
protocol ViewLoadable {
    func setupUI()
    func setupConstraint()
}

// 协议默认实现,所有遵守的类自动拥有能力
extension ViewLoadable where Self: UIView {
    func setupUI() {
        backgroundColor = .white
        layer.cornerRadius = 8
        layer.masksToBounds = true
    }
    
    func setupConstraint() {}
}

// 直接遵守,无需重复实现
class CustomView: UIView, ViewLoadable {
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}

4. Extension 核心落地场景

  • 代码模块化拆分:一个大VC用多个Extension拆分生命周期、UI、网络、点击事件
  • 协议默认实现:统一组件样式、统一业务能力、消除冗余代码
  • 系统类能力增强:字符串、日期、数组、字典工具方法
  • 类型规范补充:补全枚举、结构体业务方法

5. Extension 核心限制(避坑)

  • 绝对不能重写原有方法,编译直接报错,从语法层面杜绝覆盖风险
  • 不能添加存储属性,只能用计算属性
  • 扩展方法权限高于全局,同名扩展方法会优先调用自身

六、Category vs Extension 选型指南(团队规范)

1. OC 项目:只用 Category

OC 无成熟 Extension 工程化方案,所有工具扩展、类能力增强统一使用 Category,遵循「一个功能一个分类」。

2. Swift 项目:只用 Extension

Swift 抛弃 Category 思维,统一使用 Extension,利用协议扩展实现高阶复用。

3. 跨语言混编规范

  • OC 分类可以被 Swift 正常调用
  • Swift Extension 无法被 OC 识别
  • 混编项目通用工具,优先用 OC Category 保证双向兼容

七、高频面试题+易错点总结

1. 为什么 Category 不能加属性?

类的成员变量内存结构在编译期已经确定,Category 是运行时动态追加方法,无法修改原有内存布局,只能通过关联对象间接存储数据。

2. Category 多个同名方法谁生效?

工程编译顺序靠后分类会覆盖靠前分类,方法执行结果不可控,业务中绝对禁止同名方法

3. Extension 和 Category 最大区别?

Category 是 Runtime 动态行为,可覆盖方法、动态加载;Extension 是静态编译行为,安全、规范、支持协议扩展,无动态风险。

八、全文总结

  1. OC Category:动态、灵活、有风险,主打系统类增强、工具方法封装,需规避方法覆盖、关联对象滥用问题。

  2. Swift Extension:静态、安全、规范,主打代码拆分、协议默认实现、工程化治理,是Swift项目模块化核心手段。