OC 的类别和扩展(Category 和 Extension)

OC 的分类(类别)和扩展基本上是面试必问的一个知识点,它们在日常开发中用的也非常多,相关文章网上非常多,好多都是一样的内容,本文也不免俗,按我自己的整理记录一下这个知识点

分类/类别(Category)

分类能够做到的事情主要是:即使在你不知道一个类的源码情况下,向这个类添加扩展的方法,通过分类可以将庞大一个类的方法进行划分,从而便于代码的日后的维护、更新以及提高代码的阅读性,那么怎么样新建一个分类呢?

在项目工程里使用快捷键 Command + N 新建文件,选择 Objective-C File

然后在 File 给这个分类的取个名字, File Type 选择 CategoryClass 选择你需要为那个类新建一个分类

之后就创建好了一个叫 NSString+Utility 的分类

1
2
3
4
5
6
7
8
9
10
11
12
13
// .h
#import <Foundation/Foundation.h>
@interface NSString (Utility)
@end
// .m
#import "NSString+Utility.h"
@implementation NSString (Utility)
@end

然后我们就可以在这个分类里添加方法,既然叫 Utility 那么我就添加一个 NSString 相关的工具方法,比如增加一个给字符串 MD5 加密的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// .h
#import <Foundation/Foundation.h>
@interface NSString (Utility)
/**
* 32位MD5加密
*/
- (NSString*)MD5;
@end
// .m
#import "NSString+Utility.h"
#import <CommonCrypto/CommonDigest.h>
@implementation NSString (Utility)
/**
* 32位MD5加密
*/
- (NSString*)MD5{
// Create pointer to the string as UTF8
const char *ptr = [self UTF8String];
// Create byte array of unsigned chars
unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
// Create 16 byte MD5 hash value, store in buffer
CC_MD5(ptr, (CC_LONG)strlen(ptr), md5Buffer);
// Convert MD5 value in the buffer to NSString of hex values
NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
[output appendFormat:@"%02x",md5Buffer[i]];
return output;
}
@end

分类写好之后,在需要使用分类的文件里 #import "NSString+Utility.h" 就可以使用分类里面的方法了

使用分类需要注意

  • 分类不能为已存在的类添加属性和实例变量,但是分类可以访问原来类中的成员变量
  • 如果分类和原来类出现同名的方法, 优先调用分类中的方法, 原来类中的方法会被忽略
  • 类别可以被继承,如果一个父类中定义了类别,那么其子类中也会继承此类别
  • 在实际开发中要注意的是,分类的方法可能会覆盖于同一个类的其它分类中的方法,但也可能被覆盖,因为不法预知他们的加载优先顺序,出现这种情况通常会在编译时出错,如果在一个开发的SDK中使用了分类, 就最好保证分类名不同于使用者的分类名以及分类方法也不同于使用者的分类方法名,看过开源框架就知道,通常是通过加前缀来区分

虽然默认情况下,我们不能为分类添加属性和成员变量,但是,如果我们一定要为分类添加属性和成员变量也是可以的,这就需要用到 Runtime 里面的 objc_getAssociatedObject / objc_setAssociatedObject 来访问和生成关联对象

继续用上面的分类 NSString+Utility.h 举例,往这个分类里面添加一个 extensionName 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// .h
#import <Foundation/Foundation.h>
@interface NSString (Utility)
//@property在通常情况下,除了生成setter和getter方法以外,如果没有该成员变量还会生成一个带下划线的成员变量,而在分类中,@property只是生成setter和getter方法
@property(nonatomic, copy) NSString *extensionName;
/**
* 32位MD5加密
*/
- (NSString*)MD5;
@end
// .m
#import "NSString+Utility.h"
#import <CommonCrypto/CommonDigest.h>
#import <objc/runtime.h>
static const void *extensionNameKey = &extensionNameKey;
@implementation NSString (Utility)
- (NSString *)extensionName{
return objc_getAssociatedObject(self, extensionNameKey);
}
- (void)setExtensionName:(NSString *)extensionName{
//这个方法有四个参数,分别是:1.源对象,2.关联时的用来标记是哪一个属性的key(因为你可能要添加很多属性),3.关联的对象,4.一个关联策略
return objc_setAssociatedObject(self, extensionNameKey, extensionName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
/**
* 32位MD5加密方式
*/
- (NSString*)MD5{
// Create pointer to the string as UTF8
const char *ptr = [self UTF8String];
// Create byte array of unsigned chars
unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
// Create 16 byte MD5 hash value, store in buffer
CC_MD5(ptr, (CC_LONG)strlen(ptr), md5Buffer);
// Convert MD5 value in the buffer to NSString of hex values
NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
[output appendFormat:@"%02x",md5Buffer[i]];
NSLog(@"%lu", (unsigned long)self.length);
return output;
}
@end

成员变量和属性的区别

声明一个属性:

1
@property (nonatomic, strong) NSString *myString;

声明一个成员变量(实例变量):

1
2
3
4
5
6
@interface ViewController : UIViewController
{
NSString *_myString;
}
@end

我们声明了一个属性,因为现在我们用的编译器已经是LLVM了,所以不再需要为属性声明实例变量了。如果LLVM发现一个没有匹配实例变量的属性,它将为你生成以下划线开头的实例变量_myString,不需要自己手动再去写实例变量。而且也不需要在.m文件中写@synthesize myString;也会自动为你生成setter,getter方法 @synthesize的作用就是让编译器为你自动生成setter与getter方法。那么在.m文件中可以直接的使用 _myString 实例变量,也可以通过属性self.myString.两者都是一样的,只不过后者是通过调用_myString的setter/getter方法

@synthesize 还有一个作用,可以指定与属性对应的实例变量,例如@synthesize myString= xxxx;
那么self.myString其实是操作的实例变量xxxx,而不是_myString了

分类中用@property定义变量,只会生成变量的getter,setter方法的声明,不能生成方法实现和带下划线
的成员变量

类扩展(Extension)

类扩展(Extension)是 Category 的一个特例,有时候也被称为匿名分类,它的作用是为一个类添加一些私有的成员变量和方法,使用类扩展的方法必须在 @implementation 中实现,否则编译会报错

类扩展看起来有点复杂,类扩展即可以声明成员变量又可以声明方法,其实我们很早就已经使用过类扩展了,先看一下类扩展的写法

1
2
3
4
//这就是类扩展的写法,是不是很熟悉
@interface className ()
// ...
@end

类扩展通常定义在 .m 文件中,这种扩展方式中定义的变量和方法都是私有的,当然也可以定义在 .h 文件中,这样定义的变量和方法就是共有的都可以访问和使用,类扩展在 .m 文件中声明私有方法和私有变量是非常好的方式,现在自动生成的 .m 文件的上面都自带扩展了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// .m 文件
#import "ViewController.h"
#import "NSString+Utility.h"
//类扩展区域
@interface ViewController ()
//类扩展属性,私有
@property(nonatomic, copy) NSString *privateName;
//类扩展方法,必须实现
+ (void)extensionClassMethod;
- (void)extensionInstanceMethod;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
//类扩展方法实现
- (void)extensionInstanceMethod{
NSLog(@"This is Instance method");
}
+ (void)extensionClassMethod{
NSLog(@"This is Class method");
}
@end

当然我们也可以参照上面的新建分类的的方法用单独的文件定义类扩展,只是 File Type 选择 Extension,生成的类扩展文件名格式为:主类名_扩展名.h,类扩展文件只有 .h 文件,没有 .m 文件,我们在主类的 .m 文件中 #import 该类扩展文件,并且在主类的 .m文件中实现类扩展文件声明的方法,看看具体怎么实现

首先,新建了一个 ViewController 的类扩展文件 ViewController_ExtensionController.h ,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
//和直接在 .m 文件中使用是一样的
#import "ViewController.h"
@interface ViewController ()
//声明类扩展属性和方法
@property(nonatomic, copy) NSString *extensionName;
+ (void)extensionClassMethod;
- (void)extensionInstanceMethod;
@end

然后,在 ViewController 引入该类扩展文件,并且实现类扩展文件中声明的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import "ViewController.h"
#import "NSString+Utility.h"
#import "ViewController_ExtensionController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.extensionName = @"extensionName";
}
//类扩展方法实现
- (void)extensionInstanceMethod{
NSLog(@"This is Instance method");
}
+ (void)extensionClassMethod{
NSLog(@"This is Class method");
}
@end

类扩展需要注意

  • 类扩展是类的一部分,在编译器和头文件的 @interface 和实现文件里的 @implement 一起形成了一个完整的类
  • 类扩展伴随着类的产生而产生,也随着类的消失而消失。
  • 类扩展一般用来隐藏类的私有消息,你必须有一个类的源码才能添加一个类的 Extension,所以对于系统一些类,如 NSString,就无法添加类扩展

以上就是 OC 中分类(类别)和扩展的介绍

本人刚开始写博客,主要是为了给自己的知识点做一个笔记,方便自己以后查阅,如果能让别人有所启发也是荣幸之至!如有错误,欢迎指正!