一、分类的定义
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);
};
如上面代码显示,分类的本质是一个结构体,它可以存储类的实例方法(instanceMethods)、类方法(classMethods)、协议(protocols)、实例属性(instanceProperties)和类属性(_classProperties)。值得注意的是分类中没有存储成员变量的元素。
二、分类的特点和作用(通过和扩展比较来确定)
分类的特点:
- 分类在运行时决议
- 可以为系统类添加分类
- 分类可以添加实例方法、类方法、协议、属性。添加属性时只声明 set、get 方法,没有对应实现,不会添加成员变量
扩展的特点:
- 扩展在编译时决议
- 不可以为系统类添加扩展
- 扩展可以声明属性、方法(一般不声明方法),大多数寄生在宿主类的 .m 文件中,如果添加属性,在编译时通过 @synthesize (xcode 4.5 后可省略)自动为属性实现 get、set 方法,并添加成员变量,声明方法则需要在宿主类中去实现。
分类中的 +load 和 +initialize 方法
- +load 调用时机:是 runtime 的加载类,在程序启动装载类信息的时候调用,通过函数地址直接调用,每个 +load 都会调用且仅调用一次。
- +load 调用顺序和方式:顺序:父类 +load --> 子类 +load --> 分类 +load;类中的 + load 优先于分类中的。每个分类中的和原类中的 +load 方法都会被调用,不会互相覆盖。
- +initialize 调用时机:+initialize 是通过 objc_msgSend 调用,只在每个类只在第一次初始化时候调用一次,如果子类没有实现 +initialize 会去调用父类的 +initialize。分类中的 + initialize 会覆盖原类的实现。
- +initialize 调用顺序和方式: 在某个类第一次初始化时,会先强制为初始化过的父类先调用 +initialize,然后本类再调用 +initialize。
例如: B、C 类都继承 A,且三个类都实现了 +initialize,在依次初始化 B、C 的实例对象时,初始化 B 类时会依次调用A、B 的 +initialize,初始化 C 类时只会调用 C 类的 +initialize,因为 A 的 +initialize 已经在初始化 B 的时候调用了。但如果 B 类没有实现 +initialize,那么在初始化 B 类的时候会调用两次 A 的 +initialize。
分类的其他特点:
- 分类添加的方法可以“覆盖”原类的方法,同名分类方法谁能生效取决于编译顺序——最后被编译的分类,会优先被生效。
- 可以使用关联对象的技术模拟为分类添加成员变量,其本质与原类中的成员变量并不相同,只是实现了类似的功能。
分类的作用
- 声明私有方法
- 分解体积庞大的类文件
- 把 framework 的私有方法公开化
下面我们探究一下分类的这些特点,我会以 特点1、特点2…… 这些来代表上面分类的特点。
三、分类原理探究(重点)
为了探究分类的原理,需要下载Runtime的源码,官方的工程需要经过大量调试才能使用。这里有处理好的objc4-756.2工程,以下都是基于处理好的objc4-756.2工程说明的。
将分类添加到宿主类中的过程
我们运行 objc-debug TARGET,看看系统如何加载分类的。
首先是 runtime 的初始化函数 _objc_init(void) 函数
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);
}
我们看 _dyld_objc_notify_register 中的 &map_images,点进去后
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
再看 map_images_nolock
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
..........
前面还有一堆代码,我们先不关心
..........
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
}
我们进入 _read_images
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];
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.
bool classExists = NO;
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);
}
}
}
}
..........
后面还有一堆代码,我们先不关心
..........
}
我们可以看到 // Discover categories.
的注释,告诉我们这里是关于处理分类的。
最外层的 for 循环判断条件 EACH_HEADER,实际上是#define EACH_HEADER \ hIndex = 0; \ hIndex < hCount && (hi = hList[hIndex]); \ hIndex++
的宏定义,就是遍历 hList 取出 hi。
hi 中包含各种资源信息,其中就包括分类的信息。
category_t **catlist = _getObjc2CategoryList(hi, &count);
这句代码就是获取 hi 中的分类的信息,返回值是一个 category_t 的数组,category_t 结构体我们在一开始就有了了解
循环里面的逻辑就是:拿到 category_t 类型的 cat,if (cat->instanceMethods || cat->protocols || cat->instanceProperties)
和 if (cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties))
是对类和其元类是否需要处理分类的判断。处理的具体逻辑都在 remethodizeClass 函数中。
这里我们注意一下 addUnattachedCategoryForClass
这个函数,从名字上的意思就是“为类添加未命中的分类”,这里的 cat 就是一个未命中的分类,在之后的 remethodizeClass 函数中就是去处理未命中的分类的。
我在这里打个断点,运行后第一次进入会出现上图信息,会处理 NSObject 及其元类的分类,具体处理逻辑请看 remethodizeClass 函数。
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);
}
}
remethodizeClass 函数里面使用了 unattachedCategoriesForClass
函数,和上面的addUnattachedCategoryForClass
对应,上面是添加,这里是获取。获取到“未命中的分类”后调用了 attachCategories
函数
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);
}
这里面的 cats 是上个函数传过来的“未命中的分类”,显然“未命中的分类”可能不止一个,故而这是一个数组
下面定义的 mlists 、proplists、protolists
是类的方法列表、属性列表和协议列表。它们都是一个二维数组,对应每一个“未命中分类”的方法列表、属性列表和协议列表,以 mlists
举例,其数据类型是这样的 [[method_t, method_t], [method_t, method_t, method_t], [method_t, method_t] ...]
。因为类方法和实例方法会处理两次,所有这里如果是普通类,存储的是实例方法、属性,元类的话会存储类方法和属性。
while (i--) {...}
循环意味着倒序变量,也就意味着最先访问的是最后编译的分类。这也解释了 特点 8 的原理。这个寻做的事情就是把每个“未命中分类”中的方法、属性、协议倒序放入mlists 、proplists、protolists
中。
后面的代码就是将 mlists 、proplists、protolists
附加到 rw 对应元素的里面。auto rw = cls->data();
rw 是类的可读写数据,意味着系统最终将分类里面的元素全部添加到宿主类中了,这也是为什么分类可以为宿主类添加实例方法、类方法、协议、属性的原因。
系统通过 objc_msgSend 函数来调用类的方法,由上面可以知道,系统能够找到分类的方法,顺序是最后添加的先被找到,而分类方法和原类中的方法谁先调用,这个函数中看不出来,所有我们需要再看看 attachLists
函数
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]));
}
}
我们注意看 hasArray() 为 YES 的分支,因为这个我们可以知道分类中的元素究竟是添加在数组的前面,还是后面,这里有两个核心函数 memmove 和 memcpy
,所有我们看看它俩究竟干了什么。
// memmove :内存移动。
/* __dst : 移动内存的目的地
* __src : 被移动的内存首地址
* __len : 被移动的内存长度
* 将__src的内存移动__len块内存到__dst中
*/
void *memmove(void *__dst, const void *__src, size_t __len);
__dst : 移动内存的目的地:对应的是 array()->lists + addedCount
很明显目的地是 array 的首地址加上被添加数组的大小,也就是要把数组前面的地方给留出来,这里我们就差不多知道新元素应该是要添加到数组前面了。
为了了解全貌,我们继续看 __src : 被移动的内存首地址:对应 array()->lists
就是数组的首地址。__len : 被移动的内存长度:对应 addedCount * sizeof(array()->lists[0])
就是数组中每个元素的长度。所有 memmove
就是将原来数组的元素向后移动 addedCount
个位置。那么很显然 memcpy
就是将预留出来的地方填满数据。
// memcpy :内存拷贝。
/* __dst : 拷贝内存的拷贝目的地
* __src : 被拷贝的内存首地址
* __n : 被移动的内存长度
* 将__src的内存拷贝__n块内存到__dst中
*/
void *memcpy(void *__dst, const void *__src, size_t __n);
至此我们将 特点 8 完全讲述清楚了。我们看到分类是在运行后才加到宿主类中的,这也解释了 特点 1,也正是因为 特点 1,我们才可以为系统类添加分类(特点 2),通过分类结构体 category_t
已经刚才的流程说明了 特点 3。那么还有 特点 4、5、6、7、9 没有被解释,我们接下来继续探索。
+ load 和 + initialize
我们回到 _objc_init(void) 函数,最后一行的第二个参数 是 load_images。我们点进去看看
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// 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);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
里面调用了 call_load_methods()。
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) {
call_class_loads();
}
// 2. Call category +loads ONCE
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;
}
请看 do while
循环,代码中给了明确的三步的注释。
- 循环的调用类中的 +load 方法,直到所有类中的 +load 方法都调用一遍为止。
- 调用分类中的 +load 方法一次
- 判断是否有类中的 +load 没有调用,或者是否有分类中的 +load 方法没有调用。
这基本解释的 特性 5 中 先调用类中的 +load 方法,后调用分类中的 +load 方法 的小点。
接下来我们看一下 call_class_loads(void)
函数:
static void call_class_loads(void)
{
int I;
// Detach current loadable list.
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.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
里面有个 loadable_class 类型的数组 classes 就是实现了 +load 方法的类,有 cls 和 method 两个元素,method 就是 +load 方法的实现
struct loadable_class {
Class cls; // may be nil
IMP method;
};
我们看到 for 循环中使用了 method 直接调用,并没有判断父类,和分类,所有 +load 直接通过函数地址调用,子类不会覆盖父类,分类不会覆盖宿主类。至此 特点 4 基本解释清楚,同时也部分解释了 特性 5。
为了更准确了解 +load 的调用顺序,我们在一个新的工程中添加以下类和分类:
@interface SuperObject : NSObject
@end
@interface SubObject : SuperObject
@end
@interface SuperObject (Category1)
@end
@interface SuperObject (Category2)
@end
@interface SubObject (Category1)
@end
@interface SubObject (Category2)
@end
对应的实现
@implementation SuperObject
+ (void)load {
NSLog(@"%@", NSStringFromClass(self.class));
}
@end
@implementation SubObject
+ (void)load {
NSLog(@"%@", NSStringFromClass(self.class));
}
@end
@implementation SuperObject (Category1)
+ (void)load {
NSLog(@"%@ Category1", NSStringFromClass(self.class));
}
@end
@implementation SuperObject (Category2)
+ (void)load {
NSLog(@"%@ Category2", NSStringFromClass(self.class));
}
@end
@implementation SubObject (Category1)
+ (void)load {
NSLog(@"%@ Category1", NSStringFromClass(self.class));
}
@end
@implementation SubObject (Category2)
+ (void)load {
NSLog(@"%@ Category2", NSStringFromClass(self.class));
}
@end
我们定义好了之后不需要任何调用直接运行工程,得到打印
SuperObject
SubObject
SuperObject Category1
SuperObject Category2
SubObject Category1
SubObject Category2
至此 特点 5 完全清楚了。
下面看关于 + initialize 的相关代码,我首先找到了 initializeNonMetaClass
函数,关于这个类外界如何调用它我们先不去考虑
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
// Exceptions: A +initialize call that throws an exception
// is deemed to be a complete and successful +initialize.
//
// Only __OBJC2__ adds these handlers. !__OBJC2__ has a
// bootstrapping problem of this versus CF's call to
// objc_exception_set_functions().
#if __OBJC2__
@try
#endif
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
pthread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
// We couldn't set INITIALIZING because INITIALIZING was already set.
// If this thread set it earlier, continue normally.
// If some other thread set it, block until initialize is done.
// It's ok if INITIALIZING changes to INITIALIZED while we're here,
// because we safely check for INITIALIZED inside the lock
// before blocking.
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
// We're on the child side of fork(), facing a class that
// was initializing by some other thread when fork() was called.
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
else if (cls->isInitialized()) {
// Set CLS_INITIALIZING failed because someone else already
// initialized the class. Continue normally.
// NOTE this check must come AFTER the ISINITIALIZING case.
// Otherwise: Another thread is initializing this class. ISINITIALIZED
// is false. Skip this clause. Then the other thread finishes
// initialization and sets INITIALIZING=no and INITIALIZED=yes.
// Skip the ISINITIALIZING clause. Die horribly.
return;
}
else {
// We shouldn't be here.
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
我特意将这个函数的注释给写了处理,我们看一下最上面两句注释,它的意思就是**为所有 uninitialized 的类发送 “+initialize” 消息。并且在此之前强制 initialization 父类 **
函数实现中第 4~7 句是一个递归操作,在父类 uninitialized 情况下递归的强制父类 initialization。
这里面有一个核心函数 callInitialize
,我们看一看它的实现
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
它使用了 objc_msgSend
函数,我们知道这个函数是 OC 调用类的方法所使用的基本函数。故而 callInitialize
做的事情和一个类的普通类方法调用没什么不同,这也就是说,普通类方法子类覆盖父类调用,子类没有实现查找父类,以及分类覆盖宿主类的情况,在 callInitialize
中的情况是一样的。那么 + initialize 唯一与其他类方法不同的地方在于在调用之前回去先调用 uninitialized 的父类。至此 特点 6 和 特点 7 就解释完毕了。下面只剩下 特点 9 没有被解决了。
关联对象
///获取某个对象的关联属性
id objc_getAssociatedObject(id object, const void *key)
///给某个对象添加关联属性
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
///移除对象所有的关联属性
void objc_removeAssociatedObjects(id object)
以上三个函数时关于关联对象的三个函数。我们在分类中声明一个属性时,系统不会为我们生产 get set 方法
以上是分类中添加一个 object 属性报的警告。我们可以在分类的实现中添加 @dynamic
来消除警告,但是这个关键字只是告诉编译器“属性的 setter 与 getter 方法由用户自己实现,你不用管了”,这不过是自欺欺人,你运行时用到该属性时就会崩溃,所有无论如何都要手动去实现 get、set 方法。
但是分类中没有成员变量,使用_属性名
会报错,这样我们在类中重写 set、get 方法的方式在这里就不适用了。好在系统为我们提供了 关联对象 技术解决了这一问题,就是上面的三个函数(主要是前两个)的使用!
我们分析一下 objc_setAssociatedObject
它就是简单的调用下面的这个函数
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;
assert(object);
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// 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).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
我们从 AssociationsManager manager;
这句话开始看先看一看 AssociationsManager 这个 C++ 类。
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
它的作用就是管理一个 AssociationsHashMap
类型的数据,没有就创建,有就获取,也就是说通过这个类每次获取的 _map 都是同一个对象。
代码中先是获取了 AssociationsHashMap
类型的 associations 变量,然后做了一个 disguised_ptr_t disguised_object = DISGUISE(object)
操作,其实这个的意思是把 object 的地址按位取反。它的意义稍后再说
我们再看 new_value 不为空的分支,第一句话AssociationsHashMap::iterator i = associations.find(disguised_object);
就是获取 associations 中的元素,而且是通过 disguised_object
这个值进行的键值查找。由于 disguised_object
是 object 地址的按位取反,所以只要通过这个变量取反后直接获取地址中的内容,就可以获取到 associations 中的元素了,这样就不需要去变量 associations 元素,大大降低了系统的查找时间。这其实是一种哈希算法,OC 中的NSDictionary
的查找也是类似的哈希算法,只不过具体的算法不是简单的通过“按位取反”的方式罢了。
接下来的判断就是看是否取到了 i 元素,如果有,就对原有元素进行操作,没有,就新创建一个元素然后再操作。我们主要看第一个分支,即存在 i 元素的情况。
这里面又获取了一个元素 refs,是 ObjectAssociationMap
类型的,然后获取里面的元素 j,这个 j 是通过函数传递过来的参数 key 找到的,实际上是一种和 OC NSDictionary
类似的哈希查找方式。j 中的 second 就是属性的旧值,我们可以通过 ObjcAssociation
函数更新旧值。
我们再看一下 new_value 为空的分支,这里并不是什么都不做,而是 refs->erase(j);
将这个 key 的条目直接擦除了。
分析了objc_setAssociatedObject
代码后我们对关联对象技术有了比较深刻的理解,其他两个函数代码这里就不分析了,为了更好理解关联对象技术,请看下面的关系图
我们解决了 特点 9,关于分类的原理就探究到这里。