Category是什么?
是xcode的苹果开发中提供一种给类性增加功能的机制。
Category可以做什么?
<1>Category可以添加方法、属性
但是,属性不会自动生成对应的成员变量,不可以声明命令,使用了绑定的机制。 —— 添加的更多的是公共的功能而不是业务功能。
<2>重写替换条原来的系统方法: 类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法。
局限性
1> 不可以扩充成员变量, @protoperty 置灰生成setter和getter声明,不会生成setter和getter的实现以及成员变量。
2> 多个category中都有同名的方法,会出现问题, 因为category同名方法会优先找到, 所以我们可能因为编译器上的差异而有所不同的结果,所以,应该避免category的方法和系统的方法相同,多个category中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用。
什么时候使用category 而不是继承?
1、 系统上的类,我们想直接给这个类提供额外的属性行为,(而不是继承,对系统已有的子类并没有影响。eg:NSNumber)
2、减少单个类的体积
3、模拟多继承
4、把静态库的私有方法公开。
Category使用
// 定义了name/age属性, 通过objc_assication 来进行绑定值
@interface NSObject (Add)
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (void)action; // 定义额外的方法
@end
#import "NSObject+Add.h"
#import <objc/runtime.h>
@implementation NSObject (Add)
- (void)setAge:(NSInteger)age {
objc_setAssociatedObject(self, "age.key", @(age), OBJC_ASSOCIATION_ASSIGN);
}
- (NSInteger)age {
return [objc_getAssociatedObject(self, "age.key") integerValue];
}
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, "name.key", name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)name {
return objc_getAssociatedObject(self, @"name.key");
}
- (void)action;
{
// 额外的方法
}
@end
定义了额外的
属性和方法。
策略对应关系
常见的设置key的方式
category 原理(源码阅读)
编译之后是底层结构struct category_t, 里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候, runtime会将category的数据合并到到另类信息中(类对象、元类对象)
1> 程序在运行的时候添加到类对象、元类对象中
程序启动之后的流程图
从上面的柳柳成可以看到开始是通过load_images 方法里面执行加载到有关的内容。
最后的主要方法是: attachCategories ——> attachLists 方法
// 加载categories
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties(); // 有分类属性
size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Ignore the category.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
if (cls->isStubClass()) {
// Stub classes are never realized. Stub classes
// don't know their metaclass until they're
// initialized, so we have to add categories with
// class methods or properties to the stub itself.
// methodizeClass() will find them and add them to
// the metaclass as appropriate.
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
// 给类对象添加方法和属性
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
if (cls->isRealized()) {
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}
// 这里给元类添加了方法 、 类属性
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
};
processCatlist(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
}
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
// cls 对应的类
// cats_list 分类的列表
// cats_count 分类的数目
// flags 标记
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.
* 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.
*/
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ]; // 方法列表 [methods,[],[]] 是一个二维数组
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(); // 额外分配内存如果需要
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) { // 如果足够了64个对象就添加一次,不过这个概率很小
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount); // 这里扩展了内存
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;//从后面开始添加 (所以,后面添加的就是在前面)
fromBundle |= entry.hi->isBundle(); // 为什么有fromBundle的判断
}
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;
}
}
// 数目不够64的,就在这里进行添加
if (mcount > 0) { // 方法列表
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) {
flushCaches(cls, __func__, [](Class c){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
}
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;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount)); // 重新分配内存
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i]; // 将之前的内容往后面拷贝
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i]; // 将新的内容放在前面
free(array());
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<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;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}
}
PS: 和旧的版本对比: 不再使用realloc和memmove以及memcpy, 而是直接使用malloc 和for循环处理。
优点:
1> 原来的方法, realloc 这个方法可能在原来的基础分配失败,然后调用malloc,同事会进行数据的拷贝。 这样就多了一次数据的拷贝
2> 不再使用memmove以及memcpy, 是直接可以使用for循环可以替换掉。
3> 内存块的连续, 查找方法更加快速。
malloc / calloc / realloc 之间的区别
https://zhuanlan.zhihu.com/p/87061787
https://zhuanlan.zhihu.com/p/384034790
https://zhuanlan.zhihu.com/p/57863097
Category 和Class Extension的区别
Class Category 在编译的时候,它的数据就已经包含在类信息中
Category 是在运行的时候,才会将数据合并到类信息中。
category 的方法查找
其实就是两个for的循环进行查找有关的内容。
for(xx) {
for (xx) {
xxx 查找到对应的方法
}
}
Category中的load方法
1>+load 方法会在runtime加载类、分类时调用
2> 每个类、分类的+load,在程序运行过程中只调用一次
调用顺序
1、先调用类的+load
《1》按照偏移的先婚后顺序调用(先编译、先调用)
《2》调用子类的+load之前先调用父类的+load
2、再调用分类的+load
《1》按照编译先后顺序调用(先编译先调用)
面试题目:category中有load方法吗? load方法是怎么调用的? load方法能继承吗?
- 有load方法
- load 方法在runtime加载类、分类的时候调用
- 可以继承的 (一般不会主动调用) 消息发送机制
源码解析
+load 方法调用的方法
《0》 加载镜像
void load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories(); // 加载categories的内容
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
// 0) 准备加载的方法
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods(); // 1)调用load方法
}
《1》 准备+load方法
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
//0)获取类数组, 然后遍历类加载对应的类信息
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count); /
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// 分类的加载到队列里面面
category_t * const *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
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil); // 保证这个类已经实现了
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat); // 添加category到loadable_list中
}
}
添加类到加载队列中
// cls must already be connected.
// 调用类的+load方法的信息
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
// RW_LOADED 表示已经调用过load了
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->getSuperclass()); //0) 递归调用,先调用父类
add_class_to_loadable_list(cls); // 1)在调用子类 ,添加到loadable_list 里面
cls->setInfo(RW_LOADED); // 设置为已经调用过
}
/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod(); // 实现一个load的方法
if (!method) return; // Don't bother if cls has no +load method , 没有load的方法不处理
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
// 实现了16个分类为一个周期,如果 loadable_classes_allocated = 16 下一个就是48 2的N次方扩大内存
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls; // 类
loadable_classes[loadable_classes_used].method = method; // Load 方法
loadable_classes_used++; //加载了一个新的类
}
// 上面几个方法是将类的的+load信息加载到了loadable_classes 数组里面
添加方法到分类里面
/***********************************************************************
* add_category_to_loadable_list
* Category cat's parent class exists and the category has been attached
* to its class. Schedule this category for +load after its parent class
* becomes connected and has its own +load method called.
**********************************************************************/
void add_category_to_loadable_list(Category cat) // 添加分类到loadable列表中
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
// 这样就十号线了16个分类为一个周期
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++; //category对应的索引
}
typedef void(*load_method_t)(id, SEL); // 调用方法
// 可加载类的数据结构
struct loadable_class {
Class cls; // 类
IMP method; // +load 方法
};
// 可加载的分类数据结构
struct loadable_category {
Category cat; // 分类
IMP method; // +load 方法
};
// 加载类以及+load方法执行
// List of classes that need +load called (pending superclass +load)
// This list always has superclasses first because of the way it is constructed
static struct loadable_class *loadable_classes = nil; // 加载数组
static int loadable_classes_used = 0;// 已经加载可用于执行+load的数目
static int loadable_classes_allocated = 0; // 分配的空间大小
// 分类的
// List of categories that need +load called (pending parent class +load)
static struct loadable_category *loadable_categories = nil;
static int loadable_categories_used = 0;
static int loadable_categories_allocated = 0;
PS: 看了上面的内容, 可以判断有关的load方法的执行先后顺序。
initialize 方法的调用
+initialize 在类第一次接收到消息的时候调用。
调用顺序
- 先调用父类的+initialize ,再调用子类的+initialize
//简略核心代码
void initializeNonMetaClass(Class cls)
{
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->getSuperclass();
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls); // 递归调用 , 保证了父类先初始化
}
if (reallyInitialize) {
callInitialize(cls); // 这里调用了initialize方法
}
}
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
PS: 在objc-initialize.mm 文件里面, 处理了初始化的逻辑。
load /initialize 方法的区别是什么? 它们在category中的调用顺序? 以及出现继承时它们之间的调用过程?
区别:
1)调用方式:
+load 是根据函数地址直接调用
initialize 是通过objc_msgSend 调用
2)调用时刻
1>load 是runtime加载类、分类的时候调用(只会调用一次)
2>initialize 是类第一次吃接收到消息的时候调用,每个类只会initialize一次(因为是msgsend 有可能子类没有实现这个方法会去调用父类)
调用顺序:
load:
1> 先调用类的load
先编译的类,优先调用load
调用子类的load之前,会调用父类的load
1> 再调用分类的load
先编译的分类,,优先调用load
- initialize
1> 先初始化父类
2> 再调用子类,如果子类没有实现,就调用父类的方法。
《1》initialize 是通过objc_msgSend 调用, 所以有对应的查找过程, +load是直接通过函数的地址来调用的。
《2》如果子类没有实现+initialize, 会调用父类的+initialize(所以,父类的+initialize会多次调用)
《3》如果分类实现了+initialize , 就覆盖类本身的+initialize。[其实就是方法查找的过程]
给Category添加属性,实现类似有成员变量的效果
// (1)通过获取静态常量的地址
static const void *MJNameKey = &MJNameKey;
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, MJNameKey);
}
// (2)字符串的常量
#define MJNameKey @"name"
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, MJNameKey);
}
// (3)字符常量地址
static const char MJNameKey;
static const char MJWeightKey;
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, &MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, &MJNameKey);
}
// (4)方法地址
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
// _cmd == @selector(name) 隐式参数
return objc_getAssociatedObject(self, _cmd);
}















网友评论