目录
前言:
本文所有runtime部分, 均来自objc4苹果官方源码
一 分类 Category
1 用处
1.1 分类可以为添加类方法, 协议, 属性(只有get和set方法的声明, 没有对其实现)
1.2 分解体积庞大的类文件
1.3 可以重载系统方法
2 特点
2.1 运行时决议
分类文件在编译后, 并没有立即把其添加的内容添加到原类中, 而是在运行时, 动态的把方法,协议等内容添加到原类中
2.2 可以为系统类添加方法
3 底层原理
3.1 底层结构
分类底层是个C++的结构体, 内部存储了分类的各种信息
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; // property属性列表
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta) {
if (isMeta) return nil; // classProperties;
else return instanceProperties;
}
};
3.2 分类的加载调用栈
当程序启动之后, 系统会在运行时通过一系列的函数调用进行初始化、加载镜像文件、读取可执行文件等操作
- objc_init
- map_2_images
- map_images_nolock
之后调用了remethodizeClass函数来处理分类逻辑
static void remethodizeClass(Class cls) {
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
// 判断当前类是否为元类对象
// 如果添加的是类方法,则为YES, 实例方法为NO
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
// 这里获取了未拼接的所有分类
// 注意: 这里是运行时执行, 即分类的运行时决议特点, 在运行时添加
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// 把分类拼接到原类上
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
把分类的内容动态拼接
static void attachCategories(Class cls, category_list *cats, bool flush_caches) {
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
// 创建局部集合变量存储分类方法, 属性, 协议等
method_list_t **mlists = (method_list_t **)malloc(cats->count * sizeof(*mlists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int i = cats->count; // 原类的分类总数
3.3 敲黑板, 重点来了, 下面一段代码, 解释了分类内容什么时候被拼接到原类对象上, 以及如果多个分类有同名方法, 系统会调用哪个方法, 即哪个方法最终会生效
// 倒序遍历所有分类, 最先访问最后编译的分类
// 换句话说, 分类编译的越早, 被遍历到的越晚
while (i--) {
auto& entry = cats->list[i];
// 获取该分类的方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
// 最后编译的分类, 最先被添加到分类数组中
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
}
// 读取原类数据,包含方法列表等信息
auto rw = cls->data();
// 对要添加的分类内容作一些内存方面的处理, 为后续拼接做准备
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 注意:
// 这里, 将包括mcount个元素的mlists拼接到原类的methods方法列表中
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
}
3.4 最后的组装, 需要注意的是, 其他分类的添加的内容, 如方法, 并非是被覆盖了, 而是因为方法的位置并非在首位, 所以该方法没有机会调用, 而造成了被覆盖的假象.
同理, '原类中方法被分类中方法<覆盖实现>'的原因也是这样.
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]));
//// 把分类元素添加到原类元素列表中
/*
[
[addedLists中的第一个元素],
[addedLists中的第二个元素]...
[原有第一个元素],
[原有第二个元素]....
]
*/
//// 到此, 因为addedLists中的元素添加规则是最后编译的最先被添加
//// 所以, 以方法为例,最后编译的分类的方法最终插入到原类方法列表的第0个位置
//// 也就是, 该方法在运行时调用时, 最终被执行
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
二 关联对象 Associated Object
1 是什么
通俗来讲, 就是把一个对象关联到另一个对象上, 如给分类添加关联的成员变量. 但是, 只是关联, 并非被关联的对象属于关联对象.
2 怎么用
// Returns the value associated with a given object for a given key.
// 根据指定的key获取对应的关联对象值, 并返回
id objc_getAssociatedObject(id object, const void *key)
// Sets an associated value for a given object using a given key and association policy.
// 设定值,并通过给定的关联策略建立key和value的关联关系关联到被关联对象object上(有点绕)
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// Removes all associations for a given object.
// 移除该对象的所有关联对象
void objc_removeAssociatedObjects(id object)
3 为分类添加关联对象的本质
3.1 关联策略
相当于属性关键字, 有以下几种类型, 看到没, assign, retain, copy… 是不是很熟悉
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. * The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied. * The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. * The association is made atomically. */
};
3.2 关联本质
系统维护了一个全局的AssociationsManager单例管理类, 该类的生命周期和程序同属一个生命周期, AssociationsManager管理者维护了一个全局的AssociationsHashMap哈希表存储容器, 所有对象的关联内容在存储在这个容易中.所以说, 假如给分类添加了一个关联对象, 那么该关联内容既不需要分类管理, 也不是由原类管理.
class AssociationsManager {
static spinlock_t _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { _lock.lock(); }
~AssociationsManager() { _lock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
AssociationsHashMap内部维护了一个ObjectAssociationMap哈希表
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
ObjectAssociationMap内部维护了一个ObjcAssociation哈希表
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
他们的关系如下图
一个简单理解图, 具体的代码实现不再贴出来, 有兴趣的同学可以去官方的源代码中寻找,这里苹果官方源码
三 扩展 Extension
和分类比有什么区别?
扩展可以为类添加私有属性, 私有方法, 声明私有变量.
- 扩展是在编译时决议的, 类扩展中的方法属性, 在编译阶段就会被添加到类中, 因此扩展中的方法没有实现, 编译器会报警告;
- 类扩展没有独立的实现(@implementation), 即类扩展所声明的方法必须依托对应类的实现部分来实现;
- 类扩展即可以声明属性, 又可以声明私有成员变量;
我是个写代码的小学生, 如有不足, 万望不吝指教