首先我們來簡單的描述一下分類的一些基本概念:
1、用來給類添加新方法
2、不能給類添加成員屬性,添加了成員變量,也無法取到
3、注意:其實可以通過runtime給分類添加屬性
4、分類中用@property定義變量,只會生成變量的getter,setter方法聲明,不能生成方法實現(xiàn)和帶下劃線的成員變量。
那么問題來了, 分類是不能直接添加屬性的,那么要給分類添加屬性,那就需添加關(guān)聯(lián)對象或者重寫,在.m文件添加setter方法的實現(xiàn)。
請看下面代碼,在main.m中添加了對Person類的分類,在分類中有兩個屬性,且在main函數(shù)中創(chuàng)建Person對象并對分類的屬性賦值,其中,分類的屬性利用runtime來實現(xiàn)了getter,setter方法。
#import <Foundation/Foundation.h>
#import "Person.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "Person+LGEXT.h"
@interface Person (LG)
@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod2;
+ (void)cate_sayClassMethod;
@end
@implementation Person (LG)
- (void)cate_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)cate_sayClassMethod{
NSLog(@"%s",__func__);
}
- (void)setCate_name:(NSString *)cate_name{
/**
1: 對象
2: 標(biāo)識符
3: value
4: 策略
*/
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
return objc_getAssociatedObject(self, "cate_name");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person alloc];
person.cate_name = @"OC";
NSLog(@"%p",person);
}
return 0;
}
我們首先在setter方法出打上斷點,來看看它是否會執(zhí)行這個方法:

可以看到,會執(zhí)行這個方法,而當(dāng)我們點擊方法進去之后,你會發(fā)現(xiàn),他們的整個方法實現(xiàn)都在一塊,首先SetAssocHook,然后_base_objc_setAssociatedObject,之后就會執(zhí)行_object_set_associative_reference方法。

而_object_set_associative_reference方法中有很多代碼:
void
_object_set_associative_reference(id object, const 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;
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));
// 包裝了一下 對象
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// 包裝一下 policy - value
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
object->setHasAssociatedObjects();
}
/* establish or replace the association */
auto &refs = refs_result.first->second; // 空的桶子
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// release the old value (outside of the lock).
association.releaseHeldValue();
}
上面的代碼很多,將部分代碼收起來,可以看到一共就這么幾行代碼:

從下圖可以看到,167行代碼中的DisguisedPtr是對對象進行包裝,

而ObjcAssociation的作用同樣是對policy和value進行包裝,其中value的值是上面代碼對cate_name賦的值OC;

看下圖,acquireValue()的作用其實是對_value和policy的判斷;

接下來就是172-201行的局部作用空間代碼,如下圖:


首先看到AssociationsManager,看上圖,它的重要的地方其實是方框中的c++代碼。至于它的作用,下面我用一個例子來表示:
將main.m改成main.mm,在main.m中寫一個結(jié)構(gòu)體:
struct Objc {
Objc() { printf("來了");}
~Objc() { printf("走了"); }
};
然后在函數(shù)中調(diào)用:

看最終執(zhí)行完畢的結(jié)果:

這個函數(shù)的作用其實就是在程序開始時執(zhí)行一次,在函數(shù)結(jié)束完畢后,再執(zhí)行一次。而程序中是對代碼進行加鎖和解鎖;它可以有多個這樣的AssociationsManager。
而AssociationsHashMap從名字看,像一個哈希表,里面有很多關(guān)聯(lián)對象,里買呢的類型類似于key-value健值對的形式存在;繼續(xù)執(zhí)行內(nèi)部代碼塊:
當(dāng)執(zhí)行到if (refs_result.second) {行時,我們在控制臺來看一下一些屬性的值:
(lldb) p disguised
(DisguisedPtr<objc_object>) $0 = (value = 18446744069408092480)
(lldb) p association
(objc::ObjcAssociation) $1 = {
_policy = 3
_value = 0x0000000100004038 "OC"
}
(lldb) p manager
(objc::AssociationsManager) $2 = {}
(lldb) p associations
(objc::AssociationsHashMap) $3 = {
Buckets = 0x0000000000000000
NumEntries = 0
NumTombstones = 0
NumBuckets = 0
}
(lldb) p refs_result
(std::pair<objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>, bool>) $4 = {
first = {
Ptr = 0x0000000101006810
End = 0x0000000101006850
}
second = true
}
(lldb)
看到refs_result的時候,可以看到$4的類型很長,我們不用去管它,去看看給它賦值的函數(shù)try_emplace:
看下圖,它其實是分兩部分,一部分是false,一部分是true,從判斷中可以看出來,當(dāng)TheBucket為空時,就會執(zhí)行InsertIntoBucket,并返回true,當(dāng)TheBucket有值之后,就會返回false;

下圖是當(dāng)value有值的時候,程序執(zhí)行的代碼塊:

根據(jù)代碼執(zhí)行的流程分析的結(jié)果,程序第一次進入try_emplace走的是InsertIntoBucket,而在第二次執(zhí)行try_emplace走的同樣是InsertIntoBucket方法:
也就是說,第一次是從哈希表讀取refs_result,而disguised是傳入的值,接著判斷refs_result.second是不是第一次來,當(dāng)結(jié)果為真,調(diào)用setHasAssociatedObjects()函數(shù)。
接著引用refs_result.first的second值,對引用的refs進行第二次調(diào)用try_emplace,接著插入ObjcAssociation的值。
下面是分別進入兩次后的TheBucket的值:
(lldb) p TheBucket
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $0 = 0x000000010068b5b0
(lldb) p TheBucket
(objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> *) $1 = 0x0000000102014cd8
(lldb)
接下來看一下當(dāng)value為空時,程序執(zhí)行else部分:

上圖部分代碼的作用其實就是將關(guān)聯(lián)對象進行消除;
總結(jié): 其實就是兩層哈希map , 存取的時候兩層處理(類似二位數(shù)組)