余额不足.

扒一扒Category以及关联对象

字数统计: 2.2k阅读时长: 10 min
2019/03/20 Share

从结构说起

源码来自objc4-750

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
};

从上面的结构体,我们能发现一个分类都包含了哪些东西,从平时敲代码的经验中我们知道Category能够拓展实例方法&类方法以及协议,但是Category却不能直接添加实例变量,而在结构体中却有属性列表,why?

带着疑问咱们从OC加载的地方开始,分析category的加载过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;

// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

通过匹配镜像文件map_images,我们能找到_read_images方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
...
// Discover categories.
for (EACH_HEADER) {
category_t **catlist = _getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
// 根据分类结构体中的cls查询到一个活动的类指针
Class cls = remapClass(cat->cls);

if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}

// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
// 首先,为目标类注册category,然后对已实现的类重新构造类方法列表
bool classExists = NO;
// 如果category存在实例方法/协议/属性
if (cat->instanceMethods || cat->protocols || cat->instanceProperties) {
// 添加未附加的分类
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}

if (cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties)) {
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
...
}

_read_images()调用了remethodizeClass()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* 将外部的categories添加到已存在的类上
* Fixes up cls's method list, protocol list, and property list.
* 修正类的方法列表、协议列表、属性列表
* Updates method caches for cls and its subclasses.
* 更新类和子类的方法缓存
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void remethodizeClass(Class cls) {
category_list *cats;
bool isMeta;

runtimeLock.assertLocked();

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);
}
}

attachCategories()就是如何将category的方法、协议、属性写入类的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//  将分类中的方法列表、属性列表、协议列表添加到类中
// 假设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));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));

// Count backwards through cats to get newest categories first
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;
}
}

auto rw = cls->data();

prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);

rw->properties.attachLists(proplists, propcount);
free(proplists);

rw->protocols.attachLists(protolists, protocount);
free(protolists);
}

以上代码就是一个category的加载过程

从上面的函数中我们可以看到一个新的结构体class_rw_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;

const class_ro_t *ro;

method_array_t methods;
property_array_t properties;
protocol_array_t protocols;

Class firstSubclass;
Class nextSiblingClass;

char *demangledName;
}

在这个结构体中我们可以看见他有方法列表、属性列表、协议列表,我们都知道@property=ivar+getter()+setter(),虽然有属性列表,但实际上没有实例变量这个属性就并没有什么卵用了。

class_rw_t中有一个叫做class_ro_t的结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif

const uint8_t * ivarLayout;

const char * name;
method_list_t * baseMethodList; // 方法列表
protocol_list_t * baseProtocols; // 协议列表
const ivar_list_t * ivars; // 实例变量列表

const uint8_t * weakIvarLayout;
property_list_t *baseProperties; // 属性列表

method_list_t *baseMethods() const {
return baseMethodList;
}
}

这个结构体中的成员要比class_rw_t的多一些,而且有咱们想要ivars,那为什么category不能直接往class_ro_t中写入实例变量呢?

从名字上我们可以猜测roreadonlyrw则是readwrite,这就是不能往class_ro_t写入的原因之一,其二class_ro_t存放的是编译期间就确定的,内存已经不可更改,而category咱们都知道是在运行时决议的,当镜像加载的时候,methodizeClass方法会将 baseMethodList 添加到class_rw_tmethods列表中,之后会遍历category_list,并将category的方法也添加到methods列表中。

一定要给Category添加实例变量怎么办?

runtime有个玩意叫做关联对象

1
2
3
4
5
6
7
8
9
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)

OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)

通过以上三个函数即可给Category添加上实例变量。

先分析objc_setAssociatedObject(),该函数会调用_object_set_associative_reference()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
// 锁外对新值进行持有操作
// 如果新值存在,传入值以及持有策略
id new_value = value ? acquireValue(value, policy) : nil;
{
// 获取AssociationsHashMap
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 对对象进行伪装,disguised_object作为ObjectAssociationMap的key
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// 根据伪装对象寻找ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) { // 判断之前是否存在关联对象
// secondary table exists
ObjectAssociationMap *refs = i->second;
// 根据key寻找ObjectAssociationMap中的ObjcAssociation
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 如果之前存在ObjcAssociation,将旧值存入old_association,新值存入ObjcAssociation
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
// 如果之前不存在ObjcAssociation,创建新的ObjcAssociation,并将新值存入
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// 不存在关联对象则新建ObjectAssociationMap
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
// 将新的 [伪装对象地址 : ObjectAssociationMap] 存入AssociationsHashMap
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// 如果新值为nil,判断之前是否存在关联对象,存在则清除,不存在则不做任何操作
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
// 在锁外对旧值进行release操作
if (old_association.hasValue()) ReleaseValue()(old_association);
}

从上述代码中,我们可以得知关联对象并没有存在相关的类当中。
敲黑板了!该函数中出现了四个类,分别是AssociationsManager, AssociationsHashMap, ObjectAssociationMap,ObjcAssociation

ObjcAssociation 存有实例变量的值&实例变量的持有策略。
ObjectAssociationMap 是以实例变量的名称为key,ObjcAssociation为value的哈希表
AssociationsHashMap 是以disguised_ptr_t即类的伪装对象地址为key,ObjectAssociationMap为value的哈希表

关联对象统一由AssociationsManager来管理,AssociationsManager维护了一张AssociationsHashMap,在AssociationsHashMap中有若干个ObjectAssociationMap

有赋值就会有取值,但这里咱们就不过多的去分析了,感兴趣的同学可以自行查阅源码,与赋值过程的原理基本上一致。

到这里又引发了一个疑问,关联对象是什么时候被清除的?
在之前的文章中有介绍对象dealloc的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}

如果当前对象存在关联对象的时候,则会触发object_dispose(),然后调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void *objc_destructInstance(id obj) 
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;

}

如果存在关联对象就会调用_object_remove_assocations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}

代码也就不加注释啦,大致的流程就是:
1、通过AssociationsManager获取AssociationsHashMap
2、通过伪装对象得到disguised_ptr_t
3、根据disguised_ptr_t取得对应的ObjectAssociationMap
4、遍历ObjectAssociationMap取出ObjcAssociation,然后推进elements
5、清除AssociationsManager中的数据
6、最后遍历elements,释放数据

CATALOG
  1. 1. 从结构说起
  2. 2. 一定要给Category添加实例变量怎么办?