分类,扩展以及关联对象
1. 分类(Category)和扩展(Extension)
1) 分类(运行期)
- 分类就是为已经存在的类,在运行时添加新的方法的一种机制。
- 分类可以添加协议,类方法,实例方法和属性,不能添加实例变量。
- 添加的属性就不会生成成员变量,也不会生成
setter/getter
方法,需要手动添加setter/getter
方法。
2)扩展(编译期)
-
扩展和分类很像,扩展只有声明部分,扩展中的定义的方法需要在类的实现部分去实现。
-
可以定义协议,类方法,实例方法,属性和实例变量,在编译期将其中的定义的数据加到该类的数据类表中。如果不实现其中的方法,就会报错。
-
由于系统的类的是实现部分不对用户开放,所以不能给系统的类添加扩展。
3)区别
- 扩展在编译期决定,是类的一部分,和类的声明部分和实现部分共同组成类,伴随着类的产生而产生,消亡而消亡。扩展也可以用来隐藏类的私有部分。
- 分类是在运行期决议的,扩展可以添加实例变量,分类不能添加实例变量,原因是在运行时,对象的内存布局就已经确定好了,如果添加实例变量,会破坏类的内存布局。
2. 分类的实质
1)分类原理
typedef struct category_t *Category;
struct category_t {
const char *name; // 分类名
classref_t cls; // 类
struct method_list_t *instanceMethods; // 实例方法
struct method_list_t *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);
};
发现里面存在实例方法列表,类方法列表,实例属性列表与类属性列表。
2)分类的加载流程
- 在编译阶段将分类中的方法、属性等编译到一个数据结构
category_t
。 - 将分类中的方法、属性等合并到一个大数组中,后面参加编译的分类会在数组的前面。
- 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面。
也就是说当分类中的方法与原始类中的方法重名时,会先去调用分类中实现的方法。
2. 关联对象
通过关联对象给分类添加属性
动态添加
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
-
参数一:
id object
: 给哪个对象添加属性,这里要给自己添加属性,用self
。 -
参数二:
void * == id key : key
值,根据key获取关联对象的属性的值,在objc_getAssociatedObject
中通过次key
获得属性的值并返回。 -
** 参数三:**
id value :
关联的值,也就是set方法传入的值给属性去保存。 -
参数四:
objc_AssociationPolicy policy :
策略,属性以什么形式保存。
取值
objc_getAssociatedObject(id object, const void *key);
- 参数一:
id object
: 获取哪个对象里面的关联的属性。 - 参数二:
void * == id key
: 什么属性,与objc_setAssociatedObject
中的key
相对应,即通过key
值取出valu
移除关联对象
- (void)removeAssociatedObjects
{
// 移除关联对象
objc_removeAssociatedObjects(self);
}
应用
//分类.h文件
#import "LGPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson (cat)
@property (nonatomic, copy) NSString *lg_nickname;
@property (nonatomic, copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
//分类.m文件
#import "LGPerson+cat.h"
#import <objc/runtime.h>
@implementation LGPerson (cat)
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *) name {
return objc_getAssociatedObject(self, _cmd);
}
@end
//main文件
#import <Foundation/Foundation.h>
#import "LGPerson+cat.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *t = [[LGPerson alloc] init];
t.name = @"1";
NSLog(@"%@", t.name);
}
return 0;
}
运行结果:
总结:
- 分类中没有成员列表,因此不能添加成员变量。但是有属性列表,可以添加属性的声明,但是不会合成set与get方法,如果要使用分类中的属性,需要使用关联对象。
- 分类在运行的时候被整合到类中,扩展在编译的时候被整合到类中,因此分类中的方法不实现不会报错,扩展就会报错。
- 扩展用于声明私有属性与方法。
- 分类中的方法和类中的方法重名,分类中的方法会代替类中的方法。