分类
分类和类扩展区别、swift中的类扩展(Extensions)
load 和 initialize 方法
一、分类
1. 作用
- 增强已有类的功能,在不破坏原有类的结构的前提下,进行模块化的扩展。
- 为继承自某类的所有子类添加公有的方法(因为分类中的方法可以被子类继承)。
- 用于把一个比较庞大的类分割开来,具有相同功能的方法放到一个分类中,避免类过于庞大。
2. 局限性
- 无法直接添加实例变量(分类没有位置容纳实例变量);
- 方法名称冲突。分类中的方法名称与类中方法名称重名,分类具有更高的优先级,调用的时候执行分类中的方法。
3. 分类的结构、编译和初始化
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 _category_t 的结构体实例里面的。
RunTime 初始化时加载某个类的所有分类数据。把所有分类的数据(方法 属性 协议)合并到一个大的数组中,后面参与编译的分类数据会在数组的前面。最后将合并后的分类数据(方法 属性 协议) 插入到类原来数据的前面。
4. 分类和类中的方法的调用顺序
一个类有多个分类的时候,调用最后一个编译的分类的方法。
从runtime的源码分析为什么会按照上述规则进行调用:
// cls 本类
// cats_list 存储分类数组
// cats_count 存储分类的数量
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
if (slowpath(PrintReplacedMethods)) {
printReplacements(cls, cats_list, cats_count);
}
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
}
/*
* Only a few classes have more than 64 categories during launch.
在启动期间只有少数类拥有超过64个分类
* This uses a little stack, and avoids malloc.
* 使用少量栈避免分类
* Categories must be added in the proper order, which is back
分类肯定会按照合适的顺序被添加,这个顺序就是从后向前
* to front. To do that with the chunking, we iterate cats_list
为了做这个组块,我们迭代分类数组从前到后
* from front to back, build up the local buffers backwards,
建造一个向后的本地缓冲
* and call attachLists on the chunks. attachLists prepends the
调用附加数组在多个组块上
* lists, so the final result is in the expected order.
附加数组频道list上,所以最终的结果就是按照期待的顺序
*/
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ]; // 类方法数组
property_list_t *proplists[ATTACH_BUFSIZ]; // 对象方法数组
protocol_list_t *protolists[ATTACH_BUFSIZ]; // 协议数组
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS); // 是否是元类
auto rwe = cls->data()->extAllocIfNeeded(); // 找到本类的rw 并且判断是否需要额外分配
// 遍历分类数组
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i]; // 具体的分类
// 取出类方法
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 添加到本来的方法列表中
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
// 取出对象方法
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
// 取出协议
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
决定方法调用顺序就是下面源码:
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
// 旧的数组大小
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount; // 需要重新分配的大小
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
// 相当于把原来的数据往后面移动
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// 把新添加的数据放到最前面之前旧数据放的位置
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
5. 分类添加属性 -> 关联对象
分类不可以直接添加成员变量,需要自己实现getter和setter方法,通过关联对象添加成员变量。
objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
、
objc_getAssociatedObject(id object, const void * key)
object: 目标对象。
key: 一个对象可以关联多个新对象,我们需要一个标志来区分他们。key 就起这样的作用。这里的需要key的地址,不关心它指向谁。
value: 表示要添加的属性,
objc_AssociationPolicy:策略,包括 OBJC_ASSOCIATION_ASSIGN、OBJC_ASSOCIATION_RETAIN、OBJC_ASSOCIATION_COPY,分别对应我们在声明属性时的assign、retain、copy。
- (NSString*)title {
return objc_getAssociatedObject(self, &titleKey);
}
- (void)setTitle:(NSString*)title {
objc_setAssociatedObject(self, &titleKey, title, OBJC_ASSOCIATION_COPY);
}
- (BOOL)af_downloadProgressAnimated {
return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_downloadProgressAnimated)) boolValue];
}
- (void)af_setDownloadProgressAnimated:(BOOL)animated {
objc_setAssociatedObject(self, @selector(af_downloadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
关联对象内部设计实现: 关联对象不存储在对象本身的内存中,而是存储在一个全局统一的AssociationsManager中。如果设置关联对象为nil就相当于移除关联对象。
6. 分类和类中的方法关系
- 分为重写的方法、未重写的方法。
- 引用Category中未重写的拓展方法,必须引入Category的声明文件。
引用Category中的重写方法,不用引入Category声明文件,系统会自动调用Category的重写方法。 - 类引用自己的Category时,只能在.m实现文件中引用(如果在.h声明文件中引用自己的分类,会因为头文件原因造成死循环),子类引用父类的分类,在.h或.m文件中引用皆可。
- Category中如果重写了A类从父类继承来的方法s,理论上不会影响同级类(比如B类,A、B继承了同一个父类)中的方法s。
- 子类会不会继承父类的Category:Category中的重写方法会对子类造成影响,但是子类不会主动继承父类的Category中的非重写拓展方法。但是在子类中引入父类Category的声明文件后,子类就会继承Category里的非重写拓展方法。注意,是继承。而继承的具体表现就是:当子类里的方法和父类Category中的方法完全相同(这里的相同只除了方法体外的其他地方相同)时,那么子类里的方法会覆盖掉父类Category,因为这相当于子类重写了继承自父类的方法。
- 类别的方法中,不可以调用super方法。--类别的局限
二、其他
分类(Category)和类扩展(Extension)的区别
- 分类只能扩充方法,不能扩展属性和成员变量(如果包含成员变量会直接报错);如果分类中声明了一个属性,那分类只会生成这个属性的 setter/getter 方法声明,不会生成 setter/getter 方法,也就是不会有实现。
- 类扩展一般只针对自定义的类,对于系统类增加类扩展(无法去直接改变系统文件里的代码,也就是属性无 setter/getter,方法也不能实现)没什么意义。
- 分类是有名称的,类扩展没有名称
swift中的类扩展(Extensions)
swift中没有分类这种写法,swif中只有扩展。swif中的扩展就是向一个已有的类、结构体、枚举类型或者协议类型添加新功能。这包括在没有权限获取原始源代码的情况下扩展类型的能力(即逆向建模)。扩展和OC中的分类类似,不过更加强大,与OC不同的是 swift 的扩展没有名字。
- swift中扩展的优点:
- 代码模块化:把功能性相同的代码放到一个扩展中。如把VC中关于网络请求的、表相关的代理方法、其他视图控件的代理方法、私有方法、公开方法、视图的创建处理等分别放到各自的扩展中,使控制器的代码层次更清晰。
- 与 OC 的扩展不同,Swift 的类扩展里的方法、属性在外部都可以使用,而且支持被继承。
- 注意扩展中不能有存储类型的属性,只可以添加计算型实例属性和计算型类型属性。
三、load 和 initialize
load 和 initialize 方法,不要显示的调用 super 的对应方法。
load 方法的调用顺序
load 是类或分类是在类加载(runtime初始化)的时候被调用的,在 main 函数前,每个类的 load 函数只会自动调用一次。
load 函数调用特点:
- 分类 load 方法不会覆盖本类的 load 方法。当父类和子类都实现 load 方法时,二者的 load 方法都会被调用,父类的 load 方法执行顺序要优先于子类。
- 当子类未实现 load 方法时,在加载该子类时,不会去调用其父类 load 方法。
- 类中的 load 方法执行顺序要优先于类别(Category)。
- 多个类别(Category)都实现了 load 方法, load 方法都会执行,执行顺序与编译顺序一致。
- 当有多个不同的类的时候,每个类 load 执行顺序与编译顺序一致。
initialize 方法的调用顺序
initialize 是在类或其子类收到第一条消息之前
调用,并且只调用一次。这里的消息包括实例方法和类方法的调用。也就是说 initialize 方法是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的 initialize 方法是永远不会被调用的。
- 父类的 initialize 方法会比子类先执行。
- 当子类未实现 initialize 方法时,在该子类收到第一条消息之前,会调用父类 initialize 方法,子类实现 initialize 方法时,则会调用子类的 initialize 方法,不会调用父类的。
- 当有多个 category 都实现了 initialize 方法,会执行最后被被编译的 category 的 initialize 方法。
其他
- 给类发送了一个未实现的方法会执行initialize方法么?
会。