Category的用途:
1.类的拆解:根据不同功能将代码放入不同分类中(模拟多继承),减少单个类的体积;
2.公开“私有方法”(私有方法前向引用):类扩展中已声明私有方法,去分类.h声明;
- (a) 类扩展中声明私有方法
@interface Person ()
// 类扩展声明的私有方法
- (void)ExtensionPrivateMethod ;
@end
@implementation Person
- (void)ExtensionPrivateMethod {
NSLog(@"类扩展@interface()有声明,私有方法");
}
@end
- (b) 分类.h再次声明
#import "Person.h"
@interface Person (Category1)
// .h中声明私有方法,.m中无需实现
- (void)ExtensionPrivateMethod;
@end
#import "Person+Category1.h"
@implementation Person (Category1)
@end
- (c) 调用私有方法,需要引入类和分类
#import "Person.h"
#import "Person+Category1.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
// 私有方法前向引用
[person ExtensionPrivateMethod];
}
return 0;
}
总结:
1.如果不在分类.h中公开私有方法,可以通过performSelector方式强制调用私有方法或者拿到它的函数指针调用
2.正常的方式调用就需要在分类.h中公开(声明)私有方法,不实现
Person *person = [[Person alloc] init];
[person performSelector:@selector(ExtensionPrivateMethod)];
Category结构
分类不能直接添加成员变量,因为category中没有存储成员变量对应的指针变量;
可以利用关联对象技术实现为category添加成员变量的效果;
分类能添加属性,但是不会生成setter和getter方法;
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; // 添加的实例属性列表
struct property_list_t *_classProperties; // 类属性(class, nonatomic, strong)
}
- 关联对象技术实现为分类添加成员变量
#import "Person+Category.h"
#import <objc/runtime.h> // 类相关函数
//#import <objc/message.h> // 消息相关函数
@implementation Person (Category)
// 关联对象 能够为分类添加属性
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN);
}
- (NSString *)name {
return objc_getAssociatedObject(self, @"name");
}
@end
关联对象API
设置类关联属性 void _object_set_associative_reference(object, key, value, policy)
获取类关联属性 id _object_get_associative_reference(object, key);
移除类所有关联属性 void _object_remove_assocations(object);
- 设置类关联属性 _object_set_associative_reference(object, key, value, policy)
/**
@param object 准备被关联的对象
@param key 要关联的值对应的key 标识
@param value 关联的值
@param policy 策略
*/
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// 存放 policy 和 关联值value 默认为空
ObjcAssociation old_association(0, nil);
// 根据 策略policy 对 value 进行加工,按照策略对value进行 copy或者retain
// newValue ---> 准备关联的值
id new_value = value ? acquireValue(value, policy) : nil;
{
// 关联对象管理类,C++实现一个类
// 维护了一个单例Hash表 AssociationsHashMap对象
AssociationsManager manager;
/**
初始化一个 AssociationsHashMap 对象 associations
用来维护对象 和 ObjectAssociationMap 之间的关系
单例对象 AssociationsHashMap
*/
AssociationsHashMap &associations(manager.associations());
// 获取关联对象的索引 ---> DISGUISE 对这个指针地址 按位取反
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// 根据对象指针查找对应的一个ObjectAssociateMap结构的map
AssociationsHashMap::iterator i = associations.find(disguised_object);
// 从全局容器中找到了ObjectAssociationMap
if (i != associations.end()) {
// secondary table exists --> i->second
// i->first 表示对象指针
// i->second 表示获取 ObjectAssociationMap
ObjectAssociationMap *refs = i->second;
// 根据传递进来的key进行查找
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) { // ObjcAssociation 找到了
old_association = j->second;
// 如果关联对象已存在,则通过ObjcAssociation赋新值(替换最新的value)
j->second = ObjcAssociation(policy, new_value);
} else {
// 如果关联对象不存在,则创建新的关联对象
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
/**
create the new association (first time).
如果没有 ObjectAssociationMap 表
则第一次创建一个 ObjectAssociationMap 表
*/
ObjectAssociationMap *refs = new ObjectAssociationMap;
// 全局容器中的key = disguised_object
// 全局容器中的value = 这个新创建的map
associations[disguised_object] = refs;
/**
新关联的值 new_value 通过策略 policy 组装成 ObjcAssociation
作为新创建好的map, ObjectAssociationMap[key]
key 传进来的key
*/
(*refs)[key] = ObjcAssociation(policy, new_value);
// 最后设置这个对象是有关联对象
object->setHasAssociatedObjects();
}
} else {
/**
如果new_value为 空,那么删除该关联对象
*/
AssociationsHashMap::iterator i = associations.find(disguised_object);
// 查找到了这个ObjectAssociationMap
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
// 通过key 到 ObjectAssociationMap中查找
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 找到了ObjcAssociation
old_association = j->second;
// 擦除操作,从ObjectAssociationMap中移除
// 所以我们想移除关联对象,可以将关联对象的值设置为nil进行移除
refs->erase(j);
}
}
}
}
// 释放旧值
if (old_association.hasValue()) ReleaseValue()(old_association);
}
- 获取类关联属性 _object_get_associative_reference(object, key)
// 用来得到已有的关联对象
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
// 初始化AssociationsManager
AssociationsManager manager;
/**
初始化一个AssociationsHashMap 对象 associations
用来维护对象和ObjectAssociationMap之间的关系
*/
AssociationsHashMap &associations(manager.associations());
// 获取关联对象的索引
disguised_ptr_t disguised_object = DISGUISE(object);
// 根据对象指针查找对应的一个ObjectAssociateMap结构的map
// 通过迭代器 找到对应的 ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// i->first 表示对象地址
// i->second表示获取ObjectAssociationMap
ObjectAssociationMap *refs = i->second;
// 通过find(key)找到ObjectAssociationMap
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 获取到ObjcAssociation
ObjcAssociation &entry = j->second;
// 取值 取出value和policy
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
- 移除类所有关联对象 _object_remove_assocations(object)
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// 找到ObjectAssociationMap 开始遍历
// 拷贝所有需要删除的关联对象
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// 删除 ObjectAssociationMap
delete refs;
associations.erase(i);
}
}
// 将拷贝的值再次遍历release.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
Category的加载过程
- _objc_init() runtime入口函数,进行一些初始化操作
void _objc_init(void) {
static bool initialized = false;
if (initialized) return;
initialized = true;
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
// 注册了三个回调函数
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
- map_images() 加锁
void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[]) {
// 加锁
rwlock_writer_t lock(runtimeLock);
// 完成所有类的注册 和 fixup 等操作,还包括一些初始化工作以及 调用load类方法
return map_images_nolock(count, paths, mhdrs);
}
- map_images_nolock() 完成所有类的注册和fixup等工作,还包括一些初始化工作以及调用load类方法
void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[]) {
......
......
/**
_read_images 完成两件事
1、将category和类绑定在一起
2、重建类的方法列表
*/
if (hCount > 0) {
// 完成类的加载,协议的加载,类别的加载等工作
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
}
- _read_images() 完成类、协议、分类的加载等工作
_read_images()中主要完成了两件事情
1、addUnattachedCategoryForClass(...) 将category和类绑定在一起
2、remethodizeClass(...)重建类的方法列表
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
......
// 发现分类
for (EACH_HEADER) {
// 从编译好的文件获取所有的分类
category_t **catlist = _getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
// 遍历category列表
for (i = 0; i < count; i++) {
category_t *cat = catlist[I];
// 重映射分类所属的类
Class cls = remapClass(cat->cls);
if (!cls) {
catlist[i] = nil;
continue;
}
bool classExists = NO;
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties) {
// 绑定分类和目标类 or 将category添加到分类表中
addUnattachedCategoryForClass(cat, cls, hi);
// 判断类是否初始化
if (cls->isRealized()) {
// 重新构建方法列表
remethodizeClass(cls);
classExists = YES;
}
}
// 把category的类方法和协议添加到类的metaclass上(元类)
if (cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties)) {
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
}
if (DebugNonFragileIvars) {
realizeAllClasses();
}
}
// 将category添加到分类表中
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader) {
runtimeLock.assertWriting();
// 获取未处理的分类的表(没有把分类中的信息添加到类中去)
// NXMapTable 是哈希表
NXMapTable *cats = unattachedCategories();
category_list *list;
// 根据类获取对应的分类数组
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
// 扩容
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
// 把当前分类添加到分类数组中 list中是一系列locstamp_category_t
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
// 把新的list插入到分类表中去 key=cls, value=list
NXMapInsert(cats, cls, list);
}
- remethodizeClass() 重建类的方法列表
// 重建类的方法列表
static void remethodizeClass(Class cls) {
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
// 是否是元类
isMeta = cls->isMetaClass();
// 获取类对应的分类数组,并且从分类的哈希表中删除掉分类数组
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
// 将分类的method、protocol、property添加到class中
attachCategories(cls, cats, true /*flush caches*/);
// 释放分类
free(cats);
}
}
// 获取cls中未完成整合的所有分类
static category_list *unattachedCategoriesForClass(Class cls, bool realizing) {
runtimeLock.assertWriting();
// 返回对应的分类
return (category_list *)NXMapRemove(unattachedCategories(), cls);
}
- attachCategories() 将分类中的方法和属性列表绑定到目标类上
attachCategories 主要是创建一个新的方法列表空间,存放category中的实例方法、类方法、协议方法,然后将这个方法列表交给attachLists处理
static void attachCategories(Class cls, category_list *cats, bool flush_caches) {
// 对分类进行空判断,如果没有直接return
if (!cats) return;
// 判断是否是元类
bool isMeta = cls->isMetaClass();
/**
方法列表 二维数组
[
[method_t, method_t],
[method_t],
[method_t, method_t,method_t],
...
]
*/
// 方法列表
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
// 属性列表
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
// 协议列表
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
int mcount = 0;
int propcount = 0;
int protocount = 0;
// 原有类的分类的总数
int i = cats->count;
bool fromBundle = NO;
// 遍历所有分类,依次将每个分类里面的方法添加到临时的数组中
// 后编译的分类会先调用
while (i--) { // 倒序遍历,最先访问最后编译的分类
// 获取该分类的方法列表
auto& entry = cats->list[I];
// 返回类的方法列表,并拼接在临时的方法数组中
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
// 属性列表添加规则 同方法列表添加规则
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
// 协议列表添加规则 同方法列表添加规则
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
// 获取宿主类当中的 rw 数据,其中包含宿主类的方法列表信息
auto rw = cls->data();
// 主要是针对 分类中关于内存管理相关方法情况下的 一些特殊处理
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
/**
rw 代表类
methods 代表类的方法列表
attachLists 方法的含义是 将含有mcount个元素的mlists拼接到 rw 的methods上
*/
// 1、创建新的 方法列表 和 目标类中方法列表融合
// 1、把分类中的方法添加class中的方法列表中去
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
// 2、创建新的 属性列表 和 目标类的属性列表融合
// 2、把分类中的属性添加class中的属性列表中去
rw->properties.attachLists(proplists, propcount);
free(proplists);
// 3、创建新的 协议列表 和 目标类的协议列表融合
// 3、将分类中的协议列表添加到class中的协议列表中去
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
- attachLists() 将类中的方法和分类中的方法放到一个列表中
oldCount:目标类中方法列表长度
addedCount:category方法列表的长度
mommove:将类方法后移addedCount个偏移量
momcpy:将category中方法复制到偏移量从0到addedCount的类方法列表中
所以category中的方法并没有覆盖类中的方法,只是将category中的方法放到了类方法的前面,调用方法的时候,如果category和类中有同名方法,系统会找到前面category中的方法返回并调用。category中的方法优先级高于类中的方法
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
// 有数组的情况下
if (hasArray()) {
// many lists -> many lists
// 列表中原有元素总数 oldCount
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]));
/**
内存拷贝
[
A ---> [addedLists中的第一个元素]
B ---> [addedLists中第二个元素]
C ---> [addedLists中第三个元素]
[原有的第一个元素]
[原有的第二个元素]
]
*/
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;
// 将类方法直接移动到列表的addedCount位置
if (oldList) array()->lists[addedCount] = oldList;
// 将Category中的方法拷贝到array数组中位置从0~addedCount-1前面
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
总结:通过Runtime加载某个类的所有category数据,将所有category的方法、属性、协议信息合并到一个大数组中,后参与编译的category数据,会在数组的前面(后编译先调用while(--)),将合并后的category数据(方法、属性、协议)插入到类原来数据的前面
Category的"覆盖"问题
后编译的category中的同名方法会覆盖前面编译的category中的同名方法
category覆盖类中的同名方法
可以通过函数指针调用原有类中的方法
#import "Person.h"
#import "Person+Category1.h"
#import "Person+Category2.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person test];
// 调用person中的test方法
unsigned int count = 0;
Method *methodList = class_copyMethodList([Person class], &count);
IMP imp = NULL;
SEL sel = NULL;
for (unsigned int i = 0; i < count; i ++) {
Method method = methodList[i];
SEL methodSEL = method_getName(method);
NSString *methodName = [NSString stringWithUTF8String:sel_getName(methodSEL)];
if ([methodName isEqualToString:@"test"]) {
imp = method_getImplementation(method);
sel = method_getName(method);
}
}
((void(*)(id, SEL))(void *)imp)(person, sel);
free(methodList);
}
return 0;
}
函数指针调用
load方法加载顺序
load_images中主要做了2件事情:
1、发现load方法 prepare_load_methods();
2、调用load方法 call_load_methods();
void load_images(const char *path __unused, const struct mach_header *mh) {
// 判断是否有load方法,没有load方法直接返回
if (!hasLoadMethods((const headerType *)mh)) return;
// 递归锁
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods 发现load方法
{
// 读写锁
rwlock_writer_t lock2(runtimeLock);
// 发现load方法
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
// 调用load方法
call_load_methods();
}
- prepare_load_methods()
prepare_load_methods()中schedule_class_load(isa->superclass) 通过递归先将父类中的load方法存入loadable_class,所有得出父类的load优先于子类的load方法调用
void prepare_load_methods(const headerType *mhdr) {
size_t count, i;
runtimeLock.assertWriting();
// 获取非懒加载的类的列表
classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 核心1:【获得子类和父类中所有的load方法】load 方法如何别处理在这里 先加载父类 再加载子类
schedule_class_load(remapClass(classlist[i]));
}
// 获得非懒加载category的列表 分类没有父类
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
// 重映射获取分类所属的类
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class 忽略弱连接的分类
// 初始化类信息
realizeClass(cls);
assert(cls->ISA()->isRealized());
// 核心2:添加到loadable_categories
add_category_to_loadable_list(cat);
}
}
static void schedule_class_load(Class cls) {
// 判断是否为空
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
/**
// load方法已经被执行过
#define RW_LOADED (1<<23)
和下面的 cls->setInfo(RW_LOADED) 对应
*/
// 判断是否已经添加过load方法到load方法列表中
if (cls->data()->flags & RW_LOADED) return;
// 通过递归找到父类的load方法并添加,实际上调用自身的方法
schedule_class_load(cls->superclass);
// 将Class和IMP添加到调用列表中
add_class_to_loadable_list(cls);
// 设置class状态为已经添加过load方法 意思是设置了一个标志
cls->setInfo(RW_LOADED);
}
- call_load_methods() load方法的调用
通过do-while循环中 call_class_loads()和 call_category_load()可以看出,优先调用类的load方法,再调用分类的调用方法
load方法通过函数指针调用
void call_load_methods(void) {
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
// 1.先调用类中的+load方法
call_class_loads();
}
// 2. Call category +loads ONCE
// 2.调用分类的+load方法
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
// 调用类的load方法
static void call_class_loads(void) {
int i;
// Detach current loadable list.
// 获取当前loadable_classes列表
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
// 从子类到父类,遍历所有类中的 +load 方法
// 遍历loadable_classes列表,依次执行load方法,只遍历已添加的load方法的列表元素used
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
// 如果cls为空,则不执行其load方法
if (!cls) continue;
// 通过函数地址直接调用load方法
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
网友评论